##// END OF EJS Templates
initial commit
cin -
r0:9cf129973079 default
parent child
Show More
@@ -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,3
1 syntax: glob
2 .gradle/
3 build/
@@ -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