##// END OF EJS Templates
working on fluent configuration
cin -
r1:a51ea59f0423 default
parent child
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -0,0 +1,3
1 {
2 "java.configuration.updateBuildConfiguration": "automatic"
3 } No newline at end of file
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -0,0 +1,19
1 import { ActivationContext } from "./ActivationContext";
2
3 export class ContextResolver<S extends object> {
4 private readonly _context: ActivationContext<S>;
5
6 constructor(context: ActivationContext<S>) {
7 this._context = context;
8 }
9
10 resolve<K extends keyof S, O extends { lazy: true}>(name: K, opts?: O): () => (O extends { default: infer T } ? T : never) | NonNullable<S[K]>;
11 resolve<K extends keyof S, O extends { lazy?: false}>(name: K, opts?: O): (O extends { default: infer T } ? T : never) | NonNullable<S[K]>;
12 resolve(name: keyof S, opts?: {lazy?: boolean, default?: unknown}) {
13 if (opts && opts.lazy) {
14 return () => "default" in opts ? this._context.resolve(name, opts.default) : this._context.resolve(name);
15 } else {
16 return opts && "default" in opts ? this._context.resolve(name, opts.default) : this._context.resolve(name);
17 }
18 }
19 }
@@ -0,0 +1,1
1 import "./t/container"; No newline at end of file
@@ -0,0 +1,58
1 /* eslint max-classes-per-file: ["error", 20] */
2 import { describe, it } from "mocha";
3 import {LifetimeManager} from "../LifetimeManager";
4 import {Container} from "../Container";
5 import { fluent } from "../traits";
6
7 class Foo {
8 foo = "foo";
9 }
10
11 class Bar {
12 bar = "bar";
13
14 constructor(foo?: () => Foo) {}
15 }
16
17 interface Services {
18 foo: Foo;
19
20 bar?: Bar;
21
22 baz: Foo;
23 }
24
25 interface ServicesB {
26 // will give errors
27 // baz: Bar;
28
29 baz: Foo;
30
31 zoo?: Foo;
32 }
33
34 interface SharedServices {
35 foo: Foo;
36
37 bar?: Bar;
38
39 baz: Bar;
40 }
41
42 const config = fluent()
43 .declare<Services>()
44 .declare<ServicesB>()
45 .register({
46 bar: it => it
47 .lifetime("context")
48 .factory($ => new Bar($("zoo", {lazy: true, default: new Foo()}))),
49 foo: it => it.factory($ => new Foo()),
50 baz: it => it.value(new Foo())
51 })
52 .done();
53
54 declare const container: Container<SharedServices>;
55
56 const c2 = config.apply(container);
57
58 c2.resolve("baz"); No newline at end of file
@@ -0,0 +1,9
1 {
2 "extends": "../tsconfig",
3 "compilerOptions": {
4 "rootDirs": [
5 "ts",
6 "../main/ts"
7 ]
8 }
9 } No newline at end of file
@@ -0,0 +1,10
1 {
2 "extends": "./tsconfig.json",
3 "compilerOptions": {
4 // ensure that nobody can accidentally use this config for a build
5 "noEmit": true
6 },
7 "include": [
8 "ts"
9 ]
10 } No newline at end of file
@@ -1,27 +1,32
1 {
1 {
2 "env": {
2 "root": true,
3 "browser": true,
3 "extends": [
4 "commonjs": true,
4 "plugin:react/recommended",
5 "amd": true,
5 "eslint:recommended",
6 "node": true
6 "plugin:@typescript-eslint/eslint-recommended",
7 },
7 "plugin:@typescript-eslint/recommended",
8 "plugin:@typescript-eslint/recommended-requiring-type-checking"
9 ],
10 "parser": "@typescript-eslint/parser",
8 "parserOptions": {
11 "parserOptions": {
9 "ecmaFeatures": {
12 "ecmaFeatures": {
10 "jsx": true
13 "jsx": true
11 },
14 },
12 "sourceType": "script"
15 "ecmaVersion": 5,
16 "tsconfigRootDir": "src",
17 "project": ["tsconfig.eslint.json", "*/tsconfig.json"]
13 },
18 },
14 "extends": "eslint:recommended",
19 "plugins": [
20 "@typescript-eslint"
21 ],
15 "rules": {
22 "rules": {
16 "no-const-assign": "warn",
23 "@typescript-eslint/no-empty-function": "off",
17 "no-this-before-super": "warn",
24 "max-classes-per-file": [
18 "no-undef": "error",
25 "error",
19 "no-unreachable": "warn",
26 { "ignoreExpressions": true, "max": 1 }
20 "no-unused-vars": "warn",
27 ],
21 "constructor-super": "warn",
28 "@typescript-eslint/prefer-readonly": ["error"],
22 "valid-typeof": "warn",
29 "@typescript-eslint/semi": ["error"]
23 "semi" : "warn",
30
24 "no-invalid-this" : "error",
25 "no-console": "off"
26 }
31 }
27 } No newline at end of file
32 }
@@ -1,3 +1,4
1 syntax: glob
1 syntax: glob
2 .gradle/
2 .gradle/
3 build/
3 build/
4 node_modules/
@@ -30,23 +30,9 typescript {
30 declaration = true
30 declaration = true
31 experimentalDecorators = true
31 experimentalDecorators = true
32 strict = 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"
33 module = "commonjs"
43 target = "es2017"
44 lib = ["es2017", "dom", "scripthost"]
45 } else if (flavour == 'browser') {
46 module = "amd"
47 target = "es5"
34 target = "es5"
48 lib = ["es5", "dom", "scripthost", "es2015.promise", "es2015.symbol", "es2015.iterable" ]
35 lib = ["es2015", "dom", "scripthost"]
49 }
50 }
36 }
51 tscCmd = "$projectDir/node_modules/.bin/tsc"
37 tscCmd = "$projectDir/node_modules/.bin/tsc"
52 tsLintCmd = "$projectDir/node_modules/.bin/tslint"
38 tsLintCmd = "$projectDir/node_modules/.bin/tslint"
@@ -65,6 +51,10 if (symbols == 'local') {
65 }
51 }
66 }
52 }
67
53
54 task packSources {
55
56 }
57
68 printVersion {
58 printVersion {
69 doLast {
59 doLast {
70 println "packageName: $packageName";
60 println "packageName: $packageName";
@@ -75,3 +65,7 printVersion {
75 println "symbols: $symbols";
65 println "symbols: $symbols";
76 }
66 }
77 }
67 }
68
69 test {
70 commandLine "npx", "mocha", "${->typescript.testDir.get()}";
71 } No newline at end of file
@@ -1,31 +1,32
1 {
1 {
2 "name": "@implab/di",
2 "name": "@implab/di",
3 "version": "0.0.1-dev",
3 "version": "0.0.1-dev",
4 "description": "Dependency injection, logging, simple and fast text template engine",
4 "description": "The lightweight dependency injection IoC",
5 "main": "main.js",
5 "main": "main.js",
6 "keywords": [
6 "keywords": [
7 "di",
7 "di",
8 "ioc",
8 "ioc",
9 "logging",
10 "template engine",
11 "dependency injection"
9 "dependency injection"
12 ],
10 ],
13 "author": "Implab team",
11 "author": "Implab team",
14 "license": "BSD-2-Clause",
12 "license": "BSD-2-Clause",
15 "repository": "https://bitbucket.org/implab/implabjs",
13 "repository": "https://code.implab.org/implab/implabjs-di",
16 "peerDependencies": {
17 "dojo": "^1.10.0"
18 },
19 "devDependencies": {
14 "devDependencies": {
20 "@types/node": "^8.0.0",
15 "@types/chai": "4.3.1",
21 "@types/requirejs": "~2.1.31",
16 "@types/mocha": "9.1.1",
22 "@types/tape": "~4.2.33",
17 "@types/node": "~8.0.0",
23 "dojo": "~1.10.0",
18 "@types/tape": "~4.13.2",
24 "dojo-typings": "^1.11.9",
19 "@typescript-eslint/eslint-plugin": "^5.23.0",
25 "eslint": "6.1.0",
20 "@typescript-eslint/parser": "^5.23.0",
26 "requirejs": "latest",
21 "chai": "~4.3.6",
27 "tape": "~4.11.0",
22 "eslint": "^8.15.0",
28 "tslint": "5.18.0",
23 "eslint-config-standard": "^17.0.0",
29 "typescript": "~4.1.5"
24 "eslint-plugin-import": "^2.26.0",
25 "eslint-plugin-n": "^15.2.0",
26 "eslint-plugin-promise": "^6.0.0",
27 "eslint-plugin-react": "^7.29.4",
28 "mocha": "~10.0.0",
29 "tape": "~5.5.3",
30 "typescript": "~4.6.4"
30 }
31 }
31 }
32 }
@@ -1,27 +1,3
1 # Implabjs-core
1 # Implabjs-di
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
2
14 ### DI
3 Простой контейнер для внедрения зависимостей
15
16 Контейнер для внедрения зависимостей, позволяет гибко описывать структуру
17 приложения и создавать слабосвязанные компоненты.
18
19 ### LOG
20
21 Средства журналирования похожие на JLog, позволяют эффективно вести журнал
22 выполнения программы.
23
24 ### Cancellations
25
26 Специальные маркеры для отмены асинхронных операций, по аналогии с .NET
27 CancelationToken.
@@ -1,9 +1,5
1 import { TraceSource } from "../log/TraceSource";
1 import { Descriptor, ILifetime, RegistrationMap, LifetimeContainer, ConfigurableKeys } from "./interfaces";
2 import { argumentNotEmptyString } from "../safe";
2 import { argumentNotNull } from "./traits";
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
3
8 export interface ActivationContextInfo {
4 export interface ActivationContextInfo {
9 name: string;
5 name: string;
@@ -19,35 +15,32 let nextId = 1;
19 * tracks services with `context` activation type.
15 * tracks services with `context` activation type.
20 */
16 */
21 export class ActivationContext<S extends object> {
17 export class ActivationContext<S extends object> {
22 _cache: MapOf<any>;
18 private _cache: { [K: string]: unknown };
23
19
24 _services: ContainerServiceMap<S>;
20 private _services: Partial<RegistrationMap<S>>;
25
21
26 _visited: MapOf<any>;
22 private _name: string;
27
23
28 _name: string;
24 private _service: Descriptor<S, unknown>;
29
30 _service: Descriptor<S, any>;
31
25
32 _container: ServiceContainer<S>;
26 private readonly _containerLifetimeManager: LifetimeContainer;
33
27
34 _parent: ActivationContext<S> | undefined;
28 private _parent: ActivationContext<S> | undefined;
35
29
36 /** Creates a new activation context with the specified parameters.
30 /** Creates a new activation context with the specified parameters.
37 * @param container the container which starts the activation process
31 * @param containerLifetimeManager the container which starts the activation process
38 * @param services the initial service registrations
32 * @param services the initial service registrations
39 * @param name the name of the service being activated, this parameter is
33 * @param name the name of the service being activated, this parameter is
40 * used for the debug purpose.
34 * used for the debug purpose.
41 * @param service the service to activate, this parameter is used for the
35 * @param service the service to activate, this parameter is used for the
42 * debug purpose.
36 * debug purpose.
43 */
37 */
44 constructor(container: ServiceContainer<S>, services: ContainerServiceMap<S>, name: string, service: Descriptor<S, any>) {
38 constructor(containerLifetimeManager: LifetimeContainer, services: Partial<RegistrationMap<S>>, name: string, service: Descriptor<S, unknown>) {
45 this._name = name;
39 this._name = name;
46 this._service = service;
40 this._service = service;
47 this._visited = {};
48 this._cache = {};
41 this._cache = {};
49 this._services = services;
42 this._services = services;
50 this._container = container;
43 this._containerLifetimeManager = containerLifetimeManager;
51 }
44 }
52
45
53 /** the name of the current resolving dependency */
46 /** the name of the current resolving dependency */
@@ -55,15 +48,14 export class ActivationContext<S extends
55 return this._name;
48 return this._name;
56 }
49 }
57
50
58 /** Returns the container for which 'resolve' method was called */
51 createContainerLifetime<T>() {
59 getContainer() {
52 return this._containerLifetimeManager.createLifetime<T>();
60 return this._container;
61 }
53 }
62
54
63 /** Resolves the specified dependency in the current context
55 /** Resolves the specified dependency in the current context
64 * @param name The name of the dependency being resolved
56 * @param name The name of the dependency being resolved
65 */
57 */
66 resolve<K extends ContainerKeys<S>>(name: K): TypeOfService<S, K>;
58 resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
67 /** Resolves the specified dependency with the specified default value if
59 /** Resolves the specified dependency with the specified default value if
68 * the dependency is missing.
60 * the dependency is missing.
69 *
61 *
@@ -71,14 +63,8 export class ActivationContext<S extends
71 * @param def A default value to return in case of the specified dependency
63 * @param def A default value to return in case of the specified dependency
72 * is missing.
64 * is missing.
73 */
65 */
74 resolve<K extends ContainerKeys<S>, T>(name: K, def: T): TypeOfService<S, K> | T;
66 resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T;
75 /** Resolves the specified dependency and returns undefined in case if the
67 resolve<K extends keyof S, T>(name: K, def?: T): S[K] | T | undefined {
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];
68 const d = this._services[name];
83
69
84 if (d !== undefined) {
70 if (d !== undefined) {
@@ -87,7 +73,7 export class ActivationContext<S extends
87 if (arguments.length > 1)
73 if (arguments.length > 1)
88 return def;
74 return def;
89 else
75 else
90 throw new Error(`Service ${name} not found`);
76 throw new Error(`Service ${String(name)} not found`);
91 }
77 }
92 }
78 }
93
79
@@ -97,49 +83,38 export class ActivationContext<S extends
97 * @name{string} the name of the service
83 * @name{string} the name of the service
98 * @service{string} the service descriptor to register
84 * @service{string} the service descriptor to register
99 */
85 */
100 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>) {
86 register<K extends ConfigurableKeys<S>>(name: K, service: RegistrationMap<S>[K]) {
101 argumentNotEmptyString(name, "name");
87 argumentNotNull(name, "name");
102
88
103 this._services[name] = service as any;
89 this._services[name] = service;
104 }
90 }
105
91
106 createLifetime(): ILifetime {
92 createLifetime<T>(): ILifetime<T> {
107 const id = nextId++;
93 const id = nextId++;
108 const me = this;
109 return {
94 return {
110 initialize() {
95 initialize() {},
111 },
96 has: () => id in this._cache,
112 has() {
97 get: () => this._cache[id] as T,
113 return id in me._cache;
98 store: item => {
114 },
99 this._cache[id] = item;
115 get() {
116 return me._cache[id];
117 },
118 store(item: any) {
119 me._cache[id] = item;
120 }
100 }
121 };
101 };
122 }
102 }
123
103
124 activate<T>(d: Descriptor<S, T>, name: string) {
104 activate<T>(d: Descriptor<S, T>, name: string) {
125 if (trace.isLogEnabled())
105 // TODO: add logging
126 trace.log("enter {0} {1}", name, d);
106 // if (trace.isLogEnabled())
107 // trace.log("enter {0} {1}", name, d);
127
108
128 const ctx = this.enter(d, name);
109 const ctx = this.enter(d, name);
129 const v = d.activate(ctx);
110 const v = d.activate(ctx);
130
111
131 if (trace.isLogEnabled())
112 // if (trace.isLogEnabled())
132 trace.log(`leave ${name}`);
113 // trace.log(`leave ${name}`);
133
114
134 return v;
115 return v;
135 }
116 }
136
117
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[] {
118 getStack(): ActivationContextInfo[] {
144 const stack = [{
119 const stack = [{
145 name: this._name,
120 name: this._name,
@@ -151,10 +126,10 export class ActivationContext<S extends
151 stack;
126 stack;
152 }
127 }
153
128
154 private enter(service: Descriptor<S, any>, name: string): this {
129 private enter(service: Descriptor<S, unknown>, name: string): this {
155 const clone = Object.create(this);
130 const clone = Object.create(this) as this;
156 clone._name = name;
131 clone._name = name;
157 clone._services = Object.create(this._services);
132 clone._services = Object.create(this._services) as typeof this._services;
158 clone._parent = this;
133 clone._parent = this;
159 clone._service = service;
134 clone._service = service;
160 return clone;
135 return clone;
@@ -162,8 +137,8 export class ActivationContext<S extends
162
137
163 /** Creates a clone for the current context, used to protect it from modifications */
138 /** Creates a clone for the current context, used to protect it from modifications */
164 clone(): this {
139 clone(): this {
165 const clone = Object.create(this);
140 const clone = Object.create(this) as this;
166 clone._services = Object.create(this._services);
141 clone._services = Object.create(this._services) as typeof this._services;
167 return clone;
142 return clone;
168 }
143 }
169 }
144 }
@@ -8,11 +8,11 export class ActivationError {
8
8
9 service: string;
9 service: string;
10
10
11 innerException: any;
11 innerException: unknown;
12
12
13 message: string;
13 message: string;
14
14
15 constructor(service: string, activationStack: ActivationItem[], innerException: any) {
15 constructor(service: string, activationStack: ActivationItem[], innerException: unknown) {
16 this.message = "Failed to activate the service";
16 this.message = "Failed to activate the service";
17 this.activationStack = activationStack;
17 this.activationStack = activationStack;
18 this.service = service;
18 this.service = service;
@@ -22,15 +22,17 export class ActivationError {
22 toString() {
22 toString() {
23 const parts = [this.message];
23 const parts = [this.message];
24 if (this.service)
24 if (this.service)
25 parts.push("when activating: " + this.service.toString());
25 parts.push(`when activating: ${String(this.service)}`);
26
26
27 if (this.innerException)
27 if (this.innerException)
28 parts.push("caused by: " + this.innerException.toString());
28 parts.push(`caused by: ${String(this.innerException)}`);
29
29
30 if (this.activationStack) {
30 if (this.activationStack) {
31 parts.push("at");
31 parts.push("at");
32 parts.push.apply(null,
32 this.activationStack
33 this.activationStack
33 .forEach(x => parts.push(` ${x.name} ${x.service}`));
34 .map(({ name, service }) => ` ${name} ${service}`)
35 );
34
36
35 }
37 }
36
38
@@ -1,67 +1,58
1 import { ActivationContext } from "./ActivationContext";
1 import { ActivationContext } from "./ActivationContext";
2 import { ActivationError } from "./ActivationError";
2 import { ActivationError } from "./ActivationError";
3 import { ServiceMap, Descriptor, PartialServiceMap, ContainerServiceMap, ContainerKeys, TypeOfService, ServiceContainer } from "./interfaces";
3 import { RegistrationMap, ContainerKeys, ServiceContainer, ContainerServices, ConfigurableServices, ConfigurableKeys, ServiceLocator, Descriptor} 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";
4 import { LifetimeManager } from "./LifetimeManager";
10 import { each, isString } from "../safe";
5 import { each, isKey } from "./traits";
11 import { ContainerConfiguration, FluentRegistrations } from "./fluent/interfaces";
12 import { FluentConfiguration } from "./fluent/FluentConfiguration";
13
14 const trace = TraceSource.get("@implab/core/di/ActivationContext");
15
6
16 export class Container<S extends object = any> implements ServiceContainer<S> {
7 export class Container<S extends object > implements ServiceContainer<S> {
17 readonly _services: ContainerServiceMap<S>;
8 private readonly _services: Partial<RegistrationMap<ContainerServices<S>>>;
18
19 readonly _lifetimeManager: LifetimeManager;
20
9
21 readonly _cleanup: (() => void)[];
10 private readonly _lifetimeManager: LifetimeManager;
22
23 readonly _root: Container<S>;
24
11
25 readonly _parent?: Container<S>;
12 private readonly _cleanup: (() => void)[];
26
13
27 _disposed: boolean;
14 private _disposed: boolean;
28
15
29 constructor(parent?: Container<S>) {
16 constructor(parent?: Container<S>) {
30 this._parent = parent;
17 this._services = Object.create(parent ? parent._services : null) as typeof this._services;
31 this._services = Object.create(parent ? parent._services : null);
32 this._cleanup = [];
18 this._cleanup = [];
33 this._root = parent ? parent.getRootContainer() : this;
19 this._services.container = { activate: () => this as ServiceLocator<ContainerServices<S>>};
34 this._services.container = { activate: () => this };
35 this._services.childContainer = { activate: () => this.createChildContainer() };
20 this._services.childContainer = { activate: () => this.createChildContainer() };
36 this._disposed = false;
21 this._disposed = false;
37 this._lifetimeManager = new LifetimeManager();
22 this._lifetimeManager = new LifetimeManager();
38 }
23 }
39
24
40 getRootContainer() {
25 register<K extends ConfigurableKeys<S>>(name: K, service: Descriptor<ContainerServices<S>, ConfigurableServices<S>[K]>): void;
41 return this._root;
26 register<K extends ConfigurableKeys<S>>(services: {[k in K]: Descriptor<ContainerServices<S>, ConfigurableServices<S>[k]>}): void;
27 register<K extends ConfigurableKeys<S>>(nameOrCollection: K | {[k in K]: Descriptor<ContainerServices<S>, ConfigurableServices<S>[k]>}, service?: RegistrationMap<ContainerServices<S>>[K]) {
28 if (!isKey(nameOrCollection)) {
29 each(nameOrCollection, (v, k) => this.register(k, v));
30 } else {
31 if (!service)
32 throw new Error("The service parameter must be a descriptor");
33
34 this._services[nameOrCollection] = service;
42 }
35 }
43
44 getParent() {
45 return this._parent;
46 }
36 }
47
37
48 getLifetimeManager() {
38 createChildContainer(): ServiceContainer<S> {
49 return this._lifetimeManager;
39 return new Container(this);
50 }
40 }
51
41
52 resolve<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>): TypeOfService<S, K>;
42 resolve<K extends ContainerKeys<S>>(name: K): NonNullable<ContainerServices<S>[K]>;
53 resolve<K extends ContainerKeys<S>>(name: K, def: undefined): TypeOfService<S, K> | undefined;
43 resolve<K extends ContainerKeys<S>>(name: K, def: ContainerServices<S>[K]): ContainerServices<S>[K];
54 resolve<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>): TypeOfService<S, K> | undefined {
44 resolve<K extends ContainerKeys<S>>(name: K, def?: ContainerServices<S>[K]): ContainerServices<S>[K] | undefined {
55 trace.debug("resolve {0}", name);
45 // TODO: add logging
46 // trace.debug("resolve {0}", name);
56 const d = this._services[name];
47 const d = this._services[name];
57 if (d === undefined) {
48 if (d === undefined) {
58 if (arguments.length > 1)
49 if (arguments.length > 1)
59 return def;
50 return def;
60 else
51 else
61 throw new Error("Service '" + name + "' isn't found");
52 throw new Error(`Service '${String(name)}' isn't found`);
62 } else {
53 } else {
63
54
64 const context = new ActivationContext<S>(this, this._services, String(name), d);
55 const context = new ActivationContext(this, this._services, String(name), d);
65 try {
56 try {
66 return d.activate(context);
57 return d.activate(context);
67 } catch (error) {
58 } catch (error) {
@@ -70,34 +61,8 export class Container<S extends object
70 }
61 }
71 }
62 }
72
63
73 /**
64 createLifetime<T>() {
74 * @deprecated use resolve() method
65 return this._lifetimeManager.create<T>();
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 }
66 }
102
67
103 destroy() {
68 destroy() {
@@ -111,66 +76,4 export class Container<S extends object
111 f();
76 f();
112 this._lifetimeManager.destroy();
77 this._lifetimeManager.destroy();
113 }
78 }
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 }
79 }
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 }
@@ -1,17 +1,22
1 import { Resolver, RegistrationBuilder } from "./interfaces";
1 import { Resolver, RegistrationBuilder, LifetimeContainer, ConfigurableKeys } from "./interfaces";
2 import { Descriptor, ILifetime, ActivationType, PartialServiceMap, ServiceContainer } from "./interfaces";
2 import { Descriptor, ILifetime, ActivationType } from "./interfaces";
3 import { DescriptorImpl } from "./DescriptorImpl";
3 import { DescriptorImpl, RegistrationOverridesMap } from "./DescriptorImpl";
4 import { LifetimeManager } from "./LifetimeManager";
4 import { LifetimeManager } from "./LifetimeManager";
5 import { each, isKey, isPromise, isString, oid } from "./traits";
5
6
7 /**
8 * @template {S} Карта доступных зависимостей, как правило `ContainerServices`
9 * @template {T} Тип сервиса
10 */
6 export class DescriptorBuilder<S extends object, T> {
11 export class DescriptorBuilder<S extends object, T> {
7 private readonly _container: ServiceContainer<S>;
12 private readonly _lifetimeContainer: LifetimeContainer;
8 private readonly _cb: (d: Descriptor<S, T>) => void;
13 private readonly _cb: (d: Descriptor<S, T>) => void;
9
14
10 private readonly _eb: (err: any) => void;
15 private readonly _eb: (err: unknown) => void;
11
16
12 private _lifetime = LifetimeManager.empty();
17 private _lifetime = LifetimeManager.empty<T>();
13
18
14 private _overrides?: PartialServiceMap<S>;
19 private _overrides: RegistrationOverridesMap<S>;
15
20
16 private _cleanup?: (item: T) => void;
21 private _cleanup?: (item: T) => void;
17
22
@@ -21,36 +26,48 export class DescriptorBuilder<S extends
21
26
22 private _failed = false;
27 private _failed = false;
23
28
24 constructor(container: ServiceContainer<S>, cb: (d: Descriptor<S, T>) => void, eb: (err: any) => void) {
29 private _finalized = false;
25 this._container = container;
30
31 /**
32 * Creates new DescriptorBuilder. Accepts a lifetime container for resolving "container"
33 * lifetime.
34 *
35 * @param container The lifetime container is the container where the service is to be registered.
36 * @param cb The callback to receive the built service descriptor
37 * @param eb The callback to receive the error due
38 */
39 constructor(container: LifetimeContainer, cb: (d: Descriptor<S, T>) => void, eb: (err: unknown) => void) {
40 this._lifetimeContainer = container;
26 this._cb = cb;
41 this._cb = cb;
27 this._eb = eb;
42 this._eb = eb;
43 this._overrides = {};
28 }
44 }
29
45
30 build<T2>(): DescriptorBuilder<S, T2> {
46 private _assertBuilding() {
31 this._defer();
47 if (this._finalized)
32 return new DescriptorBuilder<S, T2>(this._container, () => this._complete(), err => this._fail(err));
48 throw new Error("The descriptor builder is finalized");
33 }
49 }
34
50
35 override<K extends keyof S>(name: K, builder: RegistrationBuilder<S, S[K]>): this;
51 private _finalize() {
36 override<K extends keyof S>(services: { [name in K]: RegistrationBuilder<S, S[K]> }): this;
52 this._finalized = true;
37 override<K extends keyof S>(nameOrServices: K | { [name in K]: RegistrationBuilder<S, S[K]> }, builder?: RegistrationBuilder<S, S[K]>): this {
53 }
38 const overrides: PartialServiceMap<S> = this._overrides ?
39 this._overrides :
40 (this._overrides = {});
41
54
55 override<K extends ConfigurableKeys<S>>(name: K, builder: RegistrationBuilder<S, NonNullable<S[K]>>): this;
56 override<K extends ConfigurableKeys<S>>(services: { [k in K]: RegistrationBuilder<S, NonNullable<S[k]>> }): this;
57 override<K extends ConfigurableKeys<S>>(nameOrServices: K | { [name in K]: RegistrationBuilder<S, NonNullable<S[K]>> }, builder?: RegistrationBuilder<S, NonNullable<S[K]>>): this {
58 this._assertBuilding();
42 const guard = (v: void | Promise<void>) => {
59 const guard = (v: void | Promise<void>) => {
43 if (isPromise(v))
60 if (isPromise(v))
44 v.catch(err => this._fail(err));
61 v.catch(err => this._fail(err));
45 };
62 };
46
63
47 if (isPrimitive(nameOrServices)) {
64 if (isKey(nameOrServices)) {
48 if (builder) {
65 if (builder) {
49 this._defer();
66 this._defer();
50 const d = new DescriptorBuilder<S, S[K]>(
67 const d = new DescriptorBuilder<S, NonNullable<S[K]>>(
51 this._container,
68 this._lifetimeContainer,
52 result => {
69 result => {
53 overrides[nameOrServices] = result;
70 this._overrides[nameOrServices] = result;
54 this._complete();
71 this._complete();
55 },
72 },
56 err => this._fail(err)
73 err => this._fail(err)
@@ -69,8 +86,9 export class DescriptorBuilder<S extends
69 }
86 }
70
87
71 lifetime(lifetime: "singleton", typeId: string): this;
88 lifetime(lifetime: "singleton", typeId: string): this;
72 lifetime(lifetime: ILifetime | Exclude<ActivationType, "singleton">): this;
89 lifetime(lifetime: ILifetime<T> | Exclude<ActivationType, "singleton">): this;
73 lifetime(lifetime: ILifetime | ActivationType, typeId?: string): this {
90 lifetime(lifetime: ILifetime<T> | ActivationType, typeId?: string): this {
91 this._assertBuilding();
74 if (isString(lifetime)) {
92 if (isString(lifetime)) {
75 this._lifetime = this._resolveLifetime(lifetime, typeId);
93 this._lifetime = this._resolveLifetime(lifetime, typeId);
76 } else {
94 } else {
@@ -80,38 +98,44 export class DescriptorBuilder<S extends
80 }
98 }
81
99
82 cleanup(cb: (item: T) => void): this {
100 cleanup(cb: (item: T) => void): this {
101 this._assertBuilding();
83 this._cleanup = cb;
102 this._cleanup = cb;
84 return this;
103 return this;
85 }
104 }
86
105
87 factory(f: (resolve: Resolver<S>) => T): void {
106 factory(f: (resolve: Resolver<S>) => T): void {
107 this._assertBuilding();
88 this._factory = f;
108 this._factory = f;
109 this._finalize();
89 this._complete();
110 this._complete();
90 }
111 }
91
112
92 value(v: T): void {
113 value(v: T): void {
114 this._assertBuilding();
93 this._cb({
115 this._cb({
94 activate() {
116 activate() {
95 return v;
117 return v;
96 }
118 }
97 });
119 });
120 this._finalize();
98 }
121 }
99
122
100 _resolveLifetime(activation: ActivationType, typeId?: string | object) {
123 _resolveLifetime<T>(activation: ActivationType, typeId?: string | object): ILifetime<T> {
101 switch (activation) {
124 switch (activation) {
102 case "container":
125 case "container":
103 return LifetimeManager.containerLifetime(this._container);
126 return LifetimeManager.containerLifetime(this._lifetimeContainer);
104 case "hierarchy":
127 case "hierarchy":
105 return LifetimeManager.hierarchyLifetime();
128 return LifetimeManager.hierarchyLifetime();
106 case "context":
129 case "context":
107 return LifetimeManager.contextLifetime();
130 return LifetimeManager.contextLifetime();
108 case "singleton":
131 case "singleton": {
109 if (!typeId)
132 if (!typeId)
110 throw Error("The singleton activation requires a typeId");
133 throw Error("The singleton activation requires a typeId");
111
134
112 const _oid = isString(typeId) ? typeId : oid(typeId);
135 const _oid = isString(typeId) ? typeId : oid(typeId);
113
136
114 return LifetimeManager.singletonLifetime(_oid);
137 return LifetimeManager.singletonLifetime(_oid);
138 }
115 default:
139 default:
116 return LifetimeManager.empty();
140 return LifetimeManager.empty();
117 }
141 }
@@ -135,7 +159,7 export class DescriptorBuilder<S extends
135 }
159 }
136 }
160 }
137
161
138 _fail(err: any) {
162 _fail(err: unknown) {
139 if (!this._failed) {
163 if (!this._failed) {
140 this._failed = true;
164 this._failed = true;
141 this._eb.call(undefined, err);
165 this._eb.call(undefined, err);
@@ -1,23 +1,26
1 import { Descriptor, PartialServiceMap, ILifetime, ContainerKeys } from "../interfaces";
1 import { Descriptor, ILifetime, ConfigurableKeys, Resolver } from "./interfaces";
2 import { ActivationContext } from "../ActivationContext";
2 import { ActivationContext } from "./ActivationContext";
3 import { each } from "../../safe";
3 import { ContextResolver } from "./ContextResolver";
4 import { DependencyOptions, LazyDependencyOptions, Resolver } from "./interfaces";
4 import { each } from "./traits";
5
6 export type RegistrationOverridesMap<S extends object> = { [k in ConfigurableKeys<S>]?: Descriptor<S, NonNullable<S[k]>> };
5
7
6 export interface DescriptorImplArgs<S extends object, T> {
8 export interface DescriptorImplArgs<S extends object, T> {
7 lifetime: ILifetime;
9 lifetime: ILifetime<T>;
8
10
9 factory: (resolve: Resolver<S>) => T;
11 factory: (resolve: Resolver<S>) => T;
10
12
11 cleanup?: (item: T) => void;
13 cleanup?: (item: T) => void;
12
14
13 overrides?: PartialServiceMap<S>;
15 overrides?: RegistrationOverridesMap<S>;
14 }
16 }
15
17
18
16 export class DescriptorImpl<S extends object, T> implements Descriptor<S, T> {
19 export class DescriptorImpl<S extends object, T> implements Descriptor<S, T> {
17
20
18 private readonly _overrides?: PartialServiceMap<S>;
21 private readonly _overrides?: RegistrationOverridesMap<S>;
19
22
20 private readonly _lifetime: ILifetime;
23 private readonly _lifetime: ILifetime<T>;
21
24
22 private readonly _factory: (resolve: Resolver<S>) => T;
25 private readonly _factory: (resolve: Resolver<S>) => T;
23
26
@@ -42,25 +45,19 export class DescriptorImpl<S extends ob
42 if (this._overrides)
45 if (this._overrides)
43 each(this._overrides, (v, k) => context.register(k, v));
46 each(this._overrides, (v, k) => context.register(k, v));
44
47
45 const resolve = (name: ContainerKeys<S>, opts?: DependencyOptions | LazyDependencyOptions) => {
48
46 if (opts && "lazy" in opts && opts.lazy) {
49 const resolver = new ContextResolver(context);
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
50
56 const instance = this._factory.call(undefined, resolve);
51
52 const instance = this._factory.call(undefined, resolver.resolve.bind(resolver));
57
53
58 this._lifetime.store(instance, this._cleanup);
54 this._lifetime.store(instance, this._cleanup);
59
55
60 return instance;
56 return instance;
61 }
57 }
62
58
59
63 toString() {
60 toString() {
64 return `[object DescriptorImpl, lifetime=${this._lifetime}]`;
61 return `[object DescriptorImpl, lifetime=${String(this._lifetime)}]`;
65 }
62 }
66 }
63 }
@@ -1,53 +1,53
1 import { argumentNotNull, each, isPrimitive, isPromise } from "../../safe";
2 import { DescriptorBuilder } from "./DescriptorBuilder";
1 import { DescriptorBuilder } from "./DescriptorBuilder";
3 import { RegistrationBuilder, FluentRegistrations, ContainerConfiguration } from "./interfaces";
2 import { ConfigurableKeys, ContainerServices, ConfigurableServices, RegistrationBuildersMap, RequiredKeys } from "./interfaces";
4 import { Cancellation } from "../../Cancellation";
3 import { ServiceContainer } from "./interfaces";
5 import { ServiceContainer } from "../interfaces";
4 import { argumentNotNull, each, isKey } from "./traits";
6
5
7 export class FluentConfiguration<S extends object, Y extends keyof S = keyof S> {
6 export class FluentConfiguration<S extends object, Y extends ConfigurableKeys<S> = ConfigurableKeys<S>> {
8
7
9 _builders: { [k in keyof S]?: RegistrationBuilder<S, S[k]> } = {};
8 _builders: Partial<RegistrationBuildersMap<S>> = {};
10
9
11 provided<K extends Y>(): FluentConfiguration<S, Exclude<Y, K>> {
10 declare<D extends Partial<Pick<S, keyof D & keyof S>>>(): FluentConfiguration<S & D, Y | ConfigurableKeys<D>> {
12 return this;
11 return this as FluentConfiguration<S & D, Y | ConfigurableKeys<D>>;
13 }
12 }
14
13
15 register<K extends Y>(name: K, builder: RegistrationBuilder<S, S[K]>): FluentConfiguration<S, Exclude<Y, K>>;
14 provided<P extends Pick<S, keyof P & keyof S>>(): FluentConfiguration<S & P, Exclude<Y, keyof P>> {
16 register<K extends Y>(config: FluentRegistrations<K, S>): FluentConfiguration<S, Exclude<Y, K>>;
15 return this as FluentConfiguration<S & P, Exclude<Y, keyof P>>;
17 register<K extends Y>(nameOrConfig: K | FluentRegistrations<K, S>, builder?: RegistrationBuilder<S, S[K]>): FluentConfiguration<S, Exclude<Y, K>> {
16 }
18 if (isPrimitive(nameOrConfig)) {
17
18 register<K extends Y>(name: K, builder: RegistrationBuildersMap<S>[K]): FluentConfiguration<S, Exclude<Y, K>>;
19 register<K extends Y>(config: RegistrationBuildersMap<S, K>): FluentConfiguration<S, Exclude<Y, K>>;
20 register<K extends Y>(nameOrConfig: K | RegistrationBuildersMap<S, K>, builder?: RegistrationBuildersMap<S>[K]) {
21 if (isKey(nameOrConfig)) {
19 argumentNotNull(builder, "builder");
22 argumentNotNull(builder, "builder");
20 this._builders[nameOrConfig] = builder;
23 this._builders[nameOrConfig] = builder;
21 } else {
24 } else {
22 each(nameOrConfig, (v, k) => this.register(k, v));
25 each(nameOrConfig, (v, k) => this.register(k, v));
23 }
26 }
24
27
28 return this as FluentConfiguration<S, Exclude<Y, K>>;
29 }
30
31 done(missing: RequiredKeys<S, Y> extends never ? void : RequiredKeys<S, Y>) {
32 if (missing !== undefined)
33 throw new Error("The configuration isn't complete");
25 return this;
34 return this;
26 }
35 }
27
36
28 configure(config: FluentRegistrations<Y, S>): ContainerConfiguration<S> {
29 return this.register(config);
30 }
31
37
32 apply<S2 extends object>(target: ServiceContainer<S2>, ct = Cancellation.none) {
38 apply<A extends Partial<S>>(target: ServiceContainer<A>) {
33
39
34 let pending = 1;
40 let pending = 1;
35
41
36 const _t2 = target as unknown as ServiceContainer<S2 & S>;
42 const _t2 = target as ServiceContainer<S>;
37
43
38 return new Promise<ServiceContainer<S2 & S>>((resolve, reject) => {
44 const reject = (ex: unknown) => { throw ex; };
39 function guard(v: void | Promise<void>) {
40 if (isPromise(v))
41 v.catch(reject);
42 }
43
45
44 function complete() {
46 const complete = () => !--pending;
45 if (!--pending)
47
46 resolve(_t2);
47 }
48 each(this._builders, (v, k) => {
48 each(this._builders, (v, k) => {
49 pending++;
49 pending++;
50 const d = new DescriptorBuilder<S2 & S, any>(_t2,
50 const d = new DescriptorBuilder<ContainerServices<S>, NonNullable<ConfigurableServices<S>[typeof k]>>(_t2,
51 result => {
51 result => {
52 _t2.register(k, result);
52 _t2.register(k, result);
53 complete();
53 complete();
@@ -55,14 +55,13 export class FluentConfiguration<S exten
55 reject
55 reject
56 );
56 );
57
57
58 try {
58
59 guard(v(d, ct));
59 v(d);
60 } catch (e) {
61 reject(e);
62 }
63 });
60 });
64 complete();
61 if (!complete())
65 });
62 throw new Error("The configuration didn't complete.");
63
64 return _t2 as ServiceContainer<A & S>;
66 }
65 }
67
66
68 }
67 }
@@ -1,23 +1,21
1 import { IDestroyable, MapOf } from "../interfaces";
1 import { IDestroyable, ILifetime } from "./interfaces";
2 import { argumentNotNull, isDestroyable, argumentNotEmptyString, isRemovable } from "../safe";
3 import { ILifetime, ServiceContainer } from "./interfaces";
4 import { ActivationContext } from "./ActivationContext";
2 import { ActivationContext } from "./ActivationContext";
3 import { argumentNotNull, isDestroyable } from "./traits";
5
4
6 function safeCall(item: () => void) {
5 const safeCall = (item: () => void) => {
7 try {
6 try {
8 item();
7 item();
9 } catch {
8 } catch {
10 // silence!
9 // silence!
11 }
10 }
12 }
11 };
13
12
14 const emptyLifetime: ILifetime = Object.freeze({
13 const emptyLifetime = Object.freeze({
15 has() {
14 has() {
16 return false;
15 return false;
17 },
16 },
18
17
19 initialize() {
18 initialize() {
20
21 },
19 },
22
20
23 get() {
21 get() {
@@ -34,7 +32,7 const emptyLifetime: ILifetime = Object.
34
32
35 });
33 });
36
34
37 const unknownLifetime: ILifetime = Object.freeze({
35 const unknownLifetime = Object.freeze({
38 has() {
36 has() {
39 return false;
37 return false;
40 },
38 },
@@ -54,52 +52,49 const unknownLifetime: ILifetime = Objec
54
52
55 let nextId = 0;
53 let nextId = 0;
56
54
57 const singletons: any = {};
55 const singletons: { [K:string]: unknown} = {};
58
56
59 export class LifetimeManager implements IDestroyable {
57 export class LifetimeManager implements IDestroyable {
60 private _cleanup: (() => void)[] = [];
58 private _cleanup: (() => void)[] = [];
61 private _cache: MapOf<any> = {};
59 private readonly _cache: {[K: string]: unknown} = {};
62 private _destroyed = false;
60 private _destroyed = false;
63
61
64 private _pending: MapOf<boolean> = {};
62 private readonly _pending: {[K: string]: unknown} = {};
65
63
66 create(): ILifetime {
64 create<T>(): ILifetime<T> {
67 const self = this;
68 const id = ++nextId;
65 const id = ++nextId;
69 return {
66 return {
70 has() {
67 has: () => id in this._cache,
71 return (id in self._cache);
68
69 get: () => {
70 const t = this._cache[id];
71 if (t === undefined)
72 throw new Error(`The item with with the key ${id} isn't found`);
73 return t as T;
72 },
74 },
73
75
74 get() {
76 initialize: () => {
75 const t = self._cache[id];
77 if (this._pending[id])
76 if (t === undefined)
78 throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`);
77 throw new Error(`The item with with the key ${id} isn't found`);
79 this._pending[id] = true;
78 return t;
79 },
80 },
80
81
81 initialize() {
82 store: (item: T, cleanup?: (item: T) => void) => {
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");
83 argumentNotNull(id, "id");
89 argumentNotNull(item, "item");
84 argumentNotNull(item, "item");
90
85
91 if (this.has())
86 if (id in this._cache)
92 throw new Error(`The item with with the key ${id} already registered with this lifetime manager`);
87 throw new Error(`The item with with the key ${id} already registered with this lifetime manager`);
93 delete self._pending[id];
88 delete this._pending[id];
94
89
95 self._cache[id] = item;
90 this._cache[id] = item;
96
91
97 if (self._destroyed)
92 if (this._destroyed)
98 throw new Error("Lifetime manager is destroyed");
93 throw new Error("Lifetime manager is destroyed");
99 if (cleanup) {
94 if (cleanup) {
100 self._cleanup.push(() => cleanup(item));
95 this._cleanup.push(() => cleanup(item));
101 } else if (isDestroyable(item)) {
96 } else if (isDestroyable(item)) {
102 self._cleanup.push(() => item.destroy());
97 this._cleanup.push(() => item.destroy());
103 }
98 }
104 }
99 }
105 };
100 };
@@ -113,18 +108,18 export class LifetimeManager implements
113 }
108 }
114 }
109 }
115
110
116 static empty(): ILifetime {
111 static empty<T>(): ILifetime<T> {
117 return emptyLifetime;
112 return emptyLifetime;
118 }
113 }
119
114
120 static hierarchyLifetime() {
115 static hierarchyLifetime<T>() {
121 let _lifetime = unknownLifetime;
116 let _lifetime: ILifetime<T> = unknownLifetime;
122 return {
117 return {
123 initialize(context: ActivationContext<any>) {
118 initialize(context: ActivationContext<object>) {
124 if (_lifetime !== unknownLifetime)
119 if (_lifetime !== unknownLifetime)
125 throw new Error("Cyclic reference activation detected");
120 throw new Error("Cyclic reference activation detected");
126
121
127 _lifetime = context.getContainer().getLifetimeManager().create();
122 _lifetime = context.createContainerLifetime<T>();
128 },
123 },
129 get() {
124 get() {
130 return _lifetime.get();
125 return _lifetime.get();
@@ -132,19 +127,19 export class LifetimeManager implements
132 has() {
127 has() {
133 return _lifetime.has();
128 return _lifetime.has();
134 },
129 },
135 store(item: any, cleanup?: (item: any) => void) {
130 store(item: T, cleanup?: (item: T) => void) {
136 return _lifetime.store(item, cleanup);
131 return _lifetime.store(item, cleanup);
137 },
132 },
138 toString() {
133 toString() {
139 return `[object HierarchyLifetime, has=${this.has()}]`;
134 return `[object HierarchyLifetime, has=${String(this.has())}]`;
140 }
135 }
141 };
136 };
142 }
137 }
143
138
144 static contextLifetime() {
139 static contextLifetime<T>() {
145 let _lifetime = unknownLifetime;
140 let _lifetime: ILifetime<T> = unknownLifetime;
146 return {
141 return {
147 initialize(context: ActivationContext<any>) {
142 initialize(context: ActivationContext<object>) {
148 if (_lifetime !== unknownLifetime)
143 if (_lifetime !== unknownLifetime)
149 throw new Error("Cyclic reference detected");
144 throw new Error("Cyclic reference detected");
150 _lifetime = context.createLifetime();
145 _lifetime = context.createLifetime();
@@ -155,17 +150,17 export class LifetimeManager implements
155 has() {
150 has() {
156 return _lifetime.has();
151 return _lifetime.has();
157 },
152 },
158 store(item: any) {
153 store(item: T) {
159 _lifetime.store(item);
154 _lifetime.store(item);
160 },
155 },
161 toString() {
156 toString() {
162 return `[object ContextLifetime, has=${this.has()}]`;
157 return `[object ContextLifetime, has=${String(this.has())}]`;
163 }
158 }
164 };
159 };
165 }
160 }
166
161
167 static singletonLifetime(typeId: string) {
162 static singletonLifetime<T>(typeId: string) {
168 argumentNotEmptyString(typeId, "typeId");
163 argumentNotNull(typeId, "typeId");
169 let pending = false;
164 let pending = false;
170 return {
165 return {
171 has() {
166 has() {
@@ -174,30 +169,30 export class LifetimeManager implements
174 get() {
169 get() {
175 if (!this.has())
170 if (!this.has())
176 throw new Error(`The instance ${typeId} doesn't exists`);
171 throw new Error(`The instance ${typeId} doesn't exists`);
177 return singletons[typeId];
172 return singletons[typeId] as T;
178 },
173 },
179 initialize() {
174 initialize() {
180 if (pending)
175 if (pending)
181 throw new Error("Cyclic reference detected");
176 throw new Error("Cyclic reference detected");
182 pending = true;
177 pending = true;
183 },
178 },
184 store(item: any) {
179 store(item: T) {
185 singletons[typeId] = item;
180 singletons[typeId] = item;
186 pending = false;
181 pending = false;
187 },
182 },
188 toString() {
183 toString() {
189 return `[object SingletonLifetime, has=${this.has()}, typeId=${typeId}]`;
184 return `[object SingletonLifetime, has=${String(this.has())}, typeId=${typeId}]`;
190 }
185 }
191 };
186 };
192 }
187 }
193
188
194 static containerLifetime(container: ServiceContainer<any>) {
189 static containerLifetime<T>(container: { createLifetime<X>(): ILifetime<X>}) {
195 let _lifetime = unknownLifetime;
190 let _lifetime: ILifetime<T> = unknownLifetime;
196 return {
191 return {
197 initialize(context: ActivationContext<any>) {
192 initialize() {
198 if (_lifetime !== unknownLifetime)
193 if (_lifetime !== unknownLifetime)
199 throw new Error("Cyclic reference detected");
194 throw new Error("Cyclic reference detected");
200 _lifetime = container.getLifetimeManager().create();
195 _lifetime = container.createLifetime();
201 },
196 },
202 get() {
197 get() {
203 return _lifetime.get();
198 return _lifetime.get();
@@ -205,11 +200,11 export class LifetimeManager implements
205 has() {
200 has() {
206 return _lifetime.has();
201 return _lifetime.has();
207 },
202 },
208 store(item: any) {
203 store(item: T) {
209 _lifetime.store(item);
204 _lifetime.store(item);
210 },
205 },
211 toString() {
206 toString() {
212 return `[object ContainerLifetime, has=${_lifetime.has()}]`;
207 return `[object ContainerLifetime, has=${String(_lifetime.has())}]`;
213 }
208 }
214 };
209 };
215 }
210 }
@@ -1,104 +1,104
1 import { ActivationContext } from "./ActivationContext";
2
1 export type primitive = number | string | null | undefined | symbol;
3 export type primitive = number | string | null | undefined | symbol;
2
4
3 export interface IDestroyable {
5 export interface IDestroyable {
4 destroy(): void;
6 destroy(): void;
5 }
7 }
6
8
7
9 /**
8 export interface DependencyOptions {
10 * @template S Карта доступных зависимостей
9 optional?: boolean;
11 */
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> {
12 export interface Resolver<S extends object> {
33 <K extends ContainerKeys<S>, O extends LazyDependencyOptions>(this: void, name: K, opts: O): () => InferReferenceType<S, K, O>;
13 /**
34 <K extends ContainerKeys<S>, O extends DependencyOptions>(this: void, name: K, opts?: O): InferReferenceType<S, K, O>;
14 * Функция для разрешения зависимостей, поддерживает создание фабричных методов,
15 * отложенную активацию и значение по-умолчанию для сервисов
16 * @template K Ключ сервиса из {@link S}
17 * @template O Тип параметра {@link opts} используется для выведения типа
18 * возвращаемого значения.
19 * @param name Ключ сервиса, который будет разрешен.
20 * @param {boolean=} opts.lazy Признак того, что требуется отложенная активация,
21 * будет возвращен фабричный метод для получения зависимости. Если не указан,
22 * то считается `false`.
23 * @param {any=} opts.default Значение по умолчанию, если в контейнере указанный
24 * сервис не зарегистрирован
25 * @returns Либо фабричный метод для получения зависимости, либо значение зависимости
26 * @throws Error Если зависимость не найдена и не предоставлено значение по-умолчанию
27 */
28 <K extends keyof S, O extends { lazy: true; default?: unknown }>(name: K, opts?: O): () => (O extends { default: infer T } ? T : never) | NonNullable<S[K]>;
29 <K extends keyof S, O extends { lazy?: false; default?: unknown }>(name: K, opts?: O): (O extends { default: infer T } ? T : never) | NonNullable<S[K]>;
35 }
30 }
36
31
37 export interface DescriptorBuilder<S extends object, T> {
32 export interface DescriptorBuilder<S extends object, T> {
33
34 /**
35 *
36 * @param f
37 */
38 factory(f: (resolve: Resolver<S>) => T): void;
38 factory(f: (resolve: Resolver<S>) => T): void;
39
39
40 build<T2>(): DescriptorBuilder<S, T2>;
40 override<K extends ConfigurableKeys<S>>(name: K, builder: RegistrationBuilder<S, NonNullable<S[K]>>): this;
41 override<K extends ConfigurableKeys<S>>(services: { [name in K]: RegistrationBuilder<S, NonNullable<S[K]>> }): this;
41
42
42 override<K extends keyof S>(name: K, builder: RegistrationBuilder<S, S[K]>): this;
43 lifetime(lifetime: "singleton", typeId: string | number | object): this;
43 override<K extends keyof S>(services: { [name in K]: RegistrationBuilder<S, S[K]> }): this;
44 lifetime(lifetime: ILifetime<T> | Exclude<ActivationType, "singleton">): this;
44
45 lifetime(lifetime: "singleton", typeId: any): this;
46 lifetime(lifetime: ILifetime | Exclude<ActivationType, "singleton">): this;
47
45
48 cleanup(cb: (item: T) => void): this;
46 cleanup(cb: (item: T) => void): this;
49
47
50 value(v: T): void;
48 value(v: T): void;
51 }
49 }
52
50
53 export type RegistrationBuilder<S extends object, T> = (d: DescriptorBuilder<S, T>, ct?: ICancellation) => void | Promise<void>;
51 export type RegistrationBuilder<S extends object, T> = (d: DescriptorBuilder<S, T>) => void;
54
52
55 export type FluentRegistrations<K extends keyof S, S extends object> = { [k in K]: RegistrationBuilder<S, S[k]> };
53 export type RegistrationBuildersMap<S extends object, K extends ConfigurableKeys<S> = ConfigurableKeys<S>> = {
54 [k in K]-?: RegistrationBuilder<ContainerServices<S>, NonNullable<ConfigurableServices<S>[k]>>
55 };
56
56
57 export interface Descriptor<S extends object = any, T = any> {
57 export interface Descriptor<S extends object, T> {
58 activate(context: ActivationContext<S>): T;
58 activate(context: ActivationContext<S>): T;
59 }
59 }
60
60
61 export type ServiceMap<S extends object> = {
61 export type ConfigurableDescriptor<S extends object, K extends ConfigurableKeys<S>> = Descriptor<ContainerServices<S>, ConfigurableServices<S>[K]>;
62 [k in keyof S]: Descriptor<S, S[k]>;
63 };
64
65 export type ContainerKeys<S extends object> = keyof S | keyof ContainerProvided<S>;
66
62
67 export type TypeOfService<S extends object, K> =
63 export type RegistrationMap<S extends object, K extends keyof S = keyof S> = {
68 K extends keyof ContainerProvided<S> ? ContainerProvided<S>[K] :
64 [k in K]-?: Descriptor<S, 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 };
65 };
74
66
75 export type PartialServiceMap<S extends object> = {
67 export interface ProvidedServices<S extends object> {
76 [k in keyof S]?: Descriptor<S, S[k]>;
68 container: ServiceLocator<ContainerServices<S>>;
77 };
69
70 childContainer: ServiceContainer<S>;
71 }
72
73 export type ProvidedKeys = keyof ProvidedServices<object>;
74
75 export type ContainerKeys<S extends object> = keyof ContainerServices<S>;
76
77 export type Mix<S, X> = { [k in keyof (S & X)]: k extends keyof X ? X[k] : S[k & keyof S] };
78
79 export type ContainerServices<S extends object> = Mix<S, ProvidedServices<S>>;
80
81 export type ConfigurableKeys<S extends object> = Exclude<keyof S, ProvidedKeys>;
82
83 export type ConfigurableServices<S extends object> = Pick<S, ConfigurableKeys<S>>;
78
84
79 export interface ServiceLocator<S extends object> {
85 export interface ServiceLocator<S extends object> {
80 resolve<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>): TypeOfService<S, K>;
86 resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
81 resolve<K extends ContainerKeys<S>>(name: K, def?: undefined): TypeOfService<S, K> | undefined;
87 resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T;
82 }
88 }
83
89
84 export interface ServiceContainer<S extends object> extends ServiceLocator<S>, IDestroyable {
90 export interface LifetimeContainer {
85 getLifetimeManager(): LifetimeManager;
91 createLifetime<T>(): ILifetime<T>;
86 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>): this;
92 }
87 register(services: PartialServiceMap<S>): this;
93
94 export interface ServiceContainer<S extends object> extends ServiceLocator<ContainerServices<S>>, LifetimeContainer, IDestroyable {
95
96 register<K extends ConfigurableKeys<S>>(name: K, service: ConfigurableDescriptor<S, K>): void;
97 register<K extends ConfigurableKeys<S>>(services: { [k in K]: ConfigurableDescriptor<S, K> }): void;
88
98
89 createChildContainer(): ServiceContainer<S>;
99 createChildContainer(): ServiceContainer<S>;
90 }
100 }
91
101
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
102
103 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
103 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
104
104
@@ -106,13 +106,16 export type ActivationType = "singleton"
106 * Интерфейс для управления жизнью экземпляра объекта. Каждая регистрация имеет
106 * Интерфейс для управления жизнью экземпляра объекта. Каждая регистрация имеет
107 * свой собственный объект `ILifetime`, который создается при первой активации
107 * свой собственный объект `ILifetime`, который создается при первой активации
108 */
108 */
109 export interface ILifetime {
109 export interface ILifetime<T> {
110 /** Проверяет, что уже создан экземпляр объекта */
110 /** Проверяет, что уже создан экземпляр объекта */
111 has(): boolean;
111 has(): boolean;
112
112
113 get(): any;
113 get(): T;
114
115 initialize(context: ActivationContext<object>): void;
114
116
115 initialize(context: ActivationContext<any>): void;
117 store(item: T, cleanup?: (item: T) => void): void;
118 }
116
119
117 store(item: any, cleanup?: (item: any) => void): void;
120 export type RequiredKeys<T, K extends keyof T = keyof T> = keyof { [p in K as (T[p] extends undefined ? never : p)]-?: T[p] };
118 }
121
@@ -1,6 +1,49
1 import { primitive } from "./interfaces";
1 import { FluentConfiguration } from "./FluentConfiguration";
2 import { IDestroyable } from "./interfaces";
3
4 export function fluent<S extends object = object>() {
5 return new FluentConfiguration<S>();
6 }
7
8 export type key = string | number | symbol;
9
10 export const isKey = (v: unknown): v is key =>
11 typeof v === "string" || typeof v === "number" || typeof v === "symbol";
12
13 export const isString = (v: unknown): v is string =>
14 typeof v === "string";
15
16
17 export const isNotNull = <T>(v: T): v is NonNullable<T> => v !== null && v !== undefined;
18
19 export const each = <T extends object>(obj: T, cb: <X extends Extract<keyof T, string>>(v: NonNullable<T[X]>, k: X) => void) =>
20 (Object.keys(obj) as (Extract<keyof T, string>)[])
21 .forEach(k => {
22 const v = obj[k];
23 isNotNull(v) && cb(v, k);
24 });
2
25
3 export function isPrimitive(val: any): val is primitive {
26 export const argumentNotNull = (arg: unknown, name: string) => {
4 return (val === null || val === undefined || typeof (val) === "string" ||
27 if (arg === null || arg === undefined)
5 typeof (val) === "number" || typeof (val) === "boolean");
28 throw new Error("The argument " + name + " can't be null or undefined");
6 } No newline at end of file
29 };
30
31 export const isPromise = <T = unknown>(val: unknown): val is PromiseLike<T> =>
32 isNotNull(val) && typeof (val as PromiseLike<T>).then === "function";
33
34 export const isDestroyable = (d: unknown): d is IDestroyable =>
35 isNotNull(d) && typeof (d as IDestroyable).destroy === "function";
36
37 let _nextOid = 0;
38 const _oid = typeof Symbol === "function" ?
39 Symbol.for("__implab__oid__") :
40 "__implab__oid__";
41
42 type OidSlot = { [k in typeof _oid]?: string };
43
44 export const oid = <T extends object>(instance: T): string => {
45 argumentNotNull(instance, "instance");
46 const val = (instance as OidSlot)[_oid];
47
48 return val ? val : ((instance as OidSlot)[_oid] = `oid_${++_nextOid}`);
49 }; No newline at end of file
@@ -1,9 +1,3
1 {
1 {
2 "extends": "../tsconfig",
2 "extends": "../tsconfig",
3 "compilerOptions": {
4 "rootDir": "ts"
5 },
6 "include": [
7 "ts/**/*.ts"
8 ]
9 } No newline at end of file
3 }
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now