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,11 +1,14 @@
1
+ import { describe, expect, it, test, vi } from 'vitest';
1
2
  import {
2
3
  CONTAINER,
3
4
  createContainer,
5
+ FACTORIES_MAP,
4
6
  PARENT_CONTAINERS,
7
+ RESOLVER,
5
8
  ResolverError,
6
9
  } from './container';
7
- import {optional, token} from './tokens';
8
- import {injectable} from './utils';
10
+ import { optional, token } from './tokens';
11
+ import { injectable } from './utils';
9
12
 
10
13
  const NUMBER = token<number>('number');
11
14
  const STRING = token<string>('string');
@@ -28,7 +31,7 @@ describe('Container', () => {
28
31
  it('should bind a result of a factory and prevent invoking it', () => {
29
32
  const container = createContainer();
30
33
 
31
- const factory = jest.fn(() => 1);
34
+ const factory = vi.fn(() => 1);
32
35
  container.bindFactory(NUMBER, factory);
33
36
  container.bindValue(NUMBER, 2);
34
37
 
@@ -54,7 +57,7 @@ describe('Container', () => {
54
57
  const container = createContainer();
55
58
 
56
59
  let containerArg;
57
- const factory = jest.fn((arg) => {
60
+ const factory = vi.fn((arg) => {
58
61
  containerArg = arg;
59
62
  return 1;
60
63
  });
@@ -68,8 +71,8 @@ describe('Container', () => {
68
71
  it('should rebind a factory in case it was bound', () => {
69
72
  const container = createContainer();
70
73
 
71
- const factory1 = jest.fn(() => 1);
72
- const factory2 = jest.fn(() => 2);
74
+ const factory1 = vi.fn(() => 1);
75
+ const factory2 = vi.fn(() => 2);
73
76
  container.bindFactory(NUMBER, factory1);
74
77
  container.bindFactory(NUMBER, factory2);
75
78
 
@@ -87,7 +90,7 @@ describe('Container', () => {
87
90
  container.bindValue(START, 20);
88
91
 
89
92
  let counter = 0;
90
- const factory = jest.fn((start) => start + ++counter);
93
+ const factory = vi.fn((start) => start + ++counter);
91
94
  parent.bindFactory(NUMBER, injectable(factory, START), {
92
95
  scope: 'singleton',
93
96
  });
@@ -113,7 +116,7 @@ describe('Container', () => {
113
116
  container.bindValue(START, 20);
114
117
 
115
118
  let counter = 0;
116
- const factory = jest.fn((start) => start + ++counter);
119
+ const factory = vi.fn((start) => start + ++counter);
117
120
  parent.bindFactory(NUMBER, injectable(factory, START), {
118
121
  scope: 'scoped',
119
122
  });
@@ -129,7 +132,7 @@ describe('Container', () => {
129
132
  it('should inherit a factory with "scoped" scope', async () => {
130
133
  const parentParent = createContainer();
131
134
  let counter = 0;
132
- const factory = jest.fn(() => ++counter);
135
+ const factory = vi.fn(() => ++counter);
133
136
 
134
137
  parentParent.bindFactory(NUMBER, factory);
135
138
 
@@ -163,7 +166,7 @@ describe('Container', () => {
163
166
  container.bindValue(START, 20);
164
167
 
165
168
  let counter = 0;
166
- const factory = jest.fn((start) => start + ++counter);
169
+ const factory = vi.fn((start) => start + ++counter);
167
170
  parent.bindFactory(NUMBER, injectable(factory, START));
168
171
 
169
172
  expect(container.get(NUMBER)).toBe(11);
@@ -190,7 +193,7 @@ describe('Container', () => {
190
193
  container3.bindValue(START, 30);
191
194
 
192
195
  let counter = 0;
193
- const factory = jest.fn((start) => start + ++counter);
196
+ const factory = vi.fn((start) => start + ++counter);
194
197
  parent.bindFactory(NUMBER, injectable(factory, START), {
195
198
  scope: 'scoped',
196
199
  });
@@ -235,7 +238,7 @@ describe('Container', () => {
235
238
  container.bindValue(START, 20);
236
239
 
237
240
  let counter = 0;
238
- const factory = jest.fn((start) => start + ++counter);
241
+ const factory = vi.fn((start) => start + ++counter);
239
242
  parent.bindFactory(NUMBER, injectable(factory, START), {
240
243
  scope: 'transient',
241
244
  });
@@ -251,9 +254,9 @@ describe('Container', () => {
251
254
  it('should bind a factory with "onRemoved" callback', () => {
252
255
  const container = createContainer();
253
256
 
254
- const factory = jest.fn(() => 1);
255
- const callback = jest.fn();
256
- container.bindFactory(NUMBER, factory, {onRemoved: callback});
257
+ const factory = vi.fn(() => 1);
258
+ const callback = vi.fn();
259
+ container.bindFactory(NUMBER, factory, { onRemoved: callback });
257
260
 
258
261
  expect(container.get(NUMBER)).toBe(1);
259
262
  container.remove(NUMBER);
@@ -284,9 +287,9 @@ describe('Container', () => {
284
287
  it('should remove "singleton" factory silently in case its value has never been resolved', () => {
285
288
  const container = createContainer();
286
289
 
287
- const factory = jest.fn(() => 1);
288
- const onRemoved = jest.fn();
289
- container.bindFactory(NUMBER, factory, {scope: 'singleton', onRemoved});
290
+ const factory = vi.fn(() => 1);
291
+ const onRemoved = vi.fn();
292
+ container.bindFactory(NUMBER, factory, { scope: 'singleton', onRemoved });
290
293
  container.remove(NUMBER);
291
294
 
292
295
  expect(container.get(NUMBER)).toBeUndefined();
@@ -297,9 +300,9 @@ describe('Container', () => {
297
300
  it('should remove "singleton" factory with calling "onRemoved" in case its value has been resolved', () => {
298
301
  const container = createContainer();
299
302
 
300
- const factory = jest.fn(() => 100);
301
- const onRemoved = jest.fn();
302
- container.bindFactory(NUMBER, factory, {scope: 'singleton', onRemoved});
303
+ const factory = vi.fn(() => 100);
304
+ const onRemoved = vi.fn();
305
+ container.bindFactory(NUMBER, factory, { scope: 'singleton', onRemoved });
303
306
 
304
307
  expect(container.get(NUMBER)).toBe(100);
305
308
  container.remove(NUMBER);
@@ -315,9 +318,9 @@ describe('Container', () => {
315
318
  const container = createContainer(parent);
316
319
 
317
320
  let count = 1;
318
- const factory = jest.fn(() => count++);
319
- const onRemoved = jest.fn();
320
- parent.bindFactory(NUMBER, factory, {scope: 'scoped', onRemoved});
321
+ const factory = vi.fn(() => count++);
322
+ const onRemoved = vi.fn();
323
+ parent.bindFactory(NUMBER, factory, { scope: 'scoped', onRemoved });
321
324
 
322
325
  expect(parent.get(NUMBER)).toBe(1);
323
326
  expect(container.get(NUMBER)).toBe(1);
@@ -332,8 +335,8 @@ describe('Container', () => {
332
335
  it('should remove "transient" factory in case its value has never been resolved', () => {
333
336
  const container = createContainer();
334
337
 
335
- const factory = jest.fn(() => 1);
336
- container.bindFactory(NUMBER, factory, {scope: 'transient'});
338
+ const factory = vi.fn(() => 1);
339
+ container.bindFactory(NUMBER, factory, { scope: 'transient' });
337
340
  container.remove(NUMBER);
338
341
 
339
342
  expect(container.get(NUMBER)).toBeUndefined();
@@ -343,8 +346,8 @@ describe('Container', () => {
343
346
  it('should remove "transient" factory in case its value has been resolved', () => {
344
347
  const container = createContainer();
345
348
 
346
- const factory = jest.fn(() => 1);
347
- container.bindFactory(NUMBER, factory, {scope: 'transient'});
349
+ const factory = vi.fn(() => 1);
350
+ container.bindFactory(NUMBER, factory, { scope: 'transient' });
348
351
  expect(container.get(NUMBER)).toBe(1);
349
352
 
350
353
  container.remove(NUMBER);
@@ -365,6 +368,32 @@ describe('Container', () => {
365
368
  });
366
369
 
367
370
  describe('removeAll()', () => {
371
+ it('should restore internal tokens after a callback throws and allow reuse', () => {
372
+ const parent = createContainer();
373
+ const container = createContainer(parent);
374
+ const child = createContainer(container);
375
+ parent.bindValue(STRING, 'parent');
376
+ container.bindFactory(NUMBER, () => 1, {
377
+ onRemoved() {
378
+ throw new Error('cleanup failed');
379
+ },
380
+ });
381
+ container.resolve(NUMBER);
382
+
383
+ expect(() => container.removeAll()).toThrow('cleanup failed');
384
+ expect(container.resolve(CONTAINER)).toBe(container);
385
+ expect(container.resolve(PARENT_CONTAINERS)).toEqual([parent]);
386
+ expect(container.resolve(RESOLVER)).toBeTypeOf('function');
387
+ expect(container.hasToken(FACTORIES_MAP)).toBe(true);
388
+ expect(child.resolve(STRING)).toBe('parent');
389
+ const onRemoved = vi.fn();
390
+ container.bindFactory(NUMBER, () => 2, { onRemoved });
391
+ expect(child.resolve(NUMBER)).toBe(2);
392
+ container.removeAll();
393
+ expect(onRemoved).toHaveBeenCalledExactlyOnceWith(2);
394
+ expect(child.get(NUMBER)).toBeUndefined();
395
+ });
396
+
368
397
  it('should remove all values and factories', () => {
369
398
  const container = createContainer();
370
399
 
@@ -382,8 +411,8 @@ describe('Container', () => {
382
411
  const F1 = token('f1');
383
412
  const F2 = token('f2');
384
413
 
385
- const unbind1 = jest.fn();
386
- const unbind2 = jest.fn();
414
+ const unbind1 = vi.fn();
415
+ const unbind2 = vi.fn();
387
416
 
388
417
  const container = createContainer();
389
418
  container.bindFactory(F1, () => 10, {
@@ -419,7 +448,7 @@ describe('Container', () => {
419
448
 
420
449
  describe('hasToken()', () => {
421
450
  it('should check if a container hierarchy has the token', () => {
422
- const factory = jest.fn();
451
+ const factory = vi.fn();
423
452
 
424
453
  const token1 = token();
425
454
  const token2 = token();
@@ -621,8 +650,8 @@ describe('Container', () => {
621
650
 
622
651
  it('should resolve a value by shared tokens', () => {
623
652
  const key = 'token-' + Date.now();
624
- const t1 = token({key});
625
- const t2 = token({key});
653
+ const t1 = token({ key });
654
+ const t2 = token({ key });
626
655
  expect(t1).not.toBe(t2);
627
656
 
628
657
  const container = createContainer();
@@ -687,5 +716,20 @@ describe('Container', () => {
687
716
 
688
717
  expect(() => child.resolve(NUMBER)).toThrow(ResolverError);
689
718
  });
719
+
720
+ it('should NOT resolve a token from a parent that does NOT provide a RESOLVER token', () => {
721
+ const T1 = token<number>('T1');
722
+
723
+ const customParent = {
724
+ hasToken: () => true,
725
+ get: (t: any) => (t === RESOLVER ? undefined : undefined),
726
+ resolve: () => {
727
+ throw new Error('Not implemented');
728
+ },
729
+ };
730
+
731
+ const container = createContainer(customParent);
732
+ expect(container.get(T1)).toBeUndefined();
733
+ });
690
734
  });
691
735
  });
package/src/container.ts CHANGED
@@ -1,4 +1,4 @@
1
- import {Token, token} from './tokens';
1
+ import { Token, token } from './tokens';
2
2
 
3
3
  /**
4
4
  * ResolverError is thrown by the resolver when a token is not found in a container.
@@ -75,6 +75,8 @@ export type Container = {
75
75
 
76
76
  /**
77
77
  * Removes all bindings in the container.
78
+ * Continues after cleanup failures and rethrows the first thrown value unchanged
79
+ * after clearing bindings and restoring internal tokens. Later failures are discarded.
78
80
  */
79
81
  removeAll(): void;
80
82
  };
@@ -178,7 +180,7 @@ export function createContainer(
178
180
  return;
179
181
  }
180
182
 
181
- factories.set(token.symbol, {factory, options});
183
+ factories.set(token.symbol, { factory, options });
182
184
  },
183
185
 
184
186
  remove<T>(token: Token<T>): void {
@@ -196,15 +198,28 @@ export function createContainer(
196
198
  },
197
199
 
198
200
  removeAll(): void {
199
- factories.forEach((context, tokenSymbol) => {
200
- if (context.options) {
201
- executeOnRemoved(tokenSymbol, context.options);
202
- }
203
- });
204
-
205
- values.clear();
206
- factories.clear();
207
- bindInternalTokens();
201
+ // A separate flag preserves thrown values such as undefined or false.
202
+ let hasError = false;
203
+ let firstError: unknown;
204
+ try {
205
+ factories.forEach((context, tokenSymbol) => {
206
+ try {
207
+ if (context.options) {
208
+ executeOnRemoved(tokenSymbol, context.options);
209
+ }
210
+ } catch (error) {
211
+ if (!hasError) {
212
+ hasError = true;
213
+ firstError = error;
214
+ }
215
+ }
216
+ });
217
+ } finally {
218
+ values.clear();
219
+ factories.clear();
220
+ bindInternalTokens();
221
+ }
222
+ if (hasError) throw firstError;
208
223
  },
209
224
 
210
225
  hasToken(token: Token<unknown>): boolean {
package/src/index.ts CHANGED
@@ -1,34 +1,32 @@
1
- export {token, optional} from './tokens';
2
- export type {RequiredToken, OptionalToken, Token} from './tokens';
3
-
4
- export {ResolverError, createContainer} from './container';
5
1
  export type {
6
- FactoryScope,
7
- FactoryOptions,
8
2
  Container,
9
3
  ContainerResolver,
4
+ FactoryOptions,
5
+ FactoryScope,
10
6
  } from './container';
11
-
12
- export {
13
- isToken,
14
- bindMultiValue,
15
- tryResolveValue,
16
- tryResolveValues,
17
- resolveValue,
18
- resolveValues,
19
- injectable,
20
- injectableClass,
21
- } from './utils';
22
-
7
+ export { createContainer, ResolverError } from './container';
8
+ export type {
9
+ AnyModuleDeclaration,
10
+ BindModuleOptions,
11
+ Module,
12
+ ModuleBindingEntry,
13
+ ModuleDeclaration,
14
+ } from './modules';
23
15
  export {
24
16
  bindModule,
25
17
  bindModules,
26
18
  declareModule,
27
19
  declareModuleBindings,
28
20
  } from './modules';
29
- export type {
30
- Module,
31
- ModuleDeclaration,
32
- BindModuleOptions,
33
- ModuleBindingEntry,
34
- } from './modules';
21
+ export type { OptionalToken, RequiredToken, Token } from './tokens';
22
+ export { optional, token } from './tokens';
23
+ export {
24
+ bindMultiValue,
25
+ injectable,
26
+ injectableClass,
27
+ isToken,
28
+ resolveValue,
29
+ resolveValues,
30
+ tryResolveValue,
31
+ tryResolveValues,
32
+ } from './utils';