##// 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
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": {
3 "browser": true,
4 "commonjs": true,
5 "amd": true,
6 "node": true
7 },
2 "root": true,
3 "extends": [
4 "plugin:react/recommended",
5 "eslint:recommended",
6 "plugin:@typescript-eslint/eslint-recommended",
7 "plugin:@typescript-eslint/recommended",
8 "plugin:@typescript-eslint/recommended-requiring-type-checking"
9 ],
10 "parser": "@typescript-eslint/parser",
8 11 "parserOptions": {
9 12 "ecmaFeatures": {
10 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 22 "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"
23 "@typescript-eslint/no-empty-function": "off",
24 "max-classes-per-file": [
25 "error",
26 { "ignoreExpressions": true, "max": 1 }
27 ],
28 "@typescript-eslint/prefer-readonly": ["error"],
29 "@typescript-eslint/semi": ["error"]
30
26 31 }
27 } No newline at end of file
32 }
@@ -1,3 +1,4
1 1 syntax: glob
2 2 .gradle/
3 3 build/
4 node_modules/
@@ -30,23 +30,9 typescript {
30 30 declaration = true
31 31 experimentalDecorators = true
32 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 }
33 module = "commonjs"
34 target = "es5"
35 lib = ["es2015", "dom", "scripthost"]
50 36 }
51 37 tscCmd = "$projectDir/node_modules/.bin/tsc"
52 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 58 printVersion {
69 59 doLast {
70 60 println "packageName: $packageName";
@@ -75,3 +65,7 printVersion {
75 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 2 "name": "@implab/di",
3 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 5 "main": "main.js",
6 6 "keywords": [
7 7 "di",
8 8 "ioc",
9 "logging",
10 "template engine",
11 9 "dependency injection"
12 10 ],
13 11 "author": "Implab team",
14 12 "license": "BSD-2-Clause",
15 "repository": "https://bitbucket.org/implab/implabjs",
16 "peerDependencies": {
17 "dojo": "^1.10.0"
18 },
13 "repository": "https://code.implab.org/implab/implabjs-di",
19 14 "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"
15 "@types/chai": "4.3.1",
16 "@types/mocha": "9.1.1",
17 "@types/node": "~8.0.0",
18 "@types/tape": "~4.13.2",
19 "@typescript-eslint/eslint-plugin": "^5.23.0",
20 "@typescript-eslint/parser": "^5.23.0",
21 "chai": "~4.3.6",
22 "eslint": "^8.15.0",
23 "eslint-config-standard": "^17.0.0",
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
2
3 Набор стандартных библиотек для создания приложений со сложным функционалом.
4 Данную библиотеку можно использовать как для разработки приложений, которые
5 будут работать в среде браузеров, так и в серверных средах.
6
7 Библиотека написана на TypeScript, некоторая часть на JavaScript, но постепенно
8 планируется перейти полностью на использование TypeScript
9
10 Более подробная документация доступна по ссылке: <https://bitbucket.org/implab/implabjs-core/src/default/docs/ru/>
11
12 ## Основные компоненты
1 # Implabjs-di
13 2
14 ### DI
15
16 Контейнер для внедрения зависимостей, позволяет гибко описывать структуру
17 приложения и создавать слабосвязанные компоненты.
18
19 ### LOG
20
21 Средства журналирования похожие на JLog, позволяют эффективно вести журнал
22 выполнения программы.
23
24 ### Cancellations
25
26 Специальные маркеры для отмены асинхронных операций, по аналогии с .NET
27 CancelationToken.
3 Простой контейнер для внедрения зависимостей
@@ -1,9 +1,5
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");
1 import { Descriptor, ILifetime, RegistrationMap, LifetimeContainer, ConfigurableKeys } from "./interfaces";
2 import { argumentNotNull } from "./traits";
7 3
8 4 export interface ActivationContextInfo {
9 5 name: string;
@@ -19,35 +15,32 let nextId = 1;
19 15 * tracks services with `context` activation type.
20 16 */
21 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;
29
30 _service: Descriptor<S, any>;
24 private _service: Descriptor<S, unknown>;
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 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 32 * @param services the initial service registrations
39 33 * @param name the name of the service being activated, this parameter is
40 34 * used for the debug purpose.
41 35 * @param service the service to activate, this parameter is used for the
42 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 39 this._name = name;
46 40 this._service = service;
47 this._visited = {};
48 41 this._cache = {};
49 42 this._services = services;
50 this._container = container;
43 this._containerLifetimeManager = containerLifetimeManager;
51 44 }
52 45
53 46 /** the name of the current resolving dependency */
@@ -55,15 +48,14 export class ActivationContext<S extends
55 48 return this._name;
56 49 }
57 50
58 /** Returns the container for which 'resolve' method was called */
59 getContainer() {
60 return this._container;
51 createContainerLifetime<T>() {
52 return this._containerLifetimeManager.createLifetime<T>();
61 53 }
62 54
63 55 /** Resolves the specified dependency in the current context
64 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 59 /** Resolves the specified dependency with the specified default value if
68 60 * the dependency is missing.
69 61 *
@@ -71,14 +63,8 export class ActivationContext<S extends
71 63 * @param def A default value to return in case of the specified dependency
72 64 * is missing.
73 65 */
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 {
66 resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T;
67 resolve<K extends keyof S, T>(name: K, def?: T): S[K] | T | undefined {
82 68 const d = this._services[name];
83 69
84 70 if (d !== undefined) {
@@ -87,7 +73,7 export class ActivationContext<S extends
87 73 if (arguments.length > 1)
88 74 return def;
89 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 83 * @name{string} the name of the service
98 84 * @service{string} the service descriptor to register
99 85 */
100 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>) {
101 argumentNotEmptyString(name, "name");
86 register<K extends ConfigurableKeys<S>>(name: K, service: RegistrationMap<S>[K]) {
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 93 const id = nextId++;
108 const me = this;
109 94 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;
95 initialize() {},
96 has: () => id in this._cache,
97 get: () => this._cache[id] as T,
98 store: item => {
99 this._cache[id] = item;
120 100 }
121 101 };
122 102 }
123 103
124 104 activate<T>(d: Descriptor<S, T>, name: string) {
125 if (trace.isLogEnabled())
126 trace.log("enter {0} {1}", name, d);
105 // TODO: add logging
106 // if (trace.isLogEnabled())
107 // trace.log("enter {0} {1}", name, d);
127 108
128 109 const ctx = this.enter(d, name);
129 110 const v = d.activate(ctx);
130 111
131 if (trace.isLogEnabled())
132 trace.log(`leave ${name}`);
112 // if (trace.isLogEnabled())
113 // trace.log(`leave ${name}`);
133 114
134 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 118 getStack(): ActivationContextInfo[] {
144 119 const stack = [{
145 120 name: this._name,
@@ -151,10 +126,10 export class ActivationContext<S extends
151 126 stack;
152 127 }
153 128
154 private enter(service: Descriptor<S, any>, name: string): this {
155 const clone = Object.create(this);
129 private enter(service: Descriptor<S, unknown>, name: string): this {
130 const clone = Object.create(this) as this;
156 131 clone._name = name;
157 clone._services = Object.create(this._services);
132 clone._services = Object.create(this._services) as typeof this._services;
158 133 clone._parent = this;
159 134 clone._service = service;
160 135 return clone;
@@ -162,8 +137,8 export class ActivationContext<S extends
162 137
163 138 /** Creates a clone for the current context, used to protect it from modifications */
164 139 clone(): this {
165 const clone = Object.create(this);
166 clone._services = Object.create(this._services);
140 const clone = Object.create(this) as this;
141 clone._services = Object.create(this._services) as typeof this._services;
167 142 return clone;
168 143 }
169 144 }
@@ -8,11 +8,11 export class ActivationError {
8 8
9 9 service: string;
10 10
11 innerException: any;
11 innerException: unknown;
12 12
13 13 message: string;
14 14
15 constructor(service: string, activationStack: ActivationItem[], innerException: any) {
15 constructor(service: string, activationStack: ActivationItem[], innerException: unknown) {
16 16 this.message = "Failed to activate the service";
17 17 this.activationStack = activationStack;
18 18 this.service = service;
@@ -22,15 +22,17 export class ActivationError {
22 22 toString() {
23 23 const parts = [this.message];
24 24 if (this.service)
25 parts.push("when activating: " + this.service.toString());
25 parts.push(`when activating: ${String(this.service)}`);
26 26
27 27 if (this.innerException)
28 parts.push("caused by: " + this.innerException.toString());
28 parts.push(`caused by: ${String(this.innerException)}`);
29 29
30 30 if (this.activationStack) {
31 31 parts.push("at");
32 this.activationStack
33 .forEach(x => parts.push(` ${x.name} ${x.service}`));
32 parts.push.apply(null,
33 this.activationStack
34 .map(({ name, service }) => ` ${name} ${service}`)
35 );
34 36
35 37 }
36 38
@@ -1,67 +1,58
1 1 import { ActivationContext } from "./ActivationContext";
2 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";
3 import { RegistrationMap, ContainerKeys, ServiceContainer, ContainerServices, ConfigurableServices, ConfigurableKeys, ServiceLocator, Descriptor} from "./interfaces";
9 4 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");
5 import { each, isKey } from "./traits";
15 6
16 export class Container<S extends object = any> implements ServiceContainer<S> {
17 readonly _services: ContainerServiceMap<S>;
18
19 readonly _lifetimeManager: LifetimeManager;
7 export class Container<S extends object > implements ServiceContainer<S> {
8 private readonly _services: Partial<RegistrationMap<ContainerServices<S>>>;
20 9
21 readonly _cleanup: (() => void)[];
22
23 readonly _root: Container<S>;
10 private readonly _lifetimeManager: LifetimeManager;
24 11
25 readonly _parent?: Container<S>;
12 private readonly _cleanup: (() => void)[];
26 13
27 _disposed: boolean;
14 private _disposed: boolean;
28 15
29 16 constructor(parent?: Container<S>) {
30 this._parent = parent;
31 this._services = Object.create(parent ? parent._services : null);
17 this._services = Object.create(parent ? parent._services : null) as typeof this._services;
32 18 this._cleanup = [];
33 this._root = parent ? parent.getRootContainer() : this;
34 this._services.container = { activate: () => this };
19 this._services.container = { activate: () => this as ServiceLocator<ContainerServices<S>>};
35 20 this._services.childContainer = { activate: () => this.createChildContainer() };
36 21 this._disposed = false;
37 22 this._lifetimeManager = new LifetimeManager();
38 23 }
39 24
40 getRootContainer() {
41 return this._root;
42 }
25 register<K extends ConfigurableKeys<S>>(name: K, service: Descriptor<ContainerServices<S>, ConfigurableServices<S>[K]>): void;
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");
43 33
44 getParent() {
45 return this._parent;
34 this._services[nameOrCollection] = service;
35 }
46 36 }
47 37
48 getLifetimeManager() {
49 return this._lifetimeManager;
38 createChildContainer(): ServiceContainer<S> {
39 return new Container(this);
50 40 }
51 41
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);
42 resolve<K extends ContainerKeys<S>>(name: K): NonNullable<ContainerServices<S>[K]>;
43 resolve<K extends ContainerKeys<S>>(name: K, def: ContainerServices<S>[K]): ContainerServices<S>[K];
44 resolve<K extends ContainerKeys<S>>(name: K, def?: ContainerServices<S>[K]): ContainerServices<S>[K] | undefined {
45 // TODO: add logging
46 // trace.debug("resolve {0}", name);
56 47 const d = this._services[name];
57 48 if (d === undefined) {
58 49 if (arguments.length > 1)
59 50 return def;
60 51 else
61 throw new Error("Service '" + name + "' isn't found");
52 throw new Error(`Service '${String(name)}' isn't found`);
62 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 56 try {
66 57 return d.activate(context);
67 58 } catch (error) {
@@ -70,34 +61,8 export class Container<S extends object
70 61 }
71 62 }
72 63
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);
64 createLifetime<T>() {
65 return this._lifetimeManager.create<T>();
101 66 }
102 67
103 68 destroy() {
@@ -111,66 +76,4 export class Container<S extends object
111 76 f();
112 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 }
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 79 }
@@ -1,17 +1,22
1 import { Resolver, RegistrationBuilder } from "./interfaces";
2 import { Descriptor, ILifetime, ActivationType, PartialServiceMap, ServiceContainer } from "./interfaces";
3 import { DescriptorImpl } from "./DescriptorImpl";
1 import { Resolver, RegistrationBuilder, LifetimeContainer, ConfigurableKeys } from "./interfaces";
2 import { Descriptor, ILifetime, ActivationType } from "./interfaces";
3 import { DescriptorImpl, RegistrationOverridesMap } from "./DescriptorImpl";
4 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 11 export class DescriptorBuilder<S extends object, T> {
7 private readonly _container: ServiceContainer<S>;
12 private readonly _lifetimeContainer: LifetimeContainer;
8 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 21 private _cleanup?: (item: T) => void;
17 22
@@ -21,36 +26,48 export class DescriptorBuilder<S extends
21 26
22 27 private _failed = false;
23 28
24 constructor(container: ServiceContainer<S>, cb: (d: Descriptor<S, T>) => void, eb: (err: any) => void) {
25 this._container = container;
29 private _finalized = false;
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 41 this._cb = cb;
27 42 this._eb = eb;
43 this._overrides = {};
28 44 }
29 45
30 build<T2>(): DescriptorBuilder<S, T2> {
31 this._defer();
32 return new DescriptorBuilder<S, T2>(this._container, () => this._complete(), err => this._fail(err));
46 private _assertBuilding() {
47 if (this._finalized)
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;
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 = {});
51 private _finalize() {
52 this._finalized = true;
53 }
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 59 const guard = (v: void | Promise<void>) => {
43 60 if (isPromise(v))
44 61 v.catch(err => this._fail(err));
45 62 };
46 63
47 if (isPrimitive(nameOrServices)) {
64 if (isKey(nameOrServices)) {
48 65 if (builder) {
49 66 this._defer();
50 const d = new DescriptorBuilder<S, S[K]>(
51 this._container,
67 const d = new DescriptorBuilder<S, NonNullable<S[K]>>(
68 this._lifetimeContainer,
52 69 result => {
53 overrides[nameOrServices] = result;
70 this._overrides[nameOrServices] = result;
54 71 this._complete();
55 72 },
56 73 err => this._fail(err)
@@ -69,8 +86,9 export class DescriptorBuilder<S extends
69 86 }
70 87
71 88 lifetime(lifetime: "singleton", typeId: string): this;
72 lifetime(lifetime: ILifetime | Exclude<ActivationType, "singleton">): this;
73 lifetime(lifetime: ILifetime | ActivationType, typeId?: string): this {
89 lifetime(lifetime: ILifetime<T> | Exclude<ActivationType, "singleton">): this;
90 lifetime(lifetime: ILifetime<T> | ActivationType, typeId?: string): this {
91 this._assertBuilding();
74 92 if (isString(lifetime)) {
75 93 this._lifetime = this._resolveLifetime(lifetime, typeId);
76 94 } else {
@@ -80,38 +98,44 export class DescriptorBuilder<S extends
80 98 }
81 99
82 100 cleanup(cb: (item: T) => void): this {
101 this._assertBuilding();
83 102 this._cleanup = cb;
84 103 return this;
85 104 }
86 105
87 106 factory(f: (resolve: Resolver<S>) => T): void {
107 this._assertBuilding();
88 108 this._factory = f;
109 this._finalize();
89 110 this._complete();
90 111 }
91 112
92 113 value(v: T): void {
114 this._assertBuilding();
93 115 this._cb({
94 116 activate() {
95 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 124 switch (activation) {
102 125 case "container":
103 return LifetimeManager.containerLifetime(this._container);
126 return LifetimeManager.containerLifetime(this._lifetimeContainer);
104 127 case "hierarchy":
105 128 return LifetimeManager.hierarchyLifetime();
106 129 case "context":
107 130 return LifetimeManager.contextLifetime();
108 case "singleton":
131 case "singleton": {
109 132 if (!typeId)
110 133 throw Error("The singleton activation requires a typeId");
111 134
112 135 const _oid = isString(typeId) ? typeId : oid(typeId);
113 136
114 137 return LifetimeManager.singletonLifetime(_oid);
138 }
115 139 default:
116 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 163 if (!this._failed) {
140 164 this._failed = true;
141 165 this._eb.call(undefined, err);
@@ -1,23 +1,26
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";
1 import { Descriptor, ILifetime, ConfigurableKeys, Resolver } from "./interfaces";
2 import { ActivationContext } from "./ActivationContext";
3 import { ContextResolver } from "./ContextResolver";
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 8 export interface DescriptorImplArgs<S extends object, T> {
7 lifetime: ILifetime;
9 lifetime: ILifetime<T>;
8 10
9 11 factory: (resolve: Resolver<S>) => T;
10 12
11 13 cleanup?: (item: T) => void;
12 14
13 overrides?: PartialServiceMap<S>;
15 overrides?: RegistrationOverridesMap<S>;
14 16 }
15 17
18
16 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 25 private readonly _factory: (resolve: Resolver<S>) => T;
23 26
@@ -42,25 +45,19 export class DescriptorImpl<S extends ob
42 45 if (this._overrides)
43 46 each(this._overrides, (v, k) => context.register(k, v));
44 47
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 };
48
49 const resolver = new ContextResolver(context);
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 54 this._lifetime.store(instance, this._cleanup);
59 55
60 56 return instance;
61 57 }
62 58
59
63 60 toString() {
64 return `[object DescriptorImpl, lifetime=${this._lifetime}]`;
61 return `[object DescriptorImpl, lifetime=${String(this._lifetime)}]`;
65 62 }
66 63 }
@@ -1,68 +1,67
1 import { argumentNotNull, each, isPrimitive, isPromise } from "../../safe";
2 1 import { DescriptorBuilder } from "./DescriptorBuilder";
3 import { RegistrationBuilder, FluentRegistrations, ContainerConfiguration } from "./interfaces";
4 import { Cancellation } from "../../Cancellation";
5 import { ServiceContainer } from "../interfaces";
2 import { ConfigurableKeys, ContainerServices, ConfigurableServices, RegistrationBuildersMap, RequiredKeys } from "./interfaces";
3 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>> {
12 return this;
10 declare<D extends Partial<Pick<S, keyof D & keyof S>>>(): FluentConfiguration<S & D, Y | ConfigurableKeys<D>> {
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>>;
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)) {
14 provided<P extends Pick<S, keyof P & keyof S>>(): FluentConfiguration<S & P, Exclude<Y, keyof P>> {
15 return this as FluentConfiguration<S & P, Exclude<Y, keyof P>>;
16 }
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 22 argumentNotNull(builder, "builder");
20 23 this._builders[nameOrConfig] = builder;
21 24 } else {
22 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 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 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) => {
39 function guard(v: void | Promise<void>) {
40 if (isPromise(v))
41 v.catch(reject);
42 }
44 const reject = (ex: unknown) => { throw ex; };
45
46 const complete = () => !--pending;
43 47
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 );
48 each(this._builders, (v, k) => {
49 pending++;
50 const d = new DescriptorBuilder<ContainerServices<S>, NonNullable<ConfigurableServices<S>[typeof k]>>(_t2,
51 result => {
52 _t2.register(k, result);
53 complete();
54 },
55 reject
56 );
57 57
58 try {
59 guard(v(d, ct));
60 } catch (e) {
61 reject(e);
62 }
63 });
64 complete();
58
59 v(d);
65 60 });
61 if (!complete())
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";
2 import { argumentNotNull, isDestroyable, argumentNotEmptyString, isRemovable } from "../safe";
3 import { ILifetime, ServiceContainer } from "./interfaces";
1 import { IDestroyable, ILifetime } from "./interfaces";
4 2 import { ActivationContext } from "./ActivationContext";
3 import { argumentNotNull, isDestroyable } from "./traits";
5 4
6 function safeCall(item: () => void) {
5 const safeCall = (item: () => void) => {
7 6 try {
8 7 item();
9 8 } catch {
10 9 // silence!
11 10 }
12 }
11 };
13 12
14 const emptyLifetime: ILifetime = Object.freeze({
13 const emptyLifetime = Object.freeze({
15 14 has() {
16 15 return false;
17 16 },
18 17
19 18 initialize() {
20
21 19 },
22 20
23 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 36 has() {
39 37 return false;
40 38 },
@@ -54,52 +52,49 const unknownLifetime: ILifetime = Objec
54 52
55 53 let nextId = 0;
56 54
57 const singletons: any = {};
55 const singletons: { [K:string]: unknown} = {};
58 56
59 57 export class LifetimeManager implements IDestroyable {
60 58 private _cleanup: (() => void)[] = [];
61 private _cache: MapOf<any> = {};
59 private readonly _cache: {[K: string]: unknown} = {};
62 60 private _destroyed = false;
63 61
64 private _pending: MapOf<boolean> = {};
62 private readonly _pending: {[K: string]: unknown} = {};
65 63
66 create(): ILifetime {
67 const self = this;
64 create<T>(): ILifetime<T> {
68 65 const id = ++nextId;
69 66 return {
70 has() {
71 return (id in self._cache);
67 has: () => id in this._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() {
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;
76 initialize: () => {
77 if (this._pending[id])
78 throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`);
79 this._pending[id] = true;
79 80 },
80 81
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) {
82 store: (item: T, cleanup?: (item: T) => void) => {
88 83 argumentNotNull(id, "id");
89 84 argumentNotNull(item, "item");
90 85
91 if (this.has())
86 if (id in this._cache)
92 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 93 throw new Error("Lifetime manager is destroyed");
99 94 if (cleanup) {
100 self._cleanup.push(() => cleanup(item));
95 this._cleanup.push(() => cleanup(item));
101 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 112 return emptyLifetime;
118 113 }
119 114
120 static hierarchyLifetime() {
121 let _lifetime = unknownLifetime;
115 static hierarchyLifetime<T>() {
116 let _lifetime: ILifetime<T> = unknownLifetime;
122 117 return {
123 initialize(context: ActivationContext<any>) {
118 initialize(context: ActivationContext<object>) {
124 119 if (_lifetime !== unknownLifetime)
125 120 throw new Error("Cyclic reference activation detected");
126 121
127 _lifetime = context.getContainer().getLifetimeManager().create();
122 _lifetime = context.createContainerLifetime<T>();
128 123 },
129 124 get() {
130 125 return _lifetime.get();
@@ -132,19 +127,19 export class LifetimeManager implements
132 127 has() {
133 128 return _lifetime.has();
134 129 },
135 store(item: any, cleanup?: (item: any) => void) {
130 store(item: T, cleanup?: (item: T) => void) {
136 131 return _lifetime.store(item, cleanup);
137 132 },
138 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() {
145 let _lifetime = unknownLifetime;
139 static contextLifetime<T>() {
140 let _lifetime: ILifetime<T> = unknownLifetime;
146 141 return {
147 initialize(context: ActivationContext<any>) {
142 initialize(context: ActivationContext<object>) {
148 143 if (_lifetime !== unknownLifetime)
149 144 throw new Error("Cyclic reference detected");
150 145 _lifetime = context.createLifetime();
@@ -155,17 +150,17 export class LifetimeManager implements
155 150 has() {
156 151 return _lifetime.has();
157 152 },
158 store(item: any) {
153 store(item: T) {
159 154 _lifetime.store(item);
160 155 },
161 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) {
168 argumentNotEmptyString(typeId, "typeId");
162 static singletonLifetime<T>(typeId: string) {
163 argumentNotNull(typeId, "typeId");
169 164 let pending = false;
170 165 return {
171 166 has() {
@@ -174,30 +169,30 export class LifetimeManager implements
174 169 get() {
175 170 if (!this.has())
176 171 throw new Error(`The instance ${typeId} doesn't exists`);
177 return singletons[typeId];
172 return singletons[typeId] as T;
178 173 },
179 174 initialize() {
180 175 if (pending)
181 176 throw new Error("Cyclic reference detected");
182 177 pending = true;
183 178 },
184 store(item: any) {
179 store(item: T) {
185 180 singletons[typeId] = item;
186 181 pending = false;
187 182 },
188 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>) {
195 let _lifetime = unknownLifetime;
189 static containerLifetime<T>(container: { createLifetime<X>(): ILifetime<X>}) {
190 let _lifetime: ILifetime<T> = unknownLifetime;
196 191 return {
197 initialize(context: ActivationContext<any>) {
192 initialize() {
198 193 if (_lifetime !== unknownLifetime)
199 194 throw new Error("Cyclic reference detected");
200 _lifetime = container.getLifetimeManager().create();
195 _lifetime = container.createLifetime();
201 196 },
202 197 get() {
203 198 return _lifetime.get();
@@ -205,11 +200,11 export class LifetimeManager implements
205 200 has() {
206 201 return _lifetime.has();
207 202 },
208 store(item: any) {
203 store(item: T) {
209 204 _lifetime.store(item);
210 205 },
211 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 3 export type primitive = number | string | null | undefined | symbol;
2 4
3 5 export interface IDestroyable {
4 6 destroy(): void;
5 7 }
6 8
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
9 /**
10 * @template S Карта доступных зависимостей
11 */
32 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>;
34 <K extends ContainerKeys<S>, O extends DependencyOptions>(this: void, name: K, opts?: O): InferReferenceType<S, K, O>;
13 /**
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 32 export interface DescriptorBuilder<S extends object, T> {
33
34 /**
35 *
36 * @param f
37 */
38 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 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;
43 lifetime(lifetime: "singleton", typeId: string | number | object): this;
44 lifetime(lifetime: ILifetime<T> | Exclude<ActivationType, "singleton">): this;
47 45
48 46 cleanup(cb: (item: T) => void): this;
49 47
50 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 58 activate(context: ActivationContext<S>): T;
59 59 }
60 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>;
61 export type ConfigurableDescriptor<S extends object, K extends ConfigurableKeys<S>> = Descriptor<ContainerServices<S>, ConfigurableServices<S>[K]>;
66 62
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>>;
63 export type RegistrationMap<S extends object, K extends keyof S = keyof S> = {
64 [k in K]-?: Descriptor<S, S[k]>;
73 65 };
74 66
75 export type PartialServiceMap<S extends object> = {
76 [k in keyof S]?: Descriptor<S, S[k]>;
77 };
67 export interface ProvidedServices<S extends object> {
68 container: ServiceLocator<ContainerServices<S>>;
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 85 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;
86 resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
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 {
85 getLifetimeManager(): LifetimeManager;
86 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>): this;
87 register(services: PartialServiceMap<S>): this;
90 export interface LifetimeContainer {
91 createLifetime<T>(): ILifetime<T>;
92 }
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 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 103 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
104 104
@@ -106,13 +106,16 export type ActivationType = "singleton"
106 106 * Интерфейс для управления жизнью экземпляра объекта. Каждая регистрация имеет
107 107 * свой собственный объект `ILifetime`, который создается при первой активации
108 108 */
109 export interface ILifetime {
109 export interface ILifetime<T> {
110 110 /** Проверяет, что уже создан экземпляр объекта */
111 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;
118 }
120 export type RequiredKeys<T, K extends keyof T = keyof T> = keyof { [p in K as (T[p] extends undefined ? never : p)]-?: T[p] };
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 {
4 return (val === null || val === undefined || typeof (val) === "string" ||
5 typeof (val) === "number" || typeof (val) === "boolean");
6 } No newline at end of file
26 export const argumentNotNull = (arg: unknown, name: string) => {
27 if (arg === null || arg === undefined)
28 throw new Error("The argument " + name + " can't be null or undefined");
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 2 "extends": "../tsconfig",
3 "compilerOptions": {
4 "rootDir": "ts"
5 },
6 "include": [
7 "ts/**/*.ts"
8 ]
9 3 } No newline at end of file
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now