| @@ -1,169 +1,169 | |||||
| 1 | import { TraceSource } from "../log/TraceSource"; |
|
1 | import { TraceSource } from "../log/TraceSource"; | |
| 2 | import { argumentNotEmptyString } from "../safe"; |
|
2 | import { argumentNotEmptyString } from "../safe"; | |
| 3 | import { Descriptor, ContainerServiceMap, ContainerKeys, TypeOfService, ILifetime, ServiceContainer } from "./interfaces"; |
|
3 | import { Descriptor, ContainerServiceMap, ContainerKeys, TypeOfService, ILifetime, ServiceContainer } from "./interfaces"; | |
| 4 | import { MapOf } from "../interfaces"; |
|
4 | import { MapOf } from "../interfaces"; | |
| 5 |
|
5 | |||
| 6 | const trace = TraceSource.get("@implab/core/di/ActivationContext"); |
|
6 | const trace = TraceSource.get("@implab/core/di/ActivationContext"); | |
| 7 |
|
7 | |||
| 8 | export interface ActivationContextInfo { |
|
8 | export interface ActivationContextInfo { | |
| 9 | name: string; |
|
9 | name: string; | |
| 10 |
|
10 | |||
| 11 | service: string; |
|
11 | service: string; | |
| 12 |
|
12 | |||
| 13 | } |
|
13 | } | |
| 14 |
|
14 | |||
| 15 | let nextId = 1; |
|
15 | let nextId = 1; | |
| 16 |
|
16 | |||
| 17 | /** This class is created once per `Container.resolve` method call and used to |
|
17 | /** This class is created once per `Container.resolve` method call and used to | |
| 18 | * cache dependencies and to track created instances. The activation context |
|
18 | * cache dependencies and to track created instances. The activation context | |
| 19 | * tracks services with `context` activation type. |
|
19 | * tracks services with `context` activation type. | |
| 20 | */ |
|
20 | */ | |
| 21 | export class ActivationContext<S extends object> { |
|
21 | export class ActivationContext<S extends object> { | |
| 22 | _cache: MapOf<any>; |
|
22 | _cache: MapOf<any>; | |
| 23 |
|
23 | |||
| 24 | _services: ContainerServiceMap<S>; |
|
24 | _services: ContainerServiceMap<S>; | |
| 25 |
|
25 | |||
| 26 | _visited: MapOf<any>; |
|
26 | _visited: MapOf<any>; | |
| 27 |
|
27 | |||
| 28 | _name: string; |
|
28 | _name: string; | |
| 29 |
|
29 | |||
| 30 | _service: Descriptor<S, any>; |
|
30 | _service: Descriptor<S, any>; | |
| 31 |
|
31 | |||
| 32 | _container: ServiceContainer<S>; |
|
32 | _container: ServiceContainer<S>; | |
| 33 |
|
33 | |||
| 34 | _parent: ActivationContext<S> | undefined; |
|
34 | _parent: ActivationContext<S> | undefined; | |
| 35 |
|
35 | |||
| 36 | /** Creates a new activation context with the specified parameters. |
|
36 | /** Creates a new activation context with the specified parameters. | |
| 37 | * @param container the container which starts the activation process |
|
37 | * @param container the container which starts the activation process | |
| 38 | * @param services the initial service registrations |
|
38 | * @param services the initial service registrations | |
| 39 | * @param name the name of the service being activated, this parameter is |
|
39 | * @param name the name of the service being activated, this parameter is | |
| 40 | * used for the debug purpose. |
|
40 | * used for the debug purpose. | |
| 41 | * @param service the service to activate, this parameter is used for the |
|
41 | * @param service the service to activate, this parameter is used for the | |
| 42 | * debug purpose. |
|
42 | * debug purpose. | |
| 43 | */ |
|
43 | */ | |
| 44 | constructor(container: ServiceContainer<S>, services: ContainerServiceMap<S>, name: string, service: Descriptor<S, any>) { |
|
44 | constructor(container: ServiceContainer<S>, services: ContainerServiceMap<S>, name: string, service: Descriptor<S, any>) { | |
| 45 | this._name = name; |
|
45 | this._name = name; | |
| 46 | this._service = service; |
|
46 | this._service = service; | |
| 47 | this._visited = {}; |
|
47 | this._visited = {}; | |
| 48 | this._cache = {}; |
|
48 | this._cache = {}; | |
| 49 | this._services = services; |
|
49 | this._services = services; | |
| 50 | this._container = container; |
|
50 | this._container = container; | |
| 51 | } |
|
51 | } | |
| 52 |
|
52 | |||
| 53 | /** the name of the current resolving dependency */ |
|
53 | /** the name of the current resolving dependency */ | |
| 54 | getName() { |
|
54 | getName() { | |
| 55 | return this._name; |
|
55 | return this._name; | |
| 56 | } |
|
56 | } | |
| 57 |
|
57 | |||
| 58 | /** Returns the container for which 'resolve' method was called */ |
|
58 | /** Returns the container for which 'resolve' method was called */ | |
| 59 | getContainer() { |
|
59 | getContainer() { | |
| 60 | return this._container; |
|
60 | return this._container; | |
| 61 | } |
|
61 | } | |
| 62 |
|
62 | |||
| 63 | /** Resolves the specified dependency in the current context |
|
63 | /** Resolves the specified dependency in the current context | |
| 64 | * @param name The name of the dependency being resolved |
|
64 | * @param name The name of the dependency being resolved | |
| 65 | */ |
|
65 | */ | |
| 66 | resolve<K extends ContainerKeys<S>>(name: K): TypeOfService<S, K>; |
|
66 | resolve<K extends ContainerKeys<S>>(name: K): TypeOfService<S, K>; | |
| 67 | /** Resolves the specified dependency with the specified default value if |
|
67 | /** Resolves the specified dependency with the specified default value if | |
| 68 | * the dependency is missing. |
|
68 | * the dependency is missing. | |
| 69 | * |
|
69 | * | |
| 70 | * @param name The name of the dependency being resolved |
|
70 | * @param name The name of the dependency being resolved | |
| 71 | * @param def A default value to return in case of the specified dependency |
|
71 | * @param def A default value to return in case of the specified dependency | |
| 72 | * is missing. |
|
72 | * is missing. | |
| 73 | */ |
|
73 | */ | |
| 74 | resolve<K extends ContainerKeys<S>, T>(name: K, def: T): TypeOfService<S, K> | T; |
|
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 |
|
75 | /** Resolves the specified dependency and returns undefined in case if the | |
| 76 | * dependency is missing. |
|
76 | * dependency is missing. | |
| 77 | * |
|
77 | * | |
| 78 | * @param name The name of the dependency being resolved |
|
78 | * @param name The name of the dependency being resolved | |
| 79 | */ |
|
79 | */ | |
| 80 | resolve<K extends ContainerKeys<S>>(name: K, def: undefined): TypeOfService<S, K> | undefined; |
|
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 { |
|
81 | resolve<K extends ContainerKeys<S>, T>(name: K, def?: T): TypeOfService<S, K> | T | undefined { | |
| 82 | const d = this._services[name]; |
|
82 | const d = this._services[name]; | |
| 83 |
|
83 | |||
| 84 | if (d !== undefined) { |
|
84 | if (d !== undefined) { | |
| 85 | return this.activate(d, name.toString()); |
|
85 | return this.activate(d, name.toString()); | |
| 86 | } else { |
|
86 | } else { | |
| 87 | if (arguments.length > 1) |
|
87 | if (arguments.length > 1) | |
| 88 | return def; |
|
88 | return def; | |
| 89 | else |
|
89 | else | |
| 90 | throw new Error(`Service ${name} not found`); |
|
90 | throw new Error(`Service ${name} not found`); | |
| 91 | } |
|
91 | } | |
| 92 | } |
|
92 | } | |
| 93 |
|
93 | |||
| 94 | /** |
|
94 | /** | |
| 95 | * registers services local to the the activation context |
|
95 | * registers services local to the the activation context | |
| 96 | * |
|
96 | * | |
| 97 | * @name{string} the name of the service |
|
97 | * @name{string} the name of the service | |
| 98 | * @service{string} the service descriptor to register |
|
98 | * @service{string} the service descriptor to register | |
| 99 | */ |
|
99 | */ | |
| 100 | register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>) { |
|
100 | register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>) { | |
| 101 | argumentNotEmptyString(name, "name"); |
|
101 | argumentNotEmptyString(name, "name"); | |
| 102 |
|
102 | |||
| 103 | this._services[name] = service as any; |
|
103 | this._services[name] = service as any; | |
| 104 | } |
|
104 | } | |
| 105 |
|
105 | |||
| 106 | createLifetime(): ILifetime { |
|
106 | createLifetime(): ILifetime { | |
| 107 | const id = nextId++; |
|
107 | const id = nextId++; | |
| 108 | const me = this; |
|
108 | const me = this; | |
| 109 | return { |
|
109 | return { | |
| 110 | initialize() { |
|
110 | initialize() { | |
| 111 | }, |
|
111 | }, | |
| 112 | has() { |
|
112 | has() { | |
| 113 | return id in me._cache; |
|
113 | return id in me._cache; | |
| 114 | }, |
|
114 | }, | |
| 115 | get() { |
|
115 | get() { | |
| 116 | return me._cache[id]; |
|
116 | return me._cache[id]; | |
| 117 | }, |
|
117 | }, | |
| 118 | store(item: any) { |
|
118 | store(item: any) { | |
| 119 | me._cache[id] = item; |
|
119 | me._cache[id] = item; | |
| 120 | } |
|
120 | } | |
| 121 | }; |
|
121 | }; | |
| 122 | } |
|
122 | } | |
| 123 |
|
123 | |||
| 124 | activate<T>(d: Descriptor<S, T>, name: string) { |
|
124 | activate<T>(d: Descriptor<S, T>, name: string) { | |
| 125 | if (trace.isLogEnabled()) |
|
125 | if (trace.isLogEnabled()) | |
| 126 |
trace.log( |
|
126 | trace.log("enter {0} {1}", name, d); | |
| 127 |
|
127 | |||
| 128 | const ctx = this.enter(d, name); |
|
128 | const ctx = this.enter(d, name); | |
| 129 | const v = d.activate(ctx); |
|
129 | const v = d.activate(ctx); | |
| 130 |
|
130 | |||
| 131 | if (trace.isLogEnabled()) |
|
131 | if (trace.isLogEnabled()) | |
| 132 | trace.log(`leave ${name}`); |
|
132 | trace.log(`leave ${name}`); | |
| 133 |
|
133 | |||
| 134 | return v; |
|
134 | return v; | |
| 135 | } |
|
135 | } | |
| 136 |
|
136 | |||
| 137 | visit(id: string) { |
|
137 | visit(id: string) { | |
| 138 | const count = this._visited[id] || 0; |
|
138 | const count = this._visited[id] || 0; | |
| 139 | this._visited[id] = count + 1; |
|
139 | this._visited[id] = count + 1; | |
| 140 | return count; |
|
140 | return count; | |
| 141 | } |
|
141 | } | |
| 142 |
|
142 | |||
| 143 | getStack(): ActivationContextInfo[] { |
|
143 | getStack(): ActivationContextInfo[] { | |
| 144 | const stack = [{ |
|
144 | const stack = [{ | |
| 145 | name: this._name, |
|
145 | name: this._name, | |
| 146 | service: this._service.toString() |
|
146 | service: this._service.toString() | |
| 147 | }]; |
|
147 | }]; | |
| 148 |
|
148 | |||
| 149 | return this._parent ? |
|
149 | return this._parent ? | |
| 150 | stack.concat(this._parent.getStack()) : |
|
150 | stack.concat(this._parent.getStack()) : | |
| 151 | stack; |
|
151 | stack; | |
| 152 | } |
|
152 | } | |
| 153 |
|
153 | |||
| 154 | private enter(service: Descriptor<S, any>, name: string): this { |
|
154 | private enter(service: Descriptor<S, any>, name: string): this { | |
| 155 | const clone = Object.create(this); |
|
155 | const clone = Object.create(this); | |
| 156 | clone._name = name; |
|
156 | clone._name = name; | |
| 157 | clone._services = Object.create(this._services); |
|
157 | clone._services = Object.create(this._services); | |
| 158 | clone._parent = this; |
|
158 | clone._parent = this; | |
| 159 | clone._service = service; |
|
159 | clone._service = service; | |
| 160 | return clone; |
|
160 | return clone; | |
| 161 | } |
|
161 | } | |
| 162 |
|
162 | |||
| 163 | /** Creates a clone for the current context, used to protect it from modifications */ |
|
163 | /** Creates a clone for the current context, used to protect it from modifications */ | |
| 164 | clone(): this { |
|
164 | clone(): this { | |
| 165 | const clone = Object.create(this); |
|
165 | const clone = Object.create(this); | |
| 166 | clone._services = Object.create(this._services); |
|
166 | clone._services = Object.create(this._services); | |
| 167 | return clone; |
|
167 | return clone; | |
| 168 | } |
|
168 | } | |
| 169 | } |
|
169 | } | |
| @@ -1,199 +1,218 | |||||
| 1 | import { IDestroyable, MapOf } from "../interfaces"; |
|
1 | import { IDestroyable, MapOf } from "../interfaces"; | |
| 2 | import { argumentNotNull, isDestroyable, argumentNotEmptyString, isRemovable } from "../safe"; |
|
2 | import { argumentNotNull, isDestroyable, argumentNotEmptyString, isRemovable } from "../safe"; | |
| 3 | import { ILifetime, ServiceContainer } from "./interfaces"; |
|
3 | import { ILifetime, ServiceContainer } from "./interfaces"; | |
| 4 | import { ActivationContext } from "./ActivationContext"; |
|
4 | import { ActivationContext } from "./ActivationContext"; | |
| 5 |
|
5 | |||
| 6 | function safeCall(item: () => void) { |
|
6 | function safeCall(item: () => void) { | |
| 7 | try { |
|
7 | try { | |
| 8 | item(); |
|
8 | item(); | |
| 9 | } catch { |
|
9 | } catch { | |
| 10 | // silence! |
|
10 | // silence! | |
| 11 | } |
|
11 | } | |
| 12 | } |
|
12 | } | |
| 13 |
|
13 | |||
| 14 | const emptyLifetime: ILifetime = Object.freeze({ |
|
14 | const emptyLifetime: ILifetime = Object.freeze({ | |
| 15 | has() { |
|
15 | has() { | |
| 16 | return false; |
|
16 | return false; | |
| 17 | }, |
|
17 | }, | |
| 18 |
|
18 | |||
| 19 | initialize() { |
|
19 | initialize() { | |
| 20 |
|
20 | |||
| 21 | }, |
|
21 | }, | |
| 22 |
|
22 | |||
| 23 | get() { |
|
23 | get() { | |
| 24 | throw new Error("The specified item isn't registered with this lifetime manager"); |
|
24 | throw new Error("The specified item isn't registered with this lifetime manager"); | |
| 25 | }, |
|
25 | }, | |
| 26 |
|
26 | |||
| 27 | store() { |
|
27 | store() { | |
| 28 | // does nothing |
|
28 | // does nothing | |
|
|
29 | }, | |||
|
|
30 | ||||
|
|
31 | toString() { | |||
|
|
32 | return `[object EmptyLifetime]`; | |||
| 29 |
|
|
33 | } | |
| 30 |
|
34 | |||
| 31 | }); |
|
35 | }); | |
| 32 |
|
36 | |||
| 33 | const unknownLifetime: ILifetime = Object.freeze({ |
|
37 | const unknownLifetime: ILifetime = Object.freeze({ | |
| 34 | has() { |
|
38 | has() { | |
| 35 | return false; |
|
39 | return false; | |
| 36 | }, |
|
40 | }, | |
| 37 | initialize() { |
|
41 | initialize() { | |
| 38 | throw new Error("Can't call initialize on the unknown lifetime object"); |
|
42 | throw new Error("Can't call initialize on the unknown lifetime object"); | |
| 39 | }, |
|
43 | }, | |
| 40 | get() { |
|
44 | get() { | |
| 41 | throw new Error("The lifetime object isn't initialized"); |
|
45 | throw new Error("The lifetime object isn't initialized"); | |
| 42 | }, |
|
46 | }, | |
| 43 | store() { |
|
47 | store() { | |
| 44 | throw new Error("Can't store a value in the unknown lifetime object"); |
|
48 | throw new Error("Can't store a value in the unknown lifetime object"); | |
|
|
49 | }, | |||
|
|
50 | toString() { | |||
|
|
51 | return `[object UnknownLifetime]`; | |||
| 45 | } |
|
52 | } | |
| 46 | }); |
|
53 | }); | |
| 47 |
|
54 | |||
| 48 | let nextId = 0; |
|
55 | let nextId = 0; | |
| 49 |
|
56 | |||
| 50 | const singletons: any = {}; |
|
57 | const singletons: any = {}; | |
| 51 |
|
58 | |||
| 52 | export class LifetimeManager implements IDestroyable { |
|
59 | export class LifetimeManager implements IDestroyable { | |
| 53 | private _cleanup: (() => void)[] = []; |
|
60 | private _cleanup: (() => void)[] = []; | |
| 54 | private _cache: MapOf<any> = {}; |
|
61 | private _cache: MapOf<any> = {}; | |
| 55 | private _destroyed = false; |
|
62 | private _destroyed = false; | |
| 56 |
|
63 | |||
| 57 | private _pending: MapOf<boolean> = {}; |
|
64 | private _pending: MapOf<boolean> = {}; | |
| 58 |
|
65 | |||
| 59 | create(): ILifetime { |
|
66 | create(): ILifetime { | |
| 60 | const self = this; |
|
67 | const self = this; | |
| 61 | const id = ++nextId; |
|
68 | const id = ++nextId; | |
| 62 | return { |
|
69 | return { | |
| 63 | has() { |
|
70 | has() { | |
| 64 | return (id in self._cache); |
|
71 | return (id in self._cache); | |
| 65 | }, |
|
72 | }, | |
| 66 |
|
73 | |||
| 67 | get() { |
|
74 | get() { | |
| 68 | const t = self._cache[id]; |
|
75 | const t = self._cache[id]; | |
| 69 | if (t === undefined) |
|
76 | if (t === undefined) | |
| 70 | throw new Error(`The item with with the key ${id} isn't found`); |
|
77 | throw new Error(`The item with with the key ${id} isn't found`); | |
| 71 | return t; |
|
78 | return t; | |
| 72 | }, |
|
79 | }, | |
| 73 |
|
80 | |||
| 74 | initialize() { |
|
81 | initialize() { | |
| 75 | if (self._pending[id]) |
|
82 | if (self._pending[id]) | |
| 76 | throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`); |
|
83 | throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`); | |
| 77 | self._pending[id] = true; |
|
84 | self._pending[id] = true; | |
| 78 | }, |
|
85 | }, | |
| 79 |
|
86 | |||
| 80 | store(item: any, cleanup?: (item: any) => void) { |
|
87 | store(item: any, cleanup?: (item: any) => void) { | |
| 81 | argumentNotNull(id, "id"); |
|
88 | argumentNotNull(id, "id"); | |
| 82 | argumentNotNull(item, "item"); |
|
89 | argumentNotNull(item, "item"); | |
| 83 |
|
90 | |||
| 84 | if (this.has()) |
|
91 | if (this.has()) | |
| 85 | throw new Error(`The item with with the key ${id} already registered with this lifetime manager`); |
|
92 | throw new Error(`The item with with the key ${id} already registered with this lifetime manager`); | |
| 86 | delete self._pending[id]; |
|
93 | delete self._pending[id]; | |
| 87 |
|
94 | |||
| 88 | self._cache[id] = item; |
|
95 | self._cache[id] = item; | |
| 89 |
|
96 | |||
| 90 | if (self._destroyed) |
|
97 | if (self._destroyed) | |
| 91 | throw new Error("Lifetime manager is destroyed"); |
|
98 | throw new Error("Lifetime manager is destroyed"); | |
| 92 | if (cleanup) { |
|
99 | if (cleanup) { | |
| 93 | self._cleanup.push(() => cleanup(item)); |
|
100 | self._cleanup.push(() => cleanup(item)); | |
| 94 | } else if (isDestroyable(item)) { |
|
101 | } else if (isDestroyable(item)) { | |
| 95 | self._cleanup.push(() => item.destroy()); |
|
102 | self._cleanup.push(() => item.destroy()); | |
| 96 | } else if (isRemovable(item)) { |
|
103 | } else if (isRemovable(item)) { | |
| 97 | self._cleanup.push(() => item.remove()); |
|
104 | self._cleanup.push(() => item.remove()); | |
| 98 | } |
|
105 | } | |
| 99 | } |
|
106 | } | |
| 100 | }; |
|
107 | }; | |
| 101 | } |
|
108 | } | |
| 102 |
|
109 | |||
| 103 | destroy() { |
|
110 | destroy() { | |
| 104 | if (!this._destroyed) { |
|
111 | if (!this._destroyed) { | |
| 105 | this._destroyed = true; |
|
112 | this._destroyed = true; | |
| 106 | this._cleanup.forEach(safeCall); |
|
113 | this._cleanup.forEach(safeCall); | |
| 107 | this._cleanup.length = 0; |
|
114 | this._cleanup.length = 0; | |
| 108 | } |
|
115 | } | |
| 109 | } |
|
116 | } | |
| 110 |
|
117 | |||
| 111 | static empty(): ILifetime { |
|
118 | static empty(): ILifetime { | |
| 112 | return emptyLifetime; |
|
119 | return emptyLifetime; | |
| 113 | } |
|
120 | } | |
| 114 |
|
121 | |||
| 115 |
static hierarchyLifetime() |
|
122 | static hierarchyLifetime() { | |
| 116 | let _lifetime = unknownLifetime; |
|
123 | let _lifetime = unknownLifetime; | |
| 117 | return { |
|
124 | return { | |
| 118 | initialize(context: ActivationContext<any>) { |
|
125 | initialize(context: ActivationContext<any>) { | |
| 119 | if (_lifetime !== unknownLifetime) |
|
126 | if (_lifetime !== unknownLifetime) | |
| 120 | throw new Error("Cyclic reference activation detected"); |
|
127 | throw new Error("Cyclic reference activation detected"); | |
| 121 |
|
128 | |||
| 122 | _lifetime = context.getContainer().getLifetimeManager().create(); |
|
129 | _lifetime = context.getContainer().getLifetimeManager().create(); | |
| 123 | }, |
|
130 | }, | |
| 124 | get() { |
|
131 | get() { | |
| 125 | return _lifetime.get(); |
|
132 | return _lifetime.get(); | |
| 126 | }, |
|
133 | }, | |
| 127 | has() { |
|
134 | has() { | |
| 128 | return _lifetime.has(); |
|
135 | return _lifetime.has(); | |
| 129 | }, |
|
136 | }, | |
| 130 | store(item: any, cleanup?: (item: any) => void) { |
|
137 | store(item: any, cleanup?: (item: any) => void) { | |
| 131 | return _lifetime.store(item, cleanup); |
|
138 | return _lifetime.store(item, cleanup); | |
|
|
139 | }, | |||
|
|
140 | toString() { | |||
|
|
141 | return `[object HierarchyLifetime, has=${this.has()}]`; | |||
| 132 | } |
|
142 | } | |
| 133 | }; |
|
143 | }; | |
| 134 | } |
|
144 | } | |
| 135 |
|
145 | |||
| 136 |
static contextLifetime() |
|
146 | static contextLifetime() { | |
| 137 | let _lifetime = unknownLifetime; |
|
147 | let _lifetime = unknownLifetime; | |
| 138 | return { |
|
148 | return { | |
| 139 | initialize(context: ActivationContext<any>) { |
|
149 | initialize(context: ActivationContext<any>) { | |
| 140 | if (_lifetime !== unknownLifetime) |
|
150 | if (_lifetime !== unknownLifetime) | |
| 141 | throw new Error("Cyclic reference detected"); |
|
151 | throw new Error("Cyclic reference detected"); | |
| 142 | _lifetime = context.createLifetime(); |
|
152 | _lifetime = context.createLifetime(); | |
| 143 | }, |
|
153 | }, | |
| 144 | get() { |
|
154 | get() { | |
| 145 | return _lifetime.get(); |
|
155 | return _lifetime.get(); | |
| 146 | }, |
|
156 | }, | |
| 147 | has() { |
|
157 | has() { | |
| 148 | return _lifetime.has(); |
|
158 | return _lifetime.has(); | |
| 149 | }, |
|
159 | }, | |
| 150 | store(item: any) { |
|
160 | store(item: any) { | |
| 151 | _lifetime.store(item); |
|
161 | _lifetime.store(item); | |
|
|
162 | }, | |||
|
|
163 | toString() { | |||
|
|
164 | return `[object ContextLifetime, has=${this.has()}]`; | |||
| 152 | } |
|
165 | } | |
| 153 | }; |
|
166 | }; | |
| 154 | } |
|
167 | } | |
| 155 |
|
168 | |||
| 156 |
static singletonLifetime(typeId: string) |
|
169 | static singletonLifetime(typeId: string) { | |
| 157 | argumentNotEmptyString(typeId, "typeId"); |
|
170 | argumentNotEmptyString(typeId, "typeId"); | |
| 158 | let pending = false; |
|
171 | let pending = false; | |
| 159 | return { |
|
172 | return { | |
| 160 | has() { |
|
173 | has() { | |
| 161 | return typeId in singletons; |
|
174 | return typeId in singletons; | |
| 162 | }, |
|
175 | }, | |
| 163 | get() { |
|
176 | get() { | |
| 164 | if (!this.has()) |
|
177 | if (!this.has()) | |
| 165 | throw new Error(`The instance ${typeId} doesn't exists`); |
|
178 | throw new Error(`The instance ${typeId} doesn't exists`); | |
| 166 | return singletons[typeId]; |
|
179 | return singletons[typeId]; | |
| 167 | }, |
|
180 | }, | |
| 168 | initialize() { |
|
181 | initialize() { | |
| 169 | if (pending) |
|
182 | if (pending) | |
| 170 | throw new Error("Cyclic reference detected"); |
|
183 | throw new Error("Cyclic reference detected"); | |
| 171 | pending = true; |
|
184 | pending = true; | |
| 172 | }, |
|
185 | }, | |
| 173 | store(item: any) { |
|
186 | store(item: any) { | |
| 174 | singletons[typeId] = item; |
|
187 | singletons[typeId] = item; | |
| 175 | pending = false; |
|
188 | pending = false; | |
|
|
189 | }, | |||
|
|
190 | toString() { | |||
|
|
191 | return `[object SingletonLifetime, has=${this.has()}, typeId=${typeId}]`; | |||
| 176 | } |
|
192 | } | |
| 177 | }; |
|
193 | }; | |
| 178 | } |
|
194 | } | |
| 179 |
|
195 | |||
| 180 | static containerLifetime(container: ServiceContainer<any>) { |
|
196 | static containerLifetime(container: ServiceContainer<any>) { | |
| 181 | let _lifetime = unknownLifetime; |
|
197 | let _lifetime = unknownLifetime; | |
| 182 | return { |
|
198 | return { | |
| 183 | initialize(context: ActivationContext<any>) { |
|
199 | initialize(context: ActivationContext<any>) { | |
| 184 | if (_lifetime !== unknownLifetime) |
|
200 | if (_lifetime !== unknownLifetime) | |
| 185 | throw new Error("Cyclic reference detected"); |
|
201 | throw new Error("Cyclic reference detected"); | |
| 186 | _lifetime = container.getLifetimeManager().create(); |
|
202 | _lifetime = container.getLifetimeManager().create(); | |
| 187 | }, |
|
203 | }, | |
| 188 | get() { |
|
204 | get() { | |
| 189 | return _lifetime.get(); |
|
205 | return _lifetime.get(); | |
| 190 | }, |
|
206 | }, | |
| 191 | has() { |
|
207 | has() { | |
| 192 | return _lifetime.has(); |
|
208 | return _lifetime.has(); | |
| 193 | }, |
|
209 | }, | |
| 194 | store(item: any) { |
|
210 | store(item: any) { | |
| 195 | _lifetime.store(item); |
|
211 | _lifetime.store(item); | |
|
|
212 | }, | |||
|
|
213 | toString() { | |||
|
|
214 | return `[object ContainerLifetime, has=${_lifetime.has()}]` | |||
| 196 | } |
|
215 | } | |
| 197 | }; |
|
216 | }; | |
| 198 | } |
|
217 | } | |
| 199 | } |
|
218 | } | |
| @@ -1,63 +1,66 | |||||
| 1 | import { Descriptor, PartialServiceMap, ILifetime, ContainerKeys } from "../interfaces"; |
|
1 | import { Descriptor, PartialServiceMap, ILifetime, ContainerKeys } from "../interfaces"; | |
| 2 | import { ActivationContext } from "../ActivationContext"; |
|
2 | import { ActivationContext } from "../ActivationContext"; | |
| 3 | import { each } from "../../safe"; |
|
3 | import { each } from "../../safe"; | |
| 4 | import { DependencyOptions, LazyDependencyOptions, Resolver } from "./interfaces"; |
|
4 | import { DependencyOptions, LazyDependencyOptions, Resolver } from "./interfaces"; | |
| 5 |
|
5 | |||
| 6 | export interface DescriptorImplArgs<S extends object, T> { |
|
6 | export interface DescriptorImplArgs<S extends object, T> { | |
| 7 | lifetime: ILifetime; |
|
7 | lifetime: ILifetime; | |
| 8 |
|
8 | |||
| 9 | factory: (resolve: Resolver<S>) => T; |
|
9 | factory: (resolve: Resolver<S>) => T; | |
| 10 |
|
10 | |||
| 11 | cleanup?: (item: T) => void; |
|
11 | cleanup?: (item: T) => void; | |
| 12 |
|
12 | |||
| 13 | overrides?: PartialServiceMap<S>; |
|
13 | overrides?: PartialServiceMap<S>; | |
| 14 | } |
|
14 | } | |
| 15 |
|
15 | |||
| 16 | export class DescriptorImpl<S extends object, T> implements Descriptor<S, T> { |
|
16 | export class DescriptorImpl<S extends object, T> implements Descriptor<S, T> { | |
| 17 |
|
17 | |||
| 18 | private readonly _overrides?: PartialServiceMap<S>; |
|
18 | private readonly _overrides?: PartialServiceMap<S>; | |
| 19 |
|
19 | |||
| 20 | private readonly _lifetime: ILifetime; |
|
20 | private readonly _lifetime: ILifetime; | |
| 21 |
|
21 | |||
| 22 | private readonly _factory: (resolve: Resolver<S>) => T; |
|
22 | private readonly _factory: (resolve: Resolver<S>) => T; | |
| 23 |
|
23 | |||
| 24 | private readonly _cleanup?: (item: T) => void; |
|
24 | private readonly _cleanup?: (item: T) => void; | |
| 25 |
|
25 | |||
| 26 | constructor(args: DescriptorImplArgs<S, T>) { |
|
26 | constructor(args: DescriptorImplArgs<S, T>) { | |
| 27 | this._lifetime = args.lifetime; |
|
27 | this._lifetime = args.lifetime; | |
| 28 | this._factory = args.factory; |
|
28 | this._factory = args.factory; | |
| 29 | if (args.cleanup) |
|
29 | if (args.cleanup) | |
| 30 | this._cleanup = args.cleanup; |
|
30 | this._cleanup = args.cleanup; | |
| 31 | if (args.overrides) |
|
31 | if (args.overrides) | |
| 32 | this._overrides = args.overrides; |
|
32 | this._overrides = args.overrides; | |
| 33 | } |
|
33 | } | |
| 34 |
|
34 | |||
| 35 | activate(context: ActivationContext<S>): T { |
|
35 | activate(context: ActivationContext<S>): T { | |
| 36 |
|
36 | |||
| 37 | if (this._lifetime.has()) |
|
37 | if (this._lifetime.has()) | |
| 38 | return this._lifetime.get(); |
|
38 | return this._lifetime.get(); | |
| 39 |
|
39 | |||
| 40 | this._lifetime.initialize(context); |
|
40 | this._lifetime.initialize(context); | |
| 41 |
|
41 | |||
| 42 | if (this._overrides) |
|
42 | if (this._overrides) | |
| 43 | each(this._overrides, (v, k) => context.register(k, v)); |
|
43 | each(this._overrides, (v, k) => context.register(k, v)); | |
| 44 |
|
44 | |||
| 45 | const resolve = (name: ContainerKeys<S>, opts?: DependencyOptions | LazyDependencyOptions) => { |
|
45 | const resolve = (name: ContainerKeys<S>, opts?: DependencyOptions | LazyDependencyOptions) => { | |
| 46 | if (opts && "lazy" in opts && opts.lazy) { |
|
46 | if (opts && "lazy" in opts && opts.lazy) { | |
| 47 | const c2 = context.clone(); |
|
47 | const c2 = context.clone(); | |
| 48 | return () => { |
|
48 | return () => { | |
| 49 | return opts.optional ? c2.resolve(name, opts.default) : c2.resolve(name); |
|
49 | return opts.optional ? c2.resolve(name, opts.default) : c2.resolve(name); | |
| 50 | }; |
|
50 | }; | |
| 51 | } else { |
|
51 | } else { | |
| 52 | return opts && opts.optional ? context.resolve(name, opts.default) : context.resolve(name); |
|
52 | return opts && opts.optional ? context.resolve(name, opts.default) : context.resolve(name); | |
| 53 | } |
|
53 | } | |
| 54 | }; |
|
54 | }; | |
| 55 |
|
55 | |||
| 56 | const instance = this._factory.call(undefined, resolve); |
|
56 | const instance = this._factory.call(undefined, resolve); | |
| 57 |
|
57 | |||
| 58 | this._lifetime.store(instance, this._cleanup); |
|
58 | this._lifetime.store(instance, this._cleanup); | |
| 59 |
|
59 | |||
| 60 | return instance; |
|
60 | return instance; | |
| 61 | } |
|
61 | } | |
| 62 |
|
62 | |||
|
|
63 | toString() { | |||
|
|
64 | return `[object DescriptorImpl, lifetime=${this._lifetime}]`; | |||
| 63 | } |
|
65 | } | |
|
|
66 | } | |||
| @@ -1,106 +1,125 | |||||
| 1 | import { test } from "./TestTraits"; |
|
1 | import { test } from "./TestTraits"; | |
| 2 | import { Container } from "../di/Container"; |
|
2 | import { Container } from "../di/Container"; | |
| 3 | import { ReferenceDescriptor } from "../di/ReferenceDescriptor"; |
|
3 | import { ReferenceDescriptor } from "../di/ReferenceDescriptor"; | |
| 4 | import { AggregateDescriptor } from "../di/AggregateDescriptor"; |
|
4 | import { AggregateDescriptor } from "../di/AggregateDescriptor"; | |
| 5 | import { ValueDescriptor } from "../di/ValueDescriptor"; |
|
5 | import { ValueDescriptor } from "../di/ValueDescriptor"; | |
| 6 | import { Foo } from "../mock/Foo"; |
|
6 | import { Foo } from "../mock/Foo"; | |
| 7 | import { Bar } from "../mock/Bar"; |
|
7 | import { Bar } from "../mock/Bar"; | |
| 8 | import { isNull } from "../safe"; |
|
8 | import { isNull } from "../safe"; | |
|
|
9 | import { Box } from "../mock/Box"; | |||
| 9 |
|
10 | |||
| 10 | test("Container register/resolve tests", async t => { |
|
11 | test("Container register/resolve tests", async t => { | |
| 11 | const container = new Container<{ |
|
12 | const container = new Container<{ | |
| 12 | "bla-bla": string; |
|
13 | "bla-bla": string; | |
| 13 | "connection": string; |
|
14 | "connection": string; | |
| 14 | "dbParams": { |
|
15 | "dbParams": { | |
| 15 | timeout: number; |
|
16 | timeout: number; | |
| 16 | connection: string; |
|
17 | connection: string; | |
| 17 | } |
|
18 | } | |
| 18 | }>(); |
|
19 | }>(); | |
| 19 |
|
20 | |||
| 20 | const connection1 = "db://localhost"; |
|
21 | const connection1 = "db://localhost"; | |
| 21 |
|
22 | |||
| 22 | t.throws( |
|
23 | t.throws( | |
| 23 | () => container.register("bla-bla", "bla-bla" as any), |
|
24 | () => container.register("bla-bla", "bla-bla" as any), | |
| 24 | "Do not allow to register anything other than descriptors" |
|
25 | "Do not allow to register anything other than descriptors" | |
| 25 | ); |
|
26 | ); | |
| 26 |
|
27 | |||
| 27 | t.doesNotThrow( |
|
28 | t.doesNotThrow( | |
| 28 | () => container.register("connection", new ValueDescriptor(connection1)), |
|
29 | () => container.register("connection", new ValueDescriptor(connection1)), | |
| 29 | "register ValueDescriptor" |
|
30 | "register ValueDescriptor" | |
| 30 | ); |
|
31 | ); | |
| 31 |
|
32 | |||
| 32 | t.equals(container.resolve("connection"), connection1, "resolve string value"); |
|
33 | t.equals(container.resolve("connection"), connection1, "resolve string value"); | |
| 33 |
|
34 | |||
| 34 | t.doesNotThrow( |
|
35 | t.doesNotThrow( | |
| 35 | () => container.register( |
|
36 | () => container.register( | |
| 36 | "dbParams", |
|
37 | "dbParams", | |
| 37 | new AggregateDescriptor({ |
|
38 | new AggregateDescriptor({ | |
| 38 | timeout: 10, |
|
39 | timeout: 10, | |
| 39 | connection: new ReferenceDescriptor({ name: "connection" }) |
|
40 | connection: new ReferenceDescriptor({ name: "connection" }) | |
| 40 | }) |
|
41 | }) | |
| 41 | ), |
|
42 | ), | |
| 42 | "register AggregateDescriptor" |
|
43 | "register AggregateDescriptor" | |
| 43 | ); |
|
44 | ); | |
| 44 |
|
45 | |||
| 45 | const dbParams = container.resolve("dbParams"); |
|
46 | const dbParams = container.resolve("dbParams"); | |
| 46 | t.equals(dbParams.connection, connection1, "should get string value 'dbParams.connection'"); |
|
47 | t.equals(dbParams.connection, connection1, "should get string value 'dbParams.connection'"); | |
| 47 | }); |
|
48 | }); | |
| 48 |
|
49 | |||
| 49 | test("Container configure/resolve tests", async t => { |
|
50 | test("Container configure/resolve tests", async t => { | |
| 50 |
|
51 | |||
| 51 | const container = new Container<{ |
|
52 | const container = new Container<{ | |
| 52 | foo: Foo; |
|
53 | foo: Foo; | |
| 53 | box: Bar; |
|
54 | box: Bar; | |
| 54 | bar: Bar; |
|
55 | bar: Bar; | |
| 55 | db: any; |
|
56 | db: any; | |
| 56 | }>(); |
|
57 | }>(); | |
| 57 |
|
58 | |||
| 58 | await container.configure({ |
|
59 | await container.configure({ | |
| 59 | foo: { |
|
60 | foo: { | |
| 60 | $type: Foo |
|
61 | $type: Foo | |
| 61 | }, |
|
62 | }, | |
| 62 |
|
63 | |||
| 63 | box: { |
|
64 | box: { | |
| 64 | $type: Bar, |
|
65 | $type: Bar, | |
| 65 | params: { |
|
66 | params: { | |
| 66 | $dependency: "foo" |
|
67 | $dependency: "foo" | |
| 67 | } |
|
68 | } | |
| 68 | }, |
|
69 | }, | |
| 69 |
|
70 | |||
| 70 | bar: { |
|
71 | bar: { | |
| 71 | $type: Bar, |
|
72 | $type: Bar, | |
| 72 | params: [{ |
|
73 | params: [{ | |
| 73 | db: { |
|
74 | db: { | |
| 74 | provider: { |
|
75 | provider: { | |
| 75 | $dependency: "db" |
|
76 | $dependency: "db" | |
| 76 | } |
|
77 | } | |
| 77 | } |
|
78 | } | |
| 78 | }] |
|
79 | }] | |
| 79 | } |
|
80 | } | |
| 80 | }); |
|
81 | }); | |
| 81 | t.pass("should configure from js object"); |
|
82 | t.pass("should configure from js object"); | |
| 82 |
|
83 | |||
| 83 | const f1 = container.resolve("foo"); |
|
84 | const f1 = container.resolve("foo"); | |
| 84 |
|
85 | |||
| 85 | t.assert(!isNull(f1), "foo should be not null"); |
|
86 | t.assert(!isNull(f1), "foo should be not null"); | |
| 86 |
|
87 | |||
| 87 | t.throws(() => container.resolve("bar"), "should not resolve dependency 'db'"); |
|
88 | t.throws(() => container.resolve("bar"), "should not resolve dependency 'db'"); | |
| 88 |
|
89 | |||
| 89 | }); |
|
90 | }); | |
| 90 |
|
91 | |||
| 91 | test("Load configuration from module", async t => { |
|
92 | test("Load configuration from module", async t => { | |
| 92 | const container = new Container(); |
|
93 | const container = new Container(); | |
| 93 |
|
94 | |||
| 94 | await container.configure("../mock/config1", { contextRequire: require }); |
|
95 | await container.configure("../mock/config1", { contextRequire: require }); | |
| 95 | t.pass("The configuration should load"); |
|
96 | t.pass("The configuration should load"); | |
| 96 |
|
97 | |||
| 97 | const f1 = container.resolve("foo"); |
|
98 | const f1 = container.resolve("foo"); | |
| 98 |
|
99 | |||
| 99 | t.assert(!isNull(f1), "foo should be not null"); |
|
100 | t.assert(!isNull(f1), "foo should be not null"); | |
| 100 |
|
101 | |||
| 101 | const b1 = container.resolve("bar") as Bar; |
|
102 | const b1 = container.resolve("bar") as Bar; | |
| 102 |
|
103 | |||
| 103 | t.assert(!isNull(b1), "bar should not be null"); |
|
104 | t.assert(!isNull(b1), "bar should not be null"); | |
| 104 | t.assert(!isNull(b1._v), "bar.foo should not be null"); |
|
105 | t.assert(!isNull(b1._v), "bar.foo should not be null"); | |
| 105 |
|
106 | |||
| 106 | }); |
|
107 | }); | |
|
|
108 | ||||
|
|
109 | test("Optional dependency with child container", async t => { | |||
|
|
110 | const container = new Container<{ | |||
|
|
111 | foo?: Foo; | |||
|
|
112 | box: Box<Foo>; | |||
|
|
113 | }>(); | |||
|
|
114 | await container.fluent({ | |||
|
|
115 | box: it => it.factory($ => new Box($("foo"))) | |||
|
|
116 | }); | |||
|
|
117 | ||||
|
|
118 | const child = await container.createChildContainer() | |||
|
|
119 | .fluent({ | |||
|
|
120 | foo: it => it.factory(() => new Foo()) | |||
|
|
121 | }) | |||
|
|
122 | ||||
|
|
123 | const box = child.resolve("box"); | |||
|
|
124 | t.assert(!isNull(box.getValue()), "'foo' dependency is declared in child container"); | |||
|
|
125 | }); No newline at end of file | |||
General Comments 0
You need to be logged in to leave comments.
Login now
