| @@ -0,0 +1,63 | |||
|
|
1 | import { Descriptor, PartialServiceMap, ILifetime, ContainerKeys } from "../interfaces"; | |
|
|
2 | import { ActivationContext } from "../ActivationContext"; | |
|
|
3 | import { each } from "../../safe"; | |
|
|
4 | import { DependencyOptions, LazyDependencyOptions, Resolver } from "./interfaces"; | |
|
|
5 | ||
|
|
6 | export interface DescriptorImplArgs<S extends object, T> { | |
|
|
7 | lifetime: ILifetime; | |
|
|
8 | ||
|
|
9 | factory: (resolve: Resolver<S>) => T; | |
|
|
10 | ||
|
|
11 | cleanup?: (item: T) => void; | |
|
|
12 | ||
|
|
13 | overrides?: PartialServiceMap<S>; | |
|
|
14 | } | |
|
|
15 | ||
|
|
16 | export class DescriptorImpl<S extends object, T> implements Descriptor<S, T> { | |
|
|
17 | ||
|
|
18 | private readonly _overrides?: PartialServiceMap<S>; | |
|
|
19 | ||
|
|
20 | private readonly _lifetime: ILifetime; | |
|
|
21 | ||
|
|
22 | private readonly _factory: (resolve: Resolver<S>) => T; | |
|
|
23 | ||
|
|
24 | private readonly _cleanup?: (item: T) => void; | |
|
|
25 | ||
|
|
26 | constructor(args: DescriptorImplArgs<S, T>) { | |
|
|
27 | this._lifetime = args.lifetime; | |
|
|
28 | this._factory = args.factory; | |
|
|
29 | if (args.cleanup) | |
|
|
30 | this._cleanup = args.cleanup; | |
|
|
31 | if (args.overrides) | |
|
|
32 | this._overrides = args.overrides; | |
|
|
33 | } | |
|
|
34 | ||
|
|
35 | activate(context: ActivationContext<S>): T { | |
|
|
36 | ||
|
|
37 | if (this._lifetime.has()) | |
|
|
38 | return this._lifetime.get(); | |
|
|
39 | ||
|
|
40 | this._lifetime.initialize(context); | |
|
|
41 | ||
|
|
42 | if (this._overrides) | |
|
|
43 | each(this._overrides, (v, k) => context.register(k, v)); | |
|
|
44 | ||
|
|
45 | const resolve = (name: ContainerKeys<S>, opts?: DependencyOptions | LazyDependencyOptions) => { | |
|
|
46 | if (opts && "lazy" in opts && opts.lazy) { | |
|
|
47 | const c2 = context.clone(); | |
|
|
48 | return () => { | |
|
|
49 | return opts.optional ? c2.resolve(name, opts.default) : c2.resolve(name); | |
|
|
50 | }; | |
|
|
51 | } else { | |
|
|
52 | return opts && opts.optional ? context.resolve(name, opts.default) : context.resolve(name); | |
|
|
53 | } | |
|
|
54 | }; | |
|
|
55 | ||
|
|
56 | const instance = this._factory.call(undefined, resolve); | |
|
|
57 | ||
|
|
58 | this._lifetime.store(instance, this._cleanup); | |
|
|
59 | ||
|
|
60 | return instance; | |
|
|
61 | } | |
|
|
62 | ||
|
|
63 | } | |
| @@ -0,0 +1,58 | |||
|
|
1 | import { Container } from "../Container"; | |
|
|
2 | import { argumentNotNull, each, isPrimitive, isPromise } from "../../safe"; | |
|
|
3 | import { DescriptorBuilder } from "./DescriptorBuilder"; | |
|
|
4 | import { RegistrationBuilder, FluentRegistrations } from "./interfaces"; | |
|
|
5 | import { Cancellation } from "../../Cancellation"; | |
|
|
6 | ||
|
|
7 | export class FluentConfiguration<S extends object, Y extends keyof S = keyof S> { | |
|
|
8 | ||
|
|
9 | _builders: { [k in keyof S]?: RegistrationBuilder<S, S[k]> } = {}; | |
|
|
10 | ||
|
|
11 | register<K extends Y>(name: K, builder: RegistrationBuilder<S, S[K]>): FluentConfiguration<S, Exclude<Y, K>>; | |
|
|
12 | register<K extends Y>(config: FluentRegistrations<K, S>): FluentConfiguration<S, Exclude<Y, K>>; | |
|
|
13 | register<K extends Y>(nameOrConfig: K | FluentRegistrations<K, S>, builder?: RegistrationBuilder<S, S[K]>): FluentConfiguration<S, Exclude<Y, K>> { | |
|
|
14 | if (isPrimitive(nameOrConfig)) { | |
|
|
15 | argumentNotNull(builder, "builder"); | |
|
|
16 | this._builders[nameOrConfig] = builder; | |
|
|
17 | } else { | |
|
|
18 | each(nameOrConfig, (v, k) => this.register(k, v)); | |
|
|
19 | } | |
|
|
20 | ||
|
|
21 | return this; | |
|
|
22 | } | |
|
|
23 | ||
|
|
24 | apply(target: Container<S>, ct = Cancellation.none) { | |
|
|
25 | ||
|
|
26 | let pending = 1; | |
|
|
27 | ||
|
|
28 | return new Promise((resolve, reject) => { | |
|
|
29 | function guard(v: void | Promise<void>) { | |
|
|
30 | if (isPromise(v)) | |
|
|
31 | v.catch(reject); | |
|
|
32 | } | |
|
|
33 | ||
|
|
34 | function complete() { | |
|
|
35 | if (!--pending) | |
|
|
36 | resolve(); | |
|
|
37 | } | |
|
|
38 | each(this._builders, (v, k) => { | |
|
|
39 | pending++; | |
|
|
40 | const d = new DescriptorBuilder<S, any>(target, | |
|
|
41 | result => { | |
|
|
42 | target.register(k, result); | |
|
|
43 | complete(); | |
|
|
44 | }, | |
|
|
45 | reject | |
|
|
46 | ); | |
|
|
47 | ||
|
|
48 | try { | |
|
|
49 | guard(v(d, ct)); | |
|
|
50 | } catch (e) { | |
|
|
51 | reject(e); | |
|
|
52 | } | |
|
|
53 | }); | |
|
|
54 | complete(); | |
|
|
55 | }); | |
|
|
56 | } | |
|
|
57 | ||
|
|
58 | } | |
| @@ -0,0 +1,54 | |||
|
|
1 | import { test } from "./TestTraits"; | |
|
|
2 | import { fluent } from "../di/traits"; | |
|
|
3 | import { Bar } from "../mock/Bar"; | |
|
|
4 | import { Container } from "../di/Container"; | |
|
|
5 | import { Foo } from "../mock/Foo"; | |
|
|
6 | import { Box } from "../mock/Box"; | |
|
|
7 | import { delay } from "../safe"; | |
|
|
8 | ||
|
|
9 | test("Simple fluent config", async t => { | |
|
|
10 | const config = fluent<{ host: string; bar: Bar; foo: Foo }>() | |
|
|
11 | .register({ | |
|
|
12 | host: it => it.value("example.com"), | |
|
|
13 | bar: it => it.factory(resolve => new Bar({ host: resolve("host") }, "s-bar")), | |
|
|
14 | foo: it => import("../mock/Foo").then(m => it.lifetime("container").factory(() => new m.Foo())) | |
|
|
15 | }); | |
|
|
16 | ||
|
|
17 | const container = new Container<{ host: string; bar: Bar; foo: Foo; }>(); | |
|
|
18 | await config.apply(container); | |
|
|
19 | ||
|
|
20 | t.equal(container.resolve("host"), "example.com", "The value should be resolved"); | |
|
|
21 | t.assert(container.resolve("bar"), "The service should de activated"); | |
|
|
22 | t.equal(container.resolve("foo"), container.resolve("foo"), "The service should be activated once"); | |
|
|
23 | }); | |
|
|
24 | ||
|
|
25 | test("Nested async configuration", async t => { | |
|
|
26 | const container = await new Container<{ | |
|
|
27 | foo: Foo; | |
|
|
28 | box: Box<Foo> | |
|
|
29 | }>().fluent({ | |
|
|
30 | foo: it => delay(0).then(() => it.factory(() => new Foo())), | |
|
|
31 | box: it => it.lifetime("context").factory($dependency => new Box($dependency("foo"))) | |
|
|
32 | }); | |
|
|
33 | ||
|
|
34 | t.assert(container.resolve("box").getValue(), "The dependency should be set"); | |
|
|
35 | t.equals(container.resolve("box").getValue(), container.resolve("box").getValue(), "The service should be activated once") | |
|
|
36 | }); | |
|
|
37 | ||
|
|
38 | test("Bad fluent config", async t => { | |
|
|
39 | try { | |
|
|
40 | await new Container<{ | |
|
|
41 | foo: Foo; | |
|
|
42 | box: Box<Foo> | |
|
|
43 | }>().fluent({ | |
|
|
44 | foo: it => delay(0).then(() => it.factory(() => new Foo())), | |
|
|
45 | box: it => it.lifetime("context") | |
|
|
46 | .override("foo", () => { throw new Error("bad override"); }) | |
|
|
47 | .factory($dependency => new Box($dependency("foo"))) | |
|
|
48 | }); | |
|
|
49 | t.fail("Should throw"); | |
|
|
50 | } catch (e) { | |
|
|
51 | t.pass("The configuration should fail"); | |
|
|
52 | t.equal(e.message, "bad override", "the error should pass"); | |
|
|
53 | } | |
|
|
54 | }); | |
| @@ -15,6 +15,10 export interface ActivationContextInfo { | |||
|
|
15 | 15 | |
|
|
16 | 16 | let nextId = 1; |
|
|
17 | 17 | |
|
|
18 | /** This class is created once per `Container.resolve` method call and used to | |
|
|
19 | * cache dependencies and to track created instances. The activation context | |
|
|
20 | * tracks services with `context` activation type. | |
|
|
21 | */ | |
|
|
18 | 22 | export class ActivationContext<S extends object> { |
|
|
19 | 23 | _cache: MapOf<any>; |
|
|
20 | 24 | |
| @@ -30,6 +34,14 export class ActivationContext<S extends | |||
|
|
30 | 34 | |
|
|
31 | 35 | _parent: ActivationContext<S> | undefined; |
|
|
32 | 36 | |
|
|
37 | /** Creates a new activation context with the specified parameters. | |
|
|
38 | * @param container the container which starts the activation process | |
|
|
39 | * @param services the initial service registrations | |
|
|
40 | * @param name the name of the service being activated, this parameter is | |
|
|
41 | * used for the debug purpose. | |
|
|
42 | * @param service the service to activate, this parameter is used for the | |
|
|
43 | * debug purpose. | |
|
|
44 | */ | |
|
|
33 | 45 | constructor(container: Container<S>, services: ContainerServiceMap<S>, name: string, service: Descriptor<S, any>) { |
|
|
34 | 46 | this._name = name; |
|
|
35 | 47 | this._service = service; |
| @@ -39,16 +51,33 export class ActivationContext<S extends | |||
|
|
39 | 51 | this._container = container; |
|
|
40 | 52 | } |
|
|
41 | 53 | |
|
|
54 | /** the name of the current resolving dependency */ | |
|
|
42 | 55 | getName() { |
|
|
43 | 56 | return this._name; |
|
|
44 | 57 | } |
|
|
45 | 58 | |
|
|
59 | /** Returns the container for which 'resolve' method was called */ | |
|
|
46 | 60 | getContainer() { |
|
|
47 | 61 | return this._container; |
|
|
48 | 62 | } |
|
|
49 | 63 | |
|
|
64 | /** Resolves the specified dependency in the current context | |
|
|
65 | * @param name The name of the dependency being resolved | |
|
|
66 | */ | |
|
|
50 | 67 | resolve<K extends ContainerKeys<S>>(name: K): TypeOfService<S, K>; |
|
|
68 | /** Resolves the specified dependency with the specified default value if | |
|
|
69 | * the dependency is missing. | |
|
|
70 | * | |
|
|
71 | * @param name The name of the dependency being resolved | |
|
|
72 | * @param def A default value to return in case of the specified dependency | |
|
|
73 | * is missing. | |
|
|
74 | */ | |
|
|
51 | 75 | resolve<K extends ContainerKeys<S>, T>(name: K, def: T): TypeOfService<S, K> | T; |
|
|
76 | /** Resolves the specified dependency and returns undefined in case if the | |
|
|
77 | * dependency is missing. | |
|
|
78 | * | |
|
|
79 | * @param name The name of the dependency being resolved | |
|
|
80 | */ | |
|
|
52 | 81 | resolve<K extends ContainerKeys<S>>(name: K, def: undefined): TypeOfService<S, K> | undefined; |
|
|
53 | 82 | resolve<K extends ContainerKeys<S>, T>(name: K, def?: T): TypeOfService<S, K> | T | undefined { |
|
|
54 | 83 | const d = this._services[name]; |
| @@ -92,6 +121,7 export class ActivationContext<S extends | |||
|
|
92 | 121 | } |
|
|
93 | 122 | }; |
|
|
94 | 123 | } |
|
|
124 | ||
|
|
95 | 125 | activate<T>(d: Descriptor<S, T>, name: string) { |
|
|
96 | 126 | if (trace.isLogEnabled()) |
|
|
97 | 127 | trace.log(`enter ${name} ${d}`); |
| @@ -1,7 +1,7 | |||
|
|
1 | 1 | import { ActivationContext } from "./ActivationContext"; |
|
|
2 | 2 | import { ValueDescriptor } from "./ValueDescriptor"; |
|
|
3 | 3 | import { ActivationError } from "./ActivationError"; |
|
|
4 |
import { ServiceMap, Descriptor, PartialServiceMap, |
|
|
|
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"; |
| @@ -9,13 +9,15 import { MapOf, IDestroyable } from "../ | |||
|
|
9 | 9 | import { isDescriptor } from "./traits"; |
|
|
10 | 10 | import { LifetimeManager } from "./LifetimeManager"; |
|
|
11 | 11 | import { each } from "../safe"; |
|
|
12 | import { FluentRegistrations } from "./fluent/interfaces"; | |
|
|
13 | import { FluentConfiguration } from "./fluent/FluentConfiguration"; | |
|
|
12 | 14 | |
|
|
13 | 15 | const trace = TraceSource.get("@implab/core/di/ActivationContext"); |
|
|
14 | 16 | |
|
|
15 | 17 | export class Container<S extends object = any> implements ServiceLocator<S>, IDestroyable { |
|
|
16 | 18 | readonly _services: ContainerServiceMap<S>; |
|
|
17 | 19 | |
|
|
18 |
readonly _lifetimeManager: |
|
|
|
20 | readonly _lifetimeManager: LifetimeManager; | |
|
|
19 | 21 | |
|
|
20 | 22 | readonly _cleanup: (() => void)[]; |
|
|
21 | 23 | |
| @@ -126,6 +128,11 export class Container<S extends object | |||
|
|
126 | 128 | } |
|
|
127 | 129 | } |
|
|
128 | 130 | |
|
|
131 | async fluent<K extends keyof S>(config: FluentRegistrations<K, S>, ct = Cancellation.none): Promise<this> { | |
|
|
132 | await new FluentConfiguration<S>().register(config).apply(this, ct); | |
|
|
133 | return this; | |
|
|
134 | } | |
|
|
135 | ||
|
|
129 | 136 | createChildContainer<S2 extends object = S>(): Container<S & S2> { |
|
|
130 | 137 | return new Container<S & S2>(this as any); |
|
|
131 | 138 | } |
| @@ -1,6 +1,6 | |||
|
|
1 | 1 | import { IDestroyable, MapOf } from "../interfaces"; |
|
|
2 | import { argumentNotNull, isDestroyable } from "../safe"; | |
|
|
3 |
import { |
|
|
|
2 | import { argumentNotNull, isDestroyable, primitive, isNull, argumentNotEmptyString } from "../safe"; | |
|
|
3 | import { ILifetime } from "./interfaces"; | |
|
|
4 | 4 | import { ActivationContext } from "./ActivationContext"; |
|
|
5 | 5 | import { Container } from "./Container"; |
|
|
6 | 6 | |
| @@ -12,7 +12,7 function safeCall(item: () => void) { | |||
|
|
12 | 12 | } |
|
|
13 | 13 | } |
|
|
14 | 14 | |
|
|
15 | const emptyLifetime: ILifetime = { | |
|
|
15 | const emptyLifetime: ILifetime = Object.freeze({ | |
|
|
16 | 16 | has() { |
|
|
17 | 17 | return false; |
|
|
18 | 18 | }, |
| @@ -29,11 +29,11 const emptyLifetime: ILifetime = { | |||
|
|
29 | 29 | // does nothing |
|
|
30 | 30 | } |
|
|
31 | 31 | |
|
|
32 | }; | |
|
|
32 | }); | |
|
|
33 | 33 | |
|
|
34 | const unknownLifetime: ILifetime = { | |
|
|
34 | const unknownLifetime: ILifetime = Object.freeze({ | |
|
|
35 | 35 | has() { |
|
|
36 | throw new Error("The lifetime is unknown"); | |
|
|
36 | return false; | |
|
|
37 | 37 | }, |
|
|
38 | 38 | initialize() { |
|
|
39 | 39 | throw new Error("Can't call initialize on the unknown lifetime object"); |
| @@ -44,11 +44,13 const unknownLifetime: ILifetime = { | |||
|
|
44 | 44 | store() { |
|
|
45 | 45 | throw new Error("Can't store a value in the unknown lifetime object"); |
|
|
46 | 46 | } |
|
|
47 | } | |
|
|
47 | }); | |
|
|
48 | 48 | |
|
|
49 | 49 | let nextId = 0; |
|
|
50 | 50 | |
|
|
51 | export class LifetimeManager implements IDestroyable, ILifetimeManager { | |
|
|
51 | const singletons: { [k in keyof any]: any; } = {}; | |
|
|
52 | ||
|
|
53 | export class LifetimeManager implements IDestroyable { | |
|
|
52 | 54 | private _cleanup: (() => void)[] = []; |
|
|
53 | 55 | private _cache: MapOf<any> = {}; |
|
|
54 | 56 | private _destroyed = false; |
| @@ -116,7 +118,7 export class LifetimeManager implements | |||
|
|
116 | 118 | if (_lifetime !== unknownLifetime) |
|
|
117 | 119 | throw new Error("Cyclic reference activation detected"); |
|
|
118 | 120 | |
|
|
119 |
_lifetime = context.getContainer().getLifetimeManager().create( |
|
|
|
121 | _lifetime = context.getContainer().getLifetimeManager().create(); | |
|
|
120 | 122 | }, |
|
|
121 | 123 | get() { |
|
|
122 | 124 | return _lifetime.get(); |
| @@ -151,7 +153,27 export class LifetimeManager implements | |||
|
|
151 | 153 | } |
|
|
152 | 154 | |
|
|
153 | 155 | static singletonLifetime(typeId: string): ILifetime { |
|
|
154 | return emptyLifetime; | |
|
|
156 | argumentNotEmptyString(typeId, "typeId"); | |
|
|
157 | let pending = false; | |
|
|
158 | return { | |
|
|
159 | has() { | |
|
|
160 | return typeId in singletons; | |
|
|
161 | }, | |
|
|
162 | get() { | |
|
|
163 | if (!this.has()) | |
|
|
164 | throw new Error(`The instance ${typeId} doesn't exists`); | |
|
|
165 | return singletons[typeId]; | |
|
|
166 | }, | |
|
|
167 | initialize() { | |
|
|
168 | if (pending) | |
|
|
169 | throw new Error("Cyclic reference detected"); | |
|
|
170 | pending = true; | |
|
|
171 | }, | |
|
|
172 | store(item: any) { | |
|
|
173 | singletons[typeId] = item; | |
|
|
174 | pending = false; | |
|
|
175 | } | |
|
|
176 | }; | |
|
|
155 | 177 | } |
|
|
156 | 178 | |
|
|
157 | 179 | static containerLifetime(container: Container<any>) { |
| @@ -160,7 +182,7 export class LifetimeManager implements | |||
|
|
160 | 182 | initialize(context: ActivationContext<any>) { |
|
|
161 | 183 | if (_lifetime !== unknownLifetime) |
|
|
162 | 184 | throw new Error("Cyclic reference detected"); |
|
|
163 |
_lifetime = container.getLifetimeManager().create( |
|
|
|
185 | _lifetime = container.getLifetimeManager().create(); | |
|
|
164 | 186 | }, |
|
|
165 | 187 | get() { |
|
|
166 | 188 | return _lifetime.get(); |
| @@ -1,5 +1,5 | |||
|
|
1 | 1 | import { ActivationContext } from "./ActivationContext"; |
|
|
2 |
import { Descriptor, ServiceMap, PartialServiceMap, |
|
|
|
2 | import { Descriptor, ServiceMap, PartialServiceMap, ILifetime } from "./interfaces"; | |
|
|
3 | 3 | import { isPrimitive, keys, isNull } from "../safe"; |
|
|
4 | 4 | import { TraceSource } from "../log/TraceSource"; |
|
|
5 | 5 | import { isDescriptor } from "./traits"; |
| @@ -1,46 +1,94 | |||
|
|
1 |
import { Resolver, |
|
|
|
1 | import { Resolver, RegistrationBuilder } from "./interfaces"; | |
|
|
2 | 2 | import { Container } from "../Container"; |
|
|
3 |
import { Descriptor, ILifetime, |
|
|
|
4 | import { ActivationContext } from "../ActivationContext"; | |
|
|
3 | import { Descriptor, ILifetime, ActivationType, PartialServiceMap } from "../interfaces"; | |
|
|
4 | import { DescriptorImpl } from "./DescriptorImpl"; | |
|
|
5 | import { LifetimeManager } from "../LifetimeManager"; | |
|
|
6 | import { isString, each, isPrimitive, isPromise, oid } from "../../safe"; | |
|
|
7 | ||
|
|
8 | export class DescriptorBuilder<S extends object, T> { | |
|
|
9 | private readonly _container: Container<S>; | |
|
|
10 | private readonly _cb: (d: Descriptor<S, T>) => void; | |
|
|
11 | ||
|
|
12 | private readonly _eb: (err: any) => void; | |
|
|
5 | 13 | |
|
|
6 | export class DescriptorBuilder<T, S extends object> { | |
|
|
7 | readonly _container: Container<S>; | |
|
|
8 | readonly _cb: (d: Descriptor<S, T>) => void; | |
|
|
14 | private _lifetime = LifetimeManager.empty(); | |
|
|
15 | ||
|
|
16 | private _overrides?: PartialServiceMap<S>; | |
|
|
17 | ||
|
|
18 | private _cleanup?: (item: T) => void; | |
|
|
9 | 19 | |
|
|
10 | constructor(container: Container<S>, cb: (d: Descriptor<S, T>) => void) { | |
|
|
20 | private _factory?: (resolve: Resolver<S>) => T; | |
|
|
21 | ||
|
|
22 | private _pending = 1; | |
|
|
23 | ||
|
|
24 | private _failed = false; | |
|
|
25 | ||
|
|
26 | constructor(container: Container<S>, cb: (d: Descriptor<S, T>) => void, eb: (err: any) => void) { | |
|
|
11 | 27 | this._container = container; |
|
|
12 | 28 | this._cb = cb; |
|
|
29 | this._eb = eb; | |
|
|
30 | } | |
|
|
31 | ||
|
|
32 | build<T2>(): DescriptorBuilder<S, T2> { | |
|
|
33 | this._defer(); | |
|
|
34 | return new DescriptorBuilder<S, T2>(this._container, () => this._complete(), err => this._fail(err)); | |
|
|
13 | 35 | } |
|
|
14 | 36 | |
|
|
15 | factory(f: (resolve: Resolver<S>, activate: (lifetime: ILifetime, factory: () => any, cleanup?: (item: any) => void) => any) => T): void { | |
|
|
16 | this._cb({ | |
|
|
17 | activate(context: ActivationContext<S>) { | |
|
|
18 | const resolve = (name: ContainerKeys<S>, opts?: DependencyOptions | LazyDependencyOptions) => { | |
|
|
19 | if (opts && "lazy" in opts && opts.lazy) { | |
|
|
20 | const c2 = context.clone(); | |
|
|
21 | return () => { | |
|
|
22 | return opts.optional ? c2.resolve(name, opts.default) : c2.resolve(name); | |
|
|
23 | }; | |
|
|
24 | } else { | |
|
|
25 | return opts && opts.optional ? context.resolve(name, opts.default) : context.resolve(name); | |
|
|
26 | } | |
|
|
27 | }; | |
|
|
37 | override<K extends keyof S>(name: K, builder: RegistrationBuilder<S, S[K]>): this; | |
|
|
38 | override<K extends keyof S>(services: { [name in K]: RegistrationBuilder<S, S[K]> }): this; | |
|
|
39 | override<K extends keyof S>(nameOrServices: K | { [name in K]: RegistrationBuilder<S, S[K]> }, builder?: RegistrationBuilder<S, S[K]>): this { | |
|
|
40 | const overrides: PartialServiceMap<S> = this._overrides ? | |
|
|
41 | this._overrides : | |
|
|
42 | (this._overrides = {}); | |
|
|
43 | ||
|
|
44 | const guard = (v: void | Promise<void>) => { | |
|
|
45 | if (isPromise(v)) | |
|
|
46 | v.catch(err => this._fail(err)); | |
|
|
47 | }; | |
|
|
48 | ||
|
|
49 | if (isPrimitive(nameOrServices)) { | |
|
|
50 | if (builder) { | |
|
|
51 | this._defer(); | |
|
|
52 | const d = new DescriptorBuilder<S, S[K]>( | |
|
|
53 | this._container, | |
|
|
54 | result => { | |
|
|
55 | overrides[nameOrServices] = result; | |
|
|
56 | this._complete(); | |
|
|
57 | }, | |
|
|
58 | err => this._fail(err) | |
|
|
59 | ); | |
|
|
28 | 60 | |
|
|
29 | const activate = (lifetime: ILifetime, factory: () => any, cleanup?: (item: any) => void) => { | |
|
|
30 |
|
|
|
|
31 | return lifetime.get(); | |
|
|
32 |
|
|
|
|
33 | lifetime.initialize(context); | |
|
|
34 | const instance = factory(); | |
|
|
35 | lifetime.store(instance, cleanup); | |
|
|
36 | return instance; | |
|
|
37 |
|
|
|
|
61 | try { | |
|
|
62 | guard(builder(d)); | |
|
|
63 | } catch (err) { | |
|
|
64 | this._fail(err); | |
|
|
65 | } | |
|
|
66 | } | |
|
|
67 | } else { | |
|
|
68 | each(nameOrServices, (v, k) => this.override(k, v)); | |
|
|
69 | } | |
|
|
70 | return this; | |
|
|
71 | } | |
|
|
38 | 72 | |
|
|
39 | }; | |
|
|
73 | lifetime(lifetime: "singleton", typeId: string): this; | |
|
|
74 | lifetime(lifetime: ILifetime | Exclude<ActivationType, "singleton">): this; | |
|
|
75 | lifetime(lifetime: ILifetime | ActivationType, typeId?: string): this { | |
|
|
76 | if (isString(lifetime)) { | |
|
|
77 | this._lifetime = this._resolveLifetime(lifetime, typeId); | |
|
|
78 | } else { | |
|
|
79 | this._lifetime = lifetime; | |
|
|
80 | } | |
|
|
81 | return this; | |
|
|
82 | } | |
|
|
40 | 83 | |
|
|
41 | return f(resolve, activate); | |
|
|
42 | } | |
|
|
43 | }); | |
|
|
84 | cleanup(cb: (item: T) => void): this { | |
|
|
85 | this._cleanup = cb; | |
|
|
86 | return this; | |
|
|
87 | } | |
|
|
88 | ||
|
|
89 | factory(f: (resolve: Resolver<S>) => T): void { | |
|
|
90 | this._factory = f; | |
|
|
91 | this._complete(); | |
|
|
44 | 92 | } |
|
|
45 | 93 | |
|
|
46 | 94 | value(v: T): void { |
| @@ -51,4 +99,49 export class DescriptorBuilder<T, S exte | |||
|
|
51 | 99 | }); |
|
|
52 | 100 | } |
|
|
53 | 101 | |
|
|
102 | _resolveLifetime(activation: ActivationType, typeId?: string | object) { | |
|
|
103 | switch (activation) { | |
|
|
104 | case "container": | |
|
|
105 | return LifetimeManager.containerLifetime(this._container); | |
|
|
106 | case "hierarchy": | |
|
|
107 | return LifetimeManager.hierarchyLifetime(); | |
|
|
108 | case "context": | |
|
|
109 | return LifetimeManager.contextLifetime(); | |
|
|
110 | case "singleton": | |
|
|
111 | if (!typeId) | |
|
|
112 | throw Error("The singleton activation requires a typeId"); | |
|
|
113 | ||
|
|
114 | const _oid = isString(typeId) ? typeId : oid(typeId); | |
|
|
115 | ||
|
|
116 | return LifetimeManager.singletonLifetime(_oid); | |
|
|
117 | default: | |
|
|
118 | return LifetimeManager.empty(); | |
|
|
119 | } | |
|
|
120 | } | |
|
|
121 | ||
|
|
122 | _defer() { | |
|
|
123 | this._pending++; | |
|
|
124 | } | |
|
|
125 | ||
|
|
126 | _complete() { | |
|
|
127 | if (--this._pending === 0) { | |
|
|
128 | if (!this._factory) | |
|
|
129 | throw new Error("The factory must be specified"); | |
|
|
130 | ||
|
|
131 | this._cb(new DescriptorImpl<S, T>({ | |
|
|
132 | lifetime: this._lifetime, | |
|
|
133 | factory: this._factory, | |
|
|
134 | overrides: this._overrides, | |
|
|
135 | cleanup: this._cleanup | |
|
|
136 | })); | |
|
|
137 | } | |
|
|
138 | } | |
|
|
139 | ||
|
|
140 | _fail(err: any) { | |
|
|
141 | if (!this._failed) { | |
|
|
142 | this._failed = true; | |
|
|
143 | this._eb.call(undefined, err); | |
|
|
144 | } | |
|
|
145 | } | |
|
|
146 | ||
|
|
54 | 147 | } |
| @@ -1,6 +1,6 | |||
|
|
1 | 1 | import { primitive } from "../../safe"; |
|
|
2 | import { AnnotationBuilder } from "../Annotations"; | |
|
|
3 |
import { I |
|
|
|
2 | import { TypeOfService, ContainerKeys, ActivationType, ILifetime } from "../interfaces"; | |
|
|
3 | import { ICancellation } from "../../interfaces"; | |
|
|
4 | 4 | |
|
|
5 | 5 | export interface DependencyOptions { |
|
|
6 | 6 | optional?: boolean; |
| @@ -22,10 +22,6 export type ExtractDependency<D, S> = D | |||
|
|
22 | 22 | export type WalkDependencies<D, S> = D extends primitive ? D : |
|
|
23 | 23 | { [K in keyof D]: ExtractDependency<D[K], S> }; |
|
|
24 | 24 | |
|
|
25 | export type ServiceModule<T, S extends object, M extends keyof any = "service"> = { | |
|
|
26 | [m in M]: AnnotationBuilder<T, S>; | |
|
|
27 | }; | |
|
|
28 | ||
|
|
29 | 25 | export type InferReferenceType<S extends object, K extends ContainerKeys<S>, O> = O extends { default: infer X } ? (TypeOfService<S, K> | X) : |
|
|
30 | 26 | O extends { optional: true } ? (TypeOfService<S, K> | undefined) : |
|
|
31 | 27 | TypeOfService<S, K>; |
| @@ -35,14 +31,22 export interface Resolver<S extends obje | |||
|
|
35 | 31 | <K extends ContainerKeys<S>, O extends DependencyOptions>(this: void, name: K, opts?: O): InferReferenceType<S, K, O>; |
|
|
36 | 32 | } |
|
|
37 | 33 | |
|
|
38 |
export interface DescriptorBuilder< |
|
|
|
39 | service(service: AnnotationBuilder<T, S> | ServiceModule<T, S>): void; | |
|
|
34 | export interface DescriptorBuilder<S extends object, T> { | |
|
|
35 | factory(f: (resolve: Resolver<S>) => T): void; | |
|
|
36 | ||
|
|
37 | build<T2>(): DescriptorBuilder<S, T2>; | |
|
|
40 | 38 | |
|
|
41 | factory(f: (resolve: Resolver<S>, activate: <T2>(lifetime: ILifetime, factory: () => T2, cleanup?: (item: T2) => void) => T2) => T): void; | |
|
|
39 | override<K extends keyof S>(name: K, builder: RegistrationBuilder<S, S[K]>): this; | |
|
|
40 | override<K extends keyof S>(services: { [name in K]: RegistrationBuilder<S, S[K]> }): this; | |
|
|
41 | ||
|
|
42 | lifetime(lifetime: "singleton", typeId: any): this; | |
|
|
43 | lifetime(lifetime: ILifetime | Exclude<ActivationType, "singleton">): this; | |
|
|
44 | ||
|
|
45 | cleanup(cb: (item: T) => void): this; | |
|
|
42 | 46 | |
|
|
43 | 47 | value(v: T): void; |
|
|
44 | 48 | } |
|
|
45 | 49 | |
|
|
46 | export interface Configuration<S extends object, Y extends keyof S = keyof S> { | |
|
|
47 | register<K extends Y>(name: K, builder: (d: DescriptorBuilder<S[K], S>) => void): Configuration<S, Exclude<Y, K>>; | |
|
|
48 | } | |
|
|
50 | export type RegistrationBuilder<S extends object, T> = (d: DescriptorBuilder<S, T>, ct?: ICancellation) => void | Promise<void>; | |
|
|
51 | ||
|
|
52 | export type FluentRegistrations<K extends keyof S, S extends object> = { [k in K]: RegistrationBuilder<S, S[k]> }; | |
| @@ -37,10 +37,6 export type ContainerRegistered<S extend | |||
|
|
37 | 37 | |
|
|
38 | 38 | export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call"; |
|
|
39 | 39 | |
|
|
40 | export interface ILifetimeManager { | |
|
|
41 | create(context: ActivationContext<any>): ILifetime; | |
|
|
42 | } | |
|
|
43 | ||
|
|
44 | 40 |
|
|
|
45 | 41 | * Интерфейс для управления жизнью экземпляра объекта. Каждая регистрация имеет |
|
|
46 | 42 | * свой собственный объект `ILifetime`, который создается при первой активации |
| @@ -1,12 +1,11 | |||
|
|
1 | 1 | import { isPrimitive } from "../safe"; |
|
|
2 | 2 | import { Descriptor } from "./interfaces"; |
|
|
3 | import { Configuration } from "./fluent/Configuration"; | |
|
|
4 | ||
|
|
3 | import { FluentConfiguration } from "./fluent/FluentConfiguration"; | |
|
|
5 | 4 | export function isDescriptor(x: any): x is Descriptor { |
|
|
6 | 5 | return (!isPrimitive(x)) && |
|
|
7 | 6 | (x.activate instanceof Function); |
|
|
8 | 7 | } |
|
|
9 | 8 | |
|
|
10 |
export function |
|
|
|
11 | return new Configuration<S>(); | |
|
|
9 | export function fluent<S extends object>() { | |
|
|
10 | return new FluentConfiguration<S>(); | |
|
|
12 | 11 | } |
| @@ -1,4 +1,4 | |||
|
|
1 | import { ICancellable, Constructor, IDestroyable } from "./interfaces"; | |
|
|
1 | import { ICancellable, Constructor, IDestroyable, PromiseOrValue } from "./interfaces"; | |
|
|
2 | 2 | import { Cancellation } from "./Cancellation"; |
|
|
3 | 3 | |
|
|
4 | 4 | let _nextOid = 0; |
| @@ -6,6 +6,8 const _oid = typeof Symbol === "function | |||
|
|
6 | 6 | Symbol("__implab__oid__") : |
|
|
7 | 7 | "__implab__oid__"; |
|
|
8 | 8 | |
|
|
9 | export function oid(instance: null | undefined): undefined; | |
|
|
10 | export function oid(instance: NonNullable<any>): string; | |
|
|
9 | 11 | export function oid(instance: any): string | undefined { |
|
|
10 | 12 | if (isNull(instance)) |
|
|
11 | 13 | return undefined; |
| @@ -1,16 +1,19 | |||
|
|
1 | 1 | import { Services } from "./services"; |
|
|
2 |
import { |
|
|
|
3 | import { LifetimeManager } from "../di/LifetimeManager"; | |
|
|
2 | import { fluent } from "../di/traits"; | |
|
|
3 | ||
|
|
4 | export default fluent<Services>().register({ | |
|
|
5 | host: it => it.value("example.com"), | |
|
|
4 | 6 | |
|
|
5 | export const config = configure<Services>() | |
|
|
6 | .register("host", s => s.value("example.com")) | |
|
|
7 | .register("bar2", bar2 => Promise.all([import("./Foo"), import("./Bar")]) | |
|
|
8 | .then(([{ Foo }, { Bar }]) => { | |
|
|
9 | const lifetime = LifetimeManager.hierarchyLifetime(); | |
|
|
10 | ||
|
|
11 | bar2.factory((resolve, activate) => { | |
|
|
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 => { | |
|
|
12 | 15 | const bar = new Bar({ |
|
|
13 |
foo: |
|
|
|
16 | foo: new Foo(), | |
|
|
14 | 17 | nested: { |
|
|
15 | 18 | lazy: resolve("foo", { lazy: true }) |
|
|
16 | 19 | }, |
| @@ -18,6 +21,6 export const config = configure<Services | |||
|
|
18 | 21 | }, "some text"); |
|
|
19 | 22 | bar.setName(resolve("host")); |
|
|
20 | 23 | return bar; |
|
|
21 |
}) |
|
|
|
22 |
|
|
|
|
23 | ); | |
|
|
24 | }) | |
|
|
25 | ) | |
|
|
26 | }); | |
| @@ -6,7 +6,6 import { ValueDescriptor } from "../di/V | |||
|
|
6 | 6 | import { Foo } from "../mock/Foo"; |
|
|
7 | 7 | import { Bar } from "../mock/Bar"; |
|
|
8 | 8 | import { isNull } from "../safe"; |
|
|
9 | import { Box } from "ts/mock/Box"; | |
|
|
10 | 9 | |
|
|
11 | 10 | test("Container register/resolve tests", async t => { |
|
|
12 | 11 | const container = new Container<{ |
| @@ -1,8 +1,6 | |||
|
|
1 | 1 | { |
|
|
2 | 2 | "extends": "../tsconfig", |
|
|
3 | 3 | "compilerOptions": { |
|
|
4 | //"rootDir": "ts", | |
|
|
5 | "baseUrl": ".", | |
|
|
6 | 4 | "rootDirs": [ |
|
|
7 | 5 | "ts", |
|
|
8 | 6 | "../main/ts" |
| @@ -6,5 +6,6 define([ | |||
|
|
6 | 6 | "./ObservableTests", |
|
|
7 | 7 | "./ContainerTests", |
|
|
8 | 8 | "./SafeTests", |
|
|
9 | "./TextTests" | |
|
|
9 | "./TextTests", | |
|
|
10 | "./FluentContainerTests" | |
|
|
10 | 11 | ]); No newline at end of file |
|
|
1 | NO CONTENT: file was removed |
|
|
1 | NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now
