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
package/README.md CHANGED
@@ -20,11 +20,12 @@ You can use the following command to install this package:
20
20
  npm install --save ditox
21
21
  ```
22
22
 
23
- The package can be used as [UMD](https://github.com/umdjs/umd) module. Use
23
+ The package can be used as an [UMD](https://github.com/umdjs/umd) module. Use
24
24
  [jsdelivr.com](https://jsdelivr.com) CDN site to load
25
25
  [ditox](https://www.jsdelivr.com/package/npm/ditox):
26
26
 
27
27
  ```html
28
+
28
29
  <script src="//cdn.jsdelivr.net/npm/ditox/dist/umd/index.js"></script>
29
30
  <script>
30
31
  const container = Ditox.createContainer();
@@ -33,8 +34,8 @@ The package can be used as [UMD](https://github.com/umdjs/umd) module. Use
33
34
 
34
35
  ## General description
35
36
 
36
- DI pattern in general allows to declare and construct a code graph of an
37
- application. It can be described by following phases:
37
+ DI pattern in general allows declaring and construct a code graph of an
38
+ application. It can be described by the following phases:
38
39
 
39
40
  1. Code declaration phase:
40
41
 
@@ -59,15 +60,18 @@ Diagram:
59
60
  ## Usage Example
60
61
 
61
62
  ```js
62
- import {createContainer, injectable, optional, token} from 'ditox';
63
+ import { createContainer, injectable, optional, token } from 'ditox';
63
64
 
64
65
  // This is app code, some factory functions and classes:
65
- function createStorage(config) {}
66
+ function createStorage(config) {
67
+ }
66
68
 
67
- function createLogger(config) {}
69
+ function createLogger(config) {
70
+ }
68
71
 
69
72
  class UserService {
70
- constructor(storage, logger) {}
73
+ constructor(storage, logger) {
74
+ }
71
75
  }
72
76
 
73
77
  // Define tokens for injections.
@@ -76,13 +80,13 @@ const LOGGER_TOKEN = token();
76
80
  const USER_SERVICE_TOKEN = token();
77
81
 
78
82
  // Token can be optional with a default value.
79
- const STORAGE_CONFIG_TOKEN = optional(token(), {name: 'default storage'});
83
+ const STORAGE_CONFIG_TOKEN = optional(token(), { name: 'default storage' });
80
84
 
81
85
  // Create the container.
82
86
  const container = createContainer();
83
87
 
84
88
  // Provide a value to the container.
85
- container.bindValue(STORAGE_CONFIG_TOKEN, {name: 'custom storage'});
89
+ container.bindValue(STORAGE_CONFIG_TOKEN, { name: 'custom storage' });
86
90
 
87
91
  // Dynamic values are provided by factories.
88
92
 
@@ -94,7 +98,7 @@ container.bindFactory(
94
98
  );
95
99
 
96
100
  // A factory can have `transient` lifetime to create a value on each resolving.
97
- container.bindFactory(LOGGER_TOKEN, createLogger, {scope: 'transient'});
101
+ container.bindFactory(LOGGER_TOKEN, createLogger, { scope: 'transient' });
98
102
 
99
103
  // A class can be injected by `injectableClass()` which calls its constructor
100
104
  // with injected dependencies as arguments.
@@ -128,13 +132,40 @@ container.remove(LOGGER_TOKEN);
128
132
  container.removeAll();
129
133
  ```
130
134
 
135
+ ### Errors during bulk cleanup
136
+
137
+ `removeAll()` continues visiting factories after an `onRemoved` callback or a
138
+ cleanup options accessor throws. It clears local bindings and restores internal
139
+ tokens before rethrowing the first thrown value unchanged, including non-`Error`
140
+ values. Later errors are discarded. The container can be reused after cleanup;
141
+ a subsequent `removeAll()` does not retry callbacks for the cleared bindings.
142
+
143
+ Bindings remain available to callbacks until explicitly removed or cleared at
144
+ the end. Cleanup does not resolve unused factories or dispose transient values
145
+ or values owned by a parent container. A failing callback must handle its own
146
+ internal cleanup; the container cannot resume execution inside it.
147
+
148
+ `remove(token)` retains its existing behavior: if its callback throws, the binding
149
+ remains and removal can be retried. This also applies to single-module removal
150
+ when `destroy()` throws.
151
+
152
+ Reentrant removal and mutation retain the existing live traversal behavior.
153
+ Nested removals run immediately and can invoke a callback more than once.
154
+ New factory entries appended during traversal can be visited. Replacing an
155
+ existing entry without deleting it does not revisit an already visited key;
156
+ deleting and reinserting it appends a new entry that can be visited again.
157
+ This fix does not add recursion guards or guarantee cleanup of every value
158
+ created during callbacks. Callbacks must
159
+ avoid unbounded recursive removal or endlessly adding bindings. Cleanup remains
160
+ synchronous; returned promises are not awaited.
161
+
131
162
  ## Container Hierarchy
132
163
 
133
164
  Ditox.js supports a "parent-child" hierarchy. If the child container cannot
134
165
  resolve a token, it asks the parent container to resolve it:
135
166
 
136
167
  ```js
137
- import {createContainer, token} from 'ditox';
168
+ import { createContainer, token } from 'ditox';
138
169
 
139
170
  const V1_TOKEN = token();
140
171
  const V2_TOKEN = token();
@@ -157,7 +188,7 @@ creating a container. During resolution, parents are queried from left to right,
157
188
  and the first parent that provides the token wins.
158
189
 
159
190
  ```js
160
- import {createContainer, token} from 'ditox';
191
+ import { createContainer, token } from 'ditox';
161
192
 
162
193
  const VALUE = token();
163
194
 
@@ -182,8 +213,8 @@ child2.resolve(VALUE); // 'from-child'
182
213
 
183
214
  ## Factory Lifetimes
184
215
 
185
- Ditox.js supports managing the lifetime of values which are produced by
186
- factories. There are the following types:
216
+ Ditox.js supports managing the lifetime of values which factories produce. There
217
+ are the following types:
187
218
 
188
219
  - `singleton` - **This is the default**. The value is created and cached by the
189
220
  most distant parent container which owns the factory function.
@@ -193,11 +224,11 @@ factories. There are the following types:
193
224
 
194
225
  ### `singleton`
195
226
 
196
- **This is the default scope**. "Singleton" allows to cache a produced value by a
227
+ **This is the default scope**. "Singleton" allows caching a produced value by a
197
228
  most distant parent container which registered the factory function:
198
229
 
199
230
  ```js
200
- import {createContainer, token} from 'ditox';
231
+ import { createContainer, token } from 'ditox';
201
232
 
202
233
  const TAG_TOKEN = token();
203
234
  const LOGGER_TOKEN = token();
@@ -223,12 +254,12 @@ container2.resolve(LOGGER_TOKEN)('bar'); // [parent] bar
223
254
 
224
255
  ### `scoped`
225
256
 
226
- "Scoped" lifetime allows to have sub-containers with own instances of some
257
+ "Scoped" lifetime allows having sub-containers with own instances of some
227
258
  services which can be disposed. For example, a context during HTTP request
228
- handling, or other unit of work:
259
+ handling, or another unit of work:
229
260
 
230
261
  ```js
231
- import {createContainer, token} from 'ditox';
262
+ import { createContainer, token } from 'ditox';
232
263
 
233
264
  const TAG_TOKEN = token();
234
265
  const LOGGER_TOKEN = token();
@@ -260,7 +291,7 @@ container1.removeAll();
260
291
  "Transient" makes the factory produce a new value for each resolution:
261
292
 
262
293
  ```js
263
- import {createContainer, token} from 'ditox';
294
+ import { createContainer, token } from 'ditox';
264
295
 
265
296
  const TAG_TOKEN = token();
266
297
  const LOGGER_TOKEN = token();
@@ -293,14 +324,22 @@ Dependencies can be organized as modules in declarative way with
293
324
  `ModuleDeclaration`. It is useful for providing pieces of functionality from
294
325
  libraries to an app which depends on them.
295
326
 
327
+ The `strategy` field determines when the module's factory is executed:
328
+
329
+ - `lazy` (**default**): The factory is called only when the module or its
330
+ exported tokens are first resolved.
331
+ - `eager`: The factory is called immediately after the module is bound to the
332
+ container. This is useful for modules that need to initialize listeners or
333
+ start background processes right away.
334
+
296
335
  ```typescript
297
- import {Module, ModuleDeclaration, token} from 'ditox';
298
- import {TRANSPORT_TOKEN} from './transport';
336
+ import { Module, ModuleDeclaration, token } from 'ditox';
337
+ import { TRANSPORT_TOKEN } from './transport';
299
338
 
300
- export type Logger = {log: (message: string) => void};
339
+ export type Logger = { log: (message: string) => void };
301
340
  export const LOGGER_TOKEN = token<Logger>();
302
341
 
303
- type LoggerModule = Module<{logger: Logger}>;
342
+ type LoggerModule = Module<{ logger: Logger }>;
304
343
 
305
344
  const LOGGER_MODULE_TOKEN = token<LoggerModule>();
306
345
 
@@ -308,10 +347,13 @@ const LOGGER_MODULE: ModuleDeclaration<LoggerModule> = {
308
347
  // An optional explicit token for a module itself
309
348
  token: LOGGER_MODULE_TOKEN,
310
349
 
350
+ // Use "eager" strategy to initialize the module immediately after binding
351
+ strategy: 'eager',
352
+
311
353
  factory: (container) => {
312
354
  const transport = container.resolve(TRANSPORT_TOKEN).open();
313
355
  return {
314
- logger: {log: (message) => transport.write(message)},
356
+ logger: { log: (message) => transport.write(message) },
315
357
  destroy: () => transport.close(),
316
358
  };
317
359
  },
@@ -419,13 +461,20 @@ const APP_MODULE = declareModuleBindings([LOGGER_MODULE, DATABASE_MODULE]);
419
461
  - Types
420
462
  - Module<Props = {}> — a module object: exported values plus optional
421
463
  destroy(): void
422
- - ModuleDeclaration<T extends Module> — declarative description of a module
464
+ - ModuleDeclaration<T extends Module> — declarative description of a module:
465
+ - token: Token<T> — required token
466
+ - imports?: ReadonlyArray<ModuleBindingEntry> — optional imports
467
+ - factory: (container: Container) => T — factory function
468
+ - exports?: Dictionary — optional exported tokens
469
+ - beforeBinding?: (container: Container) => void — optional callback
470
+ - afterBinding?: (container: Container) => void — optional callback
471
+ - strategy?: 'eager' | 'lazy' — execution strategy (default: 'lazy')
423
472
  - BindModuleOptions — { scope?: 'scoped' | 'singleton' }
424
473
  - ModuleBindingEntry — ModuleDeclaration or { module, options }
425
474
  - bindModule(container: Container, module: ModuleDeclaration<T>, options?:
426
475
  BindModuleOptions): void
427
476
  - Binds a module; exported values are bound via factories; module factory
428
- respects scope; calls destroy() and removes exports on unbind
477
+ respects scope; calls destroy() and removes exports on unbinding
429
478
  - bindModules(container: Container, modules:
430
479
  ReadonlyArray<ModuleBindingEntry>): void — binds multiple modules
431
480
  - declareModule(declaration): ModuleDeclaration<T>