ditox 2.4.1 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -13
- package/dist/browser/index.d.ts +9 -8
- package/dist/browser/index.js +1 -1
- package/dist/cjs/container.d.ts +6 -8
- package/dist/cjs/container.js +47 -26
- package/dist/cjs/container.js.map +1 -1
- package/dist/cjs/modules.d.ts +4 -3
- package/dist/cjs/modules.js +54 -20
- package/dist/cjs/modules.js.map +1 -1
- package/dist/esm/container.d.ts +6 -8
- package/dist/esm/container.js +46 -24
- package/dist/esm/container.js.map +1 -1
- package/dist/esm/modules.d.ts +4 -3
- package/dist/esm/modules.js +53 -19
- package/dist/esm/modules.js.map +1 -1
- package/dist/umd/index.d.ts +9 -8
- package/dist/umd/index.js +1 -1
- package/package.json +2 -3
- package/src/container.test.ts +132 -52
- package/src/container.ts +62 -29
- package/src/modules.test.ts +69 -6
- package/src/modules.ts +69 -27
package/src/container.test.ts
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CONTAINER,
|
|
3
3
|
createContainer,
|
|
4
|
-
|
|
5
|
-
FAKE_FACTORY,
|
|
6
|
-
PARENT_CONTAINER,
|
|
4
|
+
PARENT_CONTAINERS,
|
|
7
5
|
ResolverError,
|
|
8
6
|
} from './container';
|
|
9
|
-
import {injectable} from './utils';
|
|
10
7
|
import {optional, token} from './tokens';
|
|
8
|
+
import {injectable} from './utils';
|
|
11
9
|
|
|
12
10
|
const NUMBER = token<number>('number');
|
|
13
11
|
const STRING = token<string>('string');
|
|
@@ -44,10 +42,10 @@ describe('Container', () => {
|
|
|
44
42
|
|
|
45
43
|
const custom = createContainer();
|
|
46
44
|
container.bindValue(CONTAINER, custom);
|
|
47
|
-
container.bindValue(
|
|
45
|
+
container.bindValue(PARENT_CONTAINERS, [custom]);
|
|
48
46
|
|
|
49
47
|
expect(container.get(CONTAINER)).toBe(container);
|
|
50
|
-
expect(container.get(
|
|
48
|
+
expect(container.get(PARENT_CONTAINERS)).toEqual([parent]);
|
|
51
49
|
});
|
|
52
50
|
});
|
|
53
51
|
|
|
@@ -120,24 +118,40 @@ describe('Container', () => {
|
|
|
120
118
|
scope: 'scoped',
|
|
121
119
|
});
|
|
122
120
|
|
|
123
|
-
expect(container.get(NUMBER)).toBe(
|
|
124
|
-
expect(container.get(NUMBER)).toBe(
|
|
125
|
-
expect(parent.get(NUMBER)).toBe(
|
|
126
|
-
expect(container.get(NUMBER)).toBe(
|
|
121
|
+
expect(container.get(NUMBER)).toBe(11);
|
|
122
|
+
expect(container.get(NUMBER)).toBe(11);
|
|
123
|
+
expect(parent.get(NUMBER)).toBe(11);
|
|
124
|
+
expect(container.get(NUMBER)).toBe(11);
|
|
125
|
+
|
|
126
|
+
expect(factory).toBeCalledTimes(1);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should inherit a factory with "scoped" scope', async () => {
|
|
130
|
+
const parentParent = createContainer();
|
|
131
|
+
let counter = 0;
|
|
132
|
+
const factory = jest.fn(() => ++counter);
|
|
133
|
+
|
|
134
|
+
parentParent.bindFactory(NUMBER, factory);
|
|
135
|
+
|
|
136
|
+
expect(parentParent.get(NUMBER)).toBe(1);
|
|
137
|
+
|
|
138
|
+
const parent = createContainer(parentParent);
|
|
139
|
+
|
|
140
|
+
parent.bindFactory(NUMBER, injectable(factory), {
|
|
141
|
+
scope: 'scoped',
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
expect(parent.get(NUMBER)).toBe(2);
|
|
145
|
+
|
|
146
|
+
const container = createContainer(parent);
|
|
147
|
+
|
|
148
|
+
expect(container.get(NUMBER)).toBe(2);
|
|
149
|
+
expect(parent.get(NUMBER)).toBe(2);
|
|
127
150
|
|
|
128
151
|
expect(factory).toBeCalledTimes(2);
|
|
129
152
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const fakeContext = internalFactories.get(NUMBER.symbol);
|
|
133
|
-
expect(fakeContext?.factory).toBe(FAKE_FACTORY);
|
|
134
|
-
expect(() => fakeContext?.factory?.(container)).toThrowError(
|
|
135
|
-
'FAKE_FACTORY',
|
|
136
|
-
);
|
|
137
|
-
const internalOnRemoved =
|
|
138
|
-
fakeContext?.options?.scope === 'scoped' &&
|
|
139
|
-
fakeContext?.options?.onRemoved;
|
|
140
|
-
expect(internalOnRemoved).toBeUndefined();
|
|
153
|
+
container.removeAll();
|
|
154
|
+
expect(parent.get(NUMBER)).toBe(2);
|
|
141
155
|
});
|
|
142
156
|
|
|
143
157
|
it('should bind a factory with "singleton" scope by default', () => {
|
|
@@ -165,6 +179,9 @@ describe('Container', () => {
|
|
|
165
179
|
const container1 = createContainer(parent);
|
|
166
180
|
const container2 = createContainer(parent);
|
|
167
181
|
const container3 = createContainer(parent);
|
|
182
|
+
const childContainer1 = createContainer(container1);
|
|
183
|
+
const childContainer2 = createContainer(container2);
|
|
184
|
+
const childContainer3 = createContainer(container3);
|
|
168
185
|
|
|
169
186
|
const START = token<number>();
|
|
170
187
|
parent.bindValue(START, 0);
|
|
@@ -178,13 +195,33 @@ describe('Container', () => {
|
|
|
178
195
|
scope: 'scoped',
|
|
179
196
|
});
|
|
180
197
|
|
|
181
|
-
|
|
198
|
+
container1.bindFactory(NUMBER, injectable(factory, START), {
|
|
199
|
+
scope: 'scoped',
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
container2.bindFactory(NUMBER, injectable(factory, START), {
|
|
203
|
+
scope: 'scoped',
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
container3.bindFactory(NUMBER, injectable(factory, START), {
|
|
207
|
+
scope: 'scoped',
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
childContainer1.bindValue(START, 1);
|
|
211
|
+
childContainer2.bindValue(START, 2);
|
|
212
|
+
childContainer3.bindValue(START, 3);
|
|
213
|
+
|
|
182
214
|
expect(container1.get(NUMBER)).toBe(11);
|
|
183
215
|
expect(container2.get(NUMBER)).toBe(22);
|
|
184
|
-
expect(
|
|
216
|
+
expect(container3.get(NUMBER)).toBe(33);
|
|
217
|
+
expect(parent.get(NUMBER)).toBe(4);
|
|
185
218
|
expect(container1.get(NUMBER)).toBe(11);
|
|
186
219
|
expect(container2.get(NUMBER)).toBe(22);
|
|
187
|
-
expect(container3.get(NUMBER)).toBe(
|
|
220
|
+
expect(container3.get(NUMBER)).toBe(33);
|
|
221
|
+
|
|
222
|
+
expect(childContainer1.get(NUMBER)).toBe(11);
|
|
223
|
+
expect(childContainer2.get(NUMBER)).toBe(22);
|
|
224
|
+
expect(childContainer3.get(NUMBER)).toBe(33);
|
|
188
225
|
|
|
189
226
|
expect(factory).toBeCalledTimes(4);
|
|
190
227
|
});
|
|
@@ -228,12 +265,11 @@ describe('Container', () => {
|
|
|
228
265
|
const parent = createContainer();
|
|
229
266
|
const container = createContainer(parent);
|
|
230
267
|
|
|
231
|
-
|
|
232
|
-
container.bindFactory(
|
|
233
|
-
container.bindFactory(PARENT_CONTAINER, factory);
|
|
268
|
+
container.bindFactory(CONTAINER, () => createContainer());
|
|
269
|
+
container.bindFactory(PARENT_CONTAINERS, () => [createContainer()]);
|
|
234
270
|
|
|
235
271
|
expect(container.get(CONTAINER)).toBe(container);
|
|
236
|
-
expect(container.get(
|
|
272
|
+
expect(container.get(PARENT_CONTAINERS)).toEqual([parent]);
|
|
237
273
|
});
|
|
238
274
|
});
|
|
239
275
|
|
|
@@ -284,25 +320,13 @@ describe('Container', () => {
|
|
|
284
320
|
parent.bindFactory(NUMBER, factory, {scope: 'scoped', onRemoved});
|
|
285
321
|
|
|
286
322
|
expect(parent.get(NUMBER)).toBe(1);
|
|
287
|
-
expect(container.get(NUMBER)).toBe(
|
|
288
|
-
|
|
289
|
-
// Check for using the internal FAKE_FACTORY
|
|
290
|
-
const internalFactories = container.resolve(FACTORIES_MAP);
|
|
291
|
-
const fakeContext = internalFactories.get(NUMBER.symbol);
|
|
292
|
-
expect(fakeContext?.factory).toBe(FAKE_FACTORY);
|
|
293
|
-
expect(() => fakeContext?.factory?.(container)).toThrowError(
|
|
294
|
-
'FAKE_FACTORY',
|
|
295
|
-
);
|
|
296
|
-
const internalOnRemoved =
|
|
297
|
-
fakeContext?.options?.scope === 'scoped' &&
|
|
298
|
-
fakeContext?.options?.onRemoved;
|
|
299
|
-
expect(internalOnRemoved).toBe(onRemoved);
|
|
323
|
+
expect(container.get(NUMBER)).toBe(1);
|
|
300
324
|
|
|
301
325
|
// Continue the main test
|
|
302
326
|
parent.remove(NUMBER);
|
|
303
327
|
container.remove(NUMBER);
|
|
304
328
|
expect(onRemoved).toHaveBeenNthCalledWith(1, 1);
|
|
305
|
-
expect(onRemoved).
|
|
329
|
+
expect(onRemoved).toHaveBeenCalledTimes(1);
|
|
306
330
|
});
|
|
307
331
|
|
|
308
332
|
it('should remove "transient" factory in case its value has never been resolved', () => {
|
|
@@ -333,10 +357,10 @@ describe('Container', () => {
|
|
|
333
357
|
const container = createContainer(parent);
|
|
334
358
|
|
|
335
359
|
container.remove(CONTAINER);
|
|
336
|
-
container.remove(
|
|
360
|
+
container.remove(PARENT_CONTAINERS);
|
|
337
361
|
|
|
338
362
|
expect(container.get(CONTAINER)).toBe(container);
|
|
339
|
-
expect(container.get(
|
|
363
|
+
expect(container.get(PARENT_CONTAINERS)).toEqual([parent]);
|
|
340
364
|
});
|
|
341
365
|
});
|
|
342
366
|
|
|
@@ -389,7 +413,7 @@ describe('Container', () => {
|
|
|
389
413
|
container.removeAll();
|
|
390
414
|
|
|
391
415
|
expect(container.get(CONTAINER)).toBe(container);
|
|
392
|
-
expect(container.get(
|
|
416
|
+
expect(container.get(PARENT_CONTAINERS)).toEqual([parent]);
|
|
393
417
|
});
|
|
394
418
|
});
|
|
395
419
|
|
|
@@ -477,13 +501,13 @@ describe('Container', () => {
|
|
|
477
501
|
it('should return the parent container for PARENT_CONTAINER token', () => {
|
|
478
502
|
const parent = createContainer();
|
|
479
503
|
const container = createContainer(parent);
|
|
480
|
-
const result = container.get(
|
|
481
|
-
expect(result).
|
|
504
|
+
const result = container.get(PARENT_CONTAINERS);
|
|
505
|
+
expect(result).toEqual([parent]);
|
|
482
506
|
});
|
|
483
507
|
|
|
484
508
|
it('should return "undefined" for PARENT_CONTAINER token in case there is no parent container', () => {
|
|
485
509
|
const container = createContainer();
|
|
486
|
-
const result = container.get(
|
|
510
|
+
const result = container.get(PARENT_CONTAINERS);
|
|
487
511
|
expect(result).toBeUndefined();
|
|
488
512
|
});
|
|
489
513
|
|
|
@@ -572,15 +596,15 @@ describe('Container', () => {
|
|
|
572
596
|
it('should resolve PARENT_CONTAINER as the parent container', () => {
|
|
573
597
|
const parent = createContainer();
|
|
574
598
|
const container = createContainer(parent);
|
|
575
|
-
const result = container.get(
|
|
576
|
-
expect(result).
|
|
599
|
+
const result = container.get(PARENT_CONTAINERS);
|
|
600
|
+
expect(result).toEqual([parent]);
|
|
577
601
|
});
|
|
578
602
|
|
|
579
603
|
it('should throw ResolverError for PARENT_CONTAINER token in case there is no parent container', () => {
|
|
580
604
|
const container = createContainer();
|
|
581
|
-
expect(() => container.resolve(
|
|
605
|
+
expect(() => container.resolve(PARENT_CONTAINERS)).toThrowError(
|
|
582
606
|
new ResolverError(
|
|
583
|
-
`Token "${
|
|
607
|
+
`Token "${PARENT_CONTAINERS.symbol.description}" is not provided`,
|
|
584
608
|
),
|
|
585
609
|
);
|
|
586
610
|
});
|
|
@@ -608,4 +632,60 @@ describe('Container', () => {
|
|
|
608
632
|
expect(container.resolve(t2)).toBe(1);
|
|
609
633
|
});
|
|
610
634
|
});
|
|
635
|
+
|
|
636
|
+
describe('multiple parent containers', () => {
|
|
637
|
+
it('should expose all parent containers in order via PARENT_CONTAINERS', () => {
|
|
638
|
+
const p1 = createContainer();
|
|
639
|
+
const p2 = createContainer();
|
|
640
|
+
const child = createContainer([p1, p2]);
|
|
641
|
+
|
|
642
|
+
expect(child.get(PARENT_CONTAINERS)).toEqual([p1, p2]);
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
it('get(): should return a value from the first parent that provides it', () => {
|
|
646
|
+
const p1 = createContainer();
|
|
647
|
+
const p2 = createContainer();
|
|
648
|
+
p1.bindValue(NUMBER, 1);
|
|
649
|
+
p2.bindValue(NUMBER, 2);
|
|
650
|
+
|
|
651
|
+
const child = createContainer([p1, p2]);
|
|
652
|
+
expect(child.get(NUMBER)).toBe(1);
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
it('get(): should return a value from the next parent when previous parents do not provide it', () => {
|
|
656
|
+
const p1 = createContainer();
|
|
657
|
+
const p2 = createContainer();
|
|
658
|
+
p2.bindValue(NUMBER, 2);
|
|
659
|
+
|
|
660
|
+
const child = createContainer([p1, p2]);
|
|
661
|
+
expect(child.get(NUMBER)).toBe(2);
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
it('resolve(): should resolve a value from the first parent that provides it', () => {
|
|
665
|
+
const p1 = createContainer();
|
|
666
|
+
const p2 = createContainer();
|
|
667
|
+
p1.bindValue(NUMBER, 1);
|
|
668
|
+
p2.bindValue(NUMBER, 2);
|
|
669
|
+
|
|
670
|
+
const child = createContainer([p1, p2]);
|
|
671
|
+
expect(child.resolve(NUMBER)).toBe(1);
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
it('resolve(): should resolve a value from the next parent when previous parents do not provide it', () => {
|
|
675
|
+
const p1 = createContainer();
|
|
676
|
+
const p2 = createContainer();
|
|
677
|
+
p2.bindValue(NUMBER, 2);
|
|
678
|
+
|
|
679
|
+
const child = createContainer([p1, p2]);
|
|
680
|
+
expect(child.resolve(NUMBER)).toBe(2);
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
it('resolve(): should throw if none of the parents provide the value', () => {
|
|
684
|
+
const p1 = createContainer();
|
|
685
|
+
const p2 = createContainer();
|
|
686
|
+
const child = createContainer([p1, p2]);
|
|
687
|
+
|
|
688
|
+
expect(() => child.resolve(NUMBER)).toThrow(ResolverError);
|
|
689
|
+
});
|
|
690
|
+
});
|
|
611
691
|
});
|
package/src/container.ts
CHANGED
|
@@ -20,8 +20,8 @@ export type FactoryScope = 'scoped' | 'singleton' | 'transient';
|
|
|
20
20
|
* Options for factory binding.
|
|
21
21
|
*
|
|
22
22
|
* `scope` types:
|
|
23
|
-
* - `singleton` - **This is the default**. The value is created and cached by the container which
|
|
24
|
-
* - `scoped` - The value is created and cached by the container which
|
|
23
|
+
* - `singleton` - **This is the default**. The value is created and cached by the most distant parent container which owns the factory function.
|
|
24
|
+
* - `scoped` - The value is created and cached by the nearest container which owns the factory function.
|
|
25
25
|
* - `transient` - The value is created every time it is resolved.
|
|
26
26
|
*
|
|
27
27
|
* `scoped` and `singleton` scopes can have `onRemoved` callback. It is called when a token is removed from the container.
|
|
@@ -64,7 +64,7 @@ export type Container = {
|
|
|
64
64
|
get<T>(token: Token<T>): T | undefined;
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
|
-
* Returns a resolved value by the token
|
|
67
|
+
* Returns a resolved value by the token or throws `ResolverError` in case the token is not found.
|
|
68
68
|
*/
|
|
69
69
|
resolve<T>(token: Token<T>): T;
|
|
70
70
|
|
|
@@ -82,7 +82,7 @@ export type Container = {
|
|
|
82
82
|
/** @internal */
|
|
83
83
|
export const CONTAINER: Token<Container> = token('ditox.Container');
|
|
84
84
|
/** @internal */
|
|
85
|
-
export const
|
|
85
|
+
export const PARENT_CONTAINERS: Token<ReadonlyArray<Container>> = token(
|
|
86
86
|
'ditox.ParentContainer',
|
|
87
87
|
);
|
|
88
88
|
/** @internal */
|
|
@@ -91,11 +91,6 @@ export const RESOLVER: Token<Resolver> = token('ditox.Resolver');
|
|
|
91
91
|
/** @internal */
|
|
92
92
|
const NOT_FOUND = Symbol();
|
|
93
93
|
|
|
94
|
-
/** @internal */
|
|
95
|
-
export const FAKE_FACTORY = (): never => {
|
|
96
|
-
throw new Error('FAKE_FACTORY');
|
|
97
|
-
};
|
|
98
|
-
|
|
99
94
|
/** @internal */
|
|
100
95
|
const DEFAULT_SCOPE: FactoryScope = 'singleton';
|
|
101
96
|
|
|
@@ -135,7 +130,7 @@ function getOnRemoved<T>(options: FactoryOptions<T>) {
|
|
|
135
130
|
function isInternalToken<T>(token: Token<T>): boolean {
|
|
136
131
|
return (
|
|
137
132
|
token.symbol === CONTAINER.symbol ||
|
|
138
|
-
token.symbol ===
|
|
133
|
+
token.symbol === PARENT_CONTAINERS.symbol ||
|
|
139
134
|
token.symbol === RESOLVER.symbol
|
|
140
135
|
);
|
|
141
136
|
}
|
|
@@ -145,9 +140,17 @@ function isInternalToken<T>(token: Token<T>): boolean {
|
|
|
145
140
|
*
|
|
146
141
|
* Container can have an optional parent to chain token resolution. The parent is used in case the current container does not have a registered token.
|
|
147
142
|
*
|
|
148
|
-
* @param
|
|
143
|
+
* @param parentArg - Optional parent container or an array of containers.
|
|
149
144
|
*/
|
|
150
|
-
export function createContainer(
|
|
145
|
+
export function createContainer(
|
|
146
|
+
parentArg?: Container | ReadonlyArray<Container>,
|
|
147
|
+
): Container {
|
|
148
|
+
const parents: ReadonlyArray<Container> | undefined = parentArg
|
|
149
|
+
? Array.isArray(parentArg)
|
|
150
|
+
? [...parentArg]
|
|
151
|
+
: [parentArg]
|
|
152
|
+
: undefined;
|
|
153
|
+
|
|
151
154
|
const values: ValuesMap = new Map<symbol, any>();
|
|
152
155
|
const factories: FactoriesMap = new Map<symbol, FactoryContext<any>>();
|
|
153
156
|
|
|
@@ -202,7 +205,7 @@ export function createContainer(parentContainer?: Container): Container {
|
|
|
202
205
|
return (
|
|
203
206
|
values.has(token.symbol) ||
|
|
204
207
|
factories.has(token.symbol) ||
|
|
205
|
-
(
|
|
208
|
+
parentContainersHaveToken(parents, token)
|
|
206
209
|
);
|
|
207
210
|
},
|
|
208
211
|
|
|
@@ -247,14 +250,14 @@ export function createContainer(parentContainer?: Container): Container {
|
|
|
247
250
|
}
|
|
248
251
|
|
|
249
252
|
const factoryContext = factories.get(token.symbol);
|
|
250
|
-
if (factoryContext
|
|
253
|
+
if (factoryContext) {
|
|
251
254
|
const scope = getScope(factoryContext.options);
|
|
252
255
|
|
|
253
256
|
switch (scope) {
|
|
254
257
|
case 'singleton': {
|
|
255
258
|
if (hasValue) {
|
|
256
259
|
return value;
|
|
257
|
-
} else if (
|
|
260
|
+
} else if (parentContainersHaveToken(parents, token)) {
|
|
258
261
|
break;
|
|
259
262
|
} else {
|
|
260
263
|
// Cache the value in the same container where the factory is registered.
|
|
@@ -265,16 +268,14 @@ export function createContainer(parentContainer?: Container): Container {
|
|
|
265
268
|
}
|
|
266
269
|
|
|
267
270
|
case 'scoped': {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
271
|
+
if (hasValue) {
|
|
272
|
+
return value;
|
|
273
|
+
} else {
|
|
274
|
+
// Create a value within the factory's container and cache it.
|
|
275
|
+
const value = factoryContext.factory(container);
|
|
276
|
+
container.bindValue(token, value);
|
|
277
|
+
return value;
|
|
275
278
|
}
|
|
276
|
-
|
|
277
|
-
return value;
|
|
278
279
|
}
|
|
279
280
|
|
|
280
281
|
case 'transient': {
|
|
@@ -288,9 +289,8 @@ export function createContainer(parentContainer?: Container): Container {
|
|
|
288
289
|
return value;
|
|
289
290
|
}
|
|
290
291
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
return parentResolver(token, origin);
|
|
292
|
+
if (parents) {
|
|
293
|
+
return parentContainersResolveToken(parents, token, origin);
|
|
294
294
|
}
|
|
295
295
|
|
|
296
296
|
return NOT_FOUND;
|
|
@@ -314,11 +314,44 @@ export function createContainer(parentContainer?: Container): Container {
|
|
|
314
314
|
values.set(RESOLVER.symbol, resolver);
|
|
315
315
|
values.set(FACTORIES_MAP.symbol, factories);
|
|
316
316
|
|
|
317
|
-
if (
|
|
318
|
-
values.set(
|
|
317
|
+
if (parents) {
|
|
318
|
+
values.set(PARENT_CONTAINERS.symbol, parents);
|
|
319
319
|
}
|
|
320
320
|
}
|
|
321
321
|
|
|
322
322
|
bindInternalTokens();
|
|
323
323
|
return container;
|
|
324
324
|
}
|
|
325
|
+
|
|
326
|
+
function parentContainersHaveToken<T>(
|
|
327
|
+
parents: ReadonlyArray<Container> | undefined,
|
|
328
|
+
token: Token<T>,
|
|
329
|
+
): boolean {
|
|
330
|
+
if (!parents) return false;
|
|
331
|
+
|
|
332
|
+
for (let i = 0; i < parents.length; i++) {
|
|
333
|
+
if (parents[i].hasToken(token)) {
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function parentContainersResolveToken<T>(
|
|
342
|
+
parents: ReadonlyArray<Container>,
|
|
343
|
+
token: Token<T>,
|
|
344
|
+
origin: Container,
|
|
345
|
+
): T | typeof NOT_FOUND {
|
|
346
|
+
for (const parentContainer of parents) {
|
|
347
|
+
const parentResolver = parentContainer.get(RESOLVER);
|
|
348
|
+
if (parentResolver) {
|
|
349
|
+
const resolved = parentResolver(token, origin);
|
|
350
|
+
if (resolved !== NOT_FOUND) {
|
|
351
|
+
return resolved;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return NOT_FOUND;
|
|
357
|
+
}
|
package/src/modules.test.ts
CHANGED
|
@@ -7,8 +7,8 @@ import {
|
|
|
7
7
|
Module,
|
|
8
8
|
ModuleDeclaration,
|
|
9
9
|
} from './modules';
|
|
10
|
-
import {injectable} from './utils';
|
|
11
10
|
import {token} from './tokens';
|
|
11
|
+
import {injectable} from './utils';
|
|
12
12
|
|
|
13
13
|
describe('bindModule()', () => {
|
|
14
14
|
type TestQueries = {getValue: () => number};
|
|
@@ -107,8 +107,8 @@ describe('bindModule()', () => {
|
|
|
107
107
|
const container = createContainer(parent);
|
|
108
108
|
|
|
109
109
|
bindModule(parent, MODULE, {scope: 'scoped'});
|
|
110
|
-
expect(parent.get(MODULE_TOKEN)).
|
|
111
|
-
expect(parent.get(QUERIES_TOKEN)).
|
|
110
|
+
expect(parent.get(MODULE_TOKEN)).toBe(container.get(MODULE_TOKEN));
|
|
111
|
+
expect(parent.get(QUERIES_TOKEN)).toBe(container.get(QUERIES_TOKEN));
|
|
112
112
|
});
|
|
113
113
|
|
|
114
114
|
it('should remove "singleton" module when a container is cleaning', () => {
|
|
@@ -149,11 +149,12 @@ describe('bindModule()', () => {
|
|
|
149
149
|
parent.resolve(MODULE_TOKEN);
|
|
150
150
|
container.resolve(MODULE_TOKEN);
|
|
151
151
|
|
|
152
|
-
parent.removeAll();
|
|
153
|
-
expect(destroy).toBeCalledTimes(1);
|
|
154
|
-
|
|
155
152
|
destroy.mockClear();
|
|
156
153
|
container.removeAll();
|
|
154
|
+
expect(destroy).toBeCalledTimes(0);
|
|
155
|
+
|
|
156
|
+
destroy.mockClear();
|
|
157
|
+
parent.removeAll();
|
|
157
158
|
expect(destroy).toBeCalledTimes(1);
|
|
158
159
|
});
|
|
159
160
|
|
|
@@ -227,6 +228,68 @@ describe('bindModule()', () => {
|
|
|
227
228
|
expect(container.resolve(ARG_TOKEN)).toBe('12');
|
|
228
229
|
expect(container.resolve(RESULT_TOKEN)).toBe('123');
|
|
229
230
|
});
|
|
231
|
+
|
|
232
|
+
it('should call beforeBinding() and afterBinding() in deep-first order', () => {
|
|
233
|
+
const route: string[] = [];
|
|
234
|
+
|
|
235
|
+
const declareTestModule = (
|
|
236
|
+
id: string,
|
|
237
|
+
imports?: ModuleDeclaration<Record<string, any>>[],
|
|
238
|
+
) =>
|
|
239
|
+
declareModule({
|
|
240
|
+
imports,
|
|
241
|
+
beforeBinding: () => route.push(`${id}:before`),
|
|
242
|
+
factory: (container) => {
|
|
243
|
+
imports?.forEach((m) => container.resolve(m.token));
|
|
244
|
+
route.push(`${id}:factory`);
|
|
245
|
+
return {};
|
|
246
|
+
},
|
|
247
|
+
afterBinding: () => route.push(`${id}:after`),
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* m1
|
|
252
|
+
* / | \
|
|
253
|
+
* m2 m3 m4
|
|
254
|
+
* / \ \ |
|
|
255
|
+
* m5 m6 m7
|
|
256
|
+
*/
|
|
257
|
+
const m7 = declareTestModule('m7');
|
|
258
|
+
const m6 = declareTestModule('m6');
|
|
259
|
+
const m5 = declareTestModule('m5');
|
|
260
|
+
const m4 = declareTestModule('m4', [m7]);
|
|
261
|
+
const m3 = declareTestModule('m3', [m7]);
|
|
262
|
+
const m2 = declareTestModule('m2', [m5, m6]);
|
|
263
|
+
const m1 = declareTestModule('m1', [m2, m3, m4]);
|
|
264
|
+
|
|
265
|
+
const container = createContainer();
|
|
266
|
+
bindModule(container, m1);
|
|
267
|
+
container.resolve(m1.token);
|
|
268
|
+
|
|
269
|
+
expect(route).toEqual([
|
|
270
|
+
'm1:before',
|
|
271
|
+
'm2:before',
|
|
272
|
+
'm3:before',
|
|
273
|
+
'm4:before',
|
|
274
|
+
'm5:before',
|
|
275
|
+
'm6:before',
|
|
276
|
+
'm7:before',
|
|
277
|
+
'm7:after',
|
|
278
|
+
'm6:after',
|
|
279
|
+
'm5:after',
|
|
280
|
+
'm4:after',
|
|
281
|
+
'm3:after',
|
|
282
|
+
'm2:after',
|
|
283
|
+
'm1:after',
|
|
284
|
+
'm5:factory',
|
|
285
|
+
'm6:factory',
|
|
286
|
+
'm2:factory',
|
|
287
|
+
'm7:factory',
|
|
288
|
+
'm3:factory',
|
|
289
|
+
'm4:factory',
|
|
290
|
+
'm1:factory',
|
|
291
|
+
]);
|
|
292
|
+
});
|
|
230
293
|
});
|
|
231
294
|
|
|
232
295
|
describe('bindModules()', () => {
|