@navios/di 0.5.1 → 0.6.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 (122) hide show
  1. package/CHANGELOG.md +145 -0
  2. package/README.md +196 -219
  3. package/docs/README.md +69 -11
  4. package/docs/api-reference.md +281 -117
  5. package/docs/container.md +220 -56
  6. package/docs/examples/request-scope-example.mts +2 -2
  7. package/docs/factory.md +3 -8
  8. package/docs/getting-started.md +37 -8
  9. package/docs/migration.md +318 -37
  10. package/docs/request-contexts.md +263 -175
  11. package/docs/scopes.md +79 -42
  12. package/lib/browser/index.d.mts +1577 -0
  13. package/lib/browser/index.d.mts.map +1 -0
  14. package/lib/browser/index.mjs +3013 -0
  15. package/lib/browser/index.mjs.map +1 -0
  16. package/lib/index-7jfWsiG4.d.mts +1211 -0
  17. package/lib/index-7jfWsiG4.d.mts.map +1 -0
  18. package/lib/index-DW3K5sOX.d.cts +1206 -0
  19. package/lib/index-DW3K5sOX.d.cts.map +1 -0
  20. package/lib/index.cjs +389 -0
  21. package/lib/index.cjs.map +1 -0
  22. package/lib/index.d.cts +376 -0
  23. package/lib/index.d.cts.map +1 -0
  24. package/lib/index.d.mts +371 -78
  25. package/lib/index.d.mts.map +1 -0
  26. package/lib/index.mjs +325 -63
  27. package/lib/index.mjs.map +1 -1
  28. package/lib/testing/index.cjs +9 -0
  29. package/lib/testing/index.d.cts +2 -0
  30. package/lib/testing/index.d.mts +2 -2
  31. package/lib/testing/index.mjs +2 -72
  32. package/lib/testing-BG_fa9TJ.mjs +2656 -0
  33. package/lib/testing-BG_fa9TJ.mjs.map +1 -0
  34. package/lib/testing-DIaIRiJz.cjs +2896 -0
  35. package/lib/testing-DIaIRiJz.cjs.map +1 -0
  36. package/package.json +29 -7
  37. package/project.json +2 -2
  38. package/src/__tests__/async-local-storage.browser.spec.mts +240 -0
  39. package/src/__tests__/async-local-storage.spec.mts +333 -0
  40. package/src/__tests__/container.spec.mts +30 -25
  41. package/src/__tests__/e2e.browser.spec.mts +790 -0
  42. package/src/__tests__/e2e.spec.mts +1222 -0
  43. package/src/__tests__/factory.spec.mts +1 -1
  44. package/src/__tests__/get-injectors.spec.mts +1 -1
  45. package/src/__tests__/injectable.spec.mts +1 -1
  46. package/src/__tests__/injection-token.spec.mts +1 -1
  47. package/src/__tests__/library-findings.spec.mts +563 -0
  48. package/src/__tests__/registry.spec.mts +2 -2
  49. package/src/__tests__/request-scope.spec.mts +266 -274
  50. package/src/__tests__/service-instantiator.spec.mts +18 -17
  51. package/src/__tests__/service-locator-event-bus.spec.mts +9 -9
  52. package/src/__tests__/service-locator-manager.spec.mts +15 -15
  53. package/src/__tests__/service-locator.spec.mts +167 -244
  54. package/src/__tests__/unified-api.spec.mts +27 -27
  55. package/src/__type-tests__/factory.spec-d.mts +2 -2
  56. package/src/__type-tests__/inject.spec-d.mts +2 -2
  57. package/src/__type-tests__/injectable.spec-d.mts +1 -1
  58. package/src/browser.mts +16 -0
  59. package/src/container/container.mts +319 -0
  60. package/src/container/index.mts +2 -0
  61. package/src/container/scoped-container.mts +350 -0
  62. package/src/decorators/factory.decorator.mts +4 -4
  63. package/src/decorators/injectable.decorator.mts +5 -5
  64. package/src/errors/di-error.mts +12 -5
  65. package/src/errors/index.mts +0 -8
  66. package/src/index.mts +156 -15
  67. package/src/interfaces/container.interface.mts +82 -0
  68. package/src/interfaces/factory.interface.mts +2 -2
  69. package/src/interfaces/index.mts +1 -0
  70. package/src/internal/context/async-local-storage.mts +120 -0
  71. package/src/internal/context/factory-context.mts +18 -0
  72. package/src/internal/context/index.mts +3 -0
  73. package/src/{request-context-holder.mts → internal/context/request-context.mts} +40 -27
  74. package/src/internal/context/resolution-context.mts +63 -0
  75. package/src/internal/context/sync-local-storage.mts +51 -0
  76. package/src/internal/core/index.mts +5 -0
  77. package/src/internal/core/instance-resolver.mts +641 -0
  78. package/src/{service-instantiator.mts → internal/core/instantiator.mts} +31 -27
  79. package/src/internal/core/invalidator.mts +437 -0
  80. package/src/internal/core/service-locator.mts +202 -0
  81. package/src/{token-processor.mts → internal/core/token-processor.mts} +79 -60
  82. package/src/{base-instance-holder-manager.mts → internal/holder/base-holder-manager.mts} +91 -21
  83. package/src/internal/holder/holder-manager.mts +85 -0
  84. package/src/internal/holder/holder-storage.interface.mts +116 -0
  85. package/src/internal/holder/index.mts +6 -0
  86. package/src/internal/holder/instance-holder.mts +109 -0
  87. package/src/internal/holder/request-storage.mts +134 -0
  88. package/src/internal/holder/singleton-storage.mts +105 -0
  89. package/src/internal/index.mts +4 -0
  90. package/src/internal/lifecycle/circular-detector.mts +77 -0
  91. package/src/internal/lifecycle/index.mts +2 -0
  92. package/src/{service-locator-event-bus.mts → internal/lifecycle/lifecycle-event-bus.mts} +11 -4
  93. package/src/testing/__tests__/test-container.spec.mts +2 -2
  94. package/src/testing/test-container.mts +4 -4
  95. package/src/token/index.mts +2 -0
  96. package/src/{injection-token.mts → token/injection-token.mts} +1 -1
  97. package/src/{registry.mts → token/registry.mts} +1 -1
  98. package/src/utils/get-injectable-token.mts +1 -1
  99. package/src/utils/get-injectors.mts +32 -15
  100. package/src/utils/types.mts +1 -1
  101. package/tsdown.config.mts +67 -0
  102. package/lib/_tsup-dts-rollup.d.mts +0 -1283
  103. package/lib/_tsup-dts-rollup.d.ts +0 -1283
  104. package/lib/chunk-2M576LCC.mjs +0 -2043
  105. package/lib/chunk-2M576LCC.mjs.map +0 -1
  106. package/lib/index.d.ts +0 -78
  107. package/lib/index.js +0 -2127
  108. package/lib/index.js.map +0 -1
  109. package/lib/testing/index.d.ts +0 -2
  110. package/lib/testing/index.js +0 -2060
  111. package/lib/testing/index.js.map +0 -1
  112. package/lib/testing/index.mjs.map +0 -1
  113. package/src/container.mts +0 -227
  114. package/src/factory-context.mts +0 -8
  115. package/src/instance-resolver.mts +0 -559
  116. package/src/request-context-manager.mts +0 -149
  117. package/src/service-invalidator.mts +0 -429
  118. package/src/service-locator-instance-holder.mts +0 -70
  119. package/src/service-locator-manager.mts +0 -85
  120. package/src/service-locator.mts +0 -246
  121. package/tsup.config.mts +0 -12
  122. /package/src/{injector.mts → injectors.mts} +0 -0
package/docs/migration.md CHANGED
@@ -2,49 +2,317 @@
2
2
 
3
3
  This guide helps you migrate between different versions of Navios DI.
4
4
 
5
- ## Migrating to v0.3.x
5
+ ## Migrating to v0.6.0 (Circular Dependency Detection & Browser Support & ScopedContainer)
6
6
 
7
7
  ### New Features
8
8
 
9
- #### Request Context Management
9
+ #### Circular Dependency Detection
10
10
 
11
- The biggest addition in v0.3.x is request context management. This feature allows you to manage request-scoped services with automatic cleanup.
11
+ v0.5.x adds automatic circular dependency detection. When services form a circular dependency using `inject()`, the system now throws a clear error instead of hanging:
12
12
 
13
- **New APIs:**
13
+ ```typescript
14
+ @Injectable()
15
+ class ServiceA {
16
+ private serviceB = inject(ServiceB)
17
+ }
14
18
 
15
- - `Container.beginRequest(requestId, metadata?, priority?)`
16
- - `Container.endRequest(requestId)`
17
- - `Container.getCurrentRequestContext()`
18
- - `Container.setCurrentRequestContext(requestId)`
19
- - `RequestContextHolder` interface
19
+ @Injectable()
20
+ class ServiceB {
21
+ private serviceA = inject(ServiceA)
22
+ }
23
+
24
+ // Error: Circular dependency detected: ServiceA -> ServiceB -> ServiceA
25
+ await container.get(ServiceA)
26
+ ```
20
27
 
21
- **Example:**
28
+ **Breaking circular dependencies** - Use `asyncInject()` on at least one side:
22
29
 
23
30
  ```typescript
24
- // New request context API
25
- const container = new Container()
26
- const context = container.beginRequest('req-123', { userId: 456 })
27
- container.setCurrentRequestContext('req-123')
31
+ @Injectable()
32
+ class ServiceA {
33
+ private serviceB = asyncInject(ServiceB) // Break cycle with asyncInject
28
34
 
29
- // Add request-scoped data
30
- const REQUEST_ID_TOKEN = InjectionToken.create<string>('REQUEST_ID')
31
- context.addInstance(REQUEST_ID_TOKEN, 'req-123')
35
+ async doSomething() {
36
+ const b = await this.serviceB
37
+ return b.process()
38
+ }
39
+ }
32
40
 
33
- // Use in services
34
41
  @Injectable()
35
- class RequestService {
36
- private readonly requestId = asyncInject(REQUEST_ID_TOKEN)
42
+ class ServiceB {
43
+ private serviceA = inject(ServiceA) // This side can use inject()
44
+ }
45
+ ```
37
46
 
38
- async process() {
39
- const id = await this.requestId
40
- console.log(`Processing request: ${id}`)
41
- }
47
+ #### Browser Support
48
+
49
+ The library now supports browser environments with a dedicated entry point:
50
+
51
+ ```typescript
52
+ // Bundlers automatically select the browser entry via package.json exports
53
+ import { Container, inject } from '@navios/di'
54
+ ```
55
+
56
+ The browser build uses `SyncLocalStorage` instead of `AsyncLocalStorage`, which works for synchronous dependency tracking.
57
+
58
+ #### Cross-Storage Invalidation
59
+
60
+ Singletons that depend on request-scoped services are now properly invalidated when the request ends:
61
+
62
+ ```typescript
63
+ @Injectable({ scope: InjectableScope.Request })
64
+ class RequestData {}
65
+
66
+ @Injectable({ scope: InjectableScope.Singleton })
67
+ class SingletonConsumer {
68
+ private requestData = inject(RequestData)
42
69
  }
43
70
 
44
- // Clean up when done
71
+ // When the request ends:
72
+ await scopedContainer.endRequest()
73
+ // SingletonConsumer is automatically invalidated
74
+ ```
75
+
76
+ ### Renamed Types (Deprecated Aliases Available)
77
+
78
+ For cleaner naming, many internal types have been renamed. Deprecated aliases are provided for backward compatibility:
79
+
80
+ | Old Name | New Name |
81
+ | --------------------------------------- | ------------------------- |
82
+ | `ServiceLocatorInstanceHolder` | `InstanceHolder` |
83
+ | `ServiceLocatorInstanceHolderStatus` | `InstanceStatus` |
84
+ | `ServiceLocatorInstanceEffect` | `InstanceEffect` |
85
+ | `ServiceLocatorInstanceDestroyListener` | `InstanceDestroyListener` |
86
+ | `BaseInstanceHolderManager` | `BaseHolderManager` |
87
+ | `ServiceLocatorManager` | `HolderManager` |
88
+ | `SingletonHolderStorage` | `SingletonStorage` |
89
+ | `RequestHolderStorage` | `RequestStorage` |
90
+ | `ServiceLocatorEventBus` | `LifecycleEventBus` |
91
+ | `CircularDependencyDetector` | `CircularDetector` |
92
+ | `ServiceInstantiator` | `Instantiator` |
93
+ | `RequestContextHolder` | `RequestContext` |
94
+ | `DefaultRequestContextHolder` | `DefaultRequestContext` |
95
+ | `createRequestContextHolder` | `createRequestContext` |
96
+
97
+ ### Migration Steps
98
+
99
+ 1. **Update circular dependencies**: If your code hangs during startup, you likely have circular dependencies. Add `asyncInject()` to break the cycle.
100
+
101
+ 2. **Update type imports**: While deprecated aliases work, consider updating to new names:
102
+
103
+ ```typescript
104
+ // Before
105
+ // After
106
+ import { InstanceHolder, ServiceLocatorInstanceHolder } from '@navios/di'
107
+ ```
108
+
109
+ 3. **Browser builds**: No changes needed - bundlers automatically select the correct entry point.
110
+
111
+ ---
112
+
113
+ ## ScopedContainer
114
+
115
+ ### Breaking Changes
116
+
117
+ v0.4.x introduces `ScopedContainer` for request context management. This is a **breaking change** that fixes race conditions in concurrent request handling.
118
+
119
+ #### Removed Methods
120
+
121
+ The following `Container` methods have been **removed**:
122
+
123
+ - `Container.setCurrentRequestContext(requestId)` - removed
124
+ - `Container.getCurrentRequestContext()` - removed
125
+ - `Container.endRequest(requestId)` - removed (use `ScopedContainer.endRequest()`)
126
+
127
+ #### Changed Return Type
128
+
129
+ - `Container.beginRequest()` now returns a `ScopedContainer` instead of `RequestContextHolder`
130
+
131
+ #### New API
132
+
133
+ ```typescript
134
+ // OLD (v0.3.x) - no longer works
135
+ const context = container.beginRequest('req-123')
136
+ container.setCurrentRequestContext('req-123')
137
+ const service = await container.get(RequestService)
138
+ await container.endRequest('req-123')
139
+
140
+ // NEW (v0.4.x) - use ScopedContainer
141
+ const scoped = container.beginRequest('req-123')
142
+ const service = await scoped.get(RequestService)
143
+ await scoped.endRequest()
144
+ ```
145
+
146
+ ### Migration Steps
147
+
148
+ #### 1. Update Request Context Creation
149
+
150
+ **Before:**
151
+
152
+ ```typescript
153
+ const context = container.beginRequest('req-123', { userId: 456 })
154
+ container.setCurrentRequestContext('req-123')
155
+ ```
156
+
157
+ **After:**
158
+
159
+ ```typescript
160
+ const scoped = container.beginRequest('req-123', { userId: 456 })
161
+ ```
162
+
163
+ #### 2. Update Service Resolution
164
+
165
+ **Before:**
166
+
167
+ ```typescript
168
+ container.setCurrentRequestContext('req-123')
169
+ const service = await container.get(RequestService)
170
+ ```
171
+
172
+ **After:**
173
+
174
+ ```typescript
175
+ const scoped = container.beginRequest('req-123')
176
+ const service = await scoped.get(RequestService)
177
+ ```
178
+
179
+ #### 3. Update Cleanup
180
+
181
+ **Before:**
182
+
183
+ ```typescript
45
184
  await container.endRequest('req-123')
46
185
  ```
47
186
 
187
+ **After:**
188
+
189
+ ```typescript
190
+ await scoped.endRequest()
191
+ // or
192
+ await scoped.dispose()
193
+ ```
194
+
195
+ #### 4. Update Middleware (Express/Fastify)
196
+
197
+ **Before:**
198
+
199
+ ```typescript
200
+ app.use(async (req, res, next) => {
201
+ const requestId = `req-${Date.now()}`
202
+ const context = container.beginRequest(requestId)
203
+ container.setCurrentRequestContext(requestId)
204
+
205
+ res.on('finish', () => container.endRequest(requestId))
206
+ next()
207
+ })
208
+
209
+ app.get('/', async (req, res) => {
210
+ const service = await container.get(RequestService)
211
+ res.json(service.getData())
212
+ })
213
+ ```
214
+
215
+ **After:**
216
+
217
+ ```typescript
218
+ app.use(async (req, res, next) => {
219
+ const requestId = `req-${Date.now()}`
220
+ const scoped = container.beginRequest(requestId)
221
+ ;(req as any).scoped = scoped
222
+
223
+ res.on('finish', () => scoped.endRequest())
224
+ next()
225
+ })
226
+
227
+ app.get('/', async (req, res) => {
228
+ const scoped = (req as any).scoped
229
+ const service = await scoped.get(RequestService)
230
+ res.json(service.getData())
231
+ })
232
+ ```
233
+
234
+ ### Why This Change?
235
+
236
+ The old API had a race condition:
237
+
238
+ ```typescript
239
+ // Request A starts
240
+ container.setCurrentRequestContext('req-A')
241
+ const serviceA = await container.get(RequestService) // async...
242
+
243
+ // Request B starts while A is still resolving
244
+ container.setCurrentRequestContext('req-B')
245
+
246
+ // Service A might get Request B's context! Bug!
247
+ ```
248
+
249
+ The new API eliminates this by giving each request its own container:
250
+
251
+ ```typescript
252
+ // Request A
253
+ const scopedA = container.beginRequest('req-A')
254
+ const serviceA = await scopedA.get(RequestService) // Always gets A's context
255
+
256
+ // Request B
257
+ const scopedB = container.beginRequest('req-B')
258
+ const serviceB = await scopedB.get(RequestService) // Always gets B's context
259
+
260
+ // No race condition possible
261
+ ```
262
+
263
+ ### New Features
264
+
265
+ #### IContainer Interface
266
+
267
+ Both `Container` and `ScopedContainer` implement `IContainer`:
268
+
269
+ ```typescript
270
+ interface IContainer {
271
+ get<T>(token, args?): Promise<T>
272
+ invalidate(service: unknown): Promise<void>
273
+ isRegistered(token): boolean
274
+ dispose(): Promise<void>
275
+ ready(): Promise<void>
276
+ tryGetSync<T>(token, args?): T | null
277
+ }
278
+ ```
279
+
280
+ #### Request-Scoped Error Protection
281
+
282
+ Attempting to get a request-scoped service from Container now throws a helpful error:
283
+
284
+ ```typescript
285
+ await container.get(RequestService)
286
+ // Error: Cannot resolve request-scoped service "RequestService" from Container.
287
+ // Use beginRequest() to create a ScopedContainer for request-scoped services.
288
+ ```
289
+
290
+ #### Active Request Tracking
291
+
292
+ ```typescript
293
+ const scoped = container.beginRequest('req-123')
294
+ container.hasActiveRequest('req-123') // true
295
+ await scoped.endRequest()
296
+ container.hasActiveRequest('req-123') // false
297
+ ```
298
+
299
+ ---
300
+
301
+ ## Migrating to v0.3.x
302
+
303
+ ### New Features
304
+
305
+ #### Request Context Management (Legacy)
306
+
307
+ > **Note:** This section documents the v0.3.x API which is deprecated in v0.4.x. See "Migrating to v0.4.x" above for the current API.
308
+
309
+ **Old APIs (deprecated in v0.4.x):**
310
+
311
+ - `Container.beginRequest(requestId, metadata?, priority?)` - returns `RequestContextHolder` in v0.3.x
312
+ - `Container.endRequest(requestId)` - removed in v0.4.x
313
+ - `Container.getCurrentRequestContext()` - removed in v0.4.x
314
+ - `Container.setCurrentRequestContext(requestId)` - removed in v0.4.x
315
+
48
316
  ### Recommended Changes
49
317
 
50
318
  #### Prefer `asyncInject` over `inject`
@@ -109,10 +377,11 @@ Some internal type definitions were improved for better TypeScript support, but
109
377
 
110
378
  **Problem:** Services can't be resolved when using request contexts.
111
379
 
112
- **Solution:** Make sure you've set the current request context:
380
+ **Solution:** Use a `ScopedContainer` for request-scoped services:
113
381
 
114
382
  ```typescript
115
- container.setCurrentRequestContext('your-request-id')
383
+ const scoped = container.beginRequest('your-request-id')
384
+ const service = await scoped.get(RequestService)
116
385
  ```
117
386
 
118
387
  ### Issue: Async dependencies not ready
@@ -133,14 +402,15 @@ private readonly service = asyncInject(AsyncService)
133
402
 
134
403
  **Problem:** Request contexts not being cleaned up.
135
404
 
136
- **Solution:** Always call `endRequest()`:
405
+ **Solution:** Always call `endRequest()` on the ScopedContainer:
137
406
 
138
407
  ```typescript
408
+ const scoped = container.beginRequest('req-123')
139
409
  try {
140
- const context = container.beginRequest('req-123')
410
+ const service = await scoped.get(RequestService)
141
411
  // ... process request
142
412
  } finally {
143
- await container.endRequest('req-123')
413
+ await scoped.endRequest()
144
414
  }
145
415
  ```
146
416
 
@@ -155,7 +425,24 @@ If you encounter issues during migration:
155
425
 
156
426
  ## Changelog Summary
157
427
 
158
- ### v0.3.1
428
+ ### v0.5.x
429
+
430
+ - **Circular Dependency Detection**: Automatic detection with clear error messages
431
+ - **Browser Support**: Dedicated browser entry point with `SyncLocalStorage` polyfill
432
+ - **Cross-Storage Invalidation**: Singletons depending on request-scoped services are properly invalidated
433
+ - **Resolution Context**: Uses `AsyncLocalStorage` to track resolution across async boundaries
434
+ - **Type Renames**: Cleaner names for internal types (with deprecated aliases)
435
+ - **Platform Support**: Node.js, Bun, Deno, and browser environments
436
+
437
+ ### v0.4.x
438
+
439
+ - **ScopedContainer**: New request context management API
440
+ - **Race Condition Fix**: Eliminates concurrency issues with request-scoped services
441
+ - **IContainer Interface**: Common interface for `Container` and `ScopedContainer`
442
+ - **Request-Scoped Errors**: Clear error when resolving request-scoped from `Container`
443
+ - **Active Request Tracking**: `hasActiveRequest()` and `getActiveRequestIds()` methods
444
+
445
+ ### v0.3.x
159
446
 
160
447
  - Added request context management
161
448
  - Improved TypeScript type definitions
@@ -163,12 +450,6 @@ If you encounter issues during migration:
163
450
  - New `RequestContextHolder` interface
164
451
  - Better error messages for injection failures
165
452
 
166
- ### v0.3.0
167
-
168
- - Initial release with request context support
169
- - Container API improvements
170
- - Better async injection handling
171
-
172
453
  ### v0.2.x
173
454
 
174
455
  - Basic dependency injection functionality