##// END OF EJS Templates
tests
cin -
r136:17c7de5d346c ioc ts support
parent child
Show More
@@ -1,17 +1,28
1 1 <?xml version="1.0" encoding="UTF-8"?>
2 2 <projectDescription>
3 3 <name>core</name>
4 4 <comment>Project implabjs-core created by Buildship.</comment>
5 5 <projects>
6 6 </projects>
7 7 <buildSpec>
8 8 <buildCommand>
9 9 <name>org.eclipse.buildship.core.gradleprojectbuilder</name>
10 10 <arguments>
11 11 </arguments>
12 12 </buildCommand>
13 13 </buildSpec>
14 14 <natures>
15 15 <nature>org.eclipse.buildship.core.gradleprojectnature</nature>
16 16 </natures>
17 <filteredResources>
18 <filter>
19 <id>1599549685358</id>
20 <name></name>
21 <type>30</type>
22 <matcher>
23 <id>org.eclipse.core.resources.regexFilterMatcher</id>
24 <arguments>node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
25 </matcher>
26 </filter>
27 </filteredResources>
17 28 </projectDescription>
@@ -1,450 +1,451
1 1 import {
2 2 PartialServiceMap,
3 3 ActivationType,
4 4 ContainerKeys,
5 5 TypeOfService,
6 6 ILifetime
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 12 import { Container } from "./Container";
13 13 import { ReferenceDescriptor } from "./ReferenceDescriptor";
14 14 import { TypeServiceDescriptor } from "./TypeServiceDescriptor";
15 15 import { FactoryServiceDescriptor } from "./FactoryServiceDescriptor";
16 16 import { TraceSource } from "../log/TraceSource";
17 17 import { ConfigError } from "./ConfigError";
18 18 import { Cancellation } from "../Cancellation";
19 19 import { makeResolver } from "./ResolverHelper";
20 20 import { ICancellation } from "../interfaces";
21 21 import { isDescriptor } from "./traits";
22 22 import { LazyReferenceDescriptor } from "./LazyReferenceDescriptor";
23 23 import { LifetimeManager } from "./LifetimeManager";
24 24
25 25 export interface RegistrationScope<S extends object> {
26 26
27 27 /** сервисы, которые регистрируются в контексте активации и таким образом
28 28 * могут переопределять ранее зарегистрированные сервисы. за это свойство
29 29 * нужно платить, кроме того порядок активации будет влиять на результат
30 30 * разрешения зависимостей.
31 31 */
32 32 services?: RegistrationMap<S>;
33 33 }
34 34
35 35 /**
36 36 * Базовый интерфейс конфигурации сервисов
37 37 */
38 38 export interface ServiceRegistration<T, S extends object> extends RegistrationScope<S> {
39 39
40 40 activation?: ActivationType;
41 41
42 42 params?: any;
43 43
44 44 /** Специальный идентификатор используется при активации singleton, если
45 45 * не указан для TypeRegistration вычисляется как oid($type)
46 46 */
47 47 typeId?: string;
48 48
49 49 inject?: object | object[];
50 50
51 51 cleanup?: ((instance: T) => void) | string;
52 52 }
53 53
54 54 export interface TypeRegistration<C extends new (...args: any[]) => any, S extends object> extends ServiceRegistration<InstanceType<C>, S> {
55 55 $type: string | C;
56 56 params?: Registration<ConstructorParameters<C>, S>;
57 57 }
58 58
59 59 export interface StrictTypeRegistration<C extends new (...args: any[]) => any, S extends object> extends ServiceRegistration<InstanceType<C>, S> {
60 60 $type: C;
61 61 params?: Registration<ConstructorParameters<C>, S>;
62 62 }
63 63
64 64 export interface FactoryRegistration<F extends (...args: any[]) => any, S extends object> extends ServiceRegistration<ReturnType<F>, S> {
65 65 $factory: string | F;
66 66 }
67 67
68 68 export interface ValueRegistration<T> {
69 69 $value: T;
70 70 parse?: boolean;
71 71 }
72 72
73 73 export interface DependencyRegistration<S extends object, K extends ContainerKeys<S> = ContainerKeys<S>> extends RegistrationScope<S> {
74 74 $dependency: K;
75 75 lazy?: boolean;
76 76 optional?: boolean;
77 77 default?: TypeOfService<S, K>;
78 78 }
79 79
80 80 export interface LazyDependencyRegistration<S extends object, K extends ContainerKeys<S> = ContainerKeys<S>> extends DependencyRegistration<S, K> {
81 81 lazy: true;
82 82 }
83 83
84 84 export type Registration<T, S extends object> = T extends primitive ? T :
85 85 (
86 86 T |
87 87 { [k in keyof T]: Registration<T[k], S> } |
88 88 TypeRegistration<new (...args: any[]) => T, S> |
89 89 FactoryRegistration<(...args: any[]) => T, S> |
90 90 ValueRegistration<any> |
91 91 DependencyRegistration<S, keyof S>
92 92 );
93 93
94 94 export type RegistrationMap<S extends object> = {
95 95 [k in keyof S]?: Registration<S[k], S>;
96 96 };
97 97
98 98 const _activationTypes: { [k in ActivationType]: number; } = {
99 99 singleton: 1,
100 100 container: 2,
101 101 hierarchy: 3,
102 102 context: 4,
103 103 call: 5
104 104 };
105 105
106 106 export function isTypeRegistration(x: any): x is TypeRegistration<new () => any, any> {
107 107 return (!isPrimitive(x)) && ("$type" in x);
108 108 }
109 109
110 110 export function isFactoryRegistration(x: any): x is FactoryRegistration<() => any, any> {
111 111 return (!isPrimitive(x)) && ("$factory" in x);
112 112 }
113 113
114 114 export function isValueRegistration(x: any): x is ValueRegistration<any> {
115 115 return (!isPrimitive(x)) && ("$value" in x);
116 116 }
117 117
118 118 export function isDependencyRegistration<S extends object>(x: any): x is DependencyRegistration<S, keyof S> {
119 119 return (!isPrimitive(x)) && ("$dependency" in x);
120 120 }
121 121
122 122 export function isActivationType(x: string): x is ActivationType {
123 123 return typeof x === "string" && x in _activationTypes;
124 124 }
125 125
126 126 const trace = TraceSource.get("@implab/core/di/Configuration");
127 127 async function mapAll(data: any[], map?: (v: any, k: number) => any): Promise<any[]>;
128 128 async function mapAll(data: any, map?: (v: any, k: string) => any): Promise<any>;
129 129 async function mapAll(data: any, map?: (v: any, k: any) => any): Promise<any> {
130 130 if (data instanceof Array) {
131 131 return Promise.all(map ? data.map(map) : data);
132 132 } else {
133 133 const keys = Object.keys(data);
134 134
135 135 const o: any = {};
136 136
137 137 await Promise.all(keys.map(async k => {
138 138 const v = map ? map(data[k], k) : data[k];
139 139 o[k] = isPromise(v) ? await v : v;
140 140 }));
141 141
142 142 return o;
143 143 }
144 144 }
145 145
146 146 export type ModuleResolver = (moduleName: string, ct?: ICancellation) => any;
147 147
148 148 export class Configuration<S extends object> {
149 149
150 150 _hasInnerDescriptors = false;
151 151
152 152 readonly _container: Container<S>;
153 153
154 154 _path: Array<string>;
155 155
156 156 _configName: string | undefined;
157 157
158 158 _require: ModuleResolver | undefined;
159 159
160 160 constructor(container: Container<S>) {
161 161 argumentNotNull(container, "container");
162 162 this._container = container;
163 163 this._path = [];
164 164 }
165 165
166 166 async loadConfiguration(moduleName: string, contextRequire?: any, ct = Cancellation.none) {
167 167 argumentNotEmptyString(moduleName, "moduleName");
168 168
169 169 trace.log(
170 170 "loadConfiguration moduleName={0}, contextRequire={1}",
171 171 moduleName,
172 172 contextRequire ? typeof (contextRequire) : "<nil>"
173 173 );
174 174
175 175 this._configName = moduleName;
176 176
177 177 const r = await makeResolver(undefined, contextRequire);
178 178
179 179 const config = await r(moduleName, ct);
180 180
181 181 await this._applyConfiguration(
182 182 config,
183 183 await makeResolver(moduleName, contextRequire),
184 184 ct
185 185 );
186 186 }
187 187
188 async applyConfiguration(data: RegistrationMap<S>, contextRequire?: any, ct = Cancellation.none) {
188 async applyConfiguration(data: RegistrationMap<S>, opts: { contextRequire?: any; baseModule?: string }, ct = Cancellation.none) {
189 189 argumentNotNull(data, "data");
190 const _opts = opts || {};
190 191
191 await this._applyConfiguration(data, await makeResolver(void (0), contextRequire), ct);
192 await this._applyConfiguration(data, await makeResolver(_opts.baseModule, _opts.contextRequire), ct);
192 193 }
193 194
194 195 async _applyConfiguration(data: RegistrationMap<S>, resolver?: ModuleResolver, ct = Cancellation.none) {
195 196 trace.log("applyConfiguration");
196 197
197 198 this._configName = "$";
198 199
199 200 if (resolver)
200 201 this._require = resolver;
201 202
202 203 let services: PartialServiceMap<S>;
203 204
204 205 try {
205 206 services = await this._visitRegistrations(data, "$");
206 207 } catch (e) {
207 208 throw this._makeError(e);
208 209 }
209 210
210 211 this._container.register(services);
211 212 }
212 213
213 214 _makeError(inner: any) {
214 215 const e = new ConfigError("Failed to load configuration", inner);
215 216 e.configName = this._configName || "<inline>";
216 217 e.path = this._makePath();
217 218 return e;
218 219 }
219 220
220 221 _makePath() {
221 222 return this._path
222 223 .reduce(
223 224 (prev, cur) => typeof cur === "number" ?
224 225 `${prev}[${cur}]` :
225 226 `${prev}.${cur}`
226 227 )
227 228 .toString();
228 229 }
229 230
230 231 async _resolveType(moduleName: string, localName: string) {
231 232 trace.log("resolveType moduleName={0}, localName={1}", moduleName, localName);
232 233 try {
233 234 const m = await this._loadModule(moduleName);
234 235 if (localName) {
235 236 return get(localName, m);
236 237 } else {
237 238 if (m instanceof Function)
238 239 return m;
239 240 if ("default" in m)
240 241 return m.default;
241 242 return m;
242 243 }
243 244 } catch (e) {
244 245 trace.error("Failed to resolve type moduleName={0}, localName={1}", moduleName, localName);
245 246 throw e;
246 247 }
247 248 }
248 249
249 250 _loadModule(moduleName: string) {
250 251 trace.debug("loadModule {0}", moduleName);
251 252 if (!this._require)
252 253 throw new Error("Module loader isn't specified");
253 254
254 255 return this._require(moduleName);
255 256 }
256 257
257 258 async _visitRegistrations(data: RegistrationMap<S>, name: string) {
258 259 this._enter(name);
259 260
260 261 if (data.constructor &&
261 262 data.constructor.prototype !== Object.prototype)
262 263 throw new Error("Configuration must be a simple object");
263 264
264 265 const services = await mapAll(data, async (v, k) => {
265 266 const d = await this._visit(v, k.toString());
266 267 return isDescriptor(d) ? d : new AggregateDescriptor(d);
267 268 }) as PartialServiceMap<S>;
268 269
269 270 this._leave();
270 271
271 272 return services;
272 273 }
273 274
274 275 _enter(name: string) {
275 276 this._path.push(name.toString());
276 277 trace.debug(">{0}", name);
277 278 }
278 279
279 280 _leave() {
280 281 const name = this._path.pop();
281 282 trace.debug("<{0}", name);
282 283 }
283 284
284 285 _visit(data: any, name: string): Promise<any> {
285 286 if (isPrimitive(data))
286 287 return Promise.resolve(new ValueDescriptor(data));
287 288 if (isDescriptor(data))
288 289 return Promise.resolve(data);
289 290
290 291 if (isDependencyRegistration<S>(data)) {
291 292 return this._visitDependencyRegistration(data, name);
292 293 } else if (isValueRegistration(data)) {
293 294 return this._visitValueRegistration(data, name);
294 295 } else if (isTypeRegistration(data)) {
295 296 return this._visitTypeRegistration(data, name);
296 297 } else if (isFactoryRegistration(data)) {
297 298 return this._visitFactoryRegistration(data, name);
298 299 } else if (data instanceof Array) {
299 300 return this._visitArray(data, name);
300 301 }
301 302
302 303 return this._visitObject(data, name);
303 304 }
304 305
305 306 async _visitObject(data: any, name: string) {
306 307 if (data.constructor &&
307 308 data.constructor.prototype !== Object.prototype)
308 309 return new ValueDescriptor(data);
309 310
310 311 this._enter(name);
311 312
312 313 const v = await mapAll(data, delegate(this, "_visit"));
313 314
314 315 // TODO: handle inline descriptors properly
315 316 // const ex = {
316 317 // activate(ctx) {
317 318 // const value = ctx.activate(this.prop, "prop");
318 319 // // some code
319 320 // },
320 321 // // will be turned to ReferenceDescriptor
321 322 // prop: { $dependency: "depName" }
322 323 // };
323 324
324 325 this._leave();
325 326 return v;
326 327 }
327 328
328 329 async _visitArray(data: any[], name: string) {
329 330 if (data.constructor &&
330 331 data.constructor.prototype !== Array.prototype)
331 332 return new ValueDescriptor(data);
332 333
333 334 this._enter(name);
334 335
335 336 const v = await mapAll(data, delegate(this, "_visit"));
336 337 this._leave();
337 338
338 339 return v;
339 340 }
340 341
341 342 _makeServiceParams(data: ServiceRegistration<any, S>) {
342 343 const opts: any = {
343 344 };
344 345 if (data.services)
345 346 opts.services = this._visitRegistrations(data.services, "services");
346 347
347 348 if (data.inject) {
348 349 this._enter("inject");
349 350 opts.inject = mapAll(
350 351 data.inject instanceof Array ?
351 352 data.inject :
352 353 [data.inject],
353 354 delegate(this, "_visitObject")
354 355 );
355 356 this._leave();
356 357 }
357 358
358 359 if ("params" in data)
359 360 opts.params = data.params instanceof Array ?
360 361 this._visitArray(data.params, "params") :
361 362 this._visit(data.params, "params");
362 363
363 364 if (data.activation) {
364 365 opts.activation = this._getLifetimeManager(data.activation, data.typeId);
365 366 }
366 367
367 368 if (data.cleanup)
368 369 opts.cleanup = data.cleanup;
369 370
370 371 return opts;
371 372 }
372 373
373 374 async _visitValueRegistration<T>(data: ValueRegistration<T>, name: string) {
374 375 this._enter(name);
375 376 const d = data.parse ? new AggregateDescriptor(data.$value) : new ValueDescriptor(data.$value);
376 377 this._leave();
377 378 return d;
378 379 }
379 380
380 381 async _visitDependencyRegistration<K extends keyof S>(data: DependencyRegistration<S, K>, name: string) {
381 382 argumentNotEmptyString(data && data.$dependency, "data.$dependency");
382 383 this._enter(name);
383 384 const options = {
384 385 name: data.$dependency,
385 386 optional: data.optional,
386 387 default: data.default,
387 388 services: data.services && await this._visitRegistrations(data.services, "services")
388 389 };
389 390 const d = data.lazy ? new LazyReferenceDescriptor<S, K>(options) : new ReferenceDescriptor<S, K>(options);
390 391 this._leave();
391 392 return d;
392 393 }
393 394
394 395 async _visitTypeRegistration(data: TypeRegistration<new () => any, S>, name: string) {
395 396 argumentNotNull(data.$type, "data.$type");
396 397 this._enter(name);
397 398
398 399 const opts = this._makeServiceParams(data);
399 400 if (data.$type instanceof Function) {
400 401 opts.type = data.$type;
401 402 } else {
402 403 const [moduleName, typeName] = data.$type.split(":", 2);
403 404 opts.type = this._resolveType(moduleName, typeName).then(t => {
404 405 if (!(t instanceof Function))
405 406 throw Error("$type (" + data.$type + ") is not a constructable");
406 407 return t;
407 408 });
408 409 }
409 410
410 411 const d = new TypeServiceDescriptor<S, any, any[]>(
411 412 await mapAll(opts)
412 413 );
413 414
414 415 this._leave();
415 416
416 417 return d;
417 418 }
418 419
419 420 async _visitFactoryRegistration(data: FactoryRegistration<() => any, S>, name: string) {
420 421 argumentOfType(data.$factory, Function, "data.$factory");
421 422 this._enter(name);
422 423
423 424 const opts = this._makeServiceParams(data);
424 425 opts.factory = data.$factory;
425 426
426 427 const d = new FactoryServiceDescriptor<S, any, any[]>(
427 428 await mapAll(opts)
428 429 );
429 430
430 431 this._leave();
431 432 return d;
432 433 }
433 434
434 435 _getLifetimeManager(activation: ActivationType, typeId: string | undefined): ILifetime {
435 436 switch (activation) {
436 437 case "container":
437 438 return LifetimeManager.containerLifetime(this._container);
438 439 case "hierarchy":
439 440 return LifetimeManager.hierarchyLifetime();
440 441 case "context":
441 442 return LifetimeManager.contextLifetime();
442 443 case "singleton":
443 444 if (typeId === undefined)
444 445 throw Error("The singleton activation requires a typeId");
445 446 return LifetimeManager.singletonLifetime(typeId);
446 447 default:
447 448 return LifetimeManager.empty();
448 449 }
449 450 }
450 451 }
@@ -1,139 +1,149
1 1 import { ActivationContext } from "./ActivationContext";
2 2 import { ValueDescriptor } from "./ValueDescriptor";
3 3 import { ActivationError } from "./ActivationError";
4 4 import { ServiceMap, Descriptor, PartialServiceMap, ServiceLocator, ContainerServiceMap, ContainerKeys, TypeOfService } from "./interfaces";
5 5 import { TraceSource } from "../log/TraceSource";
6 6 import { Configuration, RegistrationMap } from "./Configuration";
7 7 import { Cancellation } from "../Cancellation";
8 8 import { MapOf, IDestroyable } from "../interfaces";
9 9 import { isDescriptor } from "./traits";
10 10 import { LifetimeManager } from "./LifetimeManager";
11 11 import { each } from "../safe";
12 12 import { 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 17 export class Container<S extends object = any> implements ServiceLocator<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 94 onDispose(callback: () => void) {
95 95 if (!(callback instanceof Function))
96 96 throw new Error("The callback must be a function");
97 97 this._cleanup.push(callback);
98 98 }
99 99
100 100 destroy() {
101 101 return this.dispose();
102 102 }
103 103 dispose() {
104 104 if (this._disposed)
105 105 return;
106 106 this._disposed = true;
107 107 for (const f of this._cleanup)
108 108 f();
109 109 }
110 110
111 111 /**
112 112 * @param{String|Object} config
113 * The configuration of the contaier. Can be either a string or an object,
113 * The configuration of the container. Can be either a string or an object,
114 114 * if the configuration is an object it's treated as a collection of
115 * services which will be registed in the contaier.
115 * services which will be registered in the container.
116 116 *
117 117 * @param{Function} opts.contextRequire
118 118 * The function which will be used to load a configuration or types for services.
119 119 *
120 120 */
121 async configure(config: string | RegistrationMap<S>, opts?: any, ct = Cancellation.none) {
122 const c = new Configuration<S>(this);
121 async configure(config: string | RegistrationMap<S>, opts?: { contextRequire: any; baseModule?: string }, ct = Cancellation.none) {
122 const _opts = Object.create(opts || null);
123 123
124 124 if (typeof (config) === "string") {
125 return c.loadConfiguration(config, opts && opts.contextRequire, ct);
125 _opts.baseModule = config;
126
127 const module = await import(config);
128 if (module && module.default && typeof (module.default.apply) === "function")
129 return module.default.apply(this);
130 else
131 return this._applyLegacyConfig(module, _opts, ct);
126 132 } else {
127 return c.applyConfiguration(config, opts && opts.contextRequire, ct);
133 return this._applyLegacyConfig(config, _opts, ct);
128 134 }
129 135 }
130 136
137 async _applyLegacyConfig(config: RegistrationMap<S>, opts: { contextRequire: any; baseModule?: string }, ct = Cancellation.none) {
138 return new Configuration<S>(this).applyConfiguration(config, opts);
139 }
140
131 141 async fluent<K extends keyof S>(config: FluentRegistrations<K, S>, ct = Cancellation.none): Promise<this> {
132 142 await new FluentConfiguration<S>().register(config).apply(this, ct);
133 143 return this;
134 144 }
135 145
136 146 createChildContainer<S2 extends object = S>(): Container<S & S2> {
137 147 return new Container<S & S2>(this as any);
138 148 }
139 149 }
@@ -1,58 +1,60
1 1 import { Container } from "../Container";
2 2 import { argumentNotNull, each, isPrimitive, isPromise } from "../../safe";
3 3 import { DescriptorBuilder } from "./DescriptorBuilder";
4 4 import { RegistrationBuilder, FluentRegistrations } from "./interfaces";
5 5 import { Cancellation } from "../../Cancellation";
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 register<K extends Y>(name: K, builder: RegistrationBuilder<S, S[K]>): FluentConfiguration<S, Exclude<Y, K>>;
12 12 register<K extends Y>(config: FluentRegistrations<K, S>): FluentConfiguration<S, Exclude<Y, K>>;
13 13 register<K extends Y>(nameOrConfig: K | FluentRegistrations<K, S>, builder?: RegistrationBuilder<S, S[K]>): FluentConfiguration<S, Exclude<Y, K>> {
14 14 if (isPrimitive(nameOrConfig)) {
15 15 argumentNotNull(builder, "builder");
16 16 this._builders[nameOrConfig] = builder;
17 17 } else {
18 18 each(nameOrConfig, (v, k) => this.register(k, v));
19 19 }
20 20
21 21 return this;
22 22 }
23 23
24 apply(target: Container<S>, ct = Cancellation.none) {
24 apply<SC extends object>(target: Container<SC>, ct = Cancellation.none) {
25 25
26 26 let pending = 1;
27 27
28 return new Promise((resolve, reject) => {
28 const _t2 = target as unknown as Container<SC & S>;
29
30 return new Promise<Container<SC & S>>((resolve, reject) => {
29 31 function guard(v: void | Promise<void>) {
30 32 if (isPromise(v))
31 33 v.catch(reject);
32 34 }
33 35
34 36 function complete() {
35 37 if (!--pending)
36 resolve();
38 resolve(_t2);
37 39 }
38 40 each(this._builders, (v, k) => {
39 41 pending++;
40 const d = new DescriptorBuilder<S, any>(target,
42 const d = new DescriptorBuilder<SC & S, any>(_t2,
41 43 result => {
42 target.register(k, result);
44 _t2.register(k, result);
43 45 complete();
44 46 },
45 47 reject
46 48 );
47 49
48 50 try {
49 51 guard(v(d, ct));
50 52 } catch (e) {
51 53 reject(e);
52 54 }
53 55 });
54 56 complete();
55 57 });
56 58 }
57 59
58 60 }
@@ -1,26 +1,14
1 1 import { Services } from "./services";
2 2 import { fluent } from "../di/traits";
3 import { Box } from "./Box";
3 4
4 5 export default fluent<Services>().register({
5 6 host: it => it.value("example.com"),
6 7
7 bar2: it => Promise.all([import("./Foo"), import("./Bar")])
8 .then(([{ Foo }, { Bar }]) => it
9 .lifetime("container")
10 .override({
11 host: it2 => it2.value("simple.org"),
12 foo: it2 => it2.value(new Foo())
13 })
14 .factory(resolve => {
15 const bar = new Bar({
16 foo: new Foo(),
17 nested: {
18 lazy: resolve("foo", { lazy: true })
19 },
20 host: resolve("host")
21 }, "some text");
22 bar.setName(resolve("host"));
23 return bar;
24 })
25 )
8 foo: it => import("./Foo").then(({ Foo }) => it
9 .factory(() => new Foo())
10 ),
11
12 box: it => it
13 .factory($dependency => new Box($dependency("foo")))
26 14 });
@@ -1,19 +1,15
1 1 import { Foo } from "./Foo";
2 2 import { Bar } from "./Bar";
3 3 import { Box } from "./Box";
4 4
5 5 /**
6 6 * Сервисы доступные внутри контейнера
7 7 */
8 8 export interface Services {
9 9 foo: Foo;
10 10
11 bar: Bar;
12
13 bar2: Bar;
14
15 box: Box<Bar>;
11 box: Box<Foo>;
16 12
17 13 host: string;
18 14
19 15 }
@@ -1,54 +1,63
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 9
9 10 test("Simple fluent config", async t => {
10 11 const config = fluent<{ host: string; bar: Bar; foo: Foo }>()
11 12 .register({
12 13 host: it => it.value("example.com"),
13 14 bar: it => it.factory(resolve => new Bar({ host: resolve("host") }, "s-bar")),
14 15 foo: it => import("../mock/Foo").then(m => it.lifetime("container").factory(() => new m.Foo()))
15 16 });
16 17
17 const container = new Container<{ host: string; bar: Bar; foo: Foo; }>();
18 await config.apply(container);
18 const c1 = new Container<{}>();
19 const container = await config.apply(c1);
19 20
20 21 t.equal(container.resolve("host"), "example.com", "The value should be resolved");
21 22 t.assert(container.resolve("bar"), "The service should de activated");
22 23 t.equal(container.resolve("foo"), container.resolve("foo"), "The service should be activated once");
23 24 });
24 25
25 26 test("Nested async configuration", async t => {
26 27 const container = await new Container<{
27 28 foo: Foo;
28 29 box: Box<Foo>
29 30 }>().fluent({
30 31 foo: it => delay(0).then(() => it.factory(() => new Foo())),
31 32 box: it => it.lifetime("context").factory($dependency => new Box($dependency("foo")))
32 33 });
33 34
34 35 t.assert(container.resolve("box").getValue(), "The dependency should be set");
35 36 t.equals(container.resolve("box").getValue(), container.resolve("box").getValue(), "The service should be activated once")
36 37 });
37 38
38 39 test("Bad fluent config", async t => {
39 40 try {
40 41 await new Container<{
41 42 foo: Foo;
42 43 box: Box<Foo>
43 44 }>().fluent({
44 45 foo: it => delay(0).then(() => it.factory(() => new Foo())),
45 46 box: it => it.lifetime("context")
46 47 .override("foo", () => { throw new Error("bad override"); })
47 48 .factory($dependency => new Box($dependency("foo")))
48 49 });
49 50 t.fail("Should throw");
50 51 } catch (e) {
51 52 t.pass("The configuration should fail");
52 53 t.equal(e.message, "bad override", "the error should pass");
53 54 }
54 55 });
56
57 test("Load fluent config", async t => {
58 const container = new Container<Services>();
59
60 await container.configure("../mock/config", { contextRequire: require });
61
62 t.assert(container.resolve("host"), "Should resolve simple value");
63 });
@@ -1,7 +1,8
1 1 import "./ActivatableTests";
2 2 import "./TraceSourceTests";
3 3 import "./CancellationTests";
4 4 import "./ObservableTests";
5 5 import "./ContainerTests";
6 6 import "./SafeTests";
7 7 import "./TextTests";
8 import "./FluentContainerTests";
General Comments 0
You need to be logged in to leave comments. Login now