ditox 3.2.0 → 3.3.1

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.
Files changed (55) hide show
  1. package/README.md +76 -27
  2. package/dist/index.cjs +531 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/{umd/index.d.ts → index.d.cts} +191 -196
  5. package/dist/{browser/index.d.ts → index.d.ts} +191 -196
  6. package/dist/index.js +514 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/index.umd.js +537 -0
  9. package/dist/index.umd.js.map +1 -0
  10. package/package.json +22 -20
  11. package/src/container.test.ts +77 -33
  12. package/src/container.ts +26 -11
  13. package/src/index.ts +22 -24
  14. package/src/modules.test.ts +190 -37
  15. package/src/modules.ts +22 -5
  16. package/src/removal-compatibility.test.ts +259 -0
  17. package/src/removal.test.ts +190 -0
  18. package/src/tokens.test.ts +9 -8
  19. package/src/tokens.ts +2 -2
  20. package/src/utils.test.ts +18 -17
  21. package/src/utils.ts +21 -21
  22. package/dist/browser/index.js +0 -2
  23. package/dist/browser/index.js.map +0 -1
  24. package/dist/cjs/container.d.ts +0 -93
  25. package/dist/cjs/container.js +0 -216
  26. package/dist/cjs/container.js.map +0 -1
  27. package/dist/cjs/index.d.ts +0 -7
  28. package/dist/cjs/index.js +0 -24
  29. package/dist/cjs/index.js.map +0 -1
  30. package/dist/cjs/modules.d.ts +0 -121
  31. package/dist/cjs/modules.js +0 -143
  32. package/dist/cjs/modules.js.map +0 -1
  33. package/dist/cjs/tokens.d.ts +0 -56
  34. package/dist/cjs/tokens.js +0 -20
  35. package/dist/cjs/tokens.js.map +0 -1
  36. package/dist/cjs/utils.d.ts +0 -137
  37. package/dist/cjs/utils.js +0 -156
  38. package/dist/cjs/utils.js.map +0 -1
  39. package/dist/esm/container.d.ts +0 -93
  40. package/dist/esm/container.js +0 -211
  41. package/dist/esm/container.js.map +0 -1
  42. package/dist/esm/index.d.ts +0 -7
  43. package/dist/esm/index.js +0 -5
  44. package/dist/esm/index.js.map +0 -1
  45. package/dist/esm/modules.d.ts +0 -121
  46. package/dist/esm/modules.js +0 -136
  47. package/dist/esm/modules.js.map +0 -1
  48. package/dist/esm/tokens.d.ts +0 -56
  49. package/dist/esm/tokens.js +0 -15
  50. package/dist/esm/tokens.js.map +0 -1
  51. package/dist/esm/utils.d.ts +0 -137
  52. package/dist/esm/utils.js +0 -145
  53. package/dist/esm/utils.js.map +0 -1
  54. package/dist/umd/index.js +0 -2
  55. package/dist/umd/index.js.map +0 -1
@@ -1,18 +1,20 @@
1
- import {createContainer} from './container';
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { createContainer } from './container';
2
3
  import {
3
4
  bindModule,
4
5
  bindModules,
5
6
  declareModule,
6
7
  declareModuleBindings,
7
8
  Module,
9
+ ModuleBindingEntry,
8
10
  ModuleDeclaration,
9
11
  } from './modules';
10
- import {token} from './tokens';
11
- import {injectable} from './utils';
12
+ import { token } from './tokens';
13
+ import { injectable } from './utils';
12
14
 
13
15
  describe('bindModule()', () => {
14
- type TestQueries = {getValue: () => number};
15
- type TestModule = Module<{queries: TestQueries}>;
16
+ type TestQueries = { getValue: () => number };
17
+ type TestModule = Module<{ queries: TestQueries }>;
16
18
 
17
19
  const MODULE_TOKEN = token<TestModule>();
18
20
  const QUERIES_TOKEN = token<TestQueries>();
@@ -39,7 +41,7 @@ describe('bindModule()', () => {
39
41
  });
40
42
 
41
43
  it('should destroy the module on removing the module token and remove tokens of its exported props', () => {
42
- const destroy = jest.fn();
44
+ const destroy = vi.fn();
43
45
  const container = createContainer();
44
46
 
45
47
  bindModule(container, {
@@ -65,10 +67,10 @@ describe('bindModule()', () => {
65
67
  const token1 = token();
66
68
  const token2 = token();
67
69
 
68
- const beforeBinding = jest.fn((container) =>
70
+ const beforeBinding = vi.fn((container) =>
69
71
  container.bindValue(token1, 'foo'),
70
72
  );
71
- const afterBinding = jest.fn((container) => {
73
+ const afterBinding = vi.fn((container) => {
72
74
  const value = container.resolve(QUERIES_TOKEN).getValue();
73
75
  container.bindValue(token2, value * 10);
74
76
  });
@@ -86,6 +88,139 @@ describe('bindModule()', () => {
86
88
  expect(container.resolve(token2)).toBe(10);
87
89
  });
88
90
 
91
+ it('should support "eager" strategy', () => {
92
+ const container = createContainer();
93
+ const factory = vi.fn(() => ({}));
94
+
95
+ const MODULE = declareModule({
96
+ factory,
97
+ strategy: 'eager',
98
+ });
99
+
100
+ bindModule(container, MODULE);
101
+ expect(factory).toBeCalledTimes(1);
102
+ });
103
+
104
+ it('should support "lazy" strategy by default', () => {
105
+ const container = createContainer();
106
+ const factory = vi.fn(() => ({}));
107
+
108
+ const MODULE = declareModule({
109
+ factory,
110
+ });
111
+
112
+ bindModule(container, MODULE);
113
+ expect(factory).toBeCalledTimes(0);
114
+
115
+ container.resolve(MODULE.token);
116
+ expect(factory).toBeCalledTimes(1);
117
+ });
118
+
119
+ it('should support "eager" strategy in imports', () => {
120
+ const container = createContainer();
121
+ const factory1 = vi.fn(() => ({}));
122
+ const factory2 = vi.fn(() => ({}));
123
+
124
+ const MODULE1 = declareModule({
125
+ factory: factory1,
126
+ strategy: 'eager',
127
+ });
128
+
129
+ const MODULE2 = declareModule({
130
+ factory: factory2,
131
+ imports: [MODULE1],
132
+ });
133
+
134
+ bindModule(container, MODULE2);
135
+ expect(factory1).toBeCalledTimes(1);
136
+ expect(factory2).toBeCalledTimes(0);
137
+ });
138
+
139
+ it('should throw an error during binding if "eager" factory fails', () => {
140
+ const container = createContainer();
141
+ const error = new Error('Eager factory failed');
142
+ const factory = vi.fn(() => {
143
+ throw error;
144
+ });
145
+
146
+ const MODULE = declareModule({
147
+ factory,
148
+ strategy: 'eager',
149
+ });
150
+
151
+ expect(() => bindModule(container, MODULE)).toThrow(error);
152
+ });
153
+
154
+ it('should throw an error during binding if imported "eager" factory fails', () => {
155
+ const container = createContainer();
156
+ const error = new Error('Eager factory failed');
157
+ const factory1 = vi.fn(() => {
158
+ throw error;
159
+ });
160
+
161
+ const MODULE1 = declareModule({
162
+ factory: factory1,
163
+ strategy: 'eager',
164
+ });
165
+
166
+ const MODULE2 = declareModule({
167
+ factory: () => ({}),
168
+ imports: [MODULE1],
169
+ });
170
+
171
+ expect(() => bindModule(container, MODULE2)).toThrow(error);
172
+ });
173
+
174
+ it('should execute "eager" factories in reverse BFS order', () => {
175
+ const route: string[] = [];
176
+
177
+ const declareEagerModule = (id: string, imports?: ModuleBindingEntry[]) =>
178
+ declareModule({
179
+ imports,
180
+ strategy: 'eager',
181
+ factory: () => {
182
+ route.push(id);
183
+ return {};
184
+ },
185
+ });
186
+
187
+ /**
188
+ * Structure:
189
+ * m1
190
+ * / | \
191
+ * m2 m3 m4
192
+ *
193
+ * BFS order: m1, m2, m3, m4
194
+ * Expected execution order (reverse): m4, m3, m2, m1
195
+ */
196
+ const m4 = declareEagerModule('m4');
197
+ const m3 = declareEagerModule('m3');
198
+ const m2 = declareEagerModule('m2');
199
+ const m1 = declareEagerModule('m1', [m2, m3, m4]);
200
+
201
+ const container = createContainer();
202
+ bindModule(container, m1);
203
+
204
+ expect(route).toEqual(['m4', 'm3', 'm2', 'm1']);
205
+ });
206
+
207
+ it('should throw an error during resolution if "lazy" factory fails', () => {
208
+ const container = createContainer();
209
+ const error = new Error('Lazy factory failed');
210
+ const factory = vi.fn(() => {
211
+ throw error;
212
+ });
213
+
214
+ const MODULE = declareModule({
215
+ factory,
216
+ });
217
+
218
+ bindModule(container, MODULE);
219
+ expect(factory).toBeCalledTimes(0);
220
+
221
+ expect(() => container.resolve(MODULE.token)).toThrow(error);
222
+ });
223
+
89
224
  it('should bind the module as singleton by default', () => {
90
225
  const parent = createContainer();
91
226
  const container = createContainer(parent);
@@ -98,7 +233,7 @@ describe('bindModule()', () => {
98
233
  const parent = createContainer();
99
234
  const container = createContainer(parent);
100
235
 
101
- bindModule(parent, MODULE, {scope: 'singleton'});
236
+ bindModule(parent, MODULE, { scope: 'singleton' });
102
237
  expect(parent.get(MODULE_TOKEN)).toBe(container.get(MODULE_TOKEN));
103
238
  });
104
239
 
@@ -106,7 +241,7 @@ describe('bindModule()', () => {
106
241
  const parent = createContainer();
107
242
  const container = createContainer(parent);
108
243
 
109
- bindModule(parent, MODULE, {scope: 'scoped'});
244
+ bindModule(parent, MODULE, { scope: 'scoped' });
110
245
  expect(parent.get(MODULE_TOKEN)).toBe(container.get(MODULE_TOKEN));
111
246
  expect(parent.get(QUERIES_TOKEN)).toBe(container.get(QUERIES_TOKEN));
112
247
  });
@@ -115,10 +250,10 @@ describe('bindModule()', () => {
115
250
  const parent = createContainer();
116
251
  const container = createContainer(parent);
117
252
 
118
- const destroy = jest.fn();
253
+ const destroy = vi.fn();
119
254
  bindModule(parent, {
120
255
  token: MODULE_TOKEN,
121
- factory: (container) => ({...MODULE.factory(container), destroy}),
256
+ factory: (container) => ({ ...MODULE.factory(container), destroy }),
122
257
  });
123
258
 
124
259
  parent.resolve(MODULE_TOKEN);
@@ -134,12 +269,12 @@ describe('bindModule()', () => {
134
269
  const parent = createContainer();
135
270
  const container = createContainer(parent);
136
271
 
137
- const destroy = jest.fn();
272
+ const destroy = vi.fn();
138
273
  bindModule(
139
274
  parent,
140
275
  {
141
276
  token: MODULE_TOKEN,
142
- factory: (container) => ({...MODULE.factory(container), destroy}),
277
+ factory: (container) => ({ ...MODULE.factory(container), destroy }),
143
278
  },
144
279
  {
145
280
  scope: 'scoped',
@@ -159,23 +294,23 @@ describe('bindModule()', () => {
159
294
  });
160
295
 
161
296
  it('should bind modules and binding entries from "imports" to the container', () => {
162
- type TestModule = Module<{value: number}>;
297
+ type TestModule = Module<{ value: number }>;
163
298
 
164
299
  const MODULE1_TOKEN = token<TestModule>();
165
300
  const MODULE1: ModuleDeclaration<TestModule> = {
166
301
  token: MODULE1_TOKEN,
167
- factory: () => ({value: 1}),
302
+ factory: () => ({ value: 1 }),
168
303
  };
169
304
 
170
305
  const MODULE2_TOKEN = token<TestModule>();
171
306
  const MODULE2: ModuleDeclaration<TestModule> = {
172
307
  token: MODULE2_TOKEN,
173
- factory: () => ({value: 2}),
308
+ factory: () => ({ value: 2 }),
174
309
  };
175
310
 
176
311
  const MODULE2_ALTERED: ModuleDeclaration<TestModule> = {
177
312
  token: MODULE2_TOKEN,
178
- factory: () => ({value: 22}),
313
+ factory: () => ({ value: 22 }),
179
314
  };
180
315
 
181
316
  const parent = createContainer();
@@ -188,7 +323,7 @@ describe('bindModule()', () => {
188
323
  factory: () => ({}),
189
324
  imports: [
190
325
  MODULE1,
191
- {module: MODULE2_ALTERED, options: {scope: 'scoped'}},
326
+ { module: MODULE2_ALTERED, options: { scope: 'scoped' } },
192
327
  ],
193
328
  }),
194
329
  );
@@ -204,13 +339,13 @@ describe('bindModule()', () => {
204
339
  const ARG_TOKEN = token<string>('arg');
205
340
  const RESULT_TOKEN = token<string>('result');
206
341
 
207
- type TestModule = Module<{value: string}>;
342
+ type TestModule = Module<{ value: string }>;
208
343
 
209
344
  const MODULE1: ModuleDeclaration<TestModule> = declareModule({
210
345
  beforeBinding: (container) =>
211
346
  container.bindValue(ARG_TOKEN, container.resolve(ARG_TOKEN) + '2'),
212
- factory: injectable((arg) => ({value: arg + '3'}), ARG_TOKEN),
213
- exports: {value: RESULT_TOKEN},
347
+ factory: injectable((arg) => ({ value: arg + '3' }), ARG_TOKEN),
348
+ exports: { value: RESULT_TOKEN },
214
349
  });
215
350
 
216
351
  const container = createContainer();
@@ -293,23 +428,23 @@ describe('bindModule()', () => {
293
428
  });
294
429
 
295
430
  describe('bindModules()', () => {
296
- type TestModule = Module<{value: number}>;
431
+ type TestModule = Module<{ value: number }>;
297
432
 
298
433
  const MODULE1_TOKEN = token<TestModule>();
299
434
  const MODULE1: ModuleDeclaration<TestModule> = {
300
435
  token: MODULE1_TOKEN,
301
- factory: () => ({value: 1}),
436
+ factory: () => ({ value: 1 }),
302
437
  };
303
438
 
304
439
  const MODULE2_TOKEN = token<TestModule>();
305
440
  const MODULE2: ModuleDeclaration<TestModule> = {
306
441
  token: MODULE2_TOKEN,
307
- factory: () => ({value: 2}),
442
+ factory: () => ({ value: 2 }),
308
443
  };
309
444
 
310
445
  const MODULE2_ALTERED: ModuleDeclaration<TestModule> = {
311
446
  token: MODULE2_TOKEN,
312
- factory: () => ({value: 22}),
447
+ factory: () => ({ value: 22 }),
313
448
  };
314
449
 
315
450
  it('should bind modules and binding entries to the container', () => {
@@ -320,7 +455,7 @@ describe('bindModules()', () => {
320
455
 
321
456
  bindModules(container, [
322
457
  MODULE1,
323
- {module: MODULE2_ALTERED, options: {scope: 'scoped'}},
458
+ { module: MODULE2_ALTERED, options: { scope: 'scoped' } },
324
459
  ]);
325
460
 
326
461
  expect(parent.get(MODULE1_TOKEN)).toBeUndefined();
@@ -336,16 +471,16 @@ describe('declareModule()', () => {
336
471
  const container = createContainer();
337
472
 
338
473
  const VALUE_TOKEN = token<number>();
339
- const MODULE_TOKEN = token<Module<{value: number}>>();
474
+ const MODULE_TOKEN = token<Module<{ value: number }>>();
340
475
  const MODULE = declareModule({
341
476
  token: MODULE_TOKEN,
342
- factory: () => ({value: 1}),
343
- exports: {value: VALUE_TOKEN},
477
+ factory: () => ({ value: 1 }),
478
+ exports: { value: VALUE_TOKEN },
344
479
  });
345
480
 
346
481
  bindModule(container, MODULE);
347
482
  expect(container.get(VALUE_TOKEN)).toBe(1);
348
- expect(container.get(MODULE_TOKEN)).toEqual({value: 1});
483
+ expect(container.get(MODULE_TOKEN)).toEqual({ value: 1 });
349
484
  });
350
485
 
351
486
  it('should declare a binding for the anonymous module', () => {
@@ -353,8 +488,8 @@ describe('declareModule()', () => {
353
488
 
354
489
  const VALUE_TOKEN = token<number>();
355
490
  const MODULE = declareModule({
356
- factory: () => ({value: 1}),
357
- exports: {value: VALUE_TOKEN},
491
+ factory: () => ({ value: 1 }),
492
+ exports: { value: VALUE_TOKEN },
358
493
  });
359
494
 
360
495
  bindModule(container, MODULE);
@@ -368,14 +503,14 @@ describe('declareModuleBindings()', () => {
368
503
 
369
504
  const VALUE1_TOKEN = token<number>();
370
505
  const MODULE1 = declareModule({
371
- factory: () => ({value: 1}),
372
- exports: {value: VALUE1_TOKEN},
506
+ factory: () => ({ value: 1 }),
507
+ exports: { value: VALUE1_TOKEN },
373
508
  });
374
509
 
375
510
  const VALUE2_TOKEN = token<number>();
376
511
  const MODULE2 = declareModule({
377
- factory: () => ({value: 2}),
378
- exports: {value: VALUE2_TOKEN},
512
+ factory: () => ({ value: 2 }),
513
+ exports: { value: VALUE2_TOKEN },
379
514
  });
380
515
 
381
516
  const MODULE_BINDINGS = declareModuleBindings([MODULE1, MODULE2]);
@@ -386,4 +521,22 @@ describe('declareModuleBindings()', () => {
386
521
 
387
522
  expect(container.get(MODULE_BINDINGS.token)).toEqual({});
388
523
  });
524
+
525
+ it('should ignore falsy tokens in exports', () => {
526
+ const container = createContainer();
527
+
528
+ const MODULE = declareModule({
529
+ factory: () => ({
530
+ valid: 1,
531
+ invalid: 2,
532
+ }),
533
+ exports: {
534
+ valid: token(),
535
+ invalid: undefined,
536
+ } as any,
537
+ });
538
+
539
+ bindModule(container, MODULE);
540
+ expect(container.get(MODULE.token)).toEqual({ valid: 1, invalid: 2 });
541
+ });
389
542
  });
package/src/modules.ts CHANGED
@@ -1,6 +1,6 @@
1
- import {Container} from './container';
2
- import {Token, token} from './tokens';
3
- import {injectable} from './utils';
1
+ import { Container } from './container';
2
+ import { Token, token } from './tokens';
3
+ import { injectable } from './utils';
4
4
 
5
5
  type AnyObject = Record<string, any>;
6
6
  type EmptyObject = Record<string, never>;
@@ -65,6 +65,13 @@ export type ModuleDeclaration<T extends Module<AnyObject>> = {
65
65
 
66
66
  /** Callback could be used to export complex dependencies from the module. It is called after binding the module. */
67
67
  afterBinding?: (container: Container) => void;
68
+
69
+ /**
70
+ * Strategy for executing the factory:
71
+ * - `lazy` - **This is the default**. The factory is called when the module is resolved.
72
+ * - `eager` - The factory is called immediately after the module is bound to the container.
73
+ */
74
+ strategy?: 'eager' | 'lazy';
68
75
  };
69
76
 
70
77
  export type AnyModuleDeclaration = ModuleDeclaration<Module<AnyObject>>;
@@ -145,6 +152,14 @@ export function bindModule<T extends Module<AnyObject>>(
145
152
  const m = 'module' in entry ? entry.module : entry;
146
153
  m.afterBinding?.(container);
147
154
  }
155
+
156
+ for (let i = bfsQueue.length - 1; i >= 0; i--) {
157
+ const entry = bfsQueue[i];
158
+ const m = 'module' in entry ? entry.module : entry;
159
+ if (m.strategy === 'eager') {
160
+ container.resolve(m.token);
161
+ }
162
+ }
148
163
  }
149
164
 
150
165
  function bindModuleEntry(
@@ -176,7 +191,7 @@ function bindModuleEntry(
176
191
  container.bindFactory(
177
192
  valueToken,
178
193
  injectable((module) => module[valueKey], module.token),
179
- {scope},
194
+ { scope },
180
195
  );
181
196
  }
182
197
  });
@@ -240,10 +255,12 @@ export function declareModule<T extends Module<AnyObject>>(
240
255
  declaration: Omit<ModuleDeclaration<T>, 'token'> &
241
256
  Partial<Pick<ModuleDeclaration<T>, 'token'>>,
242
257
  ): ModuleDeclaration<T> {
243
- return {...declaration, token: declaration.token ?? token()};
258
+ return { ...declaration, token: declaration.token ?? token() };
244
259
  }
245
260
 
246
261
  /**
262
+ * @deprecated Use `declareModule` instead
263
+ *
247
264
  * Declares bindings of several modules
248
265
  *
249
266
  * @param modules - module declaration entries