##// END OF EJS Templates
Fixed container interfaces, separated ServiceContainer
cin -
r144:f97a113c3209 v1.4.0-rc4 default
parent child
Show More
@@ -1,170 +1,169
1 1 import { TraceSource } from "../log/TraceSource";
2 2 import { argumentNotEmptyString } from "../safe";
3 import { Descriptor, ContainerServiceMap, ContainerKeys, TypeOfService, ILifetime } from "./interfaces";
4 import { Container } from "./Container";
3 import { Descriptor, ContainerServiceMap, ContainerKeys, TypeOfService, ILifetime, ServiceContainer } from "./interfaces";
5 4 import { MapOf } from "../interfaces";
6 5
7 6 const trace = TraceSource.get("@implab/core/di/ActivationContext");
8 7
9 8 export interface ActivationContextInfo {
10 9 name: string;
11 10
12 11 service: string;
13 12
14 13 }
15 14
16 15 let nextId = 1;
17 16
18 17 /** This class is created once per `Container.resolve` method call and used to
19 18 * cache dependencies and to track created instances. The activation context
20 19 * tracks services with `context` activation type.
21 20 */
22 21 export class ActivationContext<S extends object> {
23 22 _cache: MapOf<any>;
24 23
25 24 _services: ContainerServiceMap<S>;
26 25
27 26 _visited: MapOf<any>;
28 27
29 28 _name: string;
30 29
31 30 _service: Descriptor<S, any>;
32 31
33 _container: Container<S>;
32 _container: ServiceContainer<S>;
34 33
35 34 _parent: ActivationContext<S> | undefined;
36 35
37 36 /** Creates a new activation context with the specified parameters.
38 37 * @param container the container which starts the activation process
39 38 * @param services the initial service registrations
40 39 * @param name the name of the service being activated, this parameter is
41 40 * used for the debug purpose.
42 41 * @param service the service to activate, this parameter is used for the
43 42 * debug purpose.
44 43 */
45 constructor(container: Container<S>, services: ContainerServiceMap<S>, name: string, service: Descriptor<S, any>) {
44 constructor(container: ServiceContainer<S>, services: ContainerServiceMap<S>, name: string, service: Descriptor<S, any>) {
46 45 this._name = name;
47 46 this._service = service;
48 47 this._visited = {};
49 48 this._cache = {};
50 49 this._services = services;
51 50 this._container = container;
52 51 }
53 52
54 53 /** the name of the current resolving dependency */
55 54 getName() {
56 55 return this._name;
57 56 }
58 57
59 58 /** Returns the container for which 'resolve' method was called */
60 59 getContainer() {
61 60 return this._container;
62 61 }
63 62
64 63 /** Resolves the specified dependency in the current context
65 64 * @param name The name of the dependency being resolved
66 65 */
67 66 resolve<K extends ContainerKeys<S>>(name: K): TypeOfService<S, K>;
68 67 /** Resolves the specified dependency with the specified default value if
69 68 * the dependency is missing.
70 69 *
71 70 * @param name The name of the dependency being resolved
72 71 * @param def A default value to return in case of the specified dependency
73 72 * is missing.
74 73 */
75 74 resolve<K extends ContainerKeys<S>, T>(name: K, def: T): TypeOfService<S, K> | T;
76 75 /** Resolves the specified dependency and returns undefined in case if the
77 76 * dependency is missing.
78 77 *
79 78 * @param name The name of the dependency being resolved
80 79 */
81 80 resolve<K extends ContainerKeys<S>>(name: K, def: undefined): TypeOfService<S, K> | undefined;
82 81 resolve<K extends ContainerKeys<S>, T>(name: K, def?: T): TypeOfService<S, K> | T | undefined {
83 82 const d = this._services[name];
84 83
85 84 if (d !== undefined) {
86 85 return this.activate(d, name.toString());
87 86 } else {
88 87 if (arguments.length > 1)
89 88 return def;
90 89 else
91 90 throw new Error(`Service ${name} not found`);
92 91 }
93 92 }
94 93
95 94 /**
96 95 * registers services local to the the activation context
97 96 *
98 97 * @name{string} the name of the service
99 98 * @service{string} the service descriptor to register
100 99 */
101 100 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>) {
102 101 argumentNotEmptyString(name, "name");
103 102
104 103 this._services[name] = service as any;
105 104 }
106 105
107 106 createLifetime(): ILifetime {
108 107 const id = nextId++;
109 108 const me = this;
110 109 return {
111 110 initialize() {
112 111 },
113 112 has() {
114 113 return id in me._cache;
115 114 },
116 115 get() {
117 116 return me._cache[id];
118 117 },
119 118 store(item: any) {
120 119 me._cache[id] = item;
121 120 }
122 121 };
123 122 }
124 123
125 124 activate<T>(d: Descriptor<S, T>, name: string) {
126 125 if (trace.isLogEnabled())
127 126 trace.log(`enter ${name} ${d}`);
128 127
129 128 const ctx = this.enter(d, name);
130 129 const v = d.activate(ctx);
131 130
132 131 if (trace.isLogEnabled())
133 132 trace.log(`leave ${name}`);
134 133
135 134 return v;
136 135 }
137 136
138 137 visit(id: string) {
139 138 const count = this._visited[id] || 0;
140 139 this._visited[id] = count + 1;
141 140 return count;
142 141 }
143 142
144 143 getStack(): ActivationContextInfo[] {
145 144 const stack = [{
146 145 name: this._name,
147 146 service: this._service.toString()
148 147 }];
149 148
150 149 return this._parent ?
151 150 stack.concat(this._parent.getStack()) :
152 151 stack;
153 152 }
154 153
155 154 private enter(service: Descriptor<S, any>, name: string): this {
156 155 const clone = Object.create(this);
157 156 clone._name = name;
158 157 clone._services = Object.create(this._services);
159 158 clone._parent = this;
160 159 clone._service = service;
161 160 return clone;
162 161 }
163 162
164 163 /** Creates a clone for the current context, used to protect it from modifications */
165 164 clone(): this {
166 165 const clone = Object.create(this);
167 166 clone._services = Object.create(this._services);
168 167 return clone;
169 168 }
170 169 }
@@ -1,451 +1,450
1 1 import {
2 2 PartialServiceMap,
3 3 ActivationType,
4 4 ContainerKeys,
5 5 TypeOfService,
6 ILifetime
6 ILifetime, ServiceContainer
7 7 } from "./interfaces";
8 8
9 9 import { argumentNotEmptyString, isPrimitive, isPromise, delegate, argumentOfType, argumentNotNull, get, primitive } from "../safe";
10 10 import { AggregateDescriptor } from "./AggregateDescriptor";
11 11 import { ValueDescriptor } from "./ValueDescriptor";
12 import { Container } from "./Container";
13 12 import { ReferenceDescriptor } from "./ReferenceDescriptor";
14 13 import { TypeServiceDescriptor } from "./TypeServiceDescriptor";
15 14 import { FactoryServiceDescriptor } from "./FactoryServiceDescriptor";
16 15 import { TraceSource } from "../log/TraceSource";
17 16 import { ConfigError } from "./ConfigError";
18 17 import { Cancellation } from "../Cancellation";
19 18 import { makeResolver } from "./ResolverHelper";
20 19 import { ICancellation } from "../interfaces";
21 20 import { isDescriptor } from "./traits";
22 21 import { LazyReferenceDescriptor } from "./LazyReferenceDescriptor";
23 22 import { LifetimeManager } from "./LifetimeManager";
24 23
25 24 export interface RegistrationScope<S extends object> {
26 25
27 26 /** сСрвисы, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Ρ€Π΅Π³ΠΈΡΡ‚Ρ€ΠΈΡ€ΡƒΡŽΡ‚ΡΡ Π² контСкстС Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ ΠΈ Ρ‚Π°ΠΊΠΈΠΌ ΠΎΠ±Ρ€Π°Π·ΠΎΠΌ
28 27 * ΠΌΠΎΠ³ΡƒΡ‚ ΠΏΠ΅Ρ€Π΅ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΡΡ‚ΡŒ Ρ€Π°Π½Π΅Π΅ зарСгистрированныС сСрвисы. Π·Π° это свойство
29 28 * Π½ΡƒΠΆΠ½ΠΎ ΠΏΠ»Π°Ρ‚ΠΈΡ‚ΡŒ, ΠΊΡ€ΠΎΠΌΠ΅ Ρ‚ΠΎΠ³ΠΎ порядок Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ Π±ΡƒΠ΄Π΅Ρ‚ Π²Π»ΠΈΡΡ‚ΡŒ Π½Π° Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚
30 29 * Ρ€Π°Π·Ρ€Π΅ΡˆΠ΅Π½ΠΈΡ зависимостСй.
31 30 */
32 31 services?: RegistrationMap<S>;
33 32 }
34 33
35 34 /**
36 35 * Π‘Π°Π·ΠΎΠ²Ρ‹ΠΉ интСрфСйс ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ сСрвисов
37 36 */
38 37 export interface ServiceRegistration<T, S extends object> extends RegistrationScope<S> {
39 38
40 39 activation?: ActivationType;
41 40
42 41 params?: any;
43 42
44 43 /** Π‘ΠΏΠ΅Ρ†ΠΈΠ°Π»ΡŒΠ½Ρ‹ΠΉ ΠΈΠ΄Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ‚ΠΎΡ€ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ ΠΏΡ€ΠΈ Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ singleton, Ссли
45 44 * Π½Π΅ ΡƒΠΊΠ°Π·Π°Π½ для TypeRegistration вычисляСтся ΠΊΠ°ΠΊ oid($type)
46 45 */
47 46 typeId?: string;
48 47
49 48 inject?: object | object[];
50 49
51 50 cleanup?: ((instance: T) => void) | string;
52 51 }
53 52
54 53 export interface TypeRegistration<C extends new (...args: any[]) => any, S extends object> extends ServiceRegistration<InstanceType<C>, S> {
55 54 $type: string | C;
56 55 params?: Registration<ConstructorParameters<C>, S>;
57 56 }
58 57
59 58 export interface StrictTypeRegistration<C extends new (...args: any[]) => any, S extends object> extends ServiceRegistration<InstanceType<C>, S> {
60 59 $type: C;
61 60 params?: Registration<ConstructorParameters<C>, S>;
62 61 }
63 62
64 63 export interface FactoryRegistration<F extends (...args: any[]) => any, S extends object> extends ServiceRegistration<ReturnType<F>, S> {
65 64 $factory: string | F;
66 65 }
67 66
68 67 export interface ValueRegistration<T> {
69 68 $value: T;
70 69 parse?: boolean;
71 70 }
72 71
73 72 export interface DependencyRegistration<S extends object, K extends ContainerKeys<S> = ContainerKeys<S>> extends RegistrationScope<S> {
74 73 $dependency: K;
75 74 lazy?: boolean;
76 75 optional?: boolean;
77 76 default?: TypeOfService<S, K>;
78 77 }
79 78
80 79 export interface LazyDependencyRegistration<S extends object, K extends ContainerKeys<S> = ContainerKeys<S>> extends DependencyRegistration<S, K> {
81 80 lazy: true;
82 81 }
83 82
84 83 export type Registration<T, S extends object> = T extends primitive ? T :
85 84 (
86 85 T |
87 86 { [k in keyof T]: Registration<T[k], S> } |
88 87 TypeRegistration<new (...args: any[]) => T, S> |
89 88 FactoryRegistration<(...args: any[]) => T, S> |
90 89 ValueRegistration<any> |
91 90 DependencyRegistration<S, keyof S>
92 91 );
93 92
94 93 export type RegistrationMap<S extends object> = {
95 94 [k in keyof S]?: Registration<S[k], S>;
96 95 };
97 96
98 97 const _activationTypes: { [k in ActivationType]: number; } = {
99 98 singleton: 1,
100 99 container: 2,
101 100 hierarchy: 3,
102 101 context: 4,
103 102 call: 5
104 103 };
105 104
106 105 export function isTypeRegistration(x: any): x is TypeRegistration<new () => any, any> {
107 106 return (!isPrimitive(x)) && ("$type" in x);
108 107 }
109 108
110 109 export function isFactoryRegistration(x: any): x is FactoryRegistration<() => any, any> {
111 110 return (!isPrimitive(x)) && ("$factory" in x);
112 111 }
113 112
114 113 export function isValueRegistration(x: any): x is ValueRegistration<any> {
115 114 return (!isPrimitive(x)) && ("$value" in x);
116 115 }
117 116
118 117 export function isDependencyRegistration<S extends object>(x: any): x is DependencyRegistration<S, keyof S> {
119 118 return (!isPrimitive(x)) && ("$dependency" in x);
120 119 }
121 120
122 121 export function isActivationType(x: string): x is ActivationType {
123 122 return typeof x === "string" && x in _activationTypes;
124 123 }
125 124
126 125 const trace = TraceSource.get("@implab/core/di/Configuration");
127 126 async function mapAll(data: any[], map?: (v: any, k: number) => any): Promise<any[]>;
128 127 async function mapAll(data: any, map?: (v: any, k: string) => any): Promise<any>;
129 128 async function mapAll(data: any, map?: (v: any, k: any) => any): Promise<any> {
130 129 if (data instanceof Array) {
131 130 return Promise.all(map ? data.map(map) : data);
132 131 } else {
133 132 const keys = Object.keys(data);
134 133
135 134 const o: any = {};
136 135
137 136 await Promise.all(keys.map(async k => {
138 137 const v = map ? map(data[k], k) : data[k];
139 138 o[k] = isPromise(v) ? await v : v;
140 139 }));
141 140
142 141 return o;
143 142 }
144 143 }
145 144
146 145 export type ModuleResolver = (moduleName: string, ct?: ICancellation) => any;
147 146
148 147 export class Configuration<S extends object> {
149 148
150 149 _hasInnerDescriptors = false;
151 150
152 readonly _container: Container<S>;
151 readonly _container: ServiceContainer<S>;
153 152
154 153 _path: Array<string>;
155 154
156 155 _configName: string | undefined;
157 156
158 157 _require: ModuleResolver | undefined;
159 158
160 constructor(container: Container<S>) {
159 constructor(container: ServiceContainer<S>) {
161 160 argumentNotNull(container, "container");
162 161 this._container = container;
163 162 this._path = [];
164 163 }
165 164
166 165 async loadConfiguration(moduleName: string, contextRequire?: any, ct = Cancellation.none) {
167 166 argumentNotEmptyString(moduleName, "moduleName");
168 167
169 168 trace.log(
170 169 "loadConfiguration moduleName={0}, contextRequire={1}",
171 170 moduleName,
172 171 contextRequire ? typeof (contextRequire) : "<nil>"
173 172 );
174 173
175 174 this._configName = moduleName;
176 175
177 176 const r = await makeResolver(undefined, contextRequire);
178 177
179 178 const config = await r(moduleName, ct);
180 179
181 180 await this._applyConfiguration(
182 181 config,
183 182 await makeResolver(moduleName, contextRequire),
184 183 ct
185 184 );
186 185 }
187 186
188 187 async applyConfiguration(data: RegistrationMap<S>, opts: { contextRequire?: any; baseModule?: string }, ct = Cancellation.none) {
189 188 argumentNotNull(data, "data");
190 189 const _opts = opts || {};
191 190
192 191 await this._applyConfiguration(data, await makeResolver(_opts.baseModule, _opts.contextRequire), ct);
193 192 }
194 193
195 194 async _applyConfiguration(data: RegistrationMap<S>, resolver?: ModuleResolver, ct = Cancellation.none) {
196 195 trace.log("applyConfiguration");
197 196
198 197 this._configName = "$";
199 198
200 199 if (resolver)
201 200 this._require = resolver;
202 201
203 202 let services: PartialServiceMap<S>;
204 203
205 204 try {
206 205 services = await this._visitRegistrations(data, "$");
207 206 } catch (e) {
208 207 throw this._makeError(e);
209 208 }
210 209
211 210 this._container.register(services);
212 211 }
213 212
214 213 _makeError(inner: any) {
215 214 const e = new ConfigError("Failed to load configuration", inner);
216 215 e.configName = this._configName || "<inline>";
217 216 e.path = this._makePath();
218 217 return e;
219 218 }
220 219
221 220 _makePath() {
222 221 return this._path
223 222 .reduce(
224 223 (prev, cur) => typeof cur === "number" ?
225 224 `${prev}[${cur}]` :
226 225 `${prev}.${cur}`
227 226 )
228 227 .toString();
229 228 }
230 229
231 230 async _resolveType(moduleName: string, localName: string) {
232 231 trace.log("resolveType moduleName={0}, localName={1}", moduleName, localName);
233 232 try {
234 233 const m = await this._loadModule(moduleName);
235 234 if (localName) {
236 235 return get(localName, m);
237 236 } else {
238 237 if (m instanceof Function)
239 238 return m;
240 239 if ("default" in m)
241 240 return m.default;
242 241 return m;
243 242 }
244 243 } catch (e) {
245 244 trace.error("Failed to resolve type moduleName={0}, localName={1}", moduleName, localName);
246 245 throw e;
247 246 }
248 247 }
249 248
250 249 _loadModule(moduleName: string) {
251 250 trace.debug("loadModule {0}", moduleName);
252 251 if (!this._require)
253 252 throw new Error("Module loader isn't specified");
254 253
255 254 return this._require(moduleName);
256 255 }
257 256
258 257 async _visitRegistrations(data: RegistrationMap<S>, name: string) {
259 258 this._enter(name);
260 259
261 260 if (data.constructor &&
262 261 data.constructor.prototype !== Object.prototype)
263 262 throw new Error("Configuration must be a simple object");
264 263
265 264 const services = await mapAll(data, async (v, k) => {
266 265 const d = await this._visit(v, k.toString());
267 266 return isDescriptor(d) ? d : new AggregateDescriptor(d);
268 267 }) as PartialServiceMap<S>;
269 268
270 269 this._leave();
271 270
272 271 return services;
273 272 }
274 273
275 274 _enter(name: string) {
276 275 this._path.push(name.toString());
277 276 trace.debug(">{0}", name);
278 277 }
279 278
280 279 _leave() {
281 280 const name = this._path.pop();
282 281 trace.debug("<{0}", name);
283 282 }
284 283
285 284 _visit(data: any, name: string): Promise<any> {
286 285 if (isPrimitive(data))
287 286 return Promise.resolve(new ValueDescriptor(data));
288 287 if (isDescriptor(data))
289 288 return Promise.resolve(data);
290 289
291 290 if (isDependencyRegistration<S>(data)) {
292 291 return this._visitDependencyRegistration(data, name);
293 292 } else if (isValueRegistration(data)) {
294 293 return this._visitValueRegistration(data, name);
295 294 } else if (isTypeRegistration(data)) {
296 295 return this._visitTypeRegistration(data, name);
297 296 } else if (isFactoryRegistration(data)) {
298 297 return this._visitFactoryRegistration(data, name);
299 298 } else if (data instanceof Array) {
300 299 return this._visitArray(data, name);
301 300 }
302 301
303 302 return this._visitObject(data, name);
304 303 }
305 304
306 305 async _visitObject(data: any, name: string) {
307 306 if (data.constructor &&
308 307 data.constructor.prototype !== Object.prototype)
309 308 return new ValueDescriptor(data);
310 309
311 310 this._enter(name);
312 311
313 312 const v = await mapAll(data, delegate(this, "_visit"));
314 313
315 314 // TODO: handle inline descriptors properly
316 315 // const ex = {
317 316 // activate(ctx) {
318 317 // const value = ctx.activate(this.prop, "prop");
319 318 // // some code
320 319 // },
321 320 // // will be turned to ReferenceDescriptor
322 321 // prop: { $dependency: "depName" }
323 322 // };
324 323
325 324 this._leave();
326 325 return v;
327 326 }
328 327
329 328 async _visitArray(data: any[], name: string) {
330 329 if (data.constructor &&
331 330 data.constructor.prototype !== Array.prototype)
332 331 return new ValueDescriptor(data);
333 332
334 333 this._enter(name);
335 334
336 335 const v = await mapAll(data, delegate(this, "_visit"));
337 336 this._leave();
338 337
339 338 return v;
340 339 }
341 340
342 341 _makeServiceParams(data: ServiceRegistration<any, S>) {
343 342 const opts: any = {
344 343 };
345 344 if (data.services)
346 345 opts.services = this._visitRegistrations(data.services, "services");
347 346
348 347 if (data.inject) {
349 348 this._enter("inject");
350 349 opts.inject = mapAll(
351 350 data.inject instanceof Array ?
352 351 data.inject :
353 352 [data.inject],
354 353 delegate(this, "_visitObject")
355 354 );
356 355 this._leave();
357 356 }
358 357
359 358 if ("params" in data)
360 359 opts.params = data.params instanceof Array ?
361 360 this._visitArray(data.params, "params") :
362 361 this._visit(data.params, "params");
363 362
364 363 if (data.activation) {
365 364 opts.activation = this._getLifetimeManager(data.activation, data.typeId);
366 365 }
367 366
368 367 if (data.cleanup)
369 368 opts.cleanup = data.cleanup;
370 369
371 370 return opts;
372 371 }
373 372
374 373 async _visitValueRegistration<T>(data: ValueRegistration<T>, name: string) {
375 374 this._enter(name);
376 375 const d = data.parse ? new AggregateDescriptor(data.$value) : new ValueDescriptor(data.$value);
377 376 this._leave();
378 377 return d;
379 378 }
380 379
381 380 async _visitDependencyRegistration<K extends keyof S>(data: DependencyRegistration<S, K>, name: string) {
382 381 argumentNotEmptyString(data && data.$dependency, "data.$dependency");
383 382 this._enter(name);
384 383 const options = {
385 384 name: data.$dependency,
386 385 optional: data.optional,
387 386 default: data.default,
388 387 services: data.services && await this._visitRegistrations(data.services, "services")
389 388 };
390 389 const d = data.lazy ? new LazyReferenceDescriptor<S, K>(options) : new ReferenceDescriptor<S, K>(options);
391 390 this._leave();
392 391 return d;
393 392 }
394 393
395 394 async _visitTypeRegistration(data: TypeRegistration<new () => any, S>, name: string) {
396 395 argumentNotNull(data.$type, "data.$type");
397 396 this._enter(name);
398 397
399 398 const opts = this._makeServiceParams(data);
400 399 if (data.$type instanceof Function) {
401 400 opts.type = data.$type;
402 401 } else {
403 402 const [moduleName, typeName] = data.$type.split(":", 2);
404 403 opts.type = this._resolveType(moduleName, typeName).then(t => {
405 404 if (!(t instanceof Function))
406 405 throw Error("$type (" + data.$type + ") is not a constructable");
407 406 return t;
408 407 });
409 408 }
410 409
411 410 const d = new TypeServiceDescriptor<S, any, any[]>(
412 411 await mapAll(opts)
413 412 );
414 413
415 414 this._leave();
416 415
417 416 return d;
418 417 }
419 418
420 419 async _visitFactoryRegistration(data: FactoryRegistration<() => any, S>, name: string) {
421 420 argumentOfType(data.$factory, Function, "data.$factory");
422 421 this._enter(name);
423 422
424 423 const opts = this._makeServiceParams(data);
425 424 opts.factory = data.$factory;
426 425
427 426 const d = new FactoryServiceDescriptor<S, any, any[]>(
428 427 await mapAll(opts)
429 428 );
430 429
431 430 this._leave();
432 431 return d;
433 432 }
434 433
435 434 _getLifetimeManager(activation: ActivationType, typeId: string | undefined): ILifetime {
436 435 switch (activation) {
437 436 case "container":
438 437 return LifetimeManager.containerLifetime(this._container);
439 438 case "hierarchy":
440 439 return LifetimeManager.hierarchyLifetime();
441 440 case "context":
442 441 return LifetimeManager.contextLifetime();
443 442 case "singleton":
444 443 if (typeId === undefined)
445 444 throw Error("The singleton activation requires a typeId");
446 445 return LifetimeManager.singletonLifetime(typeId);
447 446 default:
448 447 return LifetimeManager.empty();
449 448 }
450 449 }
451 450 }
@@ -1,172 +1,173
1 1 import { ActivationContext } from "./ActivationContext";
2 2 import { ValueDescriptor } from "./ValueDescriptor";
3 3 import { ActivationError } from "./ActivationError";
4 import { ServiceMap, Descriptor, PartialServiceMap, ServiceLocator, ContainerServiceMap, ContainerKeys, TypeOfService } from "./interfaces";
4 import { ServiceMap, Descriptor, PartialServiceMap, ContainerServiceMap, ContainerKeys, TypeOfService, ServiceContainer } from "./interfaces";
5 5 import { TraceSource } from "../log/TraceSource";
6 6 import { Configuration, RegistrationMap } from "./Configuration";
7 7 import { Cancellation } from "../Cancellation";
8 import { IDestroyable, PromiseOrValue, ICancellation } from "../interfaces";
8 import { IDestroyable, ICancellation } from "../interfaces";
9 9 import { isDescriptor } from "./traits";
10 10 import { LifetimeManager } from "./LifetimeManager";
11 11 import { each, isString } from "../safe";
12 12 import { ContainerConfiguration, FluentRegistrations } from "./fluent/interfaces";
13 13 import { FluentConfiguration } from "./fluent/FluentConfiguration";
14 14
15 15 const trace = TraceSource.get("@implab/core/di/ActivationContext");
16 16
17 export class Container<S extends object = any> implements ServiceLocator<S>, IDestroyable {
17 export class Container<S extends object = any> implements ServiceContainer<S>, IDestroyable {
18 18 readonly _services: ContainerServiceMap<S>;
19 19
20 20 readonly _lifetimeManager: LifetimeManager;
21 21
22 22 readonly _cleanup: (() => void)[];
23 23
24 24 readonly _root: Container<S>;
25 25
26 26 readonly _parent?: Container<S>;
27 27
28 28 _disposed: boolean;
29 29
30 30 constructor(parent?: Container<S>) {
31 31 this._parent = parent;
32 32 this._services = parent ? Object.create(parent._services) : {};
33 33 this._cleanup = [];
34 34 this._root = parent ? parent.getRootContainer() : this;
35 35 this._services.container = new ValueDescriptor(this) as any;
36 36 this._disposed = false;
37 37 this._lifetimeManager = new LifetimeManager();
38 38 }
39 39
40 40 getRootContainer() {
41 41 return this._root;
42 42 }
43 43
44 44 getParent() {
45 45 return this._parent;
46 46 }
47 47
48 48 getLifetimeManager() {
49 49 return this._lifetimeManager;
50 50 }
51 51
52 52 resolve<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>): TypeOfService<S, K> {
53 53 trace.debug("resolve {0}", name);
54 54 const d = this._services[name];
55 55 if (d === undefined) {
56 56 if (def !== undefined)
57 57 return def;
58 58 else
59 59 throw new Error("Service '" + name + "' isn't found");
60 60 } else {
61 61
62 62 const context = new ActivationContext<S>(this, this._services, String(name), d);
63 63 try {
64 64 return d.activate(context);
65 65 } catch (error) {
66 66 throw new ActivationError(name.toString(), context.getStack(), error);
67 67 }
68 68 }
69 69 }
70 70
71 71 /**
72 72 * @deprecated use resolve() method
73 73 */
74 74 getService<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>) {
75 75 return this.resolve(name, def);
76 76 }
77 77
78 78 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>): this;
79 79 register(services: PartialServiceMap<S>): this;
80 80 register<K extends keyof S>(nameOrCollection: K | ServiceMap<S>, service?: Descriptor<S, S[K]>) {
81 81 if (arguments.length === 1) {
82 82 const data = nameOrCollection as ServiceMap<S>;
83 83
84 84 each(data, (v, k) => this.register(k, v));
85 85 } else {
86 86 if (!isDescriptor(service))
87 87 throw new Error("The service parameter must be a descriptor");
88 88
89 89 this._services[nameOrCollection as K] = service as any;
90 90 }
91 91 return this;
92 92 }
93 93
94 /** @deprecated use getLifetimeManager() */
94 95 onDispose(callback: () => void) {
95 96 if (!(callback instanceof Function))
96 97 throw new Error("The callback must be a function");
97 98 this._cleanup.push(callback);
98 99 }
99 100
100 101 destroy() {
101 102 return this.dispose();
102 103 }
103 104 dispose() {
104 105 if (this._disposed)
105 106 return;
106 107 this._disposed = true;
107 108 for (const f of this._cleanup)
108 109 f();
109 110 }
110 111
111 112 /**
112 113 * @param{String|Object} config
113 114 * The configuration of the container. Can be either a string or an object,
114 115 * if the configuration is an object it's treated as a collection of
115 116 * services which will be registered in the container.
116 117 *
117 118 * @param{Function} opts.contextRequire
118 119 * The function which will be used to load a configuration or types for services.
119 120 *
120 121 */
121 122 async configure(config: string | RegistrationMap<S>, opts?: { contextRequire: any; baseModule?: string }, ct = Cancellation.none) {
122 123 const _opts = Object.create(opts || null);
123 124
124 125 if (typeof (config) === "string") {
125 126 _opts.baseModule = config;
126 127
127 128 const module = await import(config);
128 129 if (module && module.default && typeof (module.default.apply) === "function")
129 130 return module.default.apply(this);
130 131 else
131 132 return this._applyLegacyConfig(module, _opts, ct);
132 133 } else {
133 134 return this._applyLegacyConfig(config, _opts, ct);
134 135 }
135 136 }
136 137
137 applyConfig<S2 extends object>(config: Promise<{ default: ContainerConfiguration<S2>; }>, ct?: ICancellation): Promise<Container<S & S2>>;
138 applyConfig<S2 extends object, P extends string>(config: Promise<{ [p in P]: ContainerConfiguration<S2>; }>, prop: P, ct?: ICancellation): Promise<Container<S & S2>>;
138 applyConfig<S2 extends object>(config: Promise<{ default: ContainerConfiguration<S2>; }>, ct?: ICancellation): Promise<ServiceContainer<S & S2>>;
139 applyConfig<S2 extends object, P extends string>(config: Promise<{ [p in P]: ContainerConfiguration<S2>; }>, prop: P, ct?: ICancellation): Promise<ServiceContainer<S & S2>>;
139 140 async applyConfig<S2 extends object, P extends string>(
140 141 config: Promise<{ [p in P | "default"]: ContainerConfiguration<S2>; }>,
141 142 propOrCt?: P | ICancellation,
142 143 ct?: ICancellation
143 ): Promise<Container<S & S2>> {
144 ): Promise<ServiceContainer<S & S2>> {
144 145 const mod = await config;
145 146
146 147 let _ct: ICancellation;
147 148 let _prop: P | "default";
148 149
149 150 if (isString(propOrCt)) {
150 151 _prop = propOrCt;
151 152 _ct = ct || Cancellation.none;
152 153 } else {
153 154 _ct = propOrCt || Cancellation.none;
154 155 _prop = "default";
155 156 }
156 157
157 158 return mod[_prop].apply(this, _ct);
158 159 }
159 160
160 161 async _applyLegacyConfig(config: RegistrationMap<S>, opts: { contextRequire: any; baseModule?: string }, ct = Cancellation.none) {
161 162 return new Configuration<S>(this).applyConfiguration(config, opts);
162 163 }
163 164
164 165 async fluent<K extends keyof S>(config: FluentRegistrations<K, S>, ct = Cancellation.none): Promise<this> {
165 166 await new FluentConfiguration<S>().register(config).apply(this, ct);
166 167 return this;
167 168 }
168 169
169 170 createChildContainer<S2 extends object = S>(): Container<S & S2> {
170 171 return new Container<S & S2>(this as any);
171 172 }
172 173 }
@@ -1,198 +1,197
1 1 import { IDestroyable, MapOf } from "../interfaces";
2 import { argumentNotNull, isDestroyable, primitive, isNull, argumentNotEmptyString } from "../safe";
3 import { ILifetime } from "./interfaces";
2 import { argumentNotNull, isDestroyable, argumentNotEmptyString } from "../safe";
3 import { ILifetime, ServiceContainer } from "./interfaces";
4 4 import { ActivationContext } from "./ActivationContext";
5 import { Container } from "./Container";
6 5
7 6 function safeCall(item: () => void) {
8 7 try {
9 8 item();
10 9 } catch {
11 10 // silence!
12 11 }
13 12 }
14 13
15 14 const emptyLifetime: ILifetime = Object.freeze({
16 15 has() {
17 16 return false;
18 17 },
19 18
20 19 initialize() {
21 20
22 21 },
23 22
24 23 get() {
25 24 throw new Error("The specified item isn't registered with this lifetime manager");
26 25 },
27 26
28 27 store() {
29 28 // does nothing
30 29 }
31 30
32 31 });
33 32
34 33 const unknownLifetime: ILifetime = Object.freeze({
35 34 has() {
36 35 return false;
37 36 },
38 37 initialize() {
39 38 throw new Error("Can't call initialize on the unknown lifetime object");
40 39 },
41 40 get() {
42 41 throw new Error("The lifetime object isn't initialized");
43 42 },
44 43 store() {
45 44 throw new Error("Can't store a value in the unknown lifetime object");
46 45 }
47 46 });
48 47
49 48 let nextId = 0;
50 49
51 const singletons: { [k in keyof any]: any; } = {};
50 const singletons: any = {};
52 51
53 52 export class LifetimeManager implements IDestroyable {
54 53 private _cleanup: (() => void)[] = [];
55 54 private _cache: MapOf<any> = {};
56 55 private _destroyed = false;
57 56
58 57 private _pending: MapOf<boolean> = {};
59 58
60 59 create(): ILifetime {
61 60 const self = this;
62 61 const id = ++nextId;
63 62 return {
64 63 has() {
65 64 return (id in self._cache);
66 65 },
67 66
68 67 get() {
69 68 const t = self._cache[id];
70 69 if (t === undefined)
71 70 throw new Error(`The item with with the key ${id} isn't found`);
72 71 return t;
73 72 },
74 73
75 74 initialize() {
76 75 if (self._pending[id])
77 76 throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`);
78 77 self._pending[id] = true;
79 78 },
80 79
81 80 store(item: any, cleanup?: (item: any) => void) {
82 81 argumentNotNull(id, "id");
83 82 argumentNotNull(item, "item");
84 83
85 84 if (this.has())
86 85 throw new Error(`The item with with the key ${id} already registered with this lifetime manager`);
87 86 delete self._pending[id];
88 87
89 88 self._cache[id] = item;
90 89
91 90 if (self._destroyed)
92 91 throw new Error("Lifetime manager is destroyed");
93 92 if (cleanup) {
94 93 self._cleanup.push(() => cleanup(item));
95 94 } else if (isDestroyable(item)) {
96 95 self._cleanup.push(() => item.destroy());
97 96 }
98 97 }
99 98 };
100 99 }
101 100
102 101 destroy() {
103 102 if (!this._destroyed) {
104 103 this._destroyed = true;
105 104 this._cleanup.forEach(safeCall);
106 105 this._cleanup.length = 0;
107 106 }
108 107 }
109 108
110 109 static empty(): ILifetime {
111 110 return emptyLifetime;
112 111 }
113 112
114 113 static hierarchyLifetime(): ILifetime {
115 114 let _lifetime = unknownLifetime;
116 115 return {
117 116 initialize(context: ActivationContext<any>) {
118 117 if (_lifetime !== unknownLifetime)
119 118 throw new Error("Cyclic reference activation detected");
120 119
121 120 _lifetime = context.getContainer().getLifetimeManager().create();
122 121 },
123 122 get() {
124 123 return _lifetime.get();
125 124 },
126 125 has() {
127 126 return _lifetime.has();
128 127 },
129 128 store(item: any, cleanup?: (item: any) => void) {
130 129 return _lifetime.store(item, cleanup);
131 130 }
132 131 };
133 132 }
134 133
135 134 static contextLifetime(): ILifetime {
136 135 let _lifetime = unknownLifetime;
137 136 return {
138 137 initialize(context: ActivationContext<any>) {
139 138 if (_lifetime !== unknownLifetime)
140 139 throw new Error("Cyclic reference detected");
141 140 _lifetime = context.createLifetime();
142 141 },
143 142 get() {
144 143 return _lifetime.get();
145 144 },
146 145 has() {
147 146 return _lifetime.has();
148 147 },
149 148 store(item: any) {
150 149 _lifetime.store(item);
151 150 }
152 151 };
153 152 }
154 153
155 154 static singletonLifetime(typeId: string): ILifetime {
156 155 argumentNotEmptyString(typeId, "typeId");
157 156 let pending = false;
158 157 return {
159 158 has() {
160 159 return typeId in singletons;
161 160 },
162 161 get() {
163 162 if (!this.has())
164 163 throw new Error(`The instance ${typeId} doesn't exists`);
165 164 return singletons[typeId];
166 165 },
167 166 initialize() {
168 167 if (pending)
169 168 throw new Error("Cyclic reference detected");
170 169 pending = true;
171 170 },
172 171 store(item: any) {
173 172 singletons[typeId] = item;
174 173 pending = false;
175 174 }
176 175 };
177 176 }
178 177
179 static containerLifetime(container: Container<any>) {
178 static containerLifetime(container: ServiceContainer<any>) {
180 179 let _lifetime = unknownLifetime;
181 180 return {
182 181 initialize(context: ActivationContext<any>) {
183 182 if (_lifetime !== unknownLifetime)
184 183 throw new Error("Cyclic reference detected");
185 184 _lifetime = container.getLifetimeManager().create();
186 185 },
187 186 get() {
188 187 return _lifetime.get();
189 188 },
190 189 has() {
191 190 return _lifetime.has();
192 191 },
193 192 store(item: any) {
194 193 _lifetime.store(item);
195 194 }
196 195 };
197 196 }
198 197 }
@@ -1,147 +1,146
1 1 import { Resolver, RegistrationBuilder } from "./interfaces";
2 import { Container } from "../Container";
3 import { Descriptor, ILifetime, ActivationType, PartialServiceMap } from "../interfaces";
2 import { Descriptor, ILifetime, ActivationType, PartialServiceMap, ServiceContainer } from "../interfaces";
4 3 import { DescriptorImpl } from "./DescriptorImpl";
5 4 import { LifetimeManager } from "../LifetimeManager";
6 5 import { isString, each, isPrimitive, isPromise, oid } from "../../safe";
7 6
8 7 export class DescriptorBuilder<S extends object, T> {
9 private readonly _container: Container<S>;
8 private readonly _container: ServiceContainer<S>;
10 9 private readonly _cb: (d: Descriptor<S, T>) => void;
11 10
12 11 private readonly _eb: (err: any) => void;
13 12
14 13 private _lifetime = LifetimeManager.empty();
15 14
16 15 private _overrides?: PartialServiceMap<S>;
17 16
18 17 private _cleanup?: (item: T) => void;
19 18
20 19 private _factory?: (resolve: Resolver<S>) => T;
21 20
22 21 private _pending = 1;
23 22
24 23 private _failed = false;
25 24
26 constructor(container: Container<S>, cb: (d: Descriptor<S, T>) => void, eb: (err: any) => void) {
25 constructor(container: ServiceContainer<S>, cb: (d: Descriptor<S, T>) => void, eb: (err: any) => void) {
27 26 this._container = container;
28 27 this._cb = cb;
29 28 this._eb = eb;
30 29 }
31 30
32 31 build<T2>(): DescriptorBuilder<S, T2> {
33 32 this._defer();
34 33 return new DescriptorBuilder<S, T2>(this._container, () => this._complete(), err => this._fail(err));
35 34 }
36 35
37 36 override<K extends keyof S>(name: K, builder: RegistrationBuilder<S, S[K]>): this;
38 37 override<K extends keyof S>(services: { [name in K]: RegistrationBuilder<S, S[K]> }): this;
39 38 override<K extends keyof S>(nameOrServices: K | { [name in K]: RegistrationBuilder<S, S[K]> }, builder?: RegistrationBuilder<S, S[K]>): this {
40 39 const overrides: PartialServiceMap<S> = this._overrides ?
41 40 this._overrides :
42 41 (this._overrides = {});
43 42
44 43 const guard = (v: void | Promise<void>) => {
45 44 if (isPromise(v))
46 45 v.catch(err => this._fail(err));
47 46 };
48 47
49 48 if (isPrimitive(nameOrServices)) {
50 49 if (builder) {
51 50 this._defer();
52 51 const d = new DescriptorBuilder<S, S[K]>(
53 52 this._container,
54 53 result => {
55 54 overrides[nameOrServices] = result;
56 55 this._complete();
57 56 },
58 57 err => this._fail(err)
59 58 );
60 59
61 60 try {
62 61 guard(builder(d));
63 62 } catch (err) {
64 63 this._fail(err);
65 64 }
66 65 }
67 66 } else {
68 67 each(nameOrServices, (v, k) => this.override(k, v));
69 68 }
70 69 return this;
71 70 }
72 71
73 72 lifetime(lifetime: "singleton", typeId: string): this;
74 73 lifetime(lifetime: ILifetime | Exclude<ActivationType, "singleton">): this;
75 74 lifetime(lifetime: ILifetime | ActivationType, typeId?: string): this {
76 75 if (isString(lifetime)) {
77 76 this._lifetime = this._resolveLifetime(lifetime, typeId);
78 77 } else {
79 78 this._lifetime = lifetime;
80 79 }
81 80 return this;
82 81 }
83 82
84 83 cleanup(cb: (item: T) => void): this {
85 84 this._cleanup = cb;
86 85 return this;
87 86 }
88 87
89 88 factory(f: (resolve: Resolver<S>) => T): void {
90 89 this._factory = f;
91 90 this._complete();
92 91 }
93 92
94 93 value(v: T): void {
95 94 this._cb({
96 95 activate() {
97 96 return v;
98 97 }
99 98 });
100 99 }
101 100
102 101 _resolveLifetime(activation: ActivationType, typeId?: string | object) {
103 102 switch (activation) {
104 103 case "container":
105 104 return LifetimeManager.containerLifetime(this._container);
106 105 case "hierarchy":
107 106 return LifetimeManager.hierarchyLifetime();
108 107 case "context":
109 108 return LifetimeManager.contextLifetime();
110 109 case "singleton":
111 110 if (!typeId)
112 111 throw Error("The singleton activation requires a typeId");
113 112
114 113 const _oid = isString(typeId) ? typeId : oid(typeId);
115 114
116 115 return LifetimeManager.singletonLifetime(_oid);
117 116 default:
118 117 return LifetimeManager.empty();
119 118 }
120 119 }
121 120
122 121 _defer() {
123 122 this._pending++;
124 123 }
125 124
126 125 _complete() {
127 126 if (--this._pending === 0) {
128 127 if (!this._factory)
129 128 throw new Error("The factory must be specified");
130 129
131 130 this._cb(new DescriptorImpl<S, T>({
132 131 lifetime: this._lifetime,
133 132 factory: this._factory,
134 133 overrides: this._overrides,
135 134 cleanup: this._cleanup
136 135 }));
137 136 }
138 137 }
139 138
140 139 _fail(err: any) {
141 140 if (!this._failed) {
142 141 this._failed = true;
143 142 this._eb.call(undefined, err);
144 143 }
145 144 }
146 145
147 146 }
@@ -1,68 +1,68
1 import { Container } from "../Container";
2 1 import { argumentNotNull, each, isPrimitive, isPromise } from "../../safe";
3 2 import { DescriptorBuilder } from "./DescriptorBuilder";
4 3 import { RegistrationBuilder, FluentRegistrations, ContainerConfiguration } from "./interfaces";
5 4 import { Cancellation } from "../../Cancellation";
5 import { ServiceContainer } from "../interfaces";
6 6
7 7 export class FluentConfiguration<S extends object, Y extends keyof S = keyof S> {
8 8
9 9 _builders: { [k in keyof S]?: RegistrationBuilder<S, S[k]> } = {};
10 10
11 11 provided<K extends Y>(): FluentConfiguration<S, Exclude<Y, K>> {
12 12 return this;
13 13 }
14 14
15 15 register<K extends Y>(name: K, builder: RegistrationBuilder<S, S[K]>): FluentConfiguration<S, Exclude<Y, K>>;
16 16 register<K extends Y>(config: FluentRegistrations<K, S>): FluentConfiguration<S, Exclude<Y, K>>;
17 17 register<K extends Y>(nameOrConfig: K | FluentRegistrations<K, S>, builder?: RegistrationBuilder<S, S[K]>): FluentConfiguration<S, Exclude<Y, K>> {
18 18 if (isPrimitive(nameOrConfig)) {
19 19 argumentNotNull(builder, "builder");
20 20 this._builders[nameOrConfig] = builder;
21 21 } else {
22 22 each(nameOrConfig, (v, k) => this.register(k, v));
23 23 }
24 24
25 25 return this;
26 26 }
27 27
28 28 configure(config: FluentRegistrations<Y, S>): ContainerConfiguration<S> {
29 29 return this.register(config);
30 30 }
31 31
32 apply<SC extends object>(target: Container<SC>, ct = Cancellation.none) {
32 apply<S2 extends object>(target: ServiceContainer<S2>, ct = Cancellation.none) {
33 33
34 34 let pending = 1;
35 35
36 const _t2 = target as unknown as Container<SC & S>;
36 const _t2 = target as unknown as ServiceContainer<S2 & S>;
37 37
38 return new Promise<Container<SC & S>>((resolve, reject) => {
38 return new Promise<ServiceContainer<S2 & S>>((resolve, reject) => {
39 39 function guard(v: void | Promise<void>) {
40 40 if (isPromise(v))
41 41 v.catch(reject);
42 42 }
43 43
44 44 function complete() {
45 45 if (!--pending)
46 46 resolve(_t2);
47 47 }
48 48 each(this._builders, (v, k) => {
49 49 pending++;
50 const d = new DescriptorBuilder<SC & S, any>(_t2,
50 const d = new DescriptorBuilder<S2 & S, any>(_t2,
51 51 result => {
52 52 _t2.register(k, result);
53 53 complete();
54 54 },
55 55 reject
56 56 );
57 57
58 58 try {
59 59 guard(v(d, ct));
60 60 } catch (e) {
61 61 reject(e);
62 62 }
63 63 });
64 64 complete();
65 65 });
66 66 }
67 67
68 68 }
@@ -1,57 +1,56
1 1 import { primitive } from "../../safe";
2 import { TypeOfService, ContainerKeys, ActivationType, ILifetime } from "../interfaces";
2 import { TypeOfService, ContainerKeys, ActivationType, ILifetime, ServiceContainer } from "../interfaces";
3 3 import { ICancellation } from "../../interfaces";
4 import { Container } from "../Container";
5 4
6 5 export interface DependencyOptions {
7 6 optional?: boolean;
8 7 default?: any;
9 8 }
10 9
11 10 export interface LazyDependencyOptions extends DependencyOptions {
12 11 lazy: true;
13 12 }
14 13
15 14 export type ExtractService<K, S> = K extends keyof S ? S[K] : never;
16 15
17 16 export type ExtractDependency<D, S> = D extends { $dependency: infer K } ?
18 17 D extends { lazy: true } ? () => ExtractService<K, S> : ExtractService<K, S> :
19 18 D extends { $type: new (...args: any[]) => infer I } ? I :
20 19 D extends { $factory: (...args: any[]) => infer R } ? R :
21 20 WalkDependencies<D, S>;
22 21
23 22 export type WalkDependencies<D, S> = D extends primitive ? D :
24 23 { [K in keyof D]: ExtractDependency<D[K], S> };
25 24
26 25 export type InferReferenceType<S extends object, K extends ContainerKeys<S>, O> = O extends { default: infer X } ? (TypeOfService<S, K> | X) :
27 26 O extends { optional: true } ? (TypeOfService<S, K> | undefined) :
28 27 TypeOfService<S, K>;
29 28
30 29 export interface Resolver<S extends object> {
31 30 <K extends ContainerKeys<S>, O extends LazyDependencyOptions>(this: void, name: K, opts: O): () => InferReferenceType<S, K, O>;
32 31 <K extends ContainerKeys<S>, O extends DependencyOptions>(this: void, name: K, opts?: O): InferReferenceType<S, K, O>;
33 32 }
34 33
35 34 export interface DescriptorBuilder<S extends object, T> {
36 35 factory(f: (resolve: Resolver<S>) => T): void;
37 36
38 37 build<T2>(): DescriptorBuilder<S, T2>;
39 38
40 39 override<K extends keyof S>(name: K, builder: RegistrationBuilder<S, S[K]>): this;
41 40 override<K extends keyof S>(services: { [name in K]: RegistrationBuilder<S, S[K]> }): this;
42 41
43 42 lifetime(lifetime: "singleton", typeId: any): this;
44 43 lifetime(lifetime: ILifetime | Exclude<ActivationType, "singleton">): this;
45 44
46 45 cleanup(cb: (item: T) => void): this;
47 46
48 47 value(v: T): void;
49 48 }
50 49
51 50 export interface ContainerConfiguration<S extends object> {
52 apply<S2 extends object>(target: Container<S2>, ct: ICancellation): Promise<Container<S2 & S>>;
51 apply<S2 extends object>(target: ServiceContainer<S2>, ct?: ICancellation): Promise<ServiceContainer<S2 & S>>;
53 52 }
54 53
55 54 export type RegistrationBuilder<S extends object, T> = (d: DescriptorBuilder<S, T>, ct?: ICancellation) => void | Promise<void>;
56 55
57 56 export type FluentRegistrations<K extends keyof S, S extends object> = { [k in K]: RegistrationBuilder<S, S[k]> };
@@ -1,53 +1,62
1 1 import { ActivationContext } from "./ActivationContext";
2 import { LifetimeManager } from "./LifetimeManager";
2 3
3 4 export interface Descriptor<S extends object = any, T = any> {
4 5 activate(context: ActivationContext<S>): T;
5 6 }
6 7
7 8 export type ServiceMap<S extends object> = {
8 9 [k in keyof S]: Descriptor<S, S[k]>;
9 10 };
10 11
11 12 export type ContainerKeys<S extends object> = keyof S | keyof ContainerProvided<S>;
12 13
13 14 export type TypeOfService<S extends object, K> =
14 15 K extends keyof ContainerProvided<S> ? ContainerProvided<S>[K] :
15 16 K extends keyof S ? S[K] : never;
16 17
17 18 export type ContainerServiceMap<S extends object> = {
18 19 [K in ContainerKeys<S>]: Descriptor<S, TypeOfService<S, K>>;
19 20 };
20 21
21 22 export type PartialServiceMap<S extends object> = {
22 23 [k in keyof S]?: Descriptor<S, S[k]>;
23 24 };
24 25
25 26 export interface ServiceLocator<S extends object> {
26 27 resolve<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>): TypeOfService<S, K>;
27 28 }
28 29
30 export interface ServiceContainer<S extends object> extends ServiceLocator<S> {
31 getLifetimeManager(): LifetimeManager;
32 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>): this;
33 register(services: PartialServiceMap<S>): this;
34
35 createChildContainer(): ServiceContainer<S>;
36 }
37
29 38 export interface ContainerProvided<S extends object> {
30 39 container: ServiceLocator<S>;
31 40 }
32 41
33 42 export type ContainerRegistered<S extends object> = /*{
34 43 [K in Exclude<keyof S, keyof ContainerProvided<S>>]: S[K];
35 44 };*/
36 45 Exclude<S, ContainerProvided<S>>;
37 46
38 47 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
39 48
40 49 /**
41 50 * Π˜Π½Ρ‚Π΅Ρ€Ρ„Π΅ΠΉΡ для управлСния Тизнью экзСмпляра ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π°. КаТдая рСгистрация ΠΈΠΌΠ΅Π΅Ρ‚
42 51 * свой собствСнный ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ `ILifetime`, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ создаСтся ΠΏΡ€ΠΈ ΠΏΠ΅Ρ€Π²ΠΎΠΉ Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ
43 52 */
44 53 export interface ILifetime {
45 54 /** ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅Ρ‚, Ρ‡Ρ‚ΠΎ ΡƒΠΆΠ΅ создан экзСмпляр ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π° */
46 55 has(): boolean;
47 56
48 57 get(): any;
49 58
50 59 initialize(context: ActivationContext<any>): void;
51 60
52 61 store(item: any, cleanup?: (item: any) => void): void;
53 62 }
@@ -1,22 +1,26
1 1 import { Foo } from "./Foo";
2 2 import { Box } from "./Box";
3 3 import { Bar } from "./Bar";
4 4
5 5 /**
6 6 * БСрвисы доступныС Π²Π½ΡƒΡ‚Ρ€ΠΈ ΠΊΠΎΠ½Ρ‚Π΅ΠΉΠ½Π΅Ρ€Π°
7 7 */
8 8 export interface Services {
9 9 foo: Foo;
10 10
11 11 box: Box<Foo>;
12 12
13 13 host: string;
14 14
15 15 }
16 16
17 17 export interface ChildServices extends Services {
18 18
19 19 foo2?: Foo;
20 20
21 21 bar: Bar;
22 22 }
23
24 export interface FooServices {
25 foo: Foo;
26 }
@@ -1,69 +1,80
1 1 import { test } from "./TestTraits";
2 2 import { fluent } from "../di/traits";
3 3 import { Bar } from "../mock/Bar";
4 4 import { Container } from "../di/Container";
5 5 import { Foo } from "../mock/Foo";
6 6 import { Box } from "../mock/Box";
7 7 import { delay } from "../safe";
8 import { Services } from "../mock/services";
8 import { FooServices, Services } from "../mock/services";
9 import { ContainerConfiguration } from "../di/fluent/interfaces";
9 10
10 11 test("Simple fluent config", async t => {
11 12 const config = fluent<{ host: string; bar: Bar; foo: Foo }>()
12 13 .register({
13 14 host: it => it.value("example.com"),
14 15 bar: it => it.factory(resolve => new Bar({ host: resolve("host") }, "s-bar")),
15 16 foo: it => import("../mock/Foo").then(m => it.lifetime("container").factory(() => new m.Foo()))
16 17 });
17 18
18 19 const c1 = new Container<{}>();
19 20 const container = await config.apply(c1);
20 21
21 22 t.equal(container.resolve("host"), "example.com", "The value should be resolved");
22 23 t.assert(container.resolve("bar"), "The service should de activated");
23 24 t.equal(container.resolve("foo"), container.resolve("foo"), "The service should be activated once");
24 25 });
25 26
26 27 test("Nested async configuration", async t => {
27 28 const container = await new Container<{
28 29 foo: Foo;
29 30 box: Box<Foo>
30 31 }>().fluent({
31 32 foo: it => delay(0).then(() => it.factory(() => new Foo())),
32 33 box: it => it.lifetime("context").factory($dependency => new Box($dependency("foo")))
33 34 });
34 35
35 36 t.assert(container.resolve("box").getValue(), "The dependency should be set");
36 37 t.equals(container.resolve("box").getValue(), container.resolve("box").getValue(), "The service should be activated once")
37 38 });
38 39
39 40 test("Bad fluent config", async t => {
40 41 try {
41 42 await new Container<{
42 43 foo: Foo;
43 44 box: Box<Foo>
44 45 }>().fluent({
45 46 foo: it => delay(0).then(() => it.factory(() => new Foo())),
46 47 box: it => it.lifetime("context")
47 48 .override("foo", () => { throw new Error("bad override"); })
48 49 .factory($dependency => new Box($dependency("foo")))
49 50 });
50 51 t.fail("Should throw");
51 52 } catch (e) {
52 53 t.pass("The configuration should fail");
53 54 t.equal(e.message, "bad override", "the error should pass");
54 55 }
55 56 });
56 57
57 58 test("Load fluent config", async t => {
58 59 const container = new Container<Services>();
59 60
60 61 await container.configure("../mock/config", { contextRequire: require });
61 62
62 63 t.assert(container.resolve("host"), "Should resolve simple value");
63 64 });
64 65
65 66 test("Container applyConfig", async t => {
66 67 const container = await new Container<{}>().applyConfig(import("../mock/config"));
67 68
68 69 t.assert(container.resolve("host"), "Should resolve simple value");
69 70 });
71
72 test("Child container config", async t => {
73 const container = await new Container<{}>().applyConfig(import("../mock/config"));
74
75 const fooServices: ContainerConfiguration<FooServices> = (await import("../mock/config2")).default;
76
77 const child = await fooServices.apply(container.createChildContainer());
78
79 t.assert(child.resolve("foo"), "foo should be resolved");
80 });
General Comments 0
You need to be logged in to leave comments. Login now