| @@ -0,0 +1,27 | |||
|
|
1 | { | |
|
|
2 | "env": { | |
|
|
3 | "browser": true, | |
|
|
4 | "commonjs": true, | |
|
|
5 | "amd": true, | |
|
|
6 | "node": true | |
|
|
7 | }, | |
|
|
8 | "parserOptions": { | |
|
|
9 | "ecmaFeatures": { | |
|
|
10 | "jsx": true | |
|
|
11 | }, | |
|
|
12 | "sourceType": "script" | |
|
|
13 | }, | |
|
|
14 | "extends": "eslint:recommended", | |
|
|
15 | "rules": { | |
|
|
16 | "no-const-assign": "warn", | |
|
|
17 | "no-this-before-super": "warn", | |
|
|
18 | "no-undef": "error", | |
|
|
19 | "no-unreachable": "warn", | |
|
|
20 | "no-unused-vars": "warn", | |
|
|
21 | "constructor-super": "warn", | |
|
|
22 | "valid-typeof": "warn", | |
|
|
23 | "semi" : "warn", | |
|
|
24 | "no-invalid-this" : "error", | |
|
|
25 | "no-console": "off" | |
|
|
26 | } | |
|
|
27 | } No newline at end of file | |
| @@ -0,0 +1,77 | |||
|
|
1 | plugins { | |
|
|
2 | id "org.implab.gradle-typescript" version "1.3.4" | |
|
|
3 | id "org.implab.gradle-mercurial" version "1.0.1" | |
|
|
4 | } | |
|
|
5 | ||
|
|
6 | if (!symbols in ['local', 'pack', 'none']) | |
|
|
7 | throw new Exception("The symbols property value is invalid: $symbols"); | |
|
|
8 | ||
|
|
9 | if (!flavour in ['browser', 'node']) | |
|
|
10 | throw new Exception("The flavour property value is invalid: $flavour"); | |
|
|
11 | ||
|
|
12 | ext { | |
|
|
13 | packageName = flavour == 'browser' ? "@$npmScope/$name-amd" : "@$npmScope/$name" | |
|
|
14 | lint = project.hasProperty('lint') ? project.lint ?: true : false | |
|
|
15 | } | |
|
|
16 | ||
|
|
17 | mercurial { | |
|
|
18 | preReleasePolicy { it | |
|
|
19 | .addPatch(versionDistance) | |
|
|
20 | .withPreRelease('dev') | |
|
|
21 | .withMeta(changeset) | |
|
|
22 | } | |
|
|
23 | ||
|
|
24 | applyVersioningPolicy() | |
|
|
25 | } | |
|
|
26 | ||
|
|
27 | typescript { | |
|
|
28 | compilerOptions { | |
|
|
29 | types = [] | |
|
|
30 | declaration = true | |
|
|
31 | experimentalDecorators = true | |
|
|
32 | strict = true | |
|
|
33 | // dojo-typings are sick | |
|
|
34 | skipLibCheck = true | |
|
|
35 | ||
|
|
36 | if(symbols != 'none') { | |
|
|
37 | sourceMap = true | |
|
|
38 | sourceRoot = "pack:${packageName}" | |
|
|
39 | } | |
|
|
40 | ||
|
|
41 | if (flavour == 'node') { | |
|
|
42 | module = "commonjs" | |
|
|
43 | target = "es2017" | |
|
|
44 | lib = ["es2017", "dom", "scripthost"] | |
|
|
45 | } else if (flavour == 'browser') { | |
|
|
46 | module = "amd" | |
|
|
47 | target = "es5" | |
|
|
48 | lib = ["es5", "dom", "scripthost", "es2015.promise", "es2015.symbol", "es2015.iterable" ] | |
|
|
49 | } | |
|
|
50 | } | |
|
|
51 | tscCmd = "$projectDir/node_modules/.bin/tsc" | |
|
|
52 | tsLintCmd = "$projectDir/node_modules/.bin/tslint" | |
|
|
53 | esLintCmd = "$projectDir/node_modules/.bin/eslint" | |
|
|
54 | } | |
|
|
55 | ||
|
|
56 | tasks.matching{ it.name =~ /^lint/ }.configureEach { | |
|
|
57 | onlyIf { lint } | |
|
|
58 | } | |
|
|
59 | ||
|
|
60 | if (symbols == 'local') { | |
|
|
61 | tasks.matching{ it.name =~ /^configureTs/ }.configureEach { | |
|
|
62 | compilerOptions { | |
|
|
63 | sourceRoot = "file://" + it.rootDir | |
|
|
64 | } | |
|
|
65 | } | |
|
|
66 | } | |
|
|
67 | ||
|
|
68 | printVersion { | |
|
|
69 | doLast { | |
|
|
70 | println "packageName: $packageName"; | |
|
|
71 | println "flavour: $flavour"; | |
|
|
72 | println "target: $typescript.compilerOptions.target"; | |
|
|
73 | println "module: $typescript.compilerOptions.module"; | |
|
|
74 | println "lint: $lint"; | |
|
|
75 | println "symbols: $symbols"; | |
|
|
76 | } | |
|
|
77 | } | |
| @@ -0,0 +1,37 | |||
|
|
1 | ```ts | |
|
|
2 | import Foo from "./Foo" | |
|
|
3 | import Services from "./services"; | |
|
|
4 | ||
|
|
5 | export default config<Services>() | |
|
|
6 | .service("foo", it => it | |
|
|
7 | .lifetime("container") | |
|
|
8 | .wants( | |
|
|
9 | { $ref: "bar", lazy: true, default: undefined }, | |
|
|
10 | "baz" | |
|
|
11 | ) | |
|
|
12 | .factory((bar,baz) => new Foo(bar,baz)) | |
|
|
13 | ) | |
|
|
14 | .service("loginForm", it => it | |
|
|
15 | .wants("authService") | |
|
|
16 | .factory(authService => LoginForm({authService})) | |
|
|
17 | ) | |
|
|
18 | .service("view", it => it | |
|
|
19 | .wants( | |
|
|
20 | { $ref: "loginForm", lazy: true }, | |
|
|
21 | { $ref: "mainForm", lazy: true, context: "new" }, | |
|
|
22 | "authService" | |
|
|
23 | ) | |
|
|
24 | .factory((login, main, authService) => View({login, main, authService})) | |
|
|
25 | ) | |
|
|
26 | .value("host", "localhost") | |
|
|
27 | .done(); | |
|
|
28 | ||
|
|
29 | const View = ({login, main, authService}) => { | |
|
|
30 | useModel(() => new ViewModel(authService)); | |
|
|
31 | ||
|
|
32 | return <Router> | |
|
|
33 | <Route path="login" component={login}/> | |
|
|
34 | <Route default component={main}/> | |
|
|
35 | </Router>; | |
|
|
36 | } | |
|
|
37 | ``` | |
| @@ -0,0 +1,9 | |||
|
|
1 | group=org.implab | |
|
|
2 | version= | |
|
|
3 | author=Implab team | |
|
|
4 | description=Dependency injection, logging, simple and fast text processing tools | |
|
|
5 | license=BSD-2-Clause | |
|
|
6 | repository=https://bitbucket.org/implab/implabjs-core | |
|
|
7 | npmScope=implab | |
|
|
8 | flavour=browser | |
|
|
9 | symbols=pack No newline at end of file | |
|
|
1 | NO CONTENT: new file 100644, binary diff hidden |
| @@ -0,0 +1,5 | |||
|
|
1 | distributionBase=GRADLE_USER_HOME | |
|
|
2 | distributionPath=wrapper/dists | |
|
|
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip | |
|
|
4 | zipStoreBase=GRADLE_USER_HOME | |
|
|
5 | zipStorePath=wrapper/dists | |
| @@ -0,0 +1,183 | |||
|
|
1 | #!/usr/bin/env sh | |
|
|
2 | ||
|
|
3 | # | |
|
|
4 | # Copyright 2015 the original author or authors. | |
|
|
5 | # | |
|
|
6 | # Licensed under the Apache License, Version 2.0 (the "License"); | |
|
|
7 | # you may not use this file except in compliance with the License. | |
|
|
8 | # You may obtain a copy of the License at | |
|
|
9 | # | |
|
|
10 | # https://www.apache.org/licenses/LICENSE-2.0 | |
|
|
11 | # | |
|
|
12 | # Unless required by applicable law or agreed to in writing, software | |
|
|
13 | # distributed under the License is distributed on an "AS IS" BASIS, | |
|
|
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
|
|
15 | # See the License for the specific language governing permissions and | |
|
|
16 | # limitations under the License. | |
|
|
17 | # | |
|
|
18 | ||
|
|
19 | ############################################################################## | |
|
|
20 | ## | |
|
|
21 | ## Gradle start up script for UN*X | |
|
|
22 | ## | |
|
|
23 | ############################################################################## | |
|
|
24 | ||
|
|
25 | # Attempt to set APP_HOME | |
|
|
26 | # Resolve links: $0 may be a link | |
|
|
27 | PRG="$0" | |
|
|
28 | # Need this for relative symlinks. | |
|
|
29 | while [ -h "$PRG" ] ; do | |
|
|
30 | ls=`ls -ld "$PRG"` | |
|
|
31 | link=`expr "$ls" : '.*-> \(.*\)$'` | |
|
|
32 | if expr "$link" : '/.*' > /dev/null; then | |
|
|
33 | PRG="$link" | |
|
|
34 | else | |
|
|
35 | PRG=`dirname "$PRG"`"/$link" | |
|
|
36 | fi | |
|
|
37 | done | |
|
|
38 | SAVED="`pwd`" | |
|
|
39 | cd "`dirname \"$PRG\"`/" >/dev/null | |
|
|
40 | APP_HOME="`pwd -P`" | |
|
|
41 | cd "$SAVED" >/dev/null | |
|
|
42 | ||
|
|
43 | APP_NAME="Gradle" | |
|
|
44 | APP_BASE_NAME=`basename "$0"` | |
|
|
45 | ||
|
|
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | |
|
|
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' | |
|
|
48 | ||
|
|
49 | # Use the maximum available, or set MAX_FD != -1 to use that value. | |
|
|
50 | MAX_FD="maximum" | |
|
|
51 | ||
|
|
52 | warn () { | |
|
|
53 | echo "$*" | |
|
|
54 | } | |
|
|
55 | ||
|
|
56 | die () { | |
|
|
57 | echo | |
|
|
58 | echo "$*" | |
|
|
59 | echo | |
|
|
60 | exit 1 | |
|
|
61 | } | |
|
|
62 | ||
|
|
63 | # OS specific support (must be 'true' or 'false'). | |
|
|
64 | cygwin=false | |
|
|
65 | msys=false | |
|
|
66 | darwin=false | |
|
|
67 | nonstop=false | |
|
|
68 | case "`uname`" in | |
|
|
69 | CYGWIN* ) | |
|
|
70 | cygwin=true | |
|
|
71 | ;; | |
|
|
72 | Darwin* ) | |
|
|
73 | darwin=true | |
|
|
74 | ;; | |
|
|
75 | MINGW* ) | |
|
|
76 | msys=true | |
|
|
77 | ;; | |
|
|
78 | NONSTOP* ) | |
|
|
79 | nonstop=true | |
|
|
80 | ;; | |
|
|
81 | esac | |
|
|
82 | ||
|
|
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar | |
|
|
84 | ||
|
|
85 | # Determine the Java command to use to start the JVM. | |
|
|
86 | if [ -n "$JAVA_HOME" ] ; then | |
|
|
87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then | |
|
|
88 | # IBM's JDK on AIX uses strange locations for the executables | |
|
|
89 | JAVACMD="$JAVA_HOME/jre/sh/java" | |
|
|
90 | else | |
|
|
91 | JAVACMD="$JAVA_HOME/bin/java" | |
|
|
92 | fi | |
|
|
93 | if [ ! -x "$JAVACMD" ] ; then | |
|
|
94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME | |
|
|
95 | ||
|
|
96 | Please set the JAVA_HOME variable in your environment to match the | |
|
|
97 | location of your Java installation." | |
|
|
98 | fi | |
|
|
99 | else | |
|
|
100 | JAVACMD="java" | |
|
|
101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | |
|
|
102 | ||
|
|
103 | Please set the JAVA_HOME variable in your environment to match the | |
|
|
104 | location of your Java installation." | |
|
|
105 | fi | |
|
|
106 | ||
|
|
107 | # Increase the maximum file descriptors if we can. | |
|
|
108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then | |
|
|
109 | MAX_FD_LIMIT=`ulimit -H -n` | |
|
|
110 | if [ $? -eq 0 ] ; then | |
|
|
111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then | |
|
|
112 | MAX_FD="$MAX_FD_LIMIT" | |
|
|
113 | fi | |
|
|
114 | ulimit -n $MAX_FD | |
|
|
115 | if [ $? -ne 0 ] ; then | |
|
|
116 | warn "Could not set maximum file descriptor limit: $MAX_FD" | |
|
|
117 | fi | |
|
|
118 | else | |
|
|
119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" | |
|
|
120 | fi | |
|
|
121 | fi | |
|
|
122 | ||
|
|
123 | # For Darwin, add options to specify how the application appears in the dock | |
|
|
124 | if $darwin; then | |
|
|
125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" | |
|
|
126 | fi | |
|
|
127 | ||
|
|
128 | # For Cygwin or MSYS, switch paths to Windows format before running java | |
|
|
129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then | |
|
|
130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` | |
|
|
131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` | |
|
|
132 | JAVACMD=`cygpath --unix "$JAVACMD"` | |
|
|
133 | ||
|
|
134 | # We build the pattern for arguments to be converted via cygpath | |
|
|
135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` | |
|
|
136 | SEP="" | |
|
|
137 | for dir in $ROOTDIRSRAW ; do | |
|
|
138 | ROOTDIRS="$ROOTDIRS$SEP$dir" | |
|
|
139 | SEP="|" | |
|
|
140 | done | |
|
|
141 | OURCYGPATTERN="(^($ROOTDIRS))" | |
|
|
142 | # Add a user-defined pattern to the cygpath arguments | |
|
|
143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then | |
|
|
144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" | |
|
|
145 | fi | |
|
|
146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh | |
|
|
147 | i=0 | |
|
|
148 | for arg in "$@" ; do | |
|
|
149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` | |
|
|
150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option | |
|
|
151 | ||
|
|
152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition | |
|
|
153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` | |
|
|
154 | else | |
|
|
155 | eval `echo args$i`="\"$arg\"" | |
|
|
156 | fi | |
|
|
157 | i=`expr $i + 1` | |
|
|
158 | done | |
|
|
159 | case $i in | |
|
|
160 | 0) set -- ;; | |
|
|
161 | 1) set -- "$args0" ;; | |
|
|
162 | 2) set -- "$args0" "$args1" ;; | |
|
|
163 | 3) set -- "$args0" "$args1" "$args2" ;; | |
|
|
164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; | |
|
|
165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; | |
|
|
166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; | |
|
|
167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; | |
|
|
168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; | |
|
|
169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; | |
|
|
170 | esac | |
|
|
171 | fi | |
|
|
172 | ||
|
|
173 | # Escape application args | |
|
|
174 | save () { | |
|
|
175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done | |
|
|
176 | echo " " | |
|
|
177 | } | |
|
|
178 | APP_ARGS=`save "$@"` | |
|
|
179 | ||
|
|
180 | # Collect all arguments for the java command, following the shell quoting and substitution rules | |
|
|
181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" | |
|
|
182 | ||
|
|
183 | exec "$JAVACMD" "$@" | |
| @@ -0,0 +1,100 | |||
|
|
1 | @rem | |
|
|
2 | @rem Copyright 2015 the original author or authors. | |
|
|
3 | @rem | |
|
|
4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); | |
|
|
5 | @rem you may not use this file except in compliance with the License. | |
|
|
6 | @rem You may obtain a copy of the License at | |
|
|
7 | @rem | |
|
|
8 | @rem https://www.apache.org/licenses/LICENSE-2.0 | |
|
|
9 | @rem | |
|
|
10 | @rem Unless required by applicable law or agreed to in writing, software | |
|
|
11 | @rem distributed under the License is distributed on an "AS IS" BASIS, | |
|
|
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
|
|
13 | @rem See the License for the specific language governing permissions and | |
|
|
14 | @rem limitations under the License. | |
|
|
15 | @rem | |
|
|
16 | ||
|
|
17 | @if "%DEBUG%" == "" @echo off | |
|
|
18 | @rem ########################################################################## | |
|
|
19 | @rem | |
|
|
20 | @rem Gradle startup script for Windows | |
|
|
21 | @rem | |
|
|
22 | @rem ########################################################################## | |
|
|
23 | ||
|
|
24 | @rem Set local scope for the variables with windows NT shell | |
|
|
25 | if "%OS%"=="Windows_NT" setlocal | |
|
|
26 | ||
|
|
27 | set DIRNAME=%~dp0 | |
|
|
28 | if "%DIRNAME%" == "" set DIRNAME=. | |
|
|
29 | set APP_BASE_NAME=%~n0 | |
|
|
30 | set APP_HOME=%DIRNAME% | |
|
|
31 | ||
|
|
32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | |
|
|
33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" | |
|
|
34 | ||
|
|
35 | @rem Find java.exe | |
|
|
36 | if defined JAVA_HOME goto findJavaFromJavaHome | |
|
|
37 | ||
|
|
38 | set JAVA_EXE=java.exe | |
|
|
39 | %JAVA_EXE% -version >NUL 2>&1 | |
|
|
40 | if "%ERRORLEVEL%" == "0" goto init | |
|
|
41 | ||
|
|
42 | echo. | |
|
|
43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | |
|
|
44 | echo. | |
|
|
45 | echo Please set the JAVA_HOME variable in your environment to match the | |
|
|
46 | echo location of your Java installation. | |
|
|
47 | ||
|
|
48 | goto fail | |
|
|
49 | ||
|
|
50 | :findJavaFromJavaHome | |
|
|
51 | set JAVA_HOME=%JAVA_HOME:"=% | |
|
|
52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe | |
|
|
53 | ||
|
|
54 | if exist "%JAVA_EXE%" goto init | |
|
|
55 | ||
|
|
56 | echo. | |
|
|
57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% | |
|
|
58 | echo. | |
|
|
59 | echo Please set the JAVA_HOME variable in your environment to match the | |
|
|
60 | echo location of your Java installation. | |
|
|
61 | ||
|
|
62 | goto fail | |
|
|
63 | ||
|
|
64 | :init | |
|
|
65 | @rem Get command-line arguments, handling Windows variants | |
|
|
66 | ||
|
|
67 | if not "%OS%" == "Windows_NT" goto win9xME_args | |
|
|
68 | ||
|
|
69 | :win9xME_args | |
|
|
70 | @rem Slurp the command line arguments. | |
|
|
71 | set CMD_LINE_ARGS= | |
|
|
72 | set _SKIP=2 | |
|
|
73 | ||
|
|
74 | :win9xME_args_slurp | |
|
|
75 | if "x%~1" == "x" goto execute | |
|
|
76 | ||
|
|
77 | set CMD_LINE_ARGS=%* | |
|
|
78 | ||
|
|
79 | :execute | |
|
|
80 | @rem Setup the command line | |
|
|
81 | ||
|
|
82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar | |
|
|
83 | ||
|
|
84 | @rem Execute Gradle | |
|
|
85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% | |
|
|
86 | ||
|
|
87 | :end | |
|
|
88 | @rem End local scope for the variables with windows NT shell | |
|
|
89 | if "%ERRORLEVEL%"=="0" goto mainEnd | |
|
|
90 | ||
|
|
91 | :fail | |
|
|
92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of | |
|
|
93 | rem the _cmd.exe /c_ return code! | |
|
|
94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 | |
|
|
95 | exit /b 1 | |
|
|
96 | ||
|
|
97 | :mainEnd | |
|
|
98 | if "%OS%"=="Windows_NT" endlocal | |
|
|
99 | ||
|
|
100 | :omega | |
| @@ -0,0 +1,31 | |||
|
|
1 | { | |
|
|
2 | "name": "@implab/di", | |
|
|
3 | "version": "0.0.1-dev", | |
|
|
4 | "description": "Dependency injection, logging, simple and fast text template engine", | |
|
|
5 | "main": "main.js", | |
|
|
6 | "keywords": [ | |
|
|
7 | "di", | |
|
|
8 | "ioc", | |
|
|
9 | "logging", | |
|
|
10 | "template engine", | |
|
|
11 | "dependency injection" | |
|
|
12 | ], | |
|
|
13 | "author": "Implab team", | |
|
|
14 | "license": "BSD-2-Clause", | |
|
|
15 | "repository": "https://bitbucket.org/implab/implabjs", | |
|
|
16 | "peerDependencies": { | |
|
|
17 | "dojo": "^1.10.0" | |
|
|
18 | }, | |
|
|
19 | "devDependencies": { | |
|
|
20 | "@types/node": "^8.0.0", | |
|
|
21 | "@types/requirejs": "~2.1.31", | |
|
|
22 | "@types/tape": "~4.2.33", | |
|
|
23 | "dojo": "~1.10.0", | |
|
|
24 | "dojo-typings": "^1.11.9", | |
|
|
25 | "eslint": "6.1.0", | |
|
|
26 | "requirejs": "latest", | |
|
|
27 | "tape": "~4.11.0", | |
|
|
28 | "tslint": "5.18.0", | |
|
|
29 | "typescript": "~4.1.5" | |
|
|
30 | } | |
|
|
31 | } | |
| @@ -0,0 +1,27 | |||
|
|
1 | # Implabjs-core | |
|
|
2 | ||
|
|
3 | Набор стандартных библиотек для создания приложений со сложным функционалом. | |
|
|
4 | Данную библиотеку можно использовать как для разработки приложений, которые | |
|
|
5 | будут работать в среде браузеров, так и в серверных средах. | |
|
|
6 | ||
|
|
7 | Библиотека написана на TypeScript, некоторая часть на JavaScript, но постепенно | |
|
|
8 | планируется перейти полностью на использование TypeScript | |
|
|
9 | ||
|
|
10 | Более подробная документация доступна по ссылке: <https://bitbucket.org/implab/implabjs-core/src/default/docs/ru/> | |
|
|
11 | ||
|
|
12 | ## Основные компоненты | |
|
|
13 | ||
|
|
14 | ### DI | |
|
|
15 | ||
|
|
16 | Контейнер для внедрения зависимостей, позволяет гибко описывать структуру | |
|
|
17 | приложения и создавать слабосвязанные компоненты. | |
|
|
18 | ||
|
|
19 | ### LOG | |
|
|
20 | ||
|
|
21 | Средства журналирования похожие на JLog, позволяют эффективно вести журнал | |
|
|
22 | выполнения программы. | |
|
|
23 | ||
|
|
24 | ### Cancellations | |
|
|
25 | ||
|
|
26 | Специальные маркеры для отмены асинхронных операций, по аналогии с .NET | |
|
|
27 | CancelationToken. | |
| @@ -0,0 +1,15 | |||
|
|
1 | /* | |
|
|
2 | * This settings file was generated by the Gradle 'init' task. | |
|
|
3 | * | |
|
|
4 | * The settings file is used to specify which projects to include in your build. | |
|
|
5 | * In a single project build this file can be empty or even removed. | |
|
|
6 | * | |
|
|
7 | * Detailed information about configuring a multi-project build in Gradle can be found | |
|
|
8 | * in the user guide at https://docs.gradle.org/3.5/userguide/multi_project_builds.html | |
|
|
9 | */ | |
|
|
10 | ||
|
|
11 | // To declare projects as part of a multi-project build use the 'include' method | |
|
|
12 | ||
|
|
13 | //include 'sub-project-name' | |
|
|
14 | ||
|
|
15 | rootProject.name = 'implabjs-di' No newline at end of file | |
| @@ -0,0 +1,169 | |||
|
|
1 | import { TraceSource } from "../log/TraceSource"; | |
|
|
2 | import { argumentNotEmptyString } from "../safe"; | |
|
|
3 | import { Descriptor, ContainerServiceMap, ContainerKeys, TypeOfService, ILifetime, ServiceContainer } from "./interfaces"; | |
|
|
4 | import { MapOf } from "../interfaces"; | |
|
|
5 | ||
|
|
6 | const trace = TraceSource.get("@implab/core/di/ActivationContext"); | |
|
|
7 | ||
|
|
8 | export interface ActivationContextInfo { | |
|
|
9 | name: string; | |
|
|
10 | ||
|
|
11 | service: string; | |
|
|
12 | ||
|
|
13 | } | |
|
|
14 | ||
|
|
15 | let nextId = 1; | |
|
|
16 | ||
|
|
17 | /** This class is created once per `Container.resolve` method call and used to | |
|
|
18 | * cache dependencies and to track created instances. The activation context | |
|
|
19 | * tracks services with `context` activation type. | |
|
|
20 | */ | |
|
|
21 | export class ActivationContext<S extends object> { | |
|
|
22 | _cache: MapOf<any>; | |
|
|
23 | ||
|
|
24 | _services: ContainerServiceMap<S>; | |
|
|
25 | ||
|
|
26 | _visited: MapOf<any>; | |
|
|
27 | ||
|
|
28 | _name: string; | |
|
|
29 | ||
|
|
30 | _service: Descriptor<S, any>; | |
|
|
31 | ||
|
|
32 | _container: ServiceContainer<S>; | |
|
|
33 | ||
|
|
34 | _parent: ActivationContext<S> | undefined; | |
|
|
35 | ||
|
|
36 | /** Creates a new activation context with the specified parameters. | |
|
|
37 | * @param container the container which starts the activation process | |
|
|
38 | * @param services the initial service registrations | |
|
|
39 | * @param name the name of the service being activated, this parameter is | |
|
|
40 | * used for the debug purpose. | |
|
|
41 | * @param service the service to activate, this parameter is used for the | |
|
|
42 | * debug purpose. | |
|
|
43 | */ | |
|
|
44 | constructor(container: ServiceContainer<S>, services: ContainerServiceMap<S>, name: string, service: Descriptor<S, any>) { | |
|
|
45 | this._name = name; | |
|
|
46 | this._service = service; | |
|
|
47 | this._visited = {}; | |
|
|
48 | this._cache = {}; | |
|
|
49 | this._services = services; | |
|
|
50 | this._container = container; | |
|
|
51 | } | |
|
|
52 | ||
|
|
53 | /** the name of the current resolving dependency */ | |
|
|
54 | getName() { | |
|
|
55 | return this._name; | |
|
|
56 | } | |
|
|
57 | ||
|
|
58 | /** Returns the container for which 'resolve' method was called */ | |
|
|
59 | getContainer() { | |
|
|
60 | return this._container; | |
|
|
61 | } | |
|
|
62 | ||
|
|
63 | /** Resolves the specified dependency in the current context | |
|
|
64 | * @param name The name of the dependency being resolved | |
|
|
65 | */ | |
|
|
66 | resolve<K extends ContainerKeys<S>>(name: K): TypeOfService<S, K>; | |
|
|
67 | /** Resolves the specified dependency with the specified default value if | |
|
|
68 | * the dependency is missing. | |
|
|
69 | * | |
|
|
70 | * @param name The name of the dependency being resolved | |
|
|
71 | * @param def A default value to return in case of the specified dependency | |
|
|
72 | * is missing. | |
|
|
73 | */ | |
|
|
74 | resolve<K extends ContainerKeys<S>, T>(name: K, def: T): TypeOfService<S, K> | T; | |
|
|
75 | /** Resolves the specified dependency and returns undefined in case if the | |
|
|
76 | * dependency is missing. | |
|
|
77 | * | |
|
|
78 | * @param name The name of the dependency being resolved | |
|
|
79 | */ | |
|
|
80 | resolve<K extends ContainerKeys<S>>(name: K, def: undefined): TypeOfService<S, K> | undefined; | |
|
|
81 | resolve<K extends ContainerKeys<S>, T>(name: K, def?: T): TypeOfService<S, K> | T | undefined { | |
|
|
82 | const d = this._services[name]; | |
|
|
83 | ||
|
|
84 | if (d !== undefined) { | |
|
|
85 | return this.activate(d, name.toString()); | |
|
|
86 | } else { | |
|
|
87 | if (arguments.length > 1) | |
|
|
88 | return def; | |
|
|
89 | else | |
|
|
90 | throw new Error(`Service ${name} not found`); | |
|
|
91 | } | |
|
|
92 | } | |
|
|
93 | ||
|
|
94 | /** | |
|
|
95 | * registers services local to the the activation context | |
|
|
96 | * | |
|
|
97 | * @name{string} the name of the service | |
|
|
98 | * @service{string} the service descriptor to register | |
|
|
99 | */ | |
|
|
100 | register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>) { | |
|
|
101 | argumentNotEmptyString(name, "name"); | |
|
|
102 | ||
|
|
103 | this._services[name] = service as any; | |
|
|
104 | } | |
|
|
105 | ||
|
|
106 | createLifetime(): ILifetime { | |
|
|
107 | const id = nextId++; | |
|
|
108 | const me = this; | |
|
|
109 | return { | |
|
|
110 | initialize() { | |
|
|
111 | }, | |
|
|
112 | has() { | |
|
|
113 | return id in me._cache; | |
|
|
114 | }, | |
|
|
115 | get() { | |
|
|
116 | return me._cache[id]; | |
|
|
117 | }, | |
|
|
118 | store(item: any) { | |
|
|
119 | me._cache[id] = item; | |
|
|
120 | } | |
|
|
121 | }; | |
|
|
122 | } | |
|
|
123 | ||
|
|
124 | activate<T>(d: Descriptor<S, T>, name: string) { | |
|
|
125 | if (trace.isLogEnabled()) | |
|
|
126 | trace.log("enter {0} {1}", name, d); | |
|
|
127 | ||
|
|
128 | const ctx = this.enter(d, name); | |
|
|
129 | const v = d.activate(ctx); | |
|
|
130 | ||
|
|
131 | if (trace.isLogEnabled()) | |
|
|
132 | trace.log(`leave ${name}`); | |
|
|
133 | ||
|
|
134 | return v; | |
|
|
135 | } | |
|
|
136 | ||
|
|
137 | visit(id: string) { | |
|
|
138 | const count = this._visited[id] || 0; | |
|
|
139 | this._visited[id] = count + 1; | |
|
|
140 | return count; | |
|
|
141 | } | |
|
|
142 | ||
|
|
143 | getStack(): ActivationContextInfo[] { | |
|
|
144 | const stack = [{ | |
|
|
145 | name: this._name, | |
|
|
146 | service: this._service.toString() | |
|
|
147 | }]; | |
|
|
148 | ||
|
|
149 | return this._parent ? | |
|
|
150 | stack.concat(this._parent.getStack()) : | |
|
|
151 | stack; | |
|
|
152 | } | |
|
|
153 | ||
|
|
154 | private enter(service: Descriptor<S, any>, name: string): this { | |
|
|
155 | const clone = Object.create(this); | |
|
|
156 | clone._name = name; | |
|
|
157 | clone._services = Object.create(this._services); | |
|
|
158 | clone._parent = this; | |
|
|
159 | clone._service = service; | |
|
|
160 | return clone; | |
|
|
161 | } | |
|
|
162 | ||
|
|
163 | /** Creates a clone for the current context, used to protect it from modifications */ | |
|
|
164 | clone(): this { | |
|
|
165 | const clone = Object.create(this); | |
|
|
166 | clone._services = Object.create(this._services); | |
|
|
167 | return clone; | |
|
|
168 | } | |
|
|
169 | } | |
| @@ -0,0 +1,39 | |||
|
|
1 | export interface ActivationItem { | |
|
|
2 | name: string; | |
|
|
3 | service: string; | |
|
|
4 | } | |
|
|
5 | ||
|
|
6 | export class ActivationError { | |
|
|
7 | activationStack: ActivationItem[]; | |
|
|
8 | ||
|
|
9 | service: string; | |
|
|
10 | ||
|
|
11 | innerException: any; | |
|
|
12 | ||
|
|
13 | message: string; | |
|
|
14 | ||
|
|
15 | constructor(service: string, activationStack: ActivationItem[], innerException: any) { | |
|
|
16 | this.message = "Failed to activate the service"; | |
|
|
17 | this.activationStack = activationStack; | |
|
|
18 | this.service = service; | |
|
|
19 | this.innerException = innerException; | |
|
|
20 | } | |
|
|
21 | ||
|
|
22 | toString() { | |
|
|
23 | const parts = [this.message]; | |
|
|
24 | if (this.service) | |
|
|
25 | parts.push("when activating: " + this.service.toString()); | |
|
|
26 | ||
|
|
27 | if (this.innerException) | |
|
|
28 | parts.push("caused by: " + this.innerException.toString()); | |
|
|
29 | ||
|
|
30 | if (this.activationStack) { | |
|
|
31 | parts.push("at"); | |
|
|
32 | this.activationStack | |
|
|
33 | .forEach(x => parts.push(` ${x.name} ${x.service}`)); | |
|
|
34 | ||
|
|
35 | } | |
|
|
36 | ||
|
|
37 | return parts.join("\n"); | |
|
|
38 | } | |
|
|
39 | } | |
| @@ -0,0 +1,176 | |||
|
|
1 | import { ActivationContext } from "./ActivationContext"; | |
|
|
2 | import { ActivationError } from "./ActivationError"; | |
|
|
3 | import { ServiceMap, Descriptor, PartialServiceMap, ContainerServiceMap, ContainerKeys, TypeOfService, ServiceContainer } from "./interfaces"; | |
|
|
4 | import { TraceSource } from "../log/TraceSource"; | |
|
|
5 | import { Configuration, RegistrationMap } from "./Configuration"; | |
|
|
6 | import { Cancellation } from "../Cancellation"; | |
|
|
7 | import { ICancellation } from "../interfaces"; | |
|
|
8 | import { isDescriptor } from "./traits"; | |
|
|
9 | import { LifetimeManager } from "./LifetimeManager"; | |
|
|
10 | import { each, isString } from "../safe"; | |
|
|
11 | import { ContainerConfiguration, FluentRegistrations } from "./fluent/interfaces"; | |
|
|
12 | import { FluentConfiguration } from "./fluent/FluentConfiguration"; | |
|
|
13 | ||
|
|
14 | const trace = TraceSource.get("@implab/core/di/ActivationContext"); | |
|
|
15 | ||
|
|
16 | export class Container<S extends object = any> implements ServiceContainer<S> { | |
|
|
17 | readonly _services: ContainerServiceMap<S>; | |
|
|
18 | ||
|
|
19 | readonly _lifetimeManager: LifetimeManager; | |
|
|
20 | ||
|
|
21 | readonly _cleanup: (() => void)[]; | |
|
|
22 | ||
|
|
23 | readonly _root: Container<S>; | |
|
|
24 | ||
|
|
25 | readonly _parent?: Container<S>; | |
|
|
26 | ||
|
|
27 | _disposed: boolean; | |
|
|
28 | ||
|
|
29 | constructor(parent?: Container<S>) { | |
|
|
30 | this._parent = parent; | |
|
|
31 | this._services = Object.create(parent ? parent._services : null); | |
|
|
32 | this._cleanup = []; | |
|
|
33 | this._root = parent ? parent.getRootContainer() : this; | |
|
|
34 | this._services.container = { activate: () => this }; | |
|
|
35 | this._services.childContainer = { activate: () => this.createChildContainer() }; | |
|
|
36 | this._disposed = false; | |
|
|
37 | this._lifetimeManager = new LifetimeManager(); | |
|
|
38 | } | |
|
|
39 | ||
|
|
40 | getRootContainer() { | |
|
|
41 | return this._root; | |
|
|
42 | } | |
|
|
43 | ||
|
|
44 | getParent() { | |
|
|
45 | return this._parent; | |
|
|
46 | } | |
|
|
47 | ||
|
|
48 | getLifetimeManager() { | |
|
|
49 | return this._lifetimeManager; | |
|
|
50 | } | |
|
|
51 | ||
|
|
52 | resolve<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>): TypeOfService<S, K>; | |
|
|
53 | resolve<K extends ContainerKeys<S>>(name: K, def: undefined): TypeOfService<S, K> | undefined; | |
|
|
54 | resolve<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>): TypeOfService<S, K> | undefined { | |
|
|
55 | trace.debug("resolve {0}", name); | |
|
|
56 | const d = this._services[name]; | |
|
|
57 | if (d === undefined) { | |
|
|
58 | if (arguments.length > 1) | |
|
|
59 | return def; | |
|
|
60 | else | |
|
|
61 | throw new Error("Service '" + name + "' isn't found"); | |
|
|
62 | } else { | |
|
|
63 | ||
|
|
64 | const context = new ActivationContext<S>(this, this._services, String(name), d); | |
|
|
65 | try { | |
|
|
66 | return d.activate(context); | |
|
|
67 | } catch (error) { | |
|
|
68 | throw new ActivationError(name.toString(), context.getStack(), error); | |
|
|
69 | } | |
|
|
70 | } | |
|
|
71 | } | |
|
|
72 | ||
|
|
73 | /** | |
|
|
74 | * @deprecated use resolve() method | |
|
|
75 | */ | |
|
|
76 | getService<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>) { | |
|
|
77 | return arguments.length === 1 ? this.resolve(name) : this.resolve(name, def); | |
|
|
78 | } | |
|
|
79 | ||
|
|
80 | register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>): this; | |
|
|
81 | register(services: PartialServiceMap<S>): this; | |
|
|
82 | register<K extends keyof S>(nameOrCollection: K | ServiceMap<S>, service?: Descriptor<S, S[K]>) { | |
|
|
83 | if (arguments.length === 1) { | |
|
|
84 | const data = nameOrCollection as ServiceMap<S>; | |
|
|
85 | ||
|
|
86 | each(data, (v, k) => this.register(k, v)); | |
|
|
87 | } else { | |
|
|
88 | if (!isDescriptor(service)) | |
|
|
89 | throw new Error("The service parameter must be a descriptor"); | |
|
|
90 | ||
|
|
91 | this._services[nameOrCollection as K] = service as any; | |
|
|
92 | } | |
|
|
93 | return this; | |
|
|
94 | } | |
|
|
95 | ||
|
|
96 | /** @deprecated use getLifetimeManager() */ | |
|
|
97 | onDispose(callback: () => void) { | |
|
|
98 | if (!(callback instanceof Function)) | |
|
|
99 | throw new Error("The callback must be a function"); | |
|
|
100 | this._cleanup.push(callback); | |
|
|
101 | } | |
|
|
102 | ||
|
|
103 | destroy() { | |
|
|
104 | return this.dispose(); | |
|
|
105 | } | |
|
|
106 | dispose() { | |
|
|
107 | if (this._disposed) | |
|
|
108 | return; | |
|
|
109 | this._disposed = true; | |
|
|
110 | for (const f of this._cleanup) | |
|
|
111 | f(); | |
|
|
112 | this._lifetimeManager.destroy(); | |
|
|
113 | } | |
|
|
114 | ||
|
|
115 | /** | |
|
|
116 | * @param{String|Object} config | |
|
|
117 | * The configuration of the container. Can be either a string or an object, | |
|
|
118 | * if the configuration is an object it's treated as a collection of | |
|
|
119 | * services which will be registered in the container. | |
|
|
120 | * | |
|
|
121 | * @param{Function} opts.contextRequire | |
|
|
122 | * The function which will be used to load a configuration or types for services. | |
|
|
123 | * | |
|
|
124 | */ | |
|
|
125 | async configure(config: string | RegistrationMap<S>, opts?: { contextRequire: any; baseModule?: string }, ct = Cancellation.none) { | |
|
|
126 | const _opts = Object.create(opts || null); | |
|
|
127 | ||
|
|
128 | if (typeof (config) === "string") { | |
|
|
129 | _opts.baseModule = config; | |
|
|
130 | ||
|
|
131 | const module = await import(config); | |
|
|
132 | if (module && module.default && typeof (module.default.apply) === "function") | |
|
|
133 | return module.default.apply(this); | |
|
|
134 | else | |
|
|
135 | return this._applyLegacyConfig(module, _opts, ct); | |
|
|
136 | } else { | |
|
|
137 | return this._applyLegacyConfig(config, _opts, ct); | |
|
|
138 | } | |
|
|
139 | } | |
|
|
140 | ||
|
|
141 | applyConfig<S2 extends object>(config: Promise<{ default: ContainerConfiguration<S2>; }>, ct?: ICancellation): Promise<ServiceContainer<S & S2>>; | |
|
|
142 | applyConfig<S2 extends object, P extends string>(config: Promise<{ [p in P]: ContainerConfiguration<S2>; }>, prop: P, ct?: ICancellation): Promise<ServiceContainer<S & S2>>; | |
|
|
143 | async applyConfig<S2 extends object, P extends string>( | |
|
|
144 | config: Promise<{ [p in P | "default"]: ContainerConfiguration<S2>; }>, | |
|
|
145 | propOrCt?: P | ICancellation, | |
|
|
146 | ct?: ICancellation | |
|
|
147 | ): Promise<ServiceContainer<S & S2>> { | |
|
|
148 | const mod = await config; | |
|
|
149 | ||
|
|
150 | let _ct: ICancellation; | |
|
|
151 | let _prop: P | "default"; | |
|
|
152 | ||
|
|
153 | if (isString(propOrCt)) { | |
|
|
154 | _prop = propOrCt; | |
|
|
155 | _ct = ct || Cancellation.none; | |
|
|
156 | } else { | |
|
|
157 | _ct = propOrCt || Cancellation.none; | |
|
|
158 | _prop = "default"; | |
|
|
159 | } | |
|
|
160 | ||
|
|
161 | return mod[_prop].apply(this, _ct); | |
|
|
162 | } | |
|
|
163 | ||
|
|
164 | async _applyLegacyConfig(config: RegistrationMap<S>, opts: { contextRequire: any; baseModule?: string }, ct = Cancellation.none) { | |
|
|
165 | return new Configuration<S>(this).applyConfiguration(config, opts); | |
|
|
166 | } | |
|
|
167 | ||
|
|
168 | async fluent<K extends keyof S>(config: FluentRegistrations<K, S>, ct = Cancellation.none): Promise<this> { | |
|
|
169 | await new FluentConfiguration<S>().register(config).apply(this, ct); | |
|
|
170 | return this; | |
|
|
171 | } | |
|
|
172 | ||
|
|
173 | createChildContainer<S2 extends object = S>(): Container<S & S2> { | |
|
|
174 | return new Container<S & S2>(this as any); | |
|
|
175 | } | |
|
|
176 | } | |
| @@ -0,0 +1,145 | |||
|
|
1 | import { Resolver, RegistrationBuilder } from "./interfaces"; | |
|
|
2 | import { Descriptor, ILifetime, ActivationType, PartialServiceMap, ServiceContainer } from "./interfaces"; | |
|
|
3 | import { DescriptorImpl } from "./DescriptorImpl"; | |
|
|
4 | import { LifetimeManager } from "./LifetimeManager"; | |
|
|
5 | ||
|
|
6 | export class DescriptorBuilder<S extends object, T> { | |
|
|
7 | private readonly _container: ServiceContainer<S>; | |
|
|
8 | private readonly _cb: (d: Descriptor<S, T>) => void; | |
|
|
9 | ||
|
|
10 | private readonly _eb: (err: any) => void; | |
|
|
11 | ||
|
|
12 | private _lifetime = LifetimeManager.empty(); | |
|
|
13 | ||
|
|
14 | private _overrides?: PartialServiceMap<S>; | |
|
|
15 | ||
|
|
16 | private _cleanup?: (item: T) => void; | |
|
|
17 | ||
|
|
18 | private _factory?: (resolve: Resolver<S>) => T; | |
|
|
19 | ||
|
|
20 | private _pending = 1; | |
|
|
21 | ||
|
|
22 | private _failed = false; | |
|
|
23 | ||
|
|
24 | constructor(container: ServiceContainer<S>, cb: (d: Descriptor<S, T>) => void, eb: (err: any) => void) { | |
|
|
25 | this._container = container; | |
|
|
26 | this._cb = cb; | |
|
|
27 | this._eb = eb; | |
|
|
28 | } | |
|
|
29 | ||
|
|
30 | build<T2>(): DescriptorBuilder<S, T2> { | |
|
|
31 | this._defer(); | |
|
|
32 | return new DescriptorBuilder<S, T2>(this._container, () => this._complete(), err => this._fail(err)); | |
|
|
33 | } | |
|
|
34 | ||
|
|
35 | override<K extends keyof S>(name: K, builder: RegistrationBuilder<S, S[K]>): this; | |
|
|
36 | override<K extends keyof S>(services: { [name in K]: RegistrationBuilder<S, S[K]> }): this; | |
|
|
37 | override<K extends keyof S>(nameOrServices: K | { [name in K]: RegistrationBuilder<S, S[K]> }, builder?: RegistrationBuilder<S, S[K]>): this { | |
|
|
38 | const overrides: PartialServiceMap<S> = this._overrides ? | |
|
|
39 | this._overrides : | |
|
|
40 | (this._overrides = {}); | |
|
|
41 | ||
|
|
42 | const guard = (v: void | Promise<void>) => { | |
|
|
43 | if (isPromise(v)) | |
|
|
44 | v.catch(err => this._fail(err)); | |
|
|
45 | }; | |
|
|
46 | ||
|
|
47 | if (isPrimitive(nameOrServices)) { | |
|
|
48 | if (builder) { | |
|
|
49 | this._defer(); | |
|
|
50 | const d = new DescriptorBuilder<S, S[K]>( | |
|
|
51 | this._container, | |
|
|
52 | result => { | |
|
|
53 | overrides[nameOrServices] = result; | |
|
|
54 | this._complete(); | |
|
|
55 | }, | |
|
|
56 | err => this._fail(err) | |
|
|
57 | ); | |
|
|
58 | ||
|
|
59 | try { | |
|
|
60 | guard(builder(d)); | |
|
|
61 | } catch (err) { | |
|
|
62 | this._fail(err); | |
|
|
63 | } | |
|
|
64 | } | |
|
|
65 | } else { | |
|
|
66 | each(nameOrServices, (v, k) => this.override(k, v)); | |
|
|
67 | } | |
|
|
68 | return this; | |
|
|
69 | } | |
|
|
70 | ||
|
|
71 | lifetime(lifetime: "singleton", typeId: string): this; | |
|
|
72 | lifetime(lifetime: ILifetime | Exclude<ActivationType, "singleton">): this; | |
|
|
73 | lifetime(lifetime: ILifetime | ActivationType, typeId?: string): this { | |
|
|
74 | if (isString(lifetime)) { | |
|
|
75 | this._lifetime = this._resolveLifetime(lifetime, typeId); | |
|
|
76 | } else { | |
|
|
77 | this._lifetime = lifetime; | |
|
|
78 | } | |
|
|
79 | return this; | |
|
|
80 | } | |
|
|
81 | ||
|
|
82 | cleanup(cb: (item: T) => void): this { | |
|
|
83 | this._cleanup = cb; | |
|
|
84 | return this; | |
|
|
85 | } | |
|
|
86 | ||
|
|
87 | factory(f: (resolve: Resolver<S>) => T): void { | |
|
|
88 | this._factory = f; | |
|
|
89 | this._complete(); | |
|
|
90 | } | |
|
|
91 | ||
|
|
92 | value(v: T): void { | |
|
|
93 | this._cb({ | |
|
|
94 | activate() { | |
|
|
95 | return v; | |
|
|
96 | } | |
|
|
97 | }); | |
|
|
98 | } | |
|
|
99 | ||
|
|
100 | _resolveLifetime(activation: ActivationType, typeId?: string | object) { | |
|
|
101 | switch (activation) { | |
|
|
102 | case "container": | |
|
|
103 | return LifetimeManager.containerLifetime(this._container); | |
|
|
104 | case "hierarchy": | |
|
|
105 | return LifetimeManager.hierarchyLifetime(); | |
|
|
106 | case "context": | |
|
|
107 | return LifetimeManager.contextLifetime(); | |
|
|
108 | case "singleton": | |
|
|
109 | if (!typeId) | |
|
|
110 | throw Error("The singleton activation requires a typeId"); | |
|
|
111 | ||
|
|
112 | const _oid = isString(typeId) ? typeId : oid(typeId); | |
|
|
113 | ||
|
|
114 | return LifetimeManager.singletonLifetime(_oid); | |
|
|
115 | default: | |
|
|
116 | return LifetimeManager.empty(); | |
|
|
117 | } | |
|
|
118 | } | |
|
|
119 | ||
|
|
120 | _defer() { | |
|
|
121 | this._pending++; | |
|
|
122 | } | |
|
|
123 | ||
|
|
124 | _complete() { | |
|
|
125 | if (--this._pending === 0) { | |
|
|
126 | if (!this._factory) | |
|
|
127 | throw new Error("The factory must be specified"); | |
|
|
128 | ||
|
|
129 | this._cb(new DescriptorImpl<S, T>({ | |
|
|
130 | lifetime: this._lifetime, | |
|
|
131 | factory: this._factory, | |
|
|
132 | overrides: this._overrides, | |
|
|
133 | cleanup: this._cleanup | |
|
|
134 | })); | |
|
|
135 | } | |
|
|
136 | } | |
|
|
137 | ||
|
|
138 | _fail(err: any) { | |
|
|
139 | if (!this._failed) { | |
|
|
140 | this._failed = true; | |
|
|
141 | this._eb.call(undefined, err); | |
|
|
142 | } | |
|
|
143 | } | |
|
|
144 | ||
|
|
145 | } | |
| @@ -0,0 +1,66 | |||
|
|
1 | import { Descriptor, PartialServiceMap, ILifetime, ContainerKeys } from "../interfaces"; | |
|
|
2 | import { ActivationContext } from "../ActivationContext"; | |
|
|
3 | import { each } from "../../safe"; | |
|
|
4 | import { DependencyOptions, LazyDependencyOptions, Resolver } from "./interfaces"; | |
|
|
5 | ||
|
|
6 | export interface DescriptorImplArgs<S extends object, T> { | |
|
|
7 | lifetime: ILifetime; | |
|
|
8 | ||
|
|
9 | factory: (resolve: Resolver<S>) => T; | |
|
|
10 | ||
|
|
11 | cleanup?: (item: T) => void; | |
|
|
12 | ||
|
|
13 | overrides?: PartialServiceMap<S>; | |
|
|
14 | } | |
|
|
15 | ||
|
|
16 | export class DescriptorImpl<S extends object, T> implements Descriptor<S, T> { | |
|
|
17 | ||
|
|
18 | private readonly _overrides?: PartialServiceMap<S>; | |
|
|
19 | ||
|
|
20 | private readonly _lifetime: ILifetime; | |
|
|
21 | ||
|
|
22 | private readonly _factory: (resolve: Resolver<S>) => T; | |
|
|
23 | ||
|
|
24 | private readonly _cleanup?: (item: T) => void; | |
|
|
25 | ||
|
|
26 | constructor(args: DescriptorImplArgs<S, T>) { | |
|
|
27 | this._lifetime = args.lifetime; | |
|
|
28 | this._factory = args.factory; | |
|
|
29 | if (args.cleanup) | |
|
|
30 | this._cleanup = args.cleanup; | |
|
|
31 | if (args.overrides) | |
|
|
32 | this._overrides = args.overrides; | |
|
|
33 | } | |
|
|
34 | ||
|
|
35 | activate(context: ActivationContext<S>): T { | |
|
|
36 | ||
|
|
37 | if (this._lifetime.has()) | |
|
|
38 | return this._lifetime.get(); | |
|
|
39 | ||
|
|
40 | this._lifetime.initialize(context); | |
|
|
41 | ||
|
|
42 | if (this._overrides) | |
|
|
43 | each(this._overrides, (v, k) => context.register(k, v)); | |
|
|
44 | ||
|
|
45 | const resolve = (name: ContainerKeys<S>, opts?: DependencyOptions | LazyDependencyOptions) => { | |
|
|
46 | if (opts && "lazy" in opts && opts.lazy) { | |
|
|
47 | const c2 = context.clone(); | |
|
|
48 | return () => { | |
|
|
49 | return opts.optional ? c2.resolve(name, opts.default) : c2.resolve(name); | |
|
|
50 | }; | |
|
|
51 | } else { | |
|
|
52 | return opts && opts.optional ? context.resolve(name, opts.default) : context.resolve(name); | |
|
|
53 | } | |
|
|
54 | }; | |
|
|
55 | ||
|
|
56 | const instance = this._factory.call(undefined, resolve); | |
|
|
57 | ||
|
|
58 | this._lifetime.store(instance, this._cleanup); | |
|
|
59 | ||
|
|
60 | return instance; | |
|
|
61 | } | |
|
|
62 | ||
|
|
63 | toString() { | |
|
|
64 | return `[object DescriptorImpl, lifetime=${this._lifetime}]`; | |
|
|
65 | } | |
|
|
66 | } | |
| @@ -0,0 +1,68 | |||
|
|
1 | import { argumentNotNull, each, isPrimitive, isPromise } from "../../safe"; | |
|
|
2 | import { DescriptorBuilder } from "./DescriptorBuilder"; | |
|
|
3 | import { RegistrationBuilder, FluentRegistrations, ContainerConfiguration } from "./interfaces"; | |
|
|
4 | import { Cancellation } from "../../Cancellation"; | |
|
|
5 | import { ServiceContainer } from "../interfaces"; | |
|
|
6 | ||
|
|
7 | export class FluentConfiguration<S extends object, Y extends keyof S = keyof S> { | |
|
|
8 | ||
|
|
9 | _builders: { [k in keyof S]?: RegistrationBuilder<S, S[k]> } = {}; | |
|
|
10 | ||
|
|
11 | provided<K extends Y>(): FluentConfiguration<S, Exclude<Y, K>> { | |
|
|
12 | return this; | |
|
|
13 | } | |
|
|
14 | ||
|
|
15 | register<K extends Y>(name: K, builder: RegistrationBuilder<S, S[K]>): FluentConfiguration<S, Exclude<Y, K>>; | |
|
|
16 | register<K extends Y>(config: FluentRegistrations<K, S>): FluentConfiguration<S, Exclude<Y, K>>; | |
|
|
17 | register<K extends Y>(nameOrConfig: K | FluentRegistrations<K, S>, builder?: RegistrationBuilder<S, S[K]>): FluentConfiguration<S, Exclude<Y, K>> { | |
|
|
18 | if (isPrimitive(nameOrConfig)) { | |
|
|
19 | argumentNotNull(builder, "builder"); | |
|
|
20 | this._builders[nameOrConfig] = builder; | |
|
|
21 | } else { | |
|
|
22 | each(nameOrConfig, (v, k) => this.register(k, v)); | |
|
|
23 | } | |
|
|
24 | ||
|
|
25 | return this; | |
|
|
26 | } | |
|
|
27 | ||
|
|
28 | configure(config: FluentRegistrations<Y, S>): ContainerConfiguration<S> { | |
|
|
29 | return this.register(config); | |
|
|
30 | } | |
|
|
31 | ||
|
|
32 | apply<S2 extends object>(target: ServiceContainer<S2>, ct = Cancellation.none) { | |
|
|
33 | ||
|
|
34 | let pending = 1; | |
|
|
35 | ||
|
|
36 | const _t2 = target as unknown as ServiceContainer<S2 & S>; | |
|
|
37 | ||
|
|
38 | return new Promise<ServiceContainer<S2 & S>>((resolve, reject) => { | |
|
|
39 | function guard(v: void | Promise<void>) { | |
|
|
40 | if (isPromise(v)) | |
|
|
41 | v.catch(reject); | |
|
|
42 | } | |
|
|
43 | ||
|
|
44 | function complete() { | |
|
|
45 | if (!--pending) | |
|
|
46 | resolve(_t2); | |
|
|
47 | } | |
|
|
48 | each(this._builders, (v, k) => { | |
|
|
49 | pending++; | |
|
|
50 | const d = new DescriptorBuilder<S2 & S, any>(_t2, | |
|
|
51 | result => { | |
|
|
52 | _t2.register(k, result); | |
|
|
53 | complete(); | |
|
|
54 | }, | |
|
|
55 | reject | |
|
|
56 | ); | |
|
|
57 | ||
|
|
58 | try { | |
|
|
59 | guard(v(d, ct)); | |
|
|
60 | } catch (e) { | |
|
|
61 | reject(e); | |
|
|
62 | } | |
|
|
63 | }); | |
|
|
64 | complete(); | |
|
|
65 | }); | |
|
|
66 | } | |
|
|
67 | ||
|
|
68 | } | |
| @@ -0,0 +1,83 | |||
|
|
1 | import { argumentNotEmptyString, each } from "../safe"; | |
|
|
2 | import { ActivationContext } from "./ActivationContext"; | |
|
|
3 | import { Descriptor, PartialServiceMap, TypeOfService, ContainerKeys } from "./interfaces"; | |
|
|
4 | import { ActivationError } from "./ActivationError"; | |
|
|
5 | ||
|
|
6 | export interface ReferenceDescriptorParams<S extends object, K extends ContainerKeys<S>> { | |
|
|
7 | name: K; | |
|
|
8 | optional?: boolean; | |
|
|
9 | default?: TypeOfService<S, K>; | |
|
|
10 | services?: PartialServiceMap<S>; | |
|
|
11 | } | |
|
|
12 | ||
|
|
13 | export class LazyReferenceDescriptor<S extends object = any, K extends ContainerKeys<S> = ContainerKeys<S>> | |
|
|
14 | implements Descriptor<S, ((args?: PartialServiceMap<S>) => TypeOfService<S, K>)> { | |
|
|
15 | ||
|
|
16 | _name: K; | |
|
|
17 | ||
|
|
18 | _optional = false; | |
|
|
19 | ||
|
|
20 | _default: TypeOfService<S, K> | undefined; | |
|
|
21 | ||
|
|
22 | _services: PartialServiceMap<S>; | |
|
|
23 | ||
|
|
24 | constructor(opts: ReferenceDescriptorParams<S, K>) { | |
|
|
25 | argumentNotEmptyString(opts && opts.name, "opts.name"); | |
|
|
26 | this._name = opts.name; | |
|
|
27 | this._optional = !!opts.optional; | |
|
|
28 | this._default = opts.default; | |
|
|
29 | ||
|
|
30 | this._services = (opts.services || {}) as PartialServiceMap<S>; | |
|
|
31 | } | |
|
|
32 | ||
|
|
33 | activate(context: ActivationContext<S>) { | |
|
|
34 | // добавляем сервисы | |
|
|
35 | if (this._services) { | |
|
|
36 | each(this._services, (v, k) => context.register(k, v)); | |
|
|
37 | } | |
|
|
38 | ||
|
|
39 | const saved = context.clone(); | |
|
|
40 | ||
|
|
41 | return (cfg?: PartialServiceMap<S>): any => { | |
|
|
42 | // защищаем контекст на случай исключения в процессе | |
|
|
43 | // активации | |
|
|
44 | const ct = cfg ? saved.clone() : saved; | |
|
|
45 | try { | |
|
|
46 | if (cfg) { | |
|
|
47 | each(cfg, (v, k) => ct.register(k, v)); | |
|
|
48 | } | |
|
|
49 | ||
|
|
50 | return this._optional ? ct.resolve(this._name, this._default) : ct | |
|
|
51 | .resolve(this._name); | |
|
|
52 | } catch (error) { | |
|
|
53 | throw new ActivationError(this._name.toString(), ct.getStack(), error); | |
|
|
54 | } | |
|
|
55 | }; | |
|
|
56 | } | |
|
|
57 | ||
|
|
58 | toString() { | |
|
|
59 | const opts = []; | |
|
|
60 | if (this._optional) | |
|
|
61 | opts.push("optional"); | |
|
|
62 | ||
|
|
63 | opts.push("lazy"); | |
|
|
64 | ||
|
|
65 | const parts = [ | |
|
|
66 | "@ref " | |
|
|
67 | ]; | |
|
|
68 | if (opts.length) { | |
|
|
69 | parts.push("{"); | |
|
|
70 | parts.push(opts.join()); | |
|
|
71 | parts.push("} "); | |
|
|
72 | } | |
|
|
73 | ||
|
|
74 | parts.push(this._name.toString()); | |
|
|
75 | ||
|
|
76 | if (this._default !== undefined && this._default !== null) { | |
|
|
77 | parts.push(" = "); | |
|
|
78 | parts.push(String(this._default)); | |
|
|
79 | } | |
|
|
80 | ||
|
|
81 | return parts.join(""); | |
|
|
82 | } | |
|
|
83 | } | |
| @@ -0,0 +1,216 | |||
|
|
1 | import { IDestroyable, MapOf } from "../interfaces"; | |
|
|
2 | import { argumentNotNull, isDestroyable, argumentNotEmptyString, isRemovable } from "../safe"; | |
|
|
3 | import { ILifetime, ServiceContainer } from "./interfaces"; | |
|
|
4 | import { ActivationContext } from "./ActivationContext"; | |
|
|
5 | ||
|
|
6 | function safeCall(item: () => void) { | |
|
|
7 | try { | |
|
|
8 | item(); | |
|
|
9 | } catch { | |
|
|
10 | // silence! | |
|
|
11 | } | |
|
|
12 | } | |
|
|
13 | ||
|
|
14 | const emptyLifetime: ILifetime = Object.freeze({ | |
|
|
15 | has() { | |
|
|
16 | return false; | |
|
|
17 | }, | |
|
|
18 | ||
|
|
19 | initialize() { | |
|
|
20 | ||
|
|
21 | }, | |
|
|
22 | ||
|
|
23 | get() { | |
|
|
24 | throw new Error("The specified item isn't registered with this lifetime manager"); | |
|
|
25 | }, | |
|
|
26 | ||
|
|
27 | store() { | |
|
|
28 | // does nothing | |
|
|
29 | }, | |
|
|
30 | ||
|
|
31 | toString() { | |
|
|
32 | return `[object EmptyLifetime]`; | |
|
|
33 | } | |
|
|
34 | ||
|
|
35 | }); | |
|
|
36 | ||
|
|
37 | const unknownLifetime: ILifetime = Object.freeze({ | |
|
|
38 | has() { | |
|
|
39 | return false; | |
|
|
40 | }, | |
|
|
41 | initialize() { | |
|
|
42 | throw new Error("Can't call initialize on the unknown lifetime object"); | |
|
|
43 | }, | |
|
|
44 | get() { | |
|
|
45 | throw new Error("The lifetime object isn't initialized"); | |
|
|
46 | }, | |
|
|
47 | store() { | |
|
|
48 | throw new Error("Can't store a value in the unknown lifetime object"); | |
|
|
49 | }, | |
|
|
50 | toString() { | |
|
|
51 | return `[object UnknownLifetime]`; | |
|
|
52 | } | |
|
|
53 | }); | |
|
|
54 | ||
|
|
55 | let nextId = 0; | |
|
|
56 | ||
|
|
57 | const singletons: any = {}; | |
|
|
58 | ||
|
|
59 | export class LifetimeManager implements IDestroyable { | |
|
|
60 | private _cleanup: (() => void)[] = []; | |
|
|
61 | private _cache: MapOf<any> = {}; | |
|
|
62 | private _destroyed = false; | |
|
|
63 | ||
|
|
64 | private _pending: MapOf<boolean> = {}; | |
|
|
65 | ||
|
|
66 | create(): ILifetime { | |
|
|
67 | const self = this; | |
|
|
68 | const id = ++nextId; | |
|
|
69 | return { | |
|
|
70 | has() { | |
|
|
71 | return (id in self._cache); | |
|
|
72 | }, | |
|
|
73 | ||
|
|
74 | get() { | |
|
|
75 | const t = self._cache[id]; | |
|
|
76 | if (t === undefined) | |
|
|
77 | throw new Error(`The item with with the key ${id} isn't found`); | |
|
|
78 | return t; | |
|
|
79 | }, | |
|
|
80 | ||
|
|
81 | initialize() { | |
|
|
82 | if (self._pending[id]) | |
|
|
83 | throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`); | |
|
|
84 | self._pending[id] = true; | |
|
|
85 | }, | |
|
|
86 | ||
|
|
87 | store(item: any, cleanup?: (item: any) => void) { | |
|
|
88 | argumentNotNull(id, "id"); | |
|
|
89 | argumentNotNull(item, "item"); | |
|
|
90 | ||
|
|
91 | if (this.has()) | |
|
|
92 | throw new Error(`The item with with the key ${id} already registered with this lifetime manager`); | |
|
|
93 | delete self._pending[id]; | |
|
|
94 | ||
|
|
95 | self._cache[id] = item; | |
|
|
96 | ||
|
|
97 | if (self._destroyed) | |
|
|
98 | throw new Error("Lifetime manager is destroyed"); | |
|
|
99 | if (cleanup) { | |
|
|
100 | self._cleanup.push(() => cleanup(item)); | |
|
|
101 | } else if (isDestroyable(item)) { | |
|
|
102 | self._cleanup.push(() => item.destroy()); | |
|
|
103 | } | |
|
|
104 | } | |
|
|
105 | }; | |
|
|
106 | } | |
|
|
107 | ||
|
|
108 | destroy() { | |
|
|
109 | if (!this._destroyed) { | |
|
|
110 | this._destroyed = true; | |
|
|
111 | this._cleanup.forEach(safeCall); | |
|
|
112 | this._cleanup.length = 0; | |
|
|
113 | } | |
|
|
114 | } | |
|
|
115 | ||
|
|
116 | static empty(): ILifetime { | |
|
|
117 | return emptyLifetime; | |
|
|
118 | } | |
|
|
119 | ||
|
|
120 | static hierarchyLifetime() { | |
|
|
121 | let _lifetime = unknownLifetime; | |
|
|
122 | return { | |
|
|
123 | initialize(context: ActivationContext<any>) { | |
|
|
124 | if (_lifetime !== unknownLifetime) | |
|
|
125 | throw new Error("Cyclic reference activation detected"); | |
|
|
126 | ||
|
|
127 | _lifetime = context.getContainer().getLifetimeManager().create(); | |
|
|
128 | }, | |
|
|
129 | get() { | |
|
|
130 | return _lifetime.get(); | |
|
|
131 | }, | |
|
|
132 | has() { | |
|
|
133 | return _lifetime.has(); | |
|
|
134 | }, | |
|
|
135 | store(item: any, cleanup?: (item: any) => void) { | |
|
|
136 | return _lifetime.store(item, cleanup); | |
|
|
137 | }, | |
|
|
138 | toString() { | |
|
|
139 | return `[object HierarchyLifetime, has=${this.has()}]`; | |
|
|
140 | } | |
|
|
141 | }; | |
|
|
142 | } | |
|
|
143 | ||
|
|
144 | static contextLifetime() { | |
|
|
145 | let _lifetime = unknownLifetime; | |
|
|
146 | return { | |
|
|
147 | initialize(context: ActivationContext<any>) { | |
|
|
148 | if (_lifetime !== unknownLifetime) | |
|
|
149 | throw new Error("Cyclic reference detected"); | |
|
|
150 | _lifetime = context.createLifetime(); | |
|
|
151 | }, | |
|
|
152 | get() { | |
|
|
153 | return _lifetime.get(); | |
|
|
154 | }, | |
|
|
155 | has() { | |
|
|
156 | return _lifetime.has(); | |
|
|
157 | }, | |
|
|
158 | store(item: any) { | |
|
|
159 | _lifetime.store(item); | |
|
|
160 | }, | |
|
|
161 | toString() { | |
|
|
162 | return `[object ContextLifetime, has=${this.has()}]`; | |
|
|
163 | } | |
|
|
164 | }; | |
|
|
165 | } | |
|
|
166 | ||
|
|
167 | static singletonLifetime(typeId: string) { | |
|
|
168 | argumentNotEmptyString(typeId, "typeId"); | |
|
|
169 | let pending = false; | |
|
|
170 | return { | |
|
|
171 | has() { | |
|
|
172 | return typeId in singletons; | |
|
|
173 | }, | |
|
|
174 | get() { | |
|
|
175 | if (!this.has()) | |
|
|
176 | throw new Error(`The instance ${typeId} doesn't exists`); | |
|
|
177 | return singletons[typeId]; | |
|
|
178 | }, | |
|
|
179 | initialize() { | |
|
|
180 | if (pending) | |
|
|
181 | throw new Error("Cyclic reference detected"); | |
|
|
182 | pending = true; | |
|
|
183 | }, | |
|
|
184 | store(item: any) { | |
|
|
185 | singletons[typeId] = item; | |
|
|
186 | pending = false; | |
|
|
187 | }, | |
|
|
188 | toString() { | |
|
|
189 | return `[object SingletonLifetime, has=${this.has()}, typeId=${typeId}]`; | |
|
|
190 | } | |
|
|
191 | }; | |
|
|
192 | } | |
|
|
193 | ||
|
|
194 | static containerLifetime(container: ServiceContainer<any>) { | |
|
|
195 | let _lifetime = unknownLifetime; | |
|
|
196 | return { | |
|
|
197 | initialize(context: ActivationContext<any>) { | |
|
|
198 | if (_lifetime !== unknownLifetime) | |
|
|
199 | throw new Error("Cyclic reference detected"); | |
|
|
200 | _lifetime = container.getLifetimeManager().create(); | |
|
|
201 | }, | |
|
|
202 | get() { | |
|
|
203 | return _lifetime.get(); | |
|
|
204 | }, | |
|
|
205 | has() { | |
|
|
206 | return _lifetime.has(); | |
|
|
207 | }, | |
|
|
208 | store(item: any) { | |
|
|
209 | _lifetime.store(item); | |
|
|
210 | }, | |
|
|
211 | toString() { | |
|
|
212 | return `[object ContainerLifetime, has=${_lifetime.has()}]`; | |
|
|
213 | } | |
|
|
214 | }; | |
|
|
215 | } | |
|
|
216 | } | |
| @@ -0,0 +1,88 | |||
|
|
1 | import { argumentNotEmptyString, each } from "../safe"; | |
|
|
2 | import { ActivationContext } from "./ActivationContext"; | |
|
|
3 | import { Descriptor, PartialServiceMap, TypeOfService, ContainerKeys } from "./interfaces"; | |
|
|
4 | ||
|
|
5 | export interface ReferenceDescriptorParams<S extends object, K extends ContainerKeys<S>> { | |
|
|
6 | /** | |
|
|
7 | * The name of the descriptor | |
|
|
8 | */ | |
|
|
9 | name: K; | |
|
|
10 | ||
|
|
11 | /** | |
|
|
12 | * The flag that indicates that the referenced service isn't required to exist. | |
|
|
13 | * If the reference is optional and the referenced service doesn't exist, | |
|
|
14 | * the undefined or a default value will be returned. | |
|
|
15 | */ | |
|
|
16 | optional?: boolean; | |
|
|
17 | ||
|
|
18 | /** | |
|
|
19 | * a default value for the reference when the referenced service doesn't exist. | |
|
|
20 | */ | |
|
|
21 | default?: TypeOfService<S, K>; | |
|
|
22 | ||
|
|
23 | /** | |
|
|
24 | * The service overrides | |
|
|
25 | */ | |
|
|
26 | services?: PartialServiceMap<S>; | |
|
|
27 | } | |
|
|
28 | ||
|
|
29 | export class ReferenceDescriptor<S extends object = any, K extends ContainerKeys<S> = ContainerKeys<S>> | |
|
|
30 | implements Descriptor<S, TypeOfService<S, K>> { | |
|
|
31 | ||
|
|
32 | _name: K; | |
|
|
33 | ||
|
|
34 | _optional = false; | |
|
|
35 | ||
|
|
36 | _default: TypeOfService<S, K> | undefined; | |
|
|
37 | ||
|
|
38 | _services: PartialServiceMap<S>; | |
|
|
39 | ||
|
|
40 | constructor(opts: ReferenceDescriptorParams<S, K>) { | |
|
|
41 | argumentNotEmptyString(opts && opts.name, "opts.name"); | |
|
|
42 | this._name = opts.name; | |
|
|
43 | this._optional = !!opts.optional; | |
|
|
44 | this._default = opts.default; | |
|
|
45 | ||
|
|
46 | this._services = (opts.services || {}) as PartialServiceMap<S>; | |
|
|
47 | } | |
|
|
48 | ||
|
|
49 | /** This method activates the referenced service if one exists | |
|
|
50 | * @param context activation context which is used during current activation | |
|
|
51 | */ | |
|
|
52 | activate(context: ActivationContext<S>): any { | |
|
|
53 | // добавляем сервисы | |
|
|
54 | if (this._services) { | |
|
|
55 | each(this._services, (v, k) => context.register(k, v)); | |
|
|
56 | } | |
|
|
57 | ||
|
|
58 | const res = this._optional ? | |
|
|
59 | context.resolve(this._name, this._default) : | |
|
|
60 | context.resolve(this._name); | |
|
|
61 | ||
|
|
62 | return res; | |
|
|
63 | } | |
|
|
64 | ||
|
|
65 | toString() { | |
|
|
66 | const opts = []; | |
|
|
67 | if (this._optional) | |
|
|
68 | opts.push("optional"); | |
|
|
69 | ||
|
|
70 | const parts = [ | |
|
|
71 | "@ref " | |
|
|
72 | ]; | |
|
|
73 | if (opts.length) { | |
|
|
74 | parts.push("{"); | |
|
|
75 | parts.push(opts.join()); | |
|
|
76 | parts.push("} "); | |
|
|
77 | } | |
|
|
78 | ||
|
|
79 | parts.push(this._name.toString()); | |
|
|
80 | ||
|
|
81 | if (this._default !== undefined && this._default !== null) { | |
|
|
82 | parts.push(" = "); | |
|
|
83 | parts.push(String(this._default)); | |
|
|
84 | } | |
|
|
85 | ||
|
|
86 | return parts.join(""); | |
|
|
87 | } | |
|
|
88 | } | |
| @@ -0,0 +1,118 | |||
|
|
1 | export type primitive = number | string | null | undefined | symbol; | |
|
|
2 | ||
|
|
3 | export interface IDestroyable { | |
|
|
4 | destroy(): void; | |
|
|
5 | } | |
|
|
6 | ||
|
|
7 | ||
|
|
8 | export interface DependencyOptions { | |
|
|
9 | optional?: boolean; | |
|
|
10 | default?: any; | |
|
|
11 | } | |
|
|
12 | ||
|
|
13 | export interface LazyDependencyOptions extends DependencyOptions { | |
|
|
14 | lazy: true; | |
|
|
15 | } | |
|
|
16 | ||
|
|
17 | export type ExtractService<K, S> = K extends keyof S ? S[K] : never; | |
|
|
18 | ||
|
|
19 | export type ExtractDependency<D, S> = D extends { $dependency: infer K } ? | |
|
|
20 | D extends { lazy: true } ? () => ExtractService<K, S> : ExtractService<K, S> : | |
|
|
21 | D extends { $type: new (...args: any[]) => infer I } ? I : | |
|
|
22 | D extends { $factory: (...args: any[]) => infer R } ? R : | |
|
|
23 | WalkDependencies<D, S>; | |
|
|
24 | ||
|
|
25 | export type WalkDependencies<D, S> = D extends primitive ? D : | |
|
|
26 | { [K in keyof D]: ExtractDependency<D[K], S> }; | |
|
|
27 | ||
|
|
28 | export type InferReferenceType<S extends object, K extends ContainerKeys<S>, O> = O extends { default: infer X } ? (TypeOfService<S, K> | X) : | |
|
|
29 | O extends { optional: true } ? (TypeOfService<S, K> | undefined) : | |
|
|
30 | TypeOfService<S, K>; | |
|
|
31 | ||
|
|
32 | export interface Resolver<S extends object> { | |
|
|
33 | <K extends ContainerKeys<S>, O extends LazyDependencyOptions>(this: void, name: K, opts: O): () => InferReferenceType<S, K, O>; | |
|
|
34 | <K extends ContainerKeys<S>, O extends DependencyOptions>(this: void, name: K, opts?: O): InferReferenceType<S, K, O>; | |
|
|
35 | } | |
|
|
36 | ||
|
|
37 | export interface DescriptorBuilder<S extends object, T> { | |
|
|
38 | factory(f: (resolve: Resolver<S>) => T): void; | |
|
|
39 | ||
|
|
40 | build<T2>(): DescriptorBuilder<S, T2>; | |
|
|
41 | ||
|
|
42 | override<K extends keyof S>(name: K, builder: RegistrationBuilder<S, S[K]>): this; | |
|
|
43 | override<K extends keyof S>(services: { [name in K]: RegistrationBuilder<S, S[K]> }): this; | |
|
|
44 | ||
|
|
45 | lifetime(lifetime: "singleton", typeId: any): this; | |
|
|
46 | lifetime(lifetime: ILifetime | Exclude<ActivationType, "singleton">): this; | |
|
|
47 | ||
|
|
48 | cleanup(cb: (item: T) => void): this; | |
|
|
49 | ||
|
|
50 | value(v: T): void; | |
|
|
51 | } | |
|
|
52 | ||
|
|
53 | export type RegistrationBuilder<S extends object, T> = (d: DescriptorBuilder<S, T>, ct?: ICancellation) => void | Promise<void>; | |
|
|
54 | ||
|
|
55 | export type FluentRegistrations<K extends keyof S, S extends object> = { [k in K]: RegistrationBuilder<S, S[k]> }; | |
|
|
56 | ||
|
|
57 | export interface Descriptor<S extends object = any, T = any> { | |
|
|
58 | activate(context: ActivationContext<S>): T; | |
|
|
59 | } | |
|
|
60 | ||
|
|
61 | export type ServiceMap<S extends object> = { | |
|
|
62 | [k in keyof S]: Descriptor<S, S[k]>; | |
|
|
63 | }; | |
|
|
64 | ||
|
|
65 | export type ContainerKeys<S extends object> = keyof S | keyof ContainerProvided<S>; | |
|
|
66 | ||
|
|
67 | export type TypeOfService<S extends object, K> = | |
|
|
68 | K extends keyof ContainerProvided<S> ? ContainerProvided<S>[K] : | |
|
|
69 | K extends keyof S ? S[K] : never; | |
|
|
70 | ||
|
|
71 | export type ContainerServiceMap<S extends object> = { | |
|
|
72 | [K in ContainerKeys<S>]: Descriptor<S, TypeOfService<S, K>>; | |
|
|
73 | }; | |
|
|
74 | ||
|
|
75 | export type PartialServiceMap<S extends object> = { | |
|
|
76 | [k in keyof S]?: Descriptor<S, S[k]>; | |
|
|
77 | }; | |
|
|
78 | ||
|
|
79 | export interface ServiceLocator<S extends object> { | |
|
|
80 | resolve<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>): TypeOfService<S, K>; | |
|
|
81 | resolve<K extends ContainerKeys<S>>(name: K, def?: undefined): TypeOfService<S, K> | undefined; | |
|
|
82 | } | |
|
|
83 | ||
|
|
84 | export interface ServiceContainer<S extends object> extends ServiceLocator<S>, IDestroyable { | |
|
|
85 | getLifetimeManager(): LifetimeManager; | |
|
|
86 | register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>): this; | |
|
|
87 | register(services: PartialServiceMap<S>): this; | |
|
|
88 | ||
|
|
89 | createChildContainer(): ServiceContainer<S>; | |
|
|
90 | } | |
|
|
91 | ||
|
|
92 | export interface ContainerProvided<S extends object> { | |
|
|
93 | container: ServiceLocator<S>; | |
|
|
94 | ||
|
|
95 | childContainer: ServiceContainer<S>; | |
|
|
96 | } | |
|
|
97 | ||
|
|
98 | export type ContainerRegistered<S extends object> = /*{ | |
|
|
99 | [K in Exclude<keyof S, keyof ContainerProvided<S>>]: S[K]; | |
|
|
100 | };*/ | |
|
|
101 | Exclude<S, ContainerProvided<S>>; | |
|
|
102 | ||
|
|
103 | export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call"; | |
|
|
104 | ||
|
|
105 | /** | |
|
|
106 | * Интерфейс для управления жизнью экземпляра объекта. Каждая регистрация имеет | |
|
|
107 | * свой собственный объект `ILifetime`, который создается при первой активации | |
|
|
108 | */ | |
|
|
109 | export interface ILifetime { | |
|
|
110 | /** Проверяет, что уже создан экземпляр объекта */ | |
|
|
111 | has(): boolean; | |
|
|
112 | ||
|
|
113 | get(): any; | |
|
|
114 | ||
|
|
115 | initialize(context: ActivationContext<any>): void; | |
|
|
116 | ||
|
|
117 | store(item: any, cleanup?: (item: any) => void): void; | |
|
|
118 | } | |
| @@ -0,0 +1,6 | |||
|
|
1 | import { primitive } from "./interfaces"; | |
|
|
2 | ||
|
|
3 | export function isPrimitive(val: any): val is primitive { | |
|
|
4 | return (val === null || val === undefined || typeof (val) === "string" || | |
|
|
5 | typeof (val) === "number" || typeof (val) === "boolean"); | |
|
|
6 | } No newline at end of file | |
| @@ -0,0 +1,9 | |||
|
|
1 | { | |
|
|
2 | "extends": "../tsconfig", | |
|
|
3 | "compilerOptions": { | |
|
|
4 | "rootDir": "ts" | |
|
|
5 | }, | |
|
|
6 | "include": [ | |
|
|
7 | "ts/**/*.ts" | |
|
|
8 | ] | |
|
|
9 | } No newline at end of file | |
| @@ -0,0 +1,12 | |||
|
|
1 | { | |
|
|
2 | "compilerOptions": { | |
|
|
3 | "moduleResolution": "node", | |
|
|
4 | "experimentalDecorators": true, | |
|
|
5 | "noEmitOnError": true, | |
|
|
6 | "listFiles": true, | |
|
|
7 | "strict": true, | |
|
|
8 | "types": [], | |
|
|
9 | "target": "ES5", | |
|
|
10 | "lib": ["es5", "es2015.promise", "es2015.symbol", "es2015.iterable", "dom", "scripthost"] | |
|
|
11 | } | |
|
|
12 | } No newline at end of file | |
General Comments 0
You need to be logged in to leave comments.
Login now
