##// END OF EJS Templates
Working on container builder
cin -
r5:83fa5814462d default
parent child
Show More
@@ -0,0 +1,46
1 import { Container } from "./Container";
2 import { DescriptorBuilder } from "./DescriptorBuilder";
3 import { Configurable, ConfigurableKeys, ContainerServices, Descriptor, IContainerBuilder, IDescriptorBuilder, RegistrationMap, ServiceContainer } from "./interfaces";
4 import { LifetimeManager } from "./LifetimeManager";
5
6 export class ContainerBuilder<S extends Configurable<S>> implements IContainerBuilder<S>{
7
8 private _pending = 1;
9
10 private readonly _services: Partial<RegistrationMap<ContainerServices<S>>>;
11
12 private readonly _lifetimeManager = new LifetimeManager();
13
14 constructor(parentServices?: object) {
15 this._services = Object.create(parentServices ? parentServices : null) as object;
16 }
17
18 createServiceBuilder<K extends keyof S>(name: K): IDescriptorBuilder<S, NonNullable<S[K]>, object> {
19 return new DescriptorBuilder(this._lifetimeManager, this._register(name), this._fail);
20 }
21
22 build(): ServiceContainer<S> {
23 this._assertBuilding();
24 if(!this._complete())
25 throw new Error("The configuration didn't complete.");
26 return new Container(this._services, this._lifetimeManager);
27 }
28
29 private readonly _register = <K extends ConfigurableKeys<S>>(name: K) => (descriptor: Descriptor<S, NonNullable<S[K]>>) => {
30 this._complete();
31 this._services[name] = descriptor;
32 };
33
34 private readonly _fail = (ex: unknown) => {
35
36 };
37
38 private _assertBuilding() {
39 throw new Error("The descriptor builder is finalized");
40 }
41
42 private _complete() {
43 return !(--this._pending);
44 }
45
46 } No newline at end of file
@@ -1,32 +1,33
1 {
1 {
2 "root": true,
2 "root": true,
3 "extends": [
3 "extends": [
4 "plugin:react/recommended",
4 "plugin:react/recommended",
5 "eslint:recommended",
5 "eslint:recommended",
6 "plugin:@typescript-eslint/eslint-recommended",
6 "plugin:@typescript-eslint/eslint-recommended",
7 "plugin:@typescript-eslint/recommended",
7 "plugin:@typescript-eslint/recommended",
8 "plugin:@typescript-eslint/recommended-requiring-type-checking"
8 "plugin:@typescript-eslint/recommended-requiring-type-checking"
9 ],
9 ],
10 "parser": "@typescript-eslint/parser",
10 "parser": "@typescript-eslint/parser",
11 "parserOptions": {
11 "parserOptions": {
12 "ecmaFeatures": {
12 "ecmaFeatures": {
13 "jsx": true
13 "jsx": true
14 },
14 },
15 "ecmaVersion": 5,
15 "ecmaVersion": 5,
16 "tsconfigRootDir": "src",
16 "tsconfigRootDir": "src",
17 "project": ["tsconfig.eslint.json", "*/tsconfig.json"]
17 "project": ["tsconfig.eslint.json", "*/tsconfig.json"]
18 },
18 },
19 "plugins": [
19 "plugins": [
20 "@typescript-eslint"
20 "@typescript-eslint"
21 ],
21 ],
22 "rules": {
22 "rules": {
23 "@typescript-eslint/no-empty-function": "off",
23 "@typescript-eslint/no-empty-function": "off",
24 "max-classes-per-file": [
24 "max-classes-per-file": [
25 "error",
25 "error",
26 { "ignoreExpressions": true, "max": 1 }
26 { "ignoreExpressions": true, "max": 1 }
27 ],
27 ],
28 "@typescript-eslint/prefer-readonly": ["error"],
28 "@typescript-eslint/prefer-readonly": ["error"],
29 "semi": "off",
29 "@typescript-eslint/semi": ["error"]
30 "@typescript-eslint/semi": ["error"]
30
31
31 }
32 }
32 }
33 }
@@ -1,138 +1,139
1 import { Descriptor, ILifetime, RegistrationMap, LifetimeContainer, ConfigurableKeys } from "./interfaces";
1 import { Descriptor, ILifetime, RegistrationMap, LifetimeContainer, ConfigurableKeys } from "./interfaces";
2 import { LifetimeManager } from "./LifetimeManager";
2 import { argumentNotNull } from "./traits";
3 import { argumentNotNull } from "./traits";
3
4
4 export interface ActivationContextInfo {
5 export interface ActivationContextInfo {
5 name: string;
6 name: string;
6
7
7 service: string;
8 service: string;
8
9
9 }
10 }
10
11
11 let nextId = 1;
12 let nextId = 1;
12
13
13 /** This class is created once per `Container.resolve` method call and used to
14 /** This class is created once per `Container.resolve` method call and used to
14 * cache dependencies and to track created instances. The activation context
15 * cache dependencies and to track created instances. The activation context
15 * tracks services with `context` activation type.
16 * tracks services with `context` activation type.
16 */
17 */
17 export class ActivationContext<S extends object> {
18 export class ActivationContext<S extends object> {
18 private readonly _cache: Record<string, unknown>;
19 private readonly _cache: Record<string, unknown>;
19
20
20 private readonly _services: Partial<RegistrationMap<S>>;
21 private readonly _services: Partial<RegistrationMap<S>>;
21
22
22 private readonly _name: string;
23 private readonly _name: string;
23
24
24 private readonly _service: Descriptor<S, unknown>;
25 private readonly _service: Descriptor<S, unknown>;
25
26
26 private readonly _containerLifetimeManager: LifetimeContainer;
27 private readonly _containerLifetimeManager: LifetimeManager;
27
28
28 private readonly _parent: ActivationContext<S> | undefined;
29 private readonly _parent: ActivationContext<S> | undefined;
29
30
30 /** Creates a new activation context with the specified parameters.
31 /** Creates a new activation context with the specified parameters.
31 * @param containerLifetimeManager the container which starts the activation process
32 * @param containerLifetimeManager the container which starts the activation process
32 * @param services the initial service registrations
33 * @param services the initial service registrations
33 * @param name the name of the service being activated, this parameter is
34 * @param name the name of the service being activated, this parameter is
34 * used for the debug purpose.
35 * used for the debug purpose.
35 * @param service the service to activate, this parameter is used for the
36 * @param service the service to activate, this parameter is used for the
36 * debug purpose.
37 * debug purpose.
37 */
38 */
38 constructor(containerLifetimeManager: LifetimeContainer, services: Partial<RegistrationMap<S>>, name: string, service: Descriptor<S, unknown>, cache = {}) {
39 constructor(containerLifetimeManager: LifetimeManager, services: Partial<RegistrationMap<S>>, name: string, service: Descriptor<S, unknown>, cache = {}) {
39 this._name = name;
40 this._name = name;
40 this._service = service;
41 this._service = service;
41 this._cache = cache;
42 this._cache = cache;
42 this._services = services;
43 this._services = services;
43 this._containerLifetimeManager = containerLifetimeManager;
44 this._containerLifetimeManager = containerLifetimeManager;
44 }
45 }
45
46
46 /** the name of the current resolving dependency */
47 /** the name of the current resolving dependency */
47 getName() {
48 getName() {
48 return this._name;
49 return this._name;
49 }
50 }
50
51
51 createContainerLifetime<T>() {
52 createContainerLifetime<T>() {
52 return this._containerLifetimeManager.createLifetime<T>();
53 return this._containerLifetimeManager.create<T>();
53 }
54 }
54
55
55 /** Resolves the specified dependency in the current context
56 /** Resolves the specified dependency in the current context
56 * @param name The name of the dependency being resolved
57 * @param name The name of the dependency being resolved
57 */
58 */
58 resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
59 resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
59 /** Resolves the specified dependency with the specified default value if
60 /** Resolves the specified dependency with the specified default value if
60 * the dependency is missing.
61 * the dependency is missing.
61 *
62 *
62 * @param name The name of the dependency being resolved
63 * @param name The name of the dependency being resolved
63 * @param def A default value to return in case of the specified dependency
64 * @param def A default value to return in case of the specified dependency
64 * is missing.
65 * is missing.
65 */
66 */
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): NonNullable<S[K]> | T;
67 resolve<K extends keyof S, T>(name: K, def?: T): S[K] | T | undefined {
68 resolve<K extends keyof S, T>(name: K, def?: T): S[K] | T | undefined {
68 const d = this._services[name];
69 const d = this._services[name];
69
70
70 if (d !== undefined) {
71 if (d !== undefined) {
71 return this.activate(d, name.toString());
72 return this.activate(d, name.toString());
72 } else {
73 } else {
73 if (arguments.length > 1)
74 if (arguments.length > 1)
74 return def;
75 return def;
75 else
76 else
76 throw new Error(`Service ${String(name)} not found`);
77 throw new Error(`Service ${String(name)} not found`);
77 }
78 }
78 }
79 }
79
80
80 /**
81 /**
81 * registers services local to the the activation context
82 * registers services local to the the activation context
82 *
83 *
83 * @name{string} the name of the service
84 * @name{string} the name of the service
84 * @service{string} the service descriptor to register
85 * @service{string} the service descriptor to register
85 */
86 */
86 register<K extends ConfigurableKeys<S>>(name: K, service: RegistrationMap<S>[K]) {
87 register<K extends ConfigurableKeys<S>>(name: K, service: RegistrationMap<S>[K]) {
87 argumentNotNull(name, "name");
88 argumentNotNull(name, "name");
88
89
89 this._services[name] = service;
90 this._services[name] = service;
90 }
91 }
91
92
92 createLifetime<T>(): ILifetime<T> {
93 createLifetime<T>(): ILifetime<T> {
93 const id = nextId++;
94 const id = nextId++;
94 return {
95 return {
95 initialize() {},
96 initialize() {},
96 has: () => id in this._cache,
97 has: () => id in this._cache,
97 get: () => this._cache[id] as T,
98 get: () => this._cache[id] as T,
98 store: item => {
99 store: item => {
99 this._cache[id] = item;
100 this._cache[id] = item;
100 }
101 }
101 };
102 };
102 }
103 }
103
104
104 activate<T>(d: Descriptor<S, T>, name: string) {
105 activate<T>(d: Descriptor<S, T>, name: string) {
105 // TODO: add logging
106 // TODO: add logging
106 // if (trace.isLogEnabled())
107 // if (trace.isLogEnabled())
107 // trace.log("enter {0} {1}", name, d);
108 // trace.log("enter {0} {1}", name, d);
108
109
109 const ctx = this.enter(d, name);
110 const ctx = this.enter(d, name);
110 const v = d.activate(ctx);
111 const v = d.activate(ctx);
111
112
112 // if (trace.isLogEnabled())
113 // if (trace.isLogEnabled())
113 // trace.log(`leave ${name}`);
114 // trace.log(`leave ${name}`);
114
115
115 return v;
116 return v;
116 }
117 }
117
118
118 getStack(): ActivationContextInfo[] {
119 getStack(): ActivationContextInfo[] {
119 const stack = [{
120 const stack = [{
120 name: this._name,
121 name: this._name,
121 service: this._service.toString()
122 service: this._service.toString()
122 }];
123 }];
123
124
124 return this._parent ?
125 return this._parent ?
125 stack.concat(this._parent.getStack()) :
126 stack.concat(this._parent.getStack()) :
126 stack;
127 stack;
127 }
128 }
128
129
129 private enter(service: Descriptor<S, unknown>, name: string) {
130 private enter(service: Descriptor<S, unknown>, name: string) {
130 return new ActivationContext(
131 return new ActivationContext(
131 this._containerLifetimeManager,
132 this._containerLifetimeManager,
132 Object.create(this._services) as typeof this._services,
133 Object.create(this._services) as typeof this._services,
133 name,
134 name,
134 service,
135 service,
135 this._cache
136 this._cache
136 );
137 );
137 }
138 }
138 }
139 }
@@ -1,79 +1,54
1 import { ActivationContext } from "./ActivationContext";
1 import { ActivationContext } from "./ActivationContext";
2 import { ActivationError } from "./ActivationError";
2 import { ActivationError } from "./ActivationError";
3 import { RegistrationMap, ContainerKeys, ServiceContainer, ContainerServices, ConfigurableServices, ConfigurableKeys, ServiceLocator, Descriptor} from "./interfaces";
3 import { ContainerBuilder } from "./ContainerBuilder";
4 import { RegistrationMap, ServiceContainer, ContainerServices, ServiceLocator, IContainerBuilder, Configurable, ContainerKeys} from "./interfaces";
4 import { LifetimeManager } from "./LifetimeManager";
5 import { LifetimeManager } from "./LifetimeManager";
5 import { each, isKey } from "./traits";
6
6
7 export class Container<S extends object > implements ServiceContainer<S> {
7 export class Container<S extends Configurable<S>> implements ServiceContainer<S> {
8 private readonly _services: Partial<RegistrationMap<ContainerServices<S>>>;
8 private readonly _services: Partial<RegistrationMap<ContainerServices<S>>>;
9
9
10 private readonly _lifetimeManager: LifetimeManager;
10 private readonly _lifetimeManager: LifetimeManager;
11
11
12 private readonly _cleanup: (() => void)[];
13
14 private _disposed: boolean;
12 private _disposed: boolean;
15
13
16 constructor(parent?: Container<S>) {
14 constructor(services: Partial<RegistrationMap<ContainerServices<S>>>, lifetimeManager: LifetimeManager) {
17 this._services = Object.create(parent ? parent._services : null) as typeof this._services;
15 this._services = services;
18 this._cleanup = [];
16 this._services.container = { activate: () => this as ContainerServices<S>["container"]};
19 this._services.container = { activate: () => this as ServiceLocator<ContainerServices<S>>};
17 this._services.childContainer = { activate: () => this.createChildContainer() as ContainerServices<S>["childContainer"] };
20 this._services.childContainer = { activate: () => this.createChildContainer() };
21 this._disposed = false;
18 this._disposed = false;
22 this._lifetimeManager = new LifetimeManager();
19 this._lifetimeManager = lifetimeManager;
23 }
20 }
24
21
25 register<K extends ConfigurableKeys<S>>(name: K, service: Descriptor<ContainerServices<S>, ConfigurableServices<S>[K]>): void;
22 createChildContainer(): IContainerBuilder<S> {
26 register<K extends ConfigurableKeys<S>>(services: {[k in K]: Descriptor<ContainerServices<S>, ConfigurableServices<S>[k]>}): void;
23 return new ContainerBuilder(this._services);
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;
35 }
36 }
24 }
37
25
38 createChildContainer(): ServiceContainer<S> {
26 resolve<K extends keyof ContainerServices<S>>(name: K): NonNullable<ContainerServices<S>[K]>;
39 return new Container(this);
27 resolve<K extends keyof ContainerServices<S>, T>(name: K, def: T): NonNullable<ContainerServices<S>[K]> | T;
40 }
28 resolve<K extends keyof ContainerServices<S>, T>(name: K, def?: T) {
41
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
29 // TODO: add logging
46 // trace.debug("resolve {0}", name);
30 // trace.debug("resolve {0}", name);
47 const d = this._services[name];
31 const d = this._services[name];
48 if (d === undefined) {
32 if (d === undefined) {
49 if (arguments.length > 1)
33 if (arguments.length > 1)
50 return def;
34 return def;
51 else
35 else
52 throw new Error(`Service '${String(name)}' isn't found`);
36 throw new Error(`Service '${String(name)}' isn't found`);
53 } else {
37 } else {
54
38
55 const context = new ActivationContext(this, this._services, String(name), d);
39 const context = new ActivationContext(this._lifetimeManager, this._services, String(name), d);
56 try {
40 try {
57 return d.activate(context);
41 return d.activate(context);
58 } catch (error) {
42 } catch (error) {
59 throw new ActivationError(name.toString(), context.getStack(), error);
43 throw new ActivationError(name.toString(), context.getStack(), error);
60 }
44 }
61 }
45 }
62 }
46 }
63
47
64 createLifetime<T>() {
65 return this._lifetimeManager.create<T>();
66 }
67
68 destroy() {
48 destroy() {
69 return this.dispose();
70 }
71 dispose() {
72 if (this._disposed)
49 if (this._disposed)
73 return;
50 return;
74 this._disposed = true;
51 this._disposed = true;
75 for (const f of this._cleanup)
76 f();
77 this._lifetimeManager.destroy();
52 this._lifetimeManager.destroy();
78 }
53 }
79 }
54 }
@@ -1,196 +1,189
1 import { RegistrationBuilder, LifetimeContainer, ConfigurableKeys, IDescriptorBuilder, Ref, Resolved, DepsMap } from "./interfaces";
1 import { RegistrationBuilder, LifetimeContainer, ConfigurableKeys, IDescriptorBuilder, Ref, Resolved, DepsMap } from "./interfaces";
2 import { Descriptor, ILifetime, ActivationType } from "./interfaces";
2 import { Descriptor, ILifetime, ActivationType } from "./interfaces";
3 import { DescriptorImpl, RegistrationOverridesMap } from "./DescriptorImpl";
3 import { DescriptorImpl, RegistrationOverridesMap } from "./DescriptorImpl";
4 import { LifetimeManager } from "./LifetimeManager";
4 import { LifetimeManager } from "./LifetimeManager";
5 import { each, isKey, isPromise, isString, key, oid } from "./traits";
5 import { each, isKey, isPromise, isString, key, oid } from "./traits";
6
6
7 /**
7 /**
8 * @template {S} Карта доступных зависимостей, как правило `ContainerServices`
8 * @template {S} Карта доступных зависимостей, как правило `ContainerServices`
9 * @template {T} Тип сервиса
9 * @template {T} Тип сервиса
10 */
10 */
11 export class DescriptorBuilder<S extends object, T, R extends object = object> implements IDescriptorBuilder<S, T, R> {
11 export class DescriptorBuilder<S extends object, T, R extends object = object> implements IDescriptorBuilder<S, T, R> {
12 private readonly _lifetimeContainer: LifetimeContainer;
12 private readonly _lifetimeManager: LifetimeManager;
13 private readonly _cb: (d: Descriptor<S, T>) => void;
13 private readonly _cb: (d: Descriptor<S, T>) => void;
14
14
15 private readonly _eb: (err: unknown) => void;
15 private readonly _eb: (err: unknown) => void;
16
16
17 private readonly _refs: DepsMap<key, keyof S>;
17 private readonly _refs: DepsMap<key, keyof S>;
18
18
19 private _lifetime = LifetimeManager.empty<T>();
19 private _lifetime = LifetimeManager.empty<T>();
20
20
21 private _overrides: RegistrationOverridesMap<S>;
21 private _overrides: RegistrationOverridesMap<S>;
22
22
23 private _cleanup?: (item: T) => void;
23 private _cleanup?: (item: T) => void;
24
24
25 private _factory?: (refs: R) => T;
25 private _factory?: (refs: R) => T;
26
26
27 private _pending = 1;
27 private _pending = 1;
28
28
29 private _failed = false;
29 private _failed = false;
30
30
31 private _finalized = false;
31 private _finalized = false;
32
32
33 /**
33 /**
34 * Creates new DescriptorBuilder. Accepts a lifetime container for resolving "container"
34 * Creates new DescriptorBuilder. Accepts a lifetime container for resolving "container"
35 * lifetime.
35 * lifetime.
36 *
36 *
37 * @param container The lifetime container is the container where the service is to be registered.
37 * @param lifetimeManager The lifetime container is the container where the service is to be registered.
38 * @param cb The callback to receive the built service descriptor
38 * @param cb The callback to receive the built service descriptor
39 * @param eb The callback to receive the error due
39 * @param eb The callback to receive the error due
40 */
40 */
41 constructor(container: LifetimeContainer, cb: (d: Descriptor<S, T>) => void, eb: (err: unknown) => void) {
41 constructor(lifetimeManager: LifetimeManager, cb: (d: Descriptor<S, T>) => void, eb: (err: unknown) => void) {
42 this._lifetimeContainer = container;
42 this._lifetimeManager = lifetimeManager;
43 this._cb = cb;
43 this._cb = cb;
44 this._eb = eb;
44 this._eb = eb;
45 this._overrides = {};
45 this._overrides = {};
46 this._refs = {};
46 this._refs = {};
47 }
47 }
48 wants<X extends { [k in Exclude<key, keyof R>]: keyof S | Ref<keyof S, boolean, unknown>; }>(refs: X):
48 wants<X extends { [k in Exclude<key, keyof R>]: keyof S | Ref<keyof S, boolean, unknown>; }>(refs: X):
49 IDescriptorBuilder<S, T, R & {
49 IDescriptorBuilder<S, T, R & {
50 [k in keyof X]:
50 [k in keyof X]:
51 X[k] extends keyof S ? NonNullable<S[X[k]]> :
51 X[k] extends keyof S ? NonNullable<S[X[k]]> :
52 X[k] extends Ref<infer K, infer L, infer D> ? Resolved<S, K & keyof S, L, D> :
52 X[k] extends Ref<infer K, infer L, infer D> ? Resolved<S, K & keyof S, L, D> :
53 never;
53 never;
54 }> {
54 }> {
55
55
56 each(refs, (v, k) => this._refs[k] = v);
56 each(refs, (v, k) => this._refs[k] = v);
57
57
58 return this as IDescriptorBuilder<S, T, R & {
58 return this as IDescriptorBuilder<S, T, R & {
59 [k in keyof X]:
59 [k in keyof X]:
60 X[k] extends keyof S ? NonNullable<S[X[k]]> :
60 X[k] extends keyof S ? NonNullable<S[X[k]]> :
61 X[k] extends Ref<infer K, infer L, infer D> ? Resolved<S, K & keyof S, L, D> :
61 X[k] extends Ref<infer K, infer L, infer D> ? Resolved<S, K & keyof S, L, D> :
62 never;
62 never;
63 }>;
63 }>;
64 }
64 }
65 factory(f: (refs: R) => T): void {
65 factory(f: (refs: R) => T): void {
66 this._assertBuilding();
66 this._assertBuilding();
67 this._factory = f;
67 this._factory = f;
68 this._finalize();
68 this._finalize();
69 this._complete();
69 this._complete();
70 }
70 }
71
71
72
72
73 private _assertBuilding() {
73 private _assertBuilding() {
74 if (this._finalized)
74 if (this._finalized)
75 throw new Error("The descriptor builder is finalized");
75 throw new Error("The descriptor builder is finalized");
76 }
76 }
77
77
78 private _finalize() {
78 private _finalize() {
79 this._finalized = true;
79 this._finalized = true;
80 }
80 }
81
81
82 override<K extends ConfigurableKeys<S>>(name: K, builder: RegistrationBuilder<S, NonNullable<S[K]>>): this;
82 override<K extends ConfigurableKeys<S>>(name: K, builder: RegistrationBuilder<S, NonNullable<S[K]>>): this;
83 override<K extends ConfigurableKeys<S>>(services: { [k in K]: RegistrationBuilder<S, NonNullable<S[k]>> }): this;
83 override<K extends ConfigurableKeys<S>>(services: { [k in K]: RegistrationBuilder<S, NonNullable<S[k]>> }): this;
84 override<K extends ConfigurableKeys<S>>(nameOrServices: K | { [name in K]: RegistrationBuilder<S, NonNullable<S[K]>> }, builder?: RegistrationBuilder<S, NonNullable<S[K]>>): this {
84 override<K extends ConfigurableKeys<S>>(nameOrServices: K | { [name in K]: RegistrationBuilder<S, NonNullable<S[K]>> }, builder?: RegistrationBuilder<S, NonNullable<S[K]>>): this {
85 this._assertBuilding();
85 this._assertBuilding();
86 const guard = (v: void | Promise<void>) => {
86 const guard = (v: void | Promise<void>) => {
87 if (isPromise(v))
87 if (isPromise(v))
88 v.catch(err => this._fail(err));
88 v.catch(err => this._fail(err));
89 };
89 };
90
90
91 if (isKey(nameOrServices)) {
91 if (isKey(nameOrServices)) {
92 if (builder) {
92 if (builder) {
93 this._defer();
93 this._defer();
94 const d = new DescriptorBuilder<S, NonNullable<S[K]>>(
94 const d = new DescriptorBuilder<S, NonNullable<S[K]>>(
95 this._lifetimeContainer,
95 this._lifetimeManager,
96 result => {
96 result => {
97 this._overrides[nameOrServices] = result;
97 this._overrides[nameOrServices] = result;
98 this._complete();
98 this._complete();
99 },
99 },
100 err => this._fail(err)
100 err => this._fail(err)
101 );
101 );
102
102
103 try {
103 try {
104 guard(builder(d));
104 guard(builder(d));
105 } catch (err) {
105 } catch (err) {
106 this._fail(err);
106 this._fail(err);
107 }
107 }
108 }
108 }
109 } else {
109 } else {
110 each(nameOrServices, (v, k) => this.override(k, v));
110 each(nameOrServices, (v, k) => this.override(k, v));
111 }
111 }
112 return this;
112 return this;
113 }
113 }
114
114
115 lifetime(lifetime: "singleton", typeId: string): this;
115 lifetime(lifetime: "singleton", typeId: string): this;
116 lifetime(lifetime: ILifetime<T> | Exclude<ActivationType, "singleton">): this;
116 lifetime(lifetime: ILifetime<T> | Exclude<ActivationType, "singleton">): this;
117 lifetime(lifetime: ILifetime<T> | ActivationType, typeId?: string): this {
117 lifetime(lifetime: ILifetime<T> | ActivationType, typeId?: string): this {
118 this._assertBuilding();
118 this._assertBuilding();
119 if (isString(lifetime)) {
119 if (isString(lifetime)) {
120 this._lifetime = this._resolveLifetime(lifetime, typeId);
120 this._lifetime = this._resolveLifetime(lifetime, typeId);
121 } else {
121 } else {
122 this._lifetime = lifetime;
122 this._lifetime = lifetime;
123 }
123 }
124 return this;
124 return this;
125 }
125 }
126
126
127 cleanup(cb: (item: T) => void): this {
127 cleanup(cb: (item: T) => void): this {
128 this._assertBuilding();
128 this._assertBuilding();
129 this._cleanup = cb;
129 this._cleanup = cb;
130 return this;
130 return this;
131 }
131 }
132
132
133 /*factory(f: (resolve: Resolver<S>) => T): void {
134 this._assertBuilding();
135 this._factory = f;
136 this._finalize();
137 this._complete();
138 }*/
139
140 value(v: T): void {
133 value(v: T): void {
141 this._assertBuilding();
134 this._assertBuilding();
142 this._cb({
135 this._cb({
143 activate() {
136 activate() {
144 return v;
137 return v;
145 }
138 }
146 });
139 });
147 this._finalize();
140 this._finalize();
148 }
141 }
149
142
150 _resolveLifetime<T>(activation: ActivationType, typeId?: string | object): ILifetime<T> {
143 _resolveLifetime<T>(activation: ActivationType, typeId?: string | object): ILifetime<T> {
151 switch (activation) {
144 switch (activation) {
152 case "container":
145 case "container":
153 return LifetimeManager.containerLifetime(this._lifetimeContainer);
146 return this._lifetimeManager.create();
154 case "hierarchy":
147 case "hierarchy":
155 return LifetimeManager.hierarchyLifetime();
148 return LifetimeManager.hierarchyLifetime();
156 case "context":
149 case "context":
157 return LifetimeManager.contextLifetime();
150 return LifetimeManager.contextLifetime();
158 case "singleton": {
151 case "singleton": {
159 if (!typeId)
152 if (!typeId)
160 throw Error("The singleton activation requires a typeId");
153 throw Error("The singleton activation requires a typeId");
161
154
162 const _oid = isString(typeId) ? typeId : oid(typeId);
155 const _oid = isString(typeId) ? typeId : oid(typeId);
163
156
164 return LifetimeManager.singletonLifetime(_oid);
157 return LifetimeManager.singletonLifetime(_oid);
165 }
158 }
166 default:
159 default:
167 return LifetimeManager.empty();
160 return LifetimeManager.empty();
168 }
161 }
169 }
162 }
170
163
171 _defer() {
164 _defer() {
172 this._pending++;
165 this._pending++;
173 }
166 }
174
167
175 _complete() {
168 _complete() {
176 if (--this._pending === 0) {
169 if (--this._pending === 0) {
177 if (!this._factory)
170 if (!this._factory)
178 throw new Error("The factory must be specified");
171 throw new Error("The factory must be specified");
179
172
180 this._cb(new DescriptorImpl<S, T>({
173 this._cb(new DescriptorImpl<S, T>({
181 lifetime: this._lifetime,
174 lifetime: this._lifetime,
182 factory: this._factory,
175 factory: this._factory,
183 overrides: this._overrides,
176 overrides: this._overrides,
184 cleanup: this._cleanup
177 cleanup: this._cleanup
185 }));
178 }));
186 }
179 }
187 }
180 }
188
181
189 _fail(err: unknown) {
182 _fail(err: unknown) {
190 if (!this._failed) {
183 if (!this._failed) {
191 this._failed = true;
184 this._failed = true;
192 this._eb.call(undefined, err);
185 this._eb.call(undefined, err);
193 }
186 }
194 }
187 }
195
188
196 }
189 }
@@ -1,83 +1,83
1 import { Descriptor, ILifetime, ConfigurableKeys, DepsMap, Ref } from "./interfaces";
1 import { Descriptor, ILifetime, ConfigurableKeys, DepsMap, Ref, IActivationContext } from "./interfaces";
2 import { ActivationContext } from "./ActivationContext";
2 import { ActivationContext } from "./ActivationContext";
3 import { each, isKey, key } from "./traits";
3 import { each, isKey, key } from "./traits";
4
4
5 export type RegistrationOverridesMap<S extends object> = { [k in ConfigurableKeys<S>]?: Descriptor<S, NonNullable<S[k]>> };
5 export type RegistrationOverridesMap<S extends object> = { [k in ConfigurableKeys<S>]?: Descriptor<S, NonNullable<S[k]>> };
6
6
7 export interface DescriptorImplArgs<S extends object, T> {
7 export interface DescriptorImplArgs<S extends object, T> {
8 lifetime: ILifetime<T>;
8 lifetime: ILifetime<T>;
9
9
10 factory: (refs: Record<key, never>) => T;
10 factory: (refs: Record<key, never>) => T;
11
11
12 cleanup?: (item: T) => void;
12 cleanup?: (item: T) => void;
13
13
14 overrides?: RegistrationOverridesMap<S>;
14 overrides?: RegistrationOverridesMap<S>;
15
15
16 dependencies?: DepsMap<key, keyof S>;
16 dependencies?: DepsMap<key, keyof S>;
17 }
17 }
18
18
19
19
20 export class DescriptorImpl<S extends object, T> implements Descriptor<S, T> {
20 export class DescriptorImpl<S extends object, T> implements Descriptor<S, T> {
21
21
22 private readonly _overrides?: RegistrationOverridesMap<S>;
22 private readonly _overrides?: RegistrationOverridesMap<S>;
23
23
24 private readonly _lifetime: ILifetime<T>;
24 private readonly _lifetime: ILifetime<T>;
25
25
26 private readonly _factory: (refs: Record<key, never>) => T;
26 private readonly _factory: (refs: Record<key, never>) => T;
27
27
28 private readonly _cleanup?: (item: T) => void;
28 private readonly _cleanup?: (item: T) => void;
29
29
30 private readonly _deps?: DepsMap<key, keyof S>;
30 private readonly _deps?: DepsMap<key, keyof S>;
31
31
32 constructor({ lifetime, factory, cleanup, overrides, dependencies }: DescriptorImplArgs<S, T>) {
32 constructor({ lifetime, factory, cleanup, overrides, dependencies }: DescriptorImplArgs<S, T>) {
33 this._lifetime = lifetime;
33 this._lifetime = lifetime;
34 this._factory = factory;
34 this._factory = factory;
35 if (cleanup)
35 if (cleanup)
36 this._cleanup = cleanup;
36 this._cleanup = cleanup;
37 if (overrides)
37 if (overrides)
38 this._overrides = overrides;
38 this._overrides = overrides;
39 if (dependencies)
39 if (dependencies)
40 this._deps = dependencies;
40 this._deps = dependencies;
41 }
41 }
42
42
43 activate(context: ActivationContext<S>): T {
43 activate(context: IActivationContext<S>): T {
44
44
45 if (this._lifetime.has())
45 if (this._lifetime.has())
46 return this._lifetime.get();
46 return this._lifetime.get();
47
47
48 this._lifetime.initialize(context);
48 this._lifetime.initialize(context);
49
49
50 if (this._overrides)
50 if (this._overrides)
51 each(this._overrides, (v, k) => context.register(k, v));
51 each(this._overrides, (v, k) => context.register(k, v));
52
52
53 const resolve = <K extends keyof S, L extends boolean, D = never>({ name, lazy, ...opts }: Ref<K, L, D>) => {
53 const resolve = <K extends keyof S, L extends boolean, D = never>({ name, lazy, ...opts }: Ref<K, L, D>) => {
54 if (lazy) {
54 if (lazy) {
55 return () => "default" in opts ? context.resolve(name, opts.default) : context.resolve(name);
55 return () => "default" in opts ? context.resolve(name, opts.default) : context.resolve(name);
56 } else {
56 } else {
57 return "default" in opts ? context.resolve(name, opts.default) : context.resolve(name);
57 return "default" in opts ? context.resolve(name, opts.default) : context.resolve(name);
58 }
58 }
59 };
59 };
60
60
61 const makeRefs = (deps?: DepsMap<key, keyof S>) => deps ?
61 const makeRefs = (deps: typeof this._deps) => deps ?
62 Object.keys(deps)
62 Object.keys(deps)
63 .map(k => {
63 .map(k => {
64 const ref = deps[k];
64 const ref = deps[k];
65 return isKey(ref) ?
65 return isKey(ref) ?
66 { [k]: resolve({ name: ref }) } :
66 { [k]: resolve({ name: ref }) } :
67 { [k]: resolve(ref) };
67 { [k]: resolve(ref) };
68 })
68 })
69 .reduce((a, p) => ({ ...a, ...p }), {} ) as Record<key, never>:
69 .reduce((a, p) => ({ ...a, ...p }), {} ) as Record<key, never>:
70 {} as Record<key, never>;
70 {} as Record<key, never>;
71
71
72 const instance = this._factory.call(undefined, makeRefs(this._deps));
72 const instance = this._factory.call(undefined, makeRefs(this._deps));
73
73
74 this._lifetime.store(instance, this._cleanup);
74 this._lifetime.store(instance, this._cleanup);
75
75
76 return instance;
76 return instance;
77 }
77 }
78
78
79
79
80 toString() {
80 toString() {
81 return `[object DescriptorImpl, lifetime=${String(this._lifetime)}]`;
81 return `[object DescriptorImpl, lifetime=${String(this._lifetime)}]`;
82 }
82 }
83 }
83 }
@@ -1,93 +1,71
1 import { DescriptorBuilder } from "./DescriptorBuilder";
1 import { DescriptorBuilder } from "./DescriptorBuilder";
2 import { ConfigurableKeys, ContainerServices, ConfigurableServices, RegistrationBuildersMap, ExtractRequired } from "./interfaces";
2 import { ConfigurableKeys, ContainerServices, RegistrationBuildersMap, ExtractRequired, IContainerBuilder } from "./interfaces";
3 import { ServiceContainer } from "./interfaces";
3 import { ServiceContainer } from "./interfaces";
4 import { argumentNotNull, each, isKey } from "./traits";
4 import { argumentNotNull, each, isKey } from "./traits";
5
5
6 export class FluentConfiguration<S extends object, Y extends ConfigurableKeys<S> = ConfigurableKeys<S>> {
6 export class FluentConfiguration<S extends object, Y extends ConfigurableKeys<S> = ConfigurableKeys<S>> {
7
7
8 private _builders: Partial<RegistrationBuildersMap<S>> = {};
8 private _builders: Partial<RegistrationBuildersMap<S>> = {};
9
9
10 /** Adds a declaration of the services to the current config.
10 /** Adds a declaration of the services to the current config.
11 *
11 *
12 * @template D The map of the services
12 * @template D The map of the services
13 * @returns self
13 * @returns self
14 */
14 */
15 declare<D extends Partial<Pick<S, keyof D & keyof S>>>(): FluentConfiguration<S & D, Y | ConfigurableKeys<D>> {
15 declare<D extends Partial<Pick<S, keyof D & keyof S>>>(): FluentConfiguration<S & D, Y | ConfigurableKeys<D>> {
16 return this as FluentConfiguration<S & D, Y | ConfigurableKeys<D>>;
16 return this as FluentConfiguration<S & D, Y | ConfigurableKeys<D>>;
17 }
17 }
18
18
19 /** Adds compile-time information about the already provided services
19 /** Adds compile-time information about the already provided services
20 *
20 *
21 * @template P The map of the provided services
21 * @template P The map of the provided services
22 * @returns self
22 * @returns self
23 */
23 */
24 provided<P extends Pick<S, keyof P & keyof S>>(): FluentConfiguration<S & P, Exclude<Y, keyof P>> {
24 provided<P extends Pick<S, keyof P & keyof S>>(): FluentConfiguration<S & P, Exclude<Y, keyof P>> {
25 return this as FluentConfiguration<S & P, Exclude<Y, keyof P>>;
25 return this as FluentConfiguration<S & P, Exclude<Y, keyof P>>;
26 }
26 }
27
27
28 /** Register the service.
28 /** Register the service.
29 *
29 *
30 * @param name The name of the service
30 * @param name The name of the service
31 * @param builder The service builder
31 * @param builder The service builder
32 * @returns self
32 * @returns self
33 */
33 */
34 register<K extends Y>(name: K, builder: RegistrationBuildersMap<S>[K]): FluentConfiguration<S, Exclude<Y, K>>;
34 register<K extends Y>(name: K, builder: RegistrationBuildersMap<S>[K]): FluentConfiguration<S, Exclude<Y, K>>;
35 /** Registers the collection of services
35 /** Registers the collection of services
36 * @param config The collection of services to register.
36 * @param config The collection of services to register.
37 * @returns self
37 * @returns self
38 */
38 */
39 register<K extends Y>(config: RegistrationBuildersMap<S, K>): FluentConfiguration<S, Exclude<Y, K>>;
39 register<K extends Y>(config: RegistrationBuildersMap<S, K>): FluentConfiguration<S, Exclude<Y, K>>;
40 register<K extends Y>(nameOrConfig: K | RegistrationBuildersMap<S, K>, builder?: RegistrationBuildersMap<S>[K]) {
40 register<K extends Y>(nameOrConfig: K | RegistrationBuildersMap<S, K>, builder?: RegistrationBuildersMap<S>[K]) {
41 if (isKey(nameOrConfig)) {
41 if (isKey(nameOrConfig)) {
42 argumentNotNull(builder, "builder");
42 argumentNotNull(builder, "builder");
43 this._builders[nameOrConfig] = builder;
43 this._builders[nameOrConfig] = builder;
44 } else {
44 } else {
45 each(nameOrConfig, (v, k) => this.register(k, v));
45 each(nameOrConfig, (v, k) => this.register(k, v));
46 }
46 }
47
47
48 return this as FluentConfiguration<S, Exclude<Y, K>>;
48 return this as FluentConfiguration<S, Exclude<Y, K>>;
49 }
49 }
50
50
51 /**
51 /**
52 * This method is used to enable a compile time check of the configuration.
52 * This method is used to enable a compile time check of the configuration.
53 * If there are not configured services in the configuration the compiler
53 * If there are not configured services in the configuration the compiler
54 * will trigger the error.
54 * will trigger the error.
55 *
55 *
56 * @param missing Empty object literal should always be specified.
56 * @param missing Empty object literal should always be specified.
57 * @returns self
57 * @returns self
58 */
58 */
59 // eslint-disable-next-line @typescript-eslint/no-unused-vars
59 // eslint-disable-next-line @typescript-eslint/no-unused-vars
60 done<M extends ExtractRequired<S,Y>>(missing: M) {
60 done<M extends ExtractRequired<S,Y>>(missing: M) {
61 return this;
61 return this;
62 }
62 }
63
63
64
64 configure<T extends IContainerBuilder<S>>(builder: T) {
65 apply<T extends ServiceContainer<S>>(target: T) {
66
67 let pending = 1;
68
69 const reject = (ex: unknown) => { throw ex; };
70
71 const complete = () => !--pending;
72
73 each(this._builders, (v, k) => {
65 each(this._builders, (v, k) => {
74 pending++;
66 v(builder.createServiceBuilder(k));
75 const d = new DescriptorBuilder<ContainerServices<S>, NonNullable<ConfigurableServices<S>[typeof k]>>(
76 target,
77 result => {
78 target.register(k, result);
79 complete();
80 },
81 reject
82 );
83
84
85 v(d);
86 });
67 });
87 if (!complete())
68 builder.build() as T & ServiceContainer<S>;
88 throw new Error("The configuration didn't complete.");
89
90 return target as T & ServiceContainer<S>;
91 }
69 }
92
70
93 }
71 }
@@ -1,211 +1,211
1 import { IDestroyable, ILifetime } from "./interfaces";
1 import { IActivationContext, IDestroyable, ILifetime } from "./interfaces";
2 import { ActivationContext } from "./ActivationContext";
2 import { ActivationContext } from "./ActivationContext";
3 import { argumentNotNull, isDestroyable } from "./traits";
3 import { argumentNotNull, isDestroyable } from "./traits";
4
4
5 const safeCall = (item: () => void) => {
5 const safeCall = (item: () => void) => {
6 try {
6 try {
7 item();
7 item();
8 } catch {
8 } catch {
9 // silence!
9 // silence!
10 }
10 }
11 };
11 };
12
12
13 const emptyLifetime = Object.freeze({
13 const emptyLifetime = Object.freeze({
14 has() {
14 has() {
15 return false;
15 return false;
16 },
16 },
17
17
18 initialize() {
18 initialize() {
19 },
19 },
20
20
21 get() {
21 get() {
22 throw new Error("The specified item isn't registered with this lifetime manager");
22 throw new Error("The specified item isn't registered with this lifetime manager");
23 },
23 },
24
24
25 store() {
25 store() {
26 // does nothing
26 // does nothing
27 },
27 },
28
28
29 toString() {
29 toString() {
30 return `[object EmptyLifetime]`;
30 return `[object EmptyLifetime]`;
31 }
31 }
32
32
33 });
33 });
34
34
35 const unknownLifetime = Object.freeze({
35 const unknownLifetime = Object.freeze({
36 has() {
36 has() {
37 return false;
37 return false;
38 },
38 },
39 initialize() {
39 initialize() {
40 throw new Error("Can't call initialize on the unknown lifetime object");
40 throw new Error("Can't call initialize on the unknown lifetime object");
41 },
41 },
42 get() {
42 get() {
43 throw new Error("The lifetime object isn't initialized");
43 throw new Error("The lifetime object isn't initialized");
44 },
44 },
45 store() {
45 store() {
46 throw new Error("Can't store a value in the unknown lifetime object");
46 throw new Error("Can't store a value in the unknown lifetime object");
47 },
47 },
48 toString() {
48 toString() {
49 return `[object UnknownLifetime]`;
49 return `[object UnknownLifetime]`;
50 }
50 }
51 });
51 });
52
52
53 let nextId = 0;
53 let nextId = 0;
54
54
55 const singletons: { [K:string]: unknown} = {};
55 const singletons: { [K:string]: unknown} = {};
56
56
57 export class LifetimeManager implements IDestroyable {
57 export class LifetimeManager implements IDestroyable {
58 private _cleanup: (() => void)[] = [];
58 private _cleanup: (() => void)[] = [];
59 private readonly _cache: {[K: string]: unknown} = {};
59 private readonly _cache: {[K: string]: unknown} = {};
60 private _destroyed = false;
60 private _destroyed = false;
61
61
62 private readonly _pending: {[K: string]: unknown} = {};
62 private readonly _pending: {[K: string]: unknown} = {};
63
63
64 create<T>(): ILifetime<T> {
64 create<T>(): ILifetime<T> {
65 const id = ++nextId;
65 const id = ++nextId;
66 return {
66 return {
67 has: () => id in this._cache,
67 has: () => id in this._cache,
68
68
69 get: () => {
69 get: () => {
70 const t = this._cache[id];
70 const t = this._cache[id];
71 if (t === undefined)
71 if (t === undefined)
72 throw new Error(`The item with with the key ${id} isn't found`);
72 throw new Error(`The item with with the key ${id} isn't found`);
73 return t as T;
73 return t as T;
74 },
74 },
75
75
76 initialize: () => {
76 initialize: () => {
77 if (this._pending[id])
77 if (this._pending[id])
78 throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`);
78 throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`);
79 this._pending[id] = true;
79 this._pending[id] = true;
80 },
80 },
81
81
82 store: (item: T, cleanup?: (item: T) => void) => {
82 store: (item: T, cleanup?: (item: T) => void) => {
83 argumentNotNull(id, "id");
83 argumentNotNull(id, "id");
84 argumentNotNull(item, "item");
84 argumentNotNull(item, "item");
85
85
86 if (id in this._cache)
86 if (id in this._cache)
87 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`);
88 delete this._pending[id];
88 delete this._pending[id];
89
89
90 this._cache[id] = item;
90 this._cache[id] = item;
91
91
92 if (this._destroyed)
92 if (this._destroyed)
93 throw new Error("Lifetime manager is destroyed");
93 throw new Error("Lifetime manager is destroyed");
94 if (cleanup) {
94 if (cleanup) {
95 this._cleanup.push(() => cleanup(item));
95 this._cleanup.push(() => cleanup(item));
96 } else if (isDestroyable(item)) {
96 } else if (isDestroyable(item)) {
97 this._cleanup.push(() => item.destroy());
97 this._cleanup.push(() => item.destroy());
98 }
98 }
99 }
99 }
100 };
100 };
101 }
101 }
102
102
103 destroy() {
103 destroy() {
104 if (!this._destroyed) {
104 if (!this._destroyed) {
105 this._destroyed = true;
105 this._destroyed = true;
106 this._cleanup.forEach(safeCall);
106 this._cleanup.forEach(safeCall);
107 this._cleanup.length = 0;
107 this._cleanup.length = 0;
108 }
108 }
109 }
109 }
110
110
111 static empty<T>(): ILifetime<T> {
111 static empty<T>(): ILifetime<T> {
112 return emptyLifetime;
112 return emptyLifetime;
113 }
113 }
114
114
115 static hierarchyLifetime<T>() {
115 static hierarchyLifetime<T>() {
116 let _lifetime: ILifetime<T> = unknownLifetime;
116 let _lifetime: ILifetime<T> = unknownLifetime;
117 return {
117 return {
118 initialize(context: ActivationContext<object>) {
118 initialize(context: IActivationContext<object>) {
119 if (_lifetime !== unknownLifetime)
119 if (_lifetime !== unknownLifetime)
120 throw new Error("Cyclic reference activation detected");
120 throw new Error("Cyclic reference activation detected");
121
121
122 _lifetime = context.createContainerLifetime<T>();
122 _lifetime = context.createContainerLifetime<T>();
123 },
123 },
124 get() {
124 get() {
125 return _lifetime.get();
125 return _lifetime.get();
126 },
126 },
127 has() {
127 has() {
128 return _lifetime.has();
128 return _lifetime.has();
129 },
129 },
130 store(item: T, cleanup?: (item: T) => void) {
130 store(item: T, cleanup?: (item: T) => void) {
131 return _lifetime.store(item, cleanup);
131 return _lifetime.store(item, cleanup);
132 },
132 },
133 toString() {
133 toString() {
134 return `[object HierarchyLifetime, has=${String(this.has())}]`;
134 return `[object HierarchyLifetime, has=${String(this.has())}]`;
135 }
135 }
136 };
136 };
137 }
137 }
138
138
139 static contextLifetime<T>() {
139 static contextLifetime<T>() {
140 let _lifetime: ILifetime<T> = unknownLifetime;
140 let _lifetime: ILifetime<T> = unknownLifetime;
141 return {
141 return {
142 initialize(context: ActivationContext<object>) {
142 initialize(context: ActivationContext<object>) {
143 if (_lifetime !== unknownLifetime)
143 if (_lifetime !== unknownLifetime)
144 throw new Error("Cyclic reference detected");
144 throw new Error("Cyclic reference detected");
145 _lifetime = context.createLifetime();
145 _lifetime = context.createLifetime();
146 },
146 },
147 get() {
147 get() {
148 return _lifetime.get();
148 return _lifetime.get();
149 },
149 },
150 has() {
150 has() {
151 return _lifetime.has();
151 return _lifetime.has();
152 },
152 },
153 store(item: T) {
153 store(item: T) {
154 _lifetime.store(item);
154 _lifetime.store(item);
155 },
155 },
156 toString() {
156 toString() {
157 return `[object ContextLifetime, has=${String(this.has())}]`;
157 return `[object ContextLifetime, has=${String(this.has())}]`;
158 }
158 }
159 };
159 };
160 }
160 }
161
161
162 static singletonLifetime<T>(typeId: string) {
162 static singletonLifetime<T>(typeId: string) {
163 argumentNotNull(typeId, "typeId");
163 argumentNotNull(typeId, "typeId");
164 let pending = false;
164 let pending = false;
165 return {
165 return {
166 has() {
166 has() {
167 return typeId in singletons;
167 return typeId in singletons;
168 },
168 },
169 get() {
169 get() {
170 if (!this.has())
170 if (!this.has())
171 throw new Error(`The instance ${typeId} doesn't exists`);
171 throw new Error(`The instance ${typeId} doesn't exists`);
172 return singletons[typeId] as T;
172 return singletons[typeId] as T;
173 },
173 },
174 initialize() {
174 initialize() {
175 if (pending)
175 if (pending)
176 throw new Error("Cyclic reference detected");
176 throw new Error("Cyclic reference detected");
177 pending = true;
177 pending = true;
178 },
178 },
179 store(item: T) {
179 store(item: T) {
180 singletons[typeId] = item;
180 singletons[typeId] = item;
181 pending = false;
181 pending = false;
182 },
182 },
183 toString() {
183 toString() {
184 return `[object SingletonLifetime, has=${String(this.has())}, typeId=${typeId}]`;
184 return `[object SingletonLifetime, has=${String(this.has())}, typeId=${typeId}]`;
185 }
185 }
186 };
186 };
187 }
187 }
188
188
189 static containerLifetime<T>(container: { createLifetime<X>(): ILifetime<X>}) {
189 static containerLifetime<T>(container: { createLifetime<X>(): ILifetime<X>}) {
190 let _lifetime: ILifetime<T> = unknownLifetime;
190 let _lifetime: ILifetime<T> = unknownLifetime;
191 return {
191 return {
192 initialize() {
192 initialize() {
193 if (_lifetime !== unknownLifetime)
193 if (_lifetime !== unknownLifetime)
194 throw new Error("Cyclic reference detected");
194 throw new Error("Cyclic reference detected");
195 _lifetime = container.createLifetime();
195 _lifetime = container.createLifetime();
196 },
196 },
197 get() {
197 get() {
198 return _lifetime.get();
198 return _lifetime.get();
199 },
199 },
200 has() {
200 has() {
201 return _lifetime.has();
201 return _lifetime.has();
202 },
202 },
203 store(item: T) {
203 store(item: T) {
204 _lifetime.store(item);
204 _lifetime.store(item);
205 },
205 },
206 toString() {
206 toString() {
207 return `[object ContainerLifetime, has=${String(_lifetime.has())}]`;
207 return `[object ContainerLifetime, has=${String(_lifetime.has())}]`;
208 }
208 }
209 };
209 };
210 }
210 }
211 }
211 }
@@ -1,137 +1,146
1 import { ActivationContext } from "./ActivationContext";
1 import { ActivationContext } from "./ActivationContext";
2 import { key } from "./traits";
2 import { key } from "./traits";
3
3
4 export type primitive = number | string | null | undefined | symbol;
5
6 export interface IDestroyable {
4 export interface IDestroyable {
7 destroy(): void;
5 destroy(): void;
8 }
6 }
9
7
10 /**
8 /**
11 * @template S Карта доступных зависимостей
9 * @template S Карта доступных зависимостей
12 */
10 */
13 export interface Resolver<S extends object> {
11 export interface Resolver<S extends object> {
14 /**
12 /**
15 * Функция для разрешения зависимостей, поддерживает создание фабричных методов,
13 * Функция для разрешения зависимостей, поддерживает создание фабричных методов,
16 * отложенную активацию и значение по-умолчанию для сервисов
14 * отложенную активацию и значение по-умолчанию для сервисов
17 * @template K Ключ сервиса из {@link S}
15 * @template K Ключ сервиса из {@link S}
18 * @template O Тип параметра {@link opts} используется для выведения типа
16 * @template O Тип параметра {@link opts} используется для выведения типа
19 * возвращаемого значения.
17 * возвращаемого значения.
20 * @param name Ключ сервиса, который будет разрешен.
18 * @param name Ключ сервиса, который будет разрешен.
21 * @param {boolean=} opts.lazy Признак того, что требуется отложенная активация,
19 * @param {boolean=} opts.lazy Признак того, что требуется отложенная активация,
22 * будет возвращен фабричный метод для получения зависимости. Если не указан,
20 * будет возвращен фабричный метод для получения зависимости. Если не указан,
23 * то считается `false`.
21 * то считается `false`.
24 * @param {any=} opts.default Значение по умолчанию, если в контейнере указанный
22 * @param {any=} opts.default Значение по умолчанию, если в контейнере указанный
25 * сервис не зарегистрирован
23 * сервис не зарегистрирован
26 * @returns Либо фабричный метод для получения зависимости, либо значение зависимости
24 * @returns Либо фабричный метод для получения зависимости, либо значение зависимости
27 * @throws Error Если зависимость не найдена и не предоставлено значение по-умолчанию
25 * @throws Error Если зависимость не найдена и не предоставлено значение по-умолчанию
28 */
26 */
29 <K extends keyof S, O extends { lazy: true; default?: unknown }>(name: K, opts?: O): () => (O extends { default: infer T } ? T : never) | NonNullable<S[K]>;
27 <K extends keyof S, O extends { lazy: true; default?: unknown }>(name: K, opts?: O): () => (O extends { default: infer T } ? T : never) | NonNullable<S[K]>;
30 <K extends keyof S, O extends { lazy?: false; default?: unknown }>(name: K, opts?: O): (O extends { default: infer T } ? T : never) | NonNullable<S[K]>;
28 <K extends keyof S, O extends { lazy?: false; default?: unknown }>(name: K, opts?: O): (O extends { default: infer T } ? T : never) | NonNullable<S[K]>;
31 }
29 }
32
30
33 export type DepsMap<K extends key, SK extends key> = { [k in K]: SK | Ref<SK, boolean, unknown> };
31 export type DepsMap<K extends key, SK extends key> = { [k in K]: SK | Ref<SK, boolean, unknown> };
34
32
35 export type Ref<K extends key, L extends boolean, D> = { name: K, lazy?: L} | { name: K, lazy?: L, default: D };
33 export type Ref<K extends key, L extends boolean, D> = { name: K, lazy?: L } | { name: K, lazy?: L, default: D };
36
34
37 export type Resolved<S, K extends keyof S, L, D> =
35 export type Resolved<S, K extends keyof S, L, D> =
38 L extends true ? () => NonNullable<S[K]> | (unknown extends D ? never : D) : NonNullable<S[K]> | (unknown extends D ? never : D);
36 L extends true ? () => NonNullable<S[K]> | (unknown extends D ? never : D) : NonNullable<S[K]> | (unknown extends D ? never : D);
39
37
40 export interface IDescriptorBuilder<S extends object, T, R extends object = object> {
38 export interface IDescriptorBuilder<S extends object, T, R extends object = object> {
41
39
42 /**
40 /**
43 *
41 *
44 * @param f
42 * @param f
45 */
43 */
46 factory(f: (refs: R) => T): void;
44 factory(f: (refs: R) => T): void;
47
45
48 wants<X extends DepsMap<Exclude<key, keyof R>, keyof S>>(refs: X):
46 wants<X extends DepsMap<Exclude<key, keyof R>, keyof S>>(refs: X):
49 IDescriptorBuilder<S, T, R & {
47 IDescriptorBuilder<S, T, R & {
50 [k in keyof X]:
48 [k in keyof X]:
51 X[k] extends keyof S ? NonNullable<S[X[k]]> :
49 X[k] extends keyof S ? NonNullable<S[X[k]]> :
52 X[k] extends Ref<infer K, infer L, infer D> ? Resolved<S,K & keyof S,L,D>:
50 X[k] extends Ref<infer K, infer L, infer D> ? Resolved<S, K & keyof S, L, D> :
53 never
51 never
54 }>
52 }>
55
53
56 override<K extends ConfigurableKeys<S>>(name: K, builder: RegistrationBuilder<S, NonNullable<S[K]>>): this;
54 override<K extends ConfigurableKeys<S>>(name: K, builder: RegistrationBuilder<S, NonNullable<S[K]>>): this;
57 override<K extends ConfigurableKeys<S>>(services: { [name in K]: RegistrationBuilder<S, NonNullable<S[K]>> }): this;
55 override<K extends ConfigurableKeys<S>>(services: { [name in K]: RegistrationBuilder<S, NonNullable<S[K]>> }): this;
58
56
59 lifetime(lifetime: "singleton", typeId: string | number | object): this;
57 lifetime(lifetime: "singleton", typeId: string | number | object): this;
60 lifetime(lifetime: ILifetime<T> | Exclude<ActivationType, "singleton">): this;
58 lifetime(lifetime: ILifetime<T> | Exclude<ActivationType, "singleton">): this;
61
59
62 cleanup(cb: (item: T) => void): this;
60 cleanup(cb: (item: T) => void): this;
63
61
64 value(v: T): void;
62 value(v: T): void;
65 }
63 }
66
64
67 export type RegistrationBuilder<S extends object, T> = (d: IDescriptorBuilder<S, T>) => void;
65 export type RegistrationBuilder<S extends object, T> = (d: IDescriptorBuilder<S, T>) => void;
68
66
69 export type RegistrationBuildersMap<S extends object, K extends ConfigurableKeys<S> = ConfigurableKeys<S>> = {
67 export type RegistrationBuildersMap<S extends object, K extends ConfigurableKeys<S> = ConfigurableKeys<S>> = {
70 [k in K]-?: RegistrationBuilder<ContainerServices<S>, NonNullable<ConfigurableServices<S>[k]>>
68 [k in K]-?: RegistrationBuilder<ContainerServices<S>, NonNullable<S[k]>>
71 };
69 };
72
70
73 export interface Descriptor<S extends object, T> {
71 export interface Descriptor<S extends object, T> {
74 activate(context: ActivationContext<S>): T;
72 activate(context: IActivationContext<S>): T;
75 }
73 }
76
74
77 export type ConfigurableDescriptor<S extends object, K extends ConfigurableKeys<S>> = Descriptor<ContainerServices<S>, ConfigurableServices<S>[K]>;
75 export interface IActivationContext<S extends object> extends ServiceLocator<S> {
76 createLifetime<T>(): ILifetime<T>;
77
78 createContainerLifetime<T>(): ILifetime<T>;
79 }
80
81 export type ConfigurableDescriptor<S extends object, K extends ConfigurableKeys<S>> = Descriptor<ContainerServices<S>, S[K]>;
78
82
79 export type RegistrationMap<S extends object, K extends keyof S = keyof S> = {
83 export type RegistrationMap<S extends object, K extends keyof S = keyof S> = {
80 [k in K]-?: Descriptor<S, S[k]>;
84 [k in K]-?: Descriptor<S, S[k]>;
81 };
85 };
82
86
83 export interface ProvidedServices<S extends object> {
87 export interface ContainerProvided<S extends Configurable<S>> {
84 container: ServiceLocator<ContainerServices<S>>;
88 container: ServiceLocator<ContainerServices<S>>;
85
89
86 childContainer: ServiceContainer<S>;
90 childContainer: IContainerBuilder<S>;
87 }
91 }
88
92
89 export type ProvidedKeys = keyof ProvidedServices<object>;
93 export type Configurable<S> = { [k in keyof S & (keyof ContainerProvided<never>)]: never; };
90
91 export type ContainerKeys<S extends object> = keyof ContainerServices<S>;
92
94
93 export type Mix<S, X> = { [k in keyof (S & X)]: k extends keyof X ? X[k] : S[k & keyof S] };
95 export type ProvidedKeys = keyof ContainerProvided<never>;
94
96
95 export type ContainerServices<S extends object> = Mix<S, ProvidedServices<S>>;
97 export type ContainerServices<S extends Configurable<S>> = S & ContainerProvided<S>;
96
98
97 export type ConfigurableKeys<S extends object> = Exclude<keyof S, ProvidedKeys>;
99 export type ConfigurableKeys<S extends object> = Exclude<keyof S, ProvidedKeys>;
98
100
99 export type ConfigurableServices<S extends object> = Pick<S, ConfigurableKeys<S>>;
101 export type ConfigurableServices<S extends object> = Pick<S, ConfigurableKeys<S>>;
100
102
103 export type ContainerKeys<S extends Configurable<S>> = keyof S | keyof ContainerProvided<never>;
104
101 export interface ServiceLocator<S extends object> {
105 export interface ServiceLocator<S extends object> {
102 resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
106 resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
103 resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T;
107 resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T;
104 }
108 }
105
109
106 export interface LifetimeContainer {
110 export interface LifetimeContainer {
107 createLifetime<T>(): ILifetime<T>;
111 createLifetime<T>(): ILifetime<T>;
108 }
112 }
109
113
110 export interface ServiceContainer<S extends object> extends ServiceLocator<ContainerServices<S>>, LifetimeContainer, IDestroyable {
114 export interface ServiceContainer<S extends Configurable<S>> extends
115 ServiceLocator<ContainerServices<S>>,
116 IDestroyable {
111
117
112 register<K extends ConfigurableKeys<S>>(name: K, service: ConfigurableDescriptor<S, K>): void;
118 createChildContainer(): IContainerBuilder<S>;
113 register<K extends ConfigurableKeys<S>>(services: { [k in K]: ConfigurableDescriptor<S, K> }): void;
119 }
114
120
115 createChildContainer(): ServiceContainer<S>;
121 export interface IContainerBuilder<S extends Configurable<S>> {
122 createServiceBuilder<K extends keyof S>(name: K): IDescriptorBuilder<S, NonNullable<S[K]>>;
123
124 build(): ServiceContainer<S>;
116 }
125 }
117
126
118
127
119 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
128 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
120
129
121 /**
130 /**
122 * Интерфейс для управления жизнью экземпляра объекта. Каждая регистрация имеет
131 * Интерфейс для управления жизнью экземпляра объекта. Каждая регистрация имеет
123 * свой собственный объект `ILifetime`, который создается при первой активации
132 * свой собственный объект `ILifetime`, который создается при первой активации
124 */
133 */
125 export interface ILifetime<T> {
134 export interface ILifetime<T> {
126 /** Проверяет, что уже создан экземпляр объекта */
135 /** Проверяет, что уже создан экземпляр объекта */
127 has(): boolean;
136 has(): boolean;
128
137
129 get(): T;
138 get(): T;
130
139
131 initialize(context: ActivationContext<object>): void;
140 initialize(context: IActivationContext<object>): void;
132
141
133 store(item: T, cleanup?: (item: T) => void): void;
142 store(item: T, cleanup?: (item: T) => void): void;
134 }
143 }
135
144
136 export type ExtractRequired<T, K extends keyof T = keyof T> = { [p in K as (undefined extends T[p] ? never : p)]-?: T[p] };
145 export type ExtractRequired<T, K extends keyof T = keyof T> = { [p in K as (undefined extends T[p] ? never : p)]-?: T[p] };
137
146
@@ -1,65 +1,70
1 /* eslint max-classes-per-file: ["error", 20] */
1 /* eslint max-classes-per-file: ["error", 20] */
2 import { describe, it } from "mocha";
2 import { describe, it } from "mocha";
3 import { Container } from "../Container";
3 import { Container } from "../Container";
4 import { ContainerServices } from "../interfaces";
4 import { fluent } from "../traits";
5 import { fluent } from "../traits";
5
6
6 class Foo {
7 class Foo {
7 foo = "foo";
8 foo = "foo";
8 }
9 }
9
10
10 class Bar {
11 class Bar {
11 bar = "bar";
12 bar = "bar";
12
13
13 constructor(foo?: () => Foo) { }
14 constructor(foo?: () => Foo) { }
14 }
15 }
15
16
16 interface Services {
17 interface Services {
17 foo: Foo;
18 foo: Foo;
18
19
19 bar?: Bar;
20 bar?: Bar;
20
21
21 baz: Foo;
22 baz: Foo;
22 }
23 }
23
24
24 interface ServicesB {
25 interface ServicesB {
25 // will give errors
26 // will give errors
26 // baz: Bar;
27 // baz: Bar;
27
28
28 baz: Foo;
29 baz: Foo;
29
30
30 zoo?: Foo;
31 zoo?: Foo;
31 }
32 }
32
33
33 interface SharedServices {
34 interface SharedServices {
34 foo: Foo;
35 foo: Foo;
35
36
36 bar?: Bar;
37 bar?: Bar;
37
38
38 baz: Bar;
39 baz: Bar;
39 }
40 }
40
41
41 const config = fluent()
42 const config = fluent()
42 .declare<Services>()
43 .declare<Services>()
43 .declare<ServicesB>()
44 .declare<ServicesB>()
44 .register({
45 .register({
45 bar: it => it
46 bar: it => it
46 .lifetime("context") // тип активации, время жизни
47 .lifetime("context") // тип активации, время жизни
47 .wants({
48 .wants({
48 zoo: "zoo", // зависимость
49 zoo: "zoo", // зависимость
49
50
50 zoo$: { name: "zoo", lazy: true } // отложенная активация,
51 zoo$: { name: "zoo", lazy: true } // отложенная активация,
51 //фабричный метод
52 //фабричный метод
52 })
53 })
53 .factory(({ zoo$ }) => // фабрика получает объект с именованными зависимостями
54 .factory(({ zoo$ }) => // фабрика получает объект с именованными зависимостями
54 // удобно для деструктурирования
55 // удобно для деструктурирования
55 new Bar(zoo$) // создается экземпляр сервиса
56 new Bar(zoo$) // создается экземпляр сервиса
56 ),
57 ),
57 foo: it => it.factory(() => new Foo()),
58 foo: it => it.factory(() => new Foo()),
58 baz: it => it.value(new Foo())
59 baz: it => it.value(new Foo())
59 })
60 })
60 .done({});
61 .done({});
61
62
62 declare const container: Container<Record<string, never>>;
63 declare const container: Container<object>;
63 const c2 = config.apply(container);
64 const c2 = config.configure(container);
64
65
65 c2.resolve("foo"); No newline at end of file
66 c2.resolve("foo");
67
68 declare const m :ContainerServices<{foo: Foo}>["container"];
69
70 m.resolve("container").resolve("container"); No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now