| @@ -0,0 +1,12 | |||||
|
|
1 | import { Cancellation } from "./Cancellation"; | |||
|
|
2 | import { ICancellation } from "./interfaces"; | |||
|
|
3 | ||||
|
|
4 | export class CancelledError extends Error { | |||
|
|
5 | readonly cancellationToken: ICancellation; | |||
|
|
6 | ||||
|
|
7 | constructor(message = "The operation is cancelled", ct = Cancellation.none) { | |||
|
|
8 | super(message); | |||
|
|
9 | this.cancellationToken = ct; | |||
|
|
10 | this.name = "CancelledError"; | |||
|
|
11 | } | |||
|
|
12 | } | |||
| @@ -1,106 +1,107 | |||||
| 1 | import { CancellationAggregate } from "./CancellationAggregate"; |
|
1 | import { CancellationAggregate } from "./CancellationAggregate"; | |
|
|
2 | import { CancelledError } from "./CancelledError"; | |||
| 2 | import { ICancellation, IDestroyable } from "./interfaces"; |
|
3 | import { ICancellation, IDestroyable } from "./interfaces"; | |
| 3 | import { argumentNotNull, destroyed } from "./safe"; |
|
4 | import { argumentNotNull, destroyed } from "./safe"; | |
| 4 |
|
5 | |||
| 5 | export class Cancellation implements ICancellation { |
|
6 | export class Cancellation implements ICancellation { | |
| 6 | private _reason: any; |
|
7 | private _reason: any; | |
| 7 | private _cbs: Array<(e: any) => void> | undefined; |
|
8 | private _cbs: Array<(e: any) => void> | undefined; | |
| 8 |
|
9 | |||
| 9 | constructor(action: (cancel: (e?: any) => void) => void) { |
|
10 | constructor(action: (cancel: (e?: any) => void) => void) { | |
| 10 | argumentNotNull(action, "action"); |
|
11 | argumentNotNull(action, "action"); | |
| 11 |
|
12 | |||
| 12 | action(this._cancel.bind(this)); |
|
13 | action(this._cancel.bind(this)); | |
| 13 | } |
|
14 | } | |
| 14 |
|
15 | |||
| 15 | isSupported(): boolean { |
|
16 | isSupported(): boolean { | |
| 16 | return true; |
|
17 | return true; | |
| 17 | } |
|
18 | } | |
| 18 | throwIfRequested(): void { |
|
19 | throwIfRequested(): void { | |
| 19 | if (this._reason) |
|
20 | if (this._reason) | |
| 20 | throw this._reason; |
|
21 | throw this._reason; | |
| 21 | } |
|
22 | } | |
| 22 |
|
23 | |||
| 23 | isRequested(): boolean { |
|
24 | isRequested(): boolean { | |
| 24 | return !!this._reason; |
|
25 | return !!this._reason; | |
| 25 | } |
|
26 | } | |
| 26 |
|
27 | |||
| 27 | register(cb: (e: any) => void): IDestroyable { |
|
28 | register(cb: (e: any) => void): IDestroyable { | |
| 28 | argumentNotNull(cb, "cb"); |
|
29 | argumentNotNull(cb, "cb"); | |
| 29 |
|
30 | |||
| 30 | if (this._reason) { |
|
31 | if (this._reason) { | |
| 31 | cb(this._reason); |
|
32 | cb(this._reason); | |
| 32 | return destroyed; |
|
33 | return destroyed; | |
| 33 | } else { |
|
34 | } else { | |
| 34 | if (!this._cbs) |
|
35 | if (!this._cbs) | |
| 35 | this._cbs = [cb]; |
|
36 | this._cbs = [cb]; | |
| 36 | else |
|
37 | else | |
| 37 | this._cbs.push(cb); |
|
38 | this._cbs.push(cb); | |
| 38 |
|
39 | |||
| 39 | const me = this; |
|
40 | const me = this; | |
| 40 | return { |
|
41 | return { | |
| 41 | destroy() { |
|
42 | destroy() { | |
| 42 | me._unregister(cb); |
|
43 | me._unregister(cb); | |
| 43 | } |
|
44 | } | |
| 44 | }; |
|
45 | }; | |
| 45 | } |
|
46 | } | |
| 46 | } |
|
47 | } | |
| 47 |
|
48 | |||
| 48 | private _unregister(cb: any) { |
|
49 | private _unregister(cb: any) { | |
| 49 | if (this._cbs) { |
|
50 | if (this._cbs) { | |
| 50 | const i = this._cbs.indexOf(cb); |
|
51 | const i = this._cbs.indexOf(cb); | |
| 51 | if (i >= 0) |
|
52 | if (i >= 0) | |
| 52 | this._cbs.splice(i, 1); |
|
53 | this._cbs.splice(i, 1); | |
| 53 | } |
|
54 | } | |
| 54 | } |
|
55 | } | |
| 55 |
|
56 | |||
| 56 | private _cancel(reason: any) { |
|
57 | private _cancel(reason: any) { | |
| 57 | if (this._reason) |
|
58 | if (this._reason) | |
| 58 | return; |
|
59 | return; | |
| 59 |
|
60 | |||
| 60 |
this._reason = (reason = reason || new Error( |
|
61 | this._reason = (reason = reason || new CancelledError(undefined, this)); | |
| 61 |
|
62 | |||
| 62 | if (this._cbs) { |
|
63 | if (this._cbs) { | |
| 63 | this._cbs.forEach(cb => cb(reason)); |
|
64 | this._cbs.forEach(cb => cb(reason)); | |
| 64 | this._cbs = undefined; |
|
65 | this._cbs = undefined; | |
| 65 | } |
|
66 | } | |
| 66 | } |
|
67 | } | |
| 67 |
|
68 | |||
| 68 | static readonly none: ICancellation = { |
|
69 | static readonly none: ICancellation = { | |
| 69 | isSupported(): boolean { |
|
70 | isSupported(): boolean { | |
| 70 | return false; |
|
71 | return false; | |
| 71 | }, |
|
72 | }, | |
| 72 |
|
73 | |||
| 73 | throwIfRequested(): void { |
|
74 | throwIfRequested(): void { | |
| 74 | }, |
|
75 | }, | |
| 75 |
|
76 | |||
| 76 | isRequested(): boolean { |
|
77 | isRequested(): boolean { | |
| 77 | return false; |
|
78 | return false; | |
| 78 | }, |
|
79 | }, | |
| 79 |
|
80 | |||
| 80 | register(_cb: (e: any) => void): IDestroyable { |
|
81 | register(_cb: (e: any) => void): IDestroyable { | |
| 81 | return destroyed; |
|
82 | return destroyed; | |
| 82 | } |
|
83 | } | |
| 83 | }; |
|
84 | }; | |
| 84 |
|
85 | |||
| 85 | /** |
|
86 | /** | |
| 86 | * Combines multiple cancellation tokens to the single aggregated token. |
|
87 | * Combines multiple cancellation tokens to the single aggregated token. | |
| 87 |
* |
|
88 | * | |
| 88 | * Aggregated token will be considered as signalled when some tokens are |
|
89 | * Aggregated token will be considered as signalled when some tokens are | |
| 89 | * signalled. The cancellation callback can be registered with the `register` |
|
90 | * signalled. The cancellation callback can be registered with the `register` | |
| 90 | * method, it will be fired once with the first signalled token, all other |
|
91 | * method, it will be fired once with the first signalled token, all other | |
| 91 | * tokens will be ignored. |
|
92 | * tokens will be ignored. | |
| 92 |
* |
|
93 | * | |
| 93 | * The tokens which don't support cancellation are filtered out, if there are |
|
94 | * The tokens which don't support cancellation are filtered out, if there are | |
| 94 | * no tokens left in the list the method returns `Cancellation.none`. |
|
95 | * no tokens left in the list the method returns `Cancellation.none`. | |
| 95 |
* |
|
96 | * | |
| 96 | * @param args The list of cancellation tokens to combine |
|
97 | * @param args The list of cancellation tokens to combine | |
| 97 | * @returns |
|
98 | * @returns Aggregated cancellation token | |
| 98 | */ |
|
99 | */ | |
| 99 | static combine(...args: ICancellation[]) { |
|
100 | static combine(...args: ICancellation[]) { | |
| 100 | const tokens = args.filter(ct => ct.isSupported()); |
|
101 | const tokens = args.filter(ct => ct.isSupported()); | |
| 101 | return tokens.length > 1 ? |
|
102 | return tokens.length > 1 ? | |
| 102 | new CancellationAggregate(tokens) : |
|
103 | new CancellationAggregate(tokens) : | |
| 103 | tokens.length == 1 ? tokens[0] : |
|
104 | tokens.length === 1 ? tokens[0] : | |
| 104 | this.none; |
|
105 | this.none; | |
| 105 | } |
|
106 | } | |
| 106 | } |
|
107 | } | |
| @@ -1,41 +1,41 | |||||
| 1 | import { ICancellation, IDestroyable } from "./interfaces"; |
|
1 | import { ICancellation, IDestroyable } from "./interfaces"; | |
| 2 |
|
2 | |||
| 3 | export class CancellationAggregate implements ICancellation { |
|
3 | export class CancellationAggregate implements ICancellation { | |
| 4 | private readonly _tokens: ICancellation[]; |
|
4 | private readonly _tokens: ICancellation[]; | |
| 5 |
|
5 | |||
| 6 | constructor(tokens: ICancellation[]) { |
|
6 | constructor(tokens: ICancellation[]) { | |
| 7 | this._tokens = tokens || []; |
|
7 | this._tokens = tokens || []; | |
| 8 | } |
|
8 | } | |
| 9 |
|
9 | |||
| 10 | throwIfRequested() { |
|
10 | throwIfRequested() { | |
| 11 | this._tokens.forEach(ct => ct.throwIfRequested()); |
|
11 | this._tokens.forEach(ct => ct.throwIfRequested()); | |
| 12 | } |
|
12 | } | |
| 13 |
|
13 | |||
| 14 | isRequested() { |
|
14 | isRequested() { | |
| 15 | return this._tokens.some(ct => ct.isRequested()); |
|
15 | return this._tokens.some(ct => ct.isRequested()); | |
| 16 | } |
|
16 | } | |
| 17 | isSupported() { |
|
17 | isSupported() { | |
| 18 | return !!this._tokens.length; |
|
18 | return !!this._tokens.length; | |
| 19 | } |
|
19 | } | |
| 20 | register(cb: (e: any) => void): IDestroyable { |
|
20 | register(cb: (e: any) => void): IDestroyable { | |
| 21 | let fired = false; |
|
21 | let fired = false; | |
| 22 |
|
22 | |||
| 23 | const once = (e: any) => { |
|
23 | const once = (e: any) => { | |
| 24 | if (!fired) { |
|
24 | if (!fired) { | |
| 25 | fired = true; |
|
25 | fired = true; | |
| 26 | destroy(); |
|
26 | destroy(); | |
| 27 | cb(e); |
|
27 | cb(e); | |
| 28 | } |
|
28 | } | |
| 29 | } |
|
29 | }; | |
| 30 |
|
30 | |||
| 31 | const destroy = () => subscriptions |
|
31 | const destroy = () => subscriptions | |
| 32 | .splice(0,subscriptions.length) // empty array |
|
32 | .splice(0, subscriptions.length) // empty array | |
| 33 | .forEach(subscription => subscription.destroy()); // cleanup |
|
33 | .forEach(subscription => subscription.destroy()); // cleanup | |
| 34 |
|
34 | |||
| 35 | const subscriptions = this._tokens.map(ct => ct.register(once)) |
|
35 | const subscriptions = this._tokens.map(ct => ct.register(once)); | |
| 36 |
|
36 | |||
| 37 | return { |
|
37 | return { | |
| 38 | destroy |
|
38 | destroy | |
| 39 | }; |
|
39 | }; | |
| 40 | } |
|
40 | } | |
| 41 | } |
|
41 | } | |
| @@ -1,35 +1,38 | |||||
| 1 | import { Cancellation } from "../Cancellation"; |
|
1 | import { Cancellation } from "../Cancellation"; | |
| 2 | import { IAsyncComponent, ICancellation, ICancellable } from "../interfaces"; |
|
2 | import { IAsyncComponent, ICancellation, ICancellable } from "../interfaces"; | |
| 3 |
|
3 | |||
| 4 | const noop = () => void (0); |
|
4 | const noop = () => void (0); | |
| 5 |
|
5 | |||
| 6 | export class AsyncComponent implements IAsyncComponent, ICancellable { |
|
6 | export class AsyncComponent implements IAsyncComponent, ICancellable { | |
| 7 | _cancel: ((e: any) => void) = noop; |
|
7 | _cancel: ((e: any) => void) = noop; | |
| 8 |
|
8 | |||
| 9 | _completion: Promise<void> = Promise.resolve(); |
|
9 | _completion: Promise<void> = Promise.resolve(); | |
| 10 |
|
10 | |||
| 11 | getCompletion() { return this._completion; } |
|
11 | getCompletion() { return this._completion; } | |
| 12 |
|
12 | |||
| 13 | runOperation(op: (ct: ICancellation) => any, ct: ICancellation = Cancellation.none) { |
|
13 | runOperation(op: (ct: ICancellation) => any, ct: ICancellation = Cancellation.none) { | |
| 14 | // create inner cancellation bound to the passed cancellation token |
|
14 | // create inner cancellation bound to the passed cancellation token | |
| 15 | const inner = new Cancellation(cancel => { |
|
15 | const inner = new Cancellation(cancel => { | |
| 16 | this._cancel = cancel; |
|
16 | this._cancel = cancel; | |
| 17 | }); |
|
17 | }); | |
| 18 |
|
18 | |||
| 19 | const guard = async () => { |
|
19 | const guard = async () => { | |
| 20 | try { |
|
20 | try { | |
| 21 |
|
|
21 | const combined = Cancellation.combine(ct, inner); | |
|
|
22 | const result = await op(combined); | |||
|
|
23 | combined.throwIfRequested(); | |||
|
|
24 | return result; | |||
| 22 | } finally { |
|
25 | } finally { | |
| 23 | // after the operation is complete we need to cleanup the |
|
26 | // after the operation is complete we need to cleanup the | |
| 24 | // resources |
|
27 | // resources | |
| 25 | this._cancel = noop; |
|
28 | this._cancel = noop; | |
| 26 | } |
|
29 | } | |
| 27 | }; |
|
30 | }; | |
| 28 |
|
31 | |||
| 29 | return this._completion = guard(); |
|
32 | return this._completion = guard(); | |
| 30 | } |
|
33 | } | |
| 31 |
|
34 | |||
| 32 | cancel(reason: any) { |
|
35 | cancel(reason: any) { | |
| 33 | this._cancel(reason); |
|
36 | this._cancel(reason); | |
| 34 | } |
|
37 | } | |
| 35 | } |
|
38 | } | |
| @@ -1,216 +1,216 | |||||
| 1 | import { IDestroyable, MapOf } from "../interfaces"; |
|
1 | import { IDestroyable, MapOf } from "../interfaces"; | |
| 2 | import { argumentNotNull, isDestroyable, argumentNotEmptyString, isRemovable } from "../safe"; |
|
2 | import { argumentNotNull, isDestroyable, argumentNotEmptyString, isRemovable } from "../safe"; | |
| 3 | import { ILifetime, ServiceContainer } from "./interfaces"; |
|
3 | import { ILifetime, ServiceContainer } from "./interfaces"; | |
| 4 | import { ActivationContext } from "./ActivationContext"; |
|
4 | import { ActivationContext } from "./ActivationContext"; | |
| 5 |
|
5 | |||
| 6 | function safeCall(item: () => void) { |
|
6 | function safeCall(item: () => void) { | |
| 7 | try { |
|
7 | try { | |
| 8 | item(); |
|
8 | item(); | |
| 9 | } catch { |
|
9 | } catch { | |
| 10 | // silence! |
|
10 | // silence! | |
| 11 | } |
|
11 | } | |
| 12 | } |
|
12 | } | |
| 13 |
|
13 | |||
| 14 | const emptyLifetime: ILifetime = Object.freeze({ |
|
14 | const emptyLifetime: ILifetime = Object.freeze({ | |
| 15 | has() { |
|
15 | has() { | |
| 16 | return false; |
|
16 | return false; | |
| 17 | }, |
|
17 | }, | |
| 18 |
|
18 | |||
| 19 | initialize() { |
|
19 | initialize() { | |
| 20 |
|
20 | |||
| 21 | }, |
|
21 | }, | |
| 22 |
|
22 | |||
| 23 | get() { |
|
23 | get() { | |
| 24 | throw new Error("The specified item isn't registered with this lifetime manager"); |
|
24 | throw new Error("The specified item isn't registered with this lifetime manager"); | |
| 25 | }, |
|
25 | }, | |
| 26 |
|
26 | |||
| 27 | store() { |
|
27 | store() { | |
| 28 | // does nothing |
|
28 | // does nothing | |
| 29 | }, |
|
29 | }, | |
| 30 |
|
30 | |||
| 31 | toString() { |
|
31 | toString() { | |
| 32 | return `[object EmptyLifetime]`; |
|
32 | return `[object EmptyLifetime]`; | |
| 33 | } |
|
33 | } | |
| 34 |
|
34 | |||
| 35 | }); |
|
35 | }); | |
| 36 |
|
36 | |||
| 37 | const unknownLifetime: ILifetime = Object.freeze({ |
|
37 | const unknownLifetime: ILifetime = Object.freeze({ | |
| 38 | has() { |
|
38 | has() { | |
| 39 | return false; |
|
39 | return false; | |
| 40 | }, |
|
40 | }, | |
| 41 | initialize() { |
|
41 | initialize() { | |
| 42 | throw new Error("Can't call initialize on the unknown lifetime object"); |
|
42 | throw new Error("Can't call initialize on the unknown lifetime object"); | |
| 43 | }, |
|
43 | }, | |
| 44 | get() { |
|
44 | get() { | |
| 45 | throw new Error("The lifetime object isn't initialized"); |
|
45 | throw new Error("The lifetime object isn't initialized"); | |
| 46 | }, |
|
46 | }, | |
| 47 | store() { |
|
47 | store() { | |
| 48 | throw new Error("Can't store a value in the unknown lifetime object"); |
|
48 | throw new Error("Can't store a value in the unknown lifetime object"); | |
| 49 | }, |
|
49 | }, | |
| 50 | toString() { |
|
50 | toString() { | |
| 51 | return `[object UnknownLifetime]`; |
|
51 | return `[object UnknownLifetime]`; | |
| 52 | } |
|
52 | } | |
| 53 | }); |
|
53 | }); | |
| 54 |
|
54 | |||
| 55 | let nextId = 0; |
|
55 | let nextId = 0; | |
| 56 |
|
56 | |||
| 57 | const singletons: any = {}; |
|
57 | const singletons: any = {}; | |
| 58 |
|
58 | |||
| 59 | export class LifetimeManager implements IDestroyable { |
|
59 | export class LifetimeManager implements IDestroyable { | |
| 60 | private _cleanup: (() => void)[] = []; |
|
60 | private _cleanup: (() => void)[] = []; | |
| 61 | private _cache: MapOf<any> = {}; |
|
61 | private _cache: MapOf<any> = {}; | |
| 62 | private _destroyed = false; |
|
62 | private _destroyed = false; | |
| 63 |
|
63 | |||
| 64 | private _pending: MapOf<boolean> = {}; |
|
64 | private _pending: MapOf<boolean> = {}; | |
| 65 |
|
65 | |||
| 66 | create(): ILifetime { |
|
66 | create(): ILifetime { | |
| 67 | const self = this; |
|
67 | const self = this; | |
| 68 | const id = ++nextId; |
|
68 | const id = ++nextId; | |
| 69 | return { |
|
69 | return { | |
| 70 | has() { |
|
70 | has() { | |
| 71 | return (id in self._cache); |
|
71 | return (id in self._cache); | |
| 72 | }, |
|
72 | }, | |
| 73 |
|
73 | |||
| 74 | get() { |
|
74 | get() { | |
| 75 | const t = self._cache[id]; |
|
75 | const t = self._cache[id]; | |
| 76 | if (t === undefined) |
|
76 | if (t === undefined) | |
| 77 | throw new Error(`The item with with the key ${id} isn't found`); |
|
77 | throw new Error(`The item with with the key ${id} isn't found`); | |
| 78 | return t; |
|
78 | return t; | |
| 79 | }, |
|
79 | }, | |
| 80 |
|
80 | |||
| 81 | initialize() { |
|
81 | initialize() { | |
| 82 | if (self._pending[id]) |
|
82 | if (self._pending[id]) | |
| 83 | throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`); |
|
83 | throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`); | |
| 84 | self._pending[id] = true; |
|
84 | self._pending[id] = true; | |
| 85 | }, |
|
85 | }, | |
| 86 |
|
86 | |||
| 87 | store(item: any, cleanup?: (item: any) => void) { |
|
87 | store(item: any, cleanup?: (item: any) => void) { | |
| 88 | argumentNotNull(id, "id"); |
|
88 | argumentNotNull(id, "id"); | |
| 89 | argumentNotNull(item, "item"); |
|
89 | argumentNotNull(item, "item"); | |
| 90 |
|
90 | |||
| 91 | if (this.has()) |
|
91 | if (this.has()) | |
| 92 | throw new Error(`The item with with the key ${id} already registered with this lifetime manager`); |
|
92 | throw new Error(`The item with with the key ${id} already registered with this lifetime manager`); | |
| 93 | delete self._pending[id]; |
|
93 | delete self._pending[id]; | |
| 94 |
|
94 | |||
| 95 | self._cache[id] = item; |
|
95 | self._cache[id] = item; | |
| 96 |
|
96 | |||
| 97 | if (self._destroyed) |
|
97 | if (self._destroyed) | |
| 98 | throw new Error("Lifetime manager is destroyed"); |
|
98 | throw new Error("Lifetime manager is destroyed"); | |
| 99 | if (cleanup) { |
|
99 | if (cleanup) { | |
| 100 | self._cleanup.push(() => cleanup(item)); |
|
100 | self._cleanup.push(() => cleanup(item)); | |
| 101 | } else if (isDestroyable(item)) { |
|
101 | } else if (isDestroyable(item)) { | |
| 102 | self._cleanup.push(() => item.destroy()); |
|
102 | self._cleanup.push(() => item.destroy()); | |
| 103 | } |
|
103 | } | |
| 104 | } |
|
104 | } | |
| 105 | }; |
|
105 | }; | |
| 106 | } |
|
106 | } | |
| 107 |
|
107 | |||
| 108 | destroy() { |
|
108 | destroy() { | |
| 109 | if (!this._destroyed) { |
|
109 | if (!this._destroyed) { | |
| 110 | this._destroyed = true; |
|
110 | this._destroyed = true; | |
| 111 | this._cleanup.forEach(safeCall); |
|
111 | this._cleanup.forEach(safeCall); | |
| 112 | this._cleanup.length = 0; |
|
112 | this._cleanup.length = 0; | |
| 113 | } |
|
113 | } | |
| 114 | } |
|
114 | } | |
| 115 |
|
115 | |||
| 116 | static empty(): ILifetime { |
|
116 | static empty(): ILifetime { | |
| 117 | return emptyLifetime; |
|
117 | return emptyLifetime; | |
| 118 | } |
|
118 | } | |
| 119 |
|
119 | |||
| 120 | static hierarchyLifetime() { |
|
120 | static hierarchyLifetime() { | |
| 121 | let _lifetime = unknownLifetime; |
|
121 | let _lifetime = unknownLifetime; | |
| 122 | return { |
|
122 | return { | |
| 123 | initialize(context: ActivationContext<any>) { |
|
123 | initialize(context: ActivationContext<any>) { | |
| 124 | if (_lifetime !== unknownLifetime) |
|
124 | if (_lifetime !== unknownLifetime) | |
| 125 | throw new Error("Cyclic reference activation detected"); |
|
125 | throw new Error("Cyclic reference activation detected"); | |
| 126 |
|
126 | |||
| 127 | _lifetime = context.getContainer().getLifetimeManager().create(); |
|
127 | _lifetime = context.getContainer().getLifetimeManager().create(); | |
| 128 | }, |
|
128 | }, | |
| 129 | get() { |
|
129 | get() { | |
| 130 | return _lifetime.get(); |
|
130 | return _lifetime.get(); | |
| 131 | }, |
|
131 | }, | |
| 132 | has() { |
|
132 | has() { | |
| 133 | return _lifetime.has(); |
|
133 | return _lifetime.has(); | |
| 134 | }, |
|
134 | }, | |
| 135 | store(item: any, cleanup?: (item: any) => void) { |
|
135 | store(item: any, cleanup?: (item: any) => void) { | |
| 136 | return _lifetime.store(item, cleanup); |
|
136 | return _lifetime.store(item, cleanup); | |
| 137 | }, |
|
137 | }, | |
| 138 | toString() { |
|
138 | toString() { | |
| 139 | return `[object HierarchyLifetime, has=${this.has()}]`; |
|
139 | return `[object HierarchyLifetime, has=${this.has()}]`; | |
| 140 | } |
|
140 | } | |
| 141 | }; |
|
141 | }; | |
| 142 | } |
|
142 | } | |
| 143 |
|
143 | |||
| 144 | static contextLifetime() { |
|
144 | static contextLifetime() { | |
| 145 | let _lifetime = unknownLifetime; |
|
145 | let _lifetime = unknownLifetime; | |
| 146 | return { |
|
146 | return { | |
| 147 | initialize(context: ActivationContext<any>) { |
|
147 | initialize(context: ActivationContext<any>) { | |
| 148 | if (_lifetime !== unknownLifetime) |
|
148 | if (_lifetime !== unknownLifetime) | |
| 149 | throw new Error("Cyclic reference detected"); |
|
149 | throw new Error("Cyclic reference detected"); | |
| 150 | _lifetime = context.createLifetime(); |
|
150 | _lifetime = context.createLifetime(); | |
| 151 | }, |
|
151 | }, | |
| 152 | get() { |
|
152 | get() { | |
| 153 | return _lifetime.get(); |
|
153 | return _lifetime.get(); | |
| 154 | }, |
|
154 | }, | |
| 155 | has() { |
|
155 | has() { | |
| 156 | return _lifetime.has(); |
|
156 | return _lifetime.has(); | |
| 157 | }, |
|
157 | }, | |
| 158 | store(item: any) { |
|
158 | store(item: any) { | |
| 159 | _lifetime.store(item); |
|
159 | _lifetime.store(item); | |
| 160 | }, |
|
160 | }, | |
| 161 | toString() { |
|
161 | toString() { | |
| 162 | return `[object ContextLifetime, has=${this.has()}]`; |
|
162 | return `[object ContextLifetime, has=${this.has()}]`; | |
| 163 | } |
|
163 | } | |
| 164 | }; |
|
164 | }; | |
| 165 | } |
|
165 | } | |
| 166 |
|
166 | |||
| 167 | static singletonLifetime(typeId: string) { |
|
167 | static singletonLifetime(typeId: string) { | |
| 168 | argumentNotEmptyString(typeId, "typeId"); |
|
168 | argumentNotEmptyString(typeId, "typeId"); | |
| 169 | let pending = false; |
|
169 | let pending = false; | |
| 170 | return { |
|
170 | return { | |
| 171 | has() { |
|
171 | has() { | |
| 172 | return typeId in singletons; |
|
172 | return typeId in singletons; | |
| 173 | }, |
|
173 | }, | |
| 174 | get() { |
|
174 | get() { | |
| 175 | if (!this.has()) |
|
175 | if (!this.has()) | |
| 176 | throw new Error(`The instance ${typeId} doesn't exists`); |
|
176 | throw new Error(`The instance ${typeId} doesn't exists`); | |
| 177 | return singletons[typeId]; |
|
177 | return singletons[typeId]; | |
| 178 | }, |
|
178 | }, | |
| 179 | initialize() { |
|
179 | initialize() { | |
| 180 | if (pending) |
|
180 | if (pending) | |
| 181 | throw new Error("Cyclic reference detected"); |
|
181 | throw new Error("Cyclic reference detected"); | |
| 182 | pending = true; |
|
182 | pending = true; | |
| 183 | }, |
|
183 | }, | |
| 184 | store(item: any) { |
|
184 | store(item: any) { | |
| 185 | singletons[typeId] = item; |
|
185 | singletons[typeId] = item; | |
| 186 | pending = false; |
|
186 | pending = false; | |
| 187 | }, |
|
187 | }, | |
| 188 | toString() { |
|
188 | toString() { | |
| 189 | return `[object SingletonLifetime, has=${this.has()}, typeId=${typeId}]`; |
|
189 | return `[object SingletonLifetime, has=${this.has()}, typeId=${typeId}]`; | |
| 190 | } |
|
190 | } | |
| 191 | }; |
|
191 | }; | |
| 192 | } |
|
192 | } | |
| 193 |
|
193 | |||
| 194 | static containerLifetime(container: ServiceContainer<any>) { |
|
194 | static containerLifetime(container: ServiceContainer<any>) { | |
| 195 | let _lifetime = unknownLifetime; |
|
195 | let _lifetime = unknownLifetime; | |
| 196 | return { |
|
196 | return { | |
| 197 | initialize(context: ActivationContext<any>) { |
|
197 | initialize(context: ActivationContext<any>) { | |
| 198 | if (_lifetime !== unknownLifetime) |
|
198 | if (_lifetime !== unknownLifetime) | |
| 199 | throw new Error("Cyclic reference detected"); |
|
199 | throw new Error("Cyclic reference detected"); | |
| 200 | _lifetime = container.getLifetimeManager().create(); |
|
200 | _lifetime = container.getLifetimeManager().create(); | |
| 201 | }, |
|
201 | }, | |
| 202 | get() { |
|
202 | get() { | |
| 203 | return _lifetime.get(); |
|
203 | return _lifetime.get(); | |
| 204 | }, |
|
204 | }, | |
| 205 | has() { |
|
205 | has() { | |
| 206 | return _lifetime.has(); |
|
206 | return _lifetime.has(); | |
| 207 | }, |
|
207 | }, | |
| 208 | store(item: any) { |
|
208 | store(item: any) { | |
| 209 | _lifetime.store(item); |
|
209 | _lifetime.store(item); | |
| 210 | }, |
|
210 | }, | |
| 211 | toString() { |
|
211 | toString() { | |
| 212 | return `[object ContainerLifetime, has=${_lifetime.has()}]` |
|
212 | return `[object ContainerLifetime, has=${_lifetime.has()}]`; | |
| 213 | } |
|
213 | } | |
| 214 | }; |
|
214 | }; | |
| 215 | } |
|
215 | } | |
| 216 | } |
|
216 | } | |
| @@ -1,126 +1,164 | |||||
| 1 | export interface Constructor<T = {}> { |
|
1 | export interface Constructor<T = {}> { | |
| 2 | new(...args: any[]): T; |
|
2 | new(...args: any[]): T; | |
| 3 | prototype: T; |
|
3 | prototype: T; | |
| 4 | } |
|
4 | } | |
| 5 |
|
5 | |||
| 6 | export type PromiseOrValue<T> = T | PromiseLike<T>; |
|
6 | export type PromiseOrValue<T> = T | PromiseLike<T>; | |
| 7 |
|
7 | |||
| 8 | export type Factory<T = {}> = (...args: any[]) => T; |
|
8 | export type Factory<T = {}> = (...args: any[]) => T; | |
| 9 |
|
9 | |||
| 10 | export type Predicate<T = any> = (x: T) => boolean; |
|
10 | export type Predicate<T = any> = (x: T) => boolean; | |
| 11 |
|
11 | |||
| 12 | export type MatchingMemberKeys<T, U> = { [K in keyof T]: T[K] extends U ? K : never}[keyof T]; |
|
12 | export type MatchingMemberKeys<T, U> = { [K in keyof T]: T[K] extends U ? K : never}[keyof T]; | |
| 13 |
|
13 | |||
| 14 | export type NotMatchingMemberKeys<T, U> = { [K in keyof T]: T[K] extends U ? never : K}[keyof T]; |
|
14 | export type NotMatchingMemberKeys<T, U> = { [K in keyof T]: T[K] extends U ? never : K}[keyof T]; | |
| 15 |
|
15 | |||
| 16 | export type ExtractMembers<T, U> = Pick<T, MatchingMemberKeys<T, U>>; |
|
16 | export type ExtractMembers<T, U> = Pick<T, MatchingMemberKeys<T, U>>; | |
| 17 |
|
17 | |||
| 18 | export type ExcludeMembers<T, U> = Pick<T, NotMatchingMemberKeys<T, U>>; |
|
18 | export type ExcludeMembers<T, U> = Pick<T, NotMatchingMemberKeys<T, U>>; | |
| 19 |
|
19 | |||
| 20 | export interface MapOf<T> { |
|
20 | export interface MapOf<T> { | |
| 21 | [key: string]: T; |
|
21 | [key: string]: T; | |
| 22 | } |
|
22 | } | |
| 23 |
|
23 | |||
| 24 | export interface IDestroyable { |
|
24 | export interface IDestroyable { | |
| 25 | destroy(): void; |
|
25 | destroy(): void; | |
| 26 | } |
|
26 | } | |
| 27 |
|
27 | |||
| 28 | export interface IRemovable { |
|
28 | export interface IRemovable { | |
| 29 | remove(): void; |
|
29 | remove(): void; | |
| 30 | } |
|
30 | } | |
| 31 |
|
31 | |||
|
|
32 | /** | |||
|
|
33 | * Interface for the cancellation token. Cancellation token is | |||
|
|
34 | * a marker indicating that the cancellation was requested, it | |||
|
|
35 | * is up to the operation to decide whether to interrupt or | |||
|
|
36 | * to complete its execution. | |||
|
|
37 | * | |||
|
|
38 | * This interface defines several methods of interaction with | |||
|
|
39 | * the cancellation i.e. either to poll its status or to react | |||
|
|
40 | * through the callback. | |||
|
|
41 | */ | |||
| 32 | export interface ICancellation { |
|
42 | export interface ICancellation { | |
|
|
43 | /** | |||
|
|
44 | * Throws an exception if the cancellation has been requested, | |||
|
|
45 | * otherwise does nothing. | |||
|
|
46 | */ | |||
| 33 | throwIfRequested(): void; |
|
47 | throwIfRequested(): void; | |
|
|
48 | ||||
|
|
49 | /** | |||
|
|
50 | * Checks whether the cancellation is requested. | |||
|
|
51 | * @returns true is the cancellation has been requested, | |||
|
|
52 | * otherwise returns false. | |||
|
|
53 | */ | |||
| 34 | isRequested(): boolean; |
|
54 | isRequested(): boolean; | |
|
|
55 | ||||
|
|
56 | /** | |||
|
|
57 | * Checks the ability of the token to be signaled. | |||
|
|
58 | * | |||
|
|
59 | * @returns true if the token is able to request | |||
|
|
60 | * the cancellation, false otherwise. | |||
|
|
61 | */ | |||
| 35 | isSupported(): boolean; |
|
62 | isSupported(): boolean; | |
|
|
63 | ||||
|
|
64 | /** | |||
|
|
65 | * Registers the callback to be called when the cancellation | |||
|
|
66 | * is requested. | |||
|
|
67 | * | |||
|
|
68 | * @param cb The callback which receives the reason of the | |||
|
|
69 | * cancellation. | |||
|
|
70 | * @returns The subscription, after the operation is completed | |||
|
|
71 | * it should unregister the callback to free resources by | |||
|
|
72 | * calling the `destroy()` method of the subscription. | |||
|
|
73 | */ | |||
| 36 | register(cb: (e: any) => void): IDestroyable; |
|
74 | register(cb: (e: any) => void): IDestroyable; | |
| 37 | } |
|
75 | } | |
| 38 |
|
76 | |||
| 39 | /** |
|
77 | /** | |
| 40 | * ΠΠ½ΡΠ΅ΡΡΠ΅ΠΉΡ ΠΏΠΎΠ΄Π΄Π΅ΡΠΆΠΈΠ²Π°ΡΡΠΈΠΉ Π°ΡΠΈΠ½Ρ ΡΠΎΠ½Π½ΡΡ Π°ΠΊΡΠΈΠ²Π°ΡΠΈΡ |
|
78 | * ΠΠ½ΡΠ΅ΡΡΠ΅ΠΉΡ ΠΏΠΎΠ΄Π΄Π΅ΡΠΆΠΈΠ²Π°ΡΡΠΈΠΉ Π°ΡΠΈΠ½Ρ ΡΠΎΠ½Π½ΡΡ Π°ΠΊΡΠΈΠ²Π°ΡΠΈΡ | |
| 41 | */ |
|
79 | */ | |
| 42 | export interface IActivatable { |
|
80 | export interface IActivatable { | |
| 43 | /** |
|
81 | /** | |
| 44 | * @returns Boolean indicates the current state |
|
82 | * @returns Boolean indicates the current state | |
| 45 | */ |
|
83 | */ | |
| 46 | isActive(): boolean; |
|
84 | isActive(): boolean; | |
| 47 |
|
85 | |||
| 48 | /** |
|
86 | /** | |
| 49 | * Starts the component activation |
|
87 | * Starts the component activation | |
| 50 | * @param ct cancellation token for this operation |
|
88 | * @param ct cancellation token for this operation | |
| 51 | */ |
|
89 | */ | |
| 52 | activate(ct?: ICancellation): Promise<void>; |
|
90 | activate(ct?: ICancellation): Promise<void>; | |
| 53 |
|
91 | |||
| 54 | /** |
|
92 | /** | |
| 55 | * Starts the component deactivation |
|
93 | * Starts the component deactivation | |
| 56 | * @param ct cancellation token for this operation |
|
94 | * @param ct cancellation token for this operation | |
| 57 | */ |
|
95 | */ | |
| 58 | deactivate(ct?: ICancellation): Promise<void>; |
|
96 | deactivate(ct?: ICancellation): Promise<void>; | |
| 59 |
|
97 | |||
| 60 | /** |
|
98 | /** | |
| 61 | * Sets the activation controller for this component |
|
99 | * Sets the activation controller for this component | |
| 62 | * @param controller The activation controller |
|
100 | * @param controller The activation controller | |
| 63 | * |
|
101 | * | |
| 64 | * Activation controller checks whether this component |
|
102 | * Activation controller checks whether this component | |
| 65 | * can be activated and manages the active state of the |
|
103 | * can be activated and manages the active state of the | |
| 66 | * component |
|
104 | * component | |
| 67 | */ |
|
105 | */ | |
| 68 | setActivationController(controller: IActivationController): void; |
|
106 | setActivationController(controller: IActivationController): void; | |
| 69 |
|
107 | |||
| 70 | /** Indicates whether this component has an activation controller */ |
|
108 | /** Indicates whether this component has an activation controller */ | |
| 71 | hasActivationController(): boolean; |
|
109 | hasActivationController(): boolean; | |
| 72 |
|
110 | |||
| 73 | /** |
|
111 | /** | |
| 74 | * Gets the current activation controller for this component |
|
112 | * Gets the current activation controller for this component | |
| 75 | */ |
|
113 | */ | |
| 76 | getActivationController(): IActivationController; |
|
114 | getActivationController(): IActivationController; | |
| 77 | } |
|
115 | } | |
| 78 |
|
116 | |||
| 79 | export interface IActivationController { |
|
117 | export interface IActivationController { | |
| 80 | activating(component: IActivatable, ct?: ICancellation): Promise<void>; |
|
118 | activating(component: IActivatable, ct?: ICancellation): Promise<void>; | |
| 81 |
|
119 | |||
| 82 | activated(component: IActivatable, ct?: ICancellation): Promise<void>; |
|
120 | activated(component: IActivatable, ct?: ICancellation): Promise<void>; | |
| 83 |
|
121 | |||
| 84 | deactivating(component: IActivatable, ct?: ICancellation): Promise<void>; |
|
122 | deactivating(component: IActivatable, ct?: ICancellation): Promise<void>; | |
| 85 |
|
123 | |||
| 86 | deactivated(component: IActivatable, ct?: ICancellation): Promise<void>; |
|
124 | deactivated(component: IActivatable, ct?: ICancellation): Promise<void>; | |
| 87 |
|
125 | |||
| 88 | deactivate(ct?: ICancellation): Promise<void>; |
|
126 | deactivate(ct?: ICancellation): Promise<void>; | |
| 89 |
|
127 | |||
| 90 | activate(component: IActivatable, ct?: ICancellation): Promise<void>; |
|
128 | activate(component: IActivatable, ct?: ICancellation): Promise<void>; | |
| 91 |
|
129 | |||
| 92 | hasActive(): boolean; |
|
130 | hasActive(): boolean; | |
| 93 |
|
131 | |||
| 94 | getActive(): IActivatable; |
|
132 | getActive(): IActivatable; | |
| 95 | } |
|
133 | } | |
| 96 |
|
134 | |||
| 97 | export interface IAsyncComponent { |
|
135 | export interface IAsyncComponent { | |
| 98 | getCompletion(): Promise<void>; |
|
136 | getCompletion(): Promise<void>; | |
| 99 | } |
|
137 | } | |
| 100 |
|
138 | |||
| 101 | export interface ICancellable { |
|
139 | export interface ICancellable { | |
| 102 | cancel(reason?: any): void; |
|
140 | cancel(reason?: any): void; | |
| 103 | } |
|
141 | } | |
| 104 |
|
142 | |||
| 105 | export interface IObservable<T> { |
|
143 | export interface IObservable<T> { | |
| 106 | on(next: (x: T) => void, error?: (e: any) => void, complete?: () => void): IDestroyable; |
|
144 | on(next: (x: T) => void, error?: (e: any) => void, complete?: () => void): IDestroyable; | |
| 107 | next(ct?: ICancellation): Promise<T>; |
|
145 | next(ct?: ICancellation): Promise<T>; | |
| 108 | } |
|
146 | } | |
| 109 |
|
147 | |||
| 110 | export interface IObserver<T> { |
|
148 | export interface IObserver<T> { | |
| 111 | next(event: T): void; |
|
149 | next(event: T): void; | |
| 112 |
|
150 | |||
| 113 | error(e: any): void; |
|
151 | error(e: any): void; | |
| 114 |
|
152 | |||
| 115 | complete(): void; |
|
153 | complete(): void; | |
| 116 | } |
|
154 | } | |
| 117 |
|
155 | |||
| 118 | export interface TextWriter { |
|
156 | export interface TextWriter { | |
| 119 | write(obj: any): void; |
|
157 | write(obj: any): void; | |
| 120 | write(format: string, ...args: any[]): void; |
|
158 | write(format: string, ...args: any[]): void; | |
| 121 |
|
159 | |||
| 122 | writeLine(obj?: any): void; |
|
160 | writeLine(obj?: any): void; | |
| 123 | writeLine(format: string, ...args: any[]): void; |
|
161 | writeLine(format: string, ...args: any[]): void; | |
| 124 |
|
162 | |||
| 125 | writeValue(value: any, spec?: string): void; |
|
163 | writeValue(value: any, spec?: string): void; | |
| 126 | } |
|
164 | } | |
| @@ -1,80 +1,80 | |||||
| 1 | import { test } from "./TestTraits"; |
|
1 | import { test } from "./TestTraits"; | |
| 2 | import { fluent } from "../di/traits"; |
|
2 | import { fluent } from "../di/traits"; | |
| 3 | import { Bar } from "../mock/Bar"; |
|
3 | import { Bar } from "../mock/Bar"; | |
| 4 | import { Container } from "../di/Container"; |
|
4 | import { Container } from "../di/Container"; | |
| 5 | import { Foo } from "../mock/Foo"; |
|
5 | import { Foo } from "../mock/Foo"; | |
| 6 | import { Box } from "../mock/Box"; |
|
6 | import { Box } from "../mock/Box"; | |
| 7 | import { delay } from "../safe"; |
|
7 | import { delay } from "../safe"; | |
| 8 | import { FooServices, Services } from "../mock/services"; |
|
8 | import { FooServices, Services } from "../mock/services"; | |
| 9 | import { ContainerConfiguration } from "../di/fluent/interfaces"; |
|
9 | import { ContainerConfiguration } from "../di/fluent/interfaces"; | |
| 10 |
|
10 | |||
| 11 | test("Simple fluent config", async t => { |
|
11 | test("Simple fluent config", async t => { | |
| 12 | const config = fluent<{ host: string; bar: Bar; foo: Foo }>() |
|
12 | const config = fluent<{ host: string; bar: Bar; foo: Foo }>() | |
| 13 | .register({ |
|
13 | .register({ | |
| 14 | host: it => it.value("example.com"), |
|
14 | host: it => it.value("example.com"), | |
| 15 | bar: it => it.factory(resolve => new Bar({ host: resolve("host") }, "s-bar")), |
|
15 | bar: it => it.factory(resolve => new Bar({ host: resolve("host") }, "s-bar")), | |
| 16 | foo: it => import("../mock/Foo").then(m => it.lifetime("container").factory(() => new m.Foo())) |
|
16 | foo: it => import("../mock/Foo").then(m => it.lifetime("container").factory(() => new m.Foo())) | |
| 17 | }); |
|
17 | }); | |
| 18 |
|
18 | |||
| 19 | const c1 = new Container<{}>(); |
|
19 | const c1 = new Container<{}>(); | |
| 20 | const container = await config.apply(c1); |
|
20 | const container = await config.apply(c1); | |
| 21 |
|
21 | |||
| 22 | t.equal(container.resolve("host"), "example.com", "The value should be resolved"); |
|
22 | t.equal(container.resolve("host"), "example.com", "The value should be resolved"); | |
| 23 | t.assert(container.resolve("bar"), "The service should de activated"); |
|
23 | t.assert(container.resolve("bar"), "The service should de activated"); | |
| 24 | t.equal(container.resolve("foo"), container.resolve("foo"), "The service should be activated once"); |
|
24 | t.equal(container.resolve("foo"), container.resolve("foo"), "The service should be activated once"); | |
| 25 | }); |
|
25 | }); | |
| 26 |
|
26 | |||
| 27 | test("Nested async configuration", async t => { |
|
27 | test("Nested async configuration", async t => { | |
| 28 | const container = await new Container<{ |
|
28 | const container = await new Container<{ | |
| 29 | foo: Foo; |
|
29 | foo: Foo; | |
| 30 | box: Box<Foo> |
|
30 | box: Box<Foo> | |
| 31 | }>().fluent({ |
|
31 | }>().fluent({ | |
| 32 | foo: it => delay(0).then(() => it.factory(() => new Foo())), |
|
32 | foo: it => delay(0).then(() => it.factory(() => new Foo())), | |
| 33 | box: it => it.lifetime("context").factory($dependency => new Box($dependency("foo"))) |
|
33 | box: it => it.lifetime("context").factory($dependency => new Box($dependency("foo"))) | |
| 34 | }); |
|
34 | }); | |
| 35 |
|
35 | |||
| 36 | t.assert(container.resolve("box").getValue(), "The dependency should be set"); |
|
36 | t.assert(container.resolve("box").getValue(), "The dependency should be set"); | |
| 37 | t.equals(container.resolve("box").getValue(), container.resolve("box").getValue(), "The service should be activated once") |
|
37 | t.equals(container.resolve("box").getValue(), container.resolve("box").getValue(), "The service should be activated once"); | |
| 38 | }); |
|
38 | }); | |
| 39 |
|
39 | |||
| 40 | test("Bad fluent config", async t => { |
|
40 | test("Bad fluent config", async t => { | |
| 41 | try { |
|
41 | try { | |
| 42 | await new Container<{ |
|
42 | await new Container<{ | |
| 43 | foo: Foo; |
|
43 | foo: Foo; | |
| 44 | box: Box<Foo> |
|
44 | box: Box<Foo> | |
| 45 | }>().fluent({ |
|
45 | }>().fluent({ | |
| 46 | foo: it => delay(0).then(() => it.factory(() => new Foo())), |
|
46 | foo: it => delay(0).then(() => it.factory(() => new Foo())), | |
| 47 | box: it => it.lifetime("context") |
|
47 | box: it => it.lifetime("context") | |
| 48 | .override("foo", () => { throw new Error("bad override"); }) |
|
48 | .override("foo", () => { throw new Error("bad override"); }) | |
| 49 | .factory($dependency => new Box($dependency("foo"))) |
|
49 | .factory($dependency => new Box($dependency("foo"))) | |
| 50 | }); |
|
50 | }); | |
| 51 | t.fail("Should throw"); |
|
51 | t.fail("Should throw"); | |
| 52 | } catch (e) { |
|
52 | } catch (e) { | |
| 53 | t.pass("The configuration should fail"); |
|
53 | t.pass("The configuration should fail"); | |
| 54 | t.equal(e.message, "bad override", "the error should pass"); |
|
54 | t.equal(e.message, "bad override", "the error should pass"); | |
| 55 | } |
|
55 | } | |
| 56 | }); |
|
56 | }); | |
| 57 |
|
57 | |||
| 58 | test("Load fluent config", async t => { |
|
58 | test("Load fluent config", async t => { | |
| 59 | const container = new Container<Services>(); |
|
59 | const container = new Container<Services>(); | |
| 60 |
|
60 | |||
| 61 | await container.configure("../mock/config", { contextRequire: require }); |
|
61 | await container.configure("../mock/config", { contextRequire: require }); | |
| 62 |
|
62 | |||
| 63 | t.assert(container.resolve("host"), "Should resolve simple value"); |
|
63 | t.assert(container.resolve("host"), "Should resolve simple value"); | |
| 64 | }); |
|
64 | }); | |
| 65 |
|
65 | |||
| 66 | test("Container applyConfig", async t => { |
|
66 | test("Container applyConfig", async t => { | |
| 67 | const container = await new Container<{}>().applyConfig(import("../mock/config")); |
|
67 | const container = await new Container<{}>().applyConfig(import("../mock/config")); | |
| 68 |
|
68 | |||
| 69 | t.assert(container.resolve("host"), "Should resolve simple value"); |
|
69 | t.assert(container.resolve("host"), "Should resolve simple value"); | |
| 70 | }); |
|
70 | }); | |
| 71 |
|
71 | |||
| 72 | test("Child container config", async t => { |
|
72 | test("Child container config", async t => { | |
| 73 | const container = await new Container<{}>().applyConfig(import("../mock/config")); |
|
73 | const container = await new Container<{}>().applyConfig(import("../mock/config")); | |
| 74 |
|
74 | |||
| 75 | const fooServices: ContainerConfiguration<FooServices> = (await import("../mock/config2")).default; |
|
75 | const fooServices: ContainerConfiguration<FooServices> = (await import("../mock/config2")).default; | |
| 76 |
|
76 | |||
| 77 | const child = await fooServices.apply(container.createChildContainer()); |
|
77 | const child = await fooServices.apply(container.createChildContainer()); | |
| 78 |
|
78 | |||
| 79 | t.assert(child.resolve("foo"), "foo should be resolved"); |
|
79 | t.assert(child.resolve("foo"), "foo should be resolved"); | |
| 80 | }); |
|
80 | }); | |
General Comments 0
You need to be logged in to leave comments.
Login now
