@navios/di 0.5.1 → 0.6.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.
Files changed (123) hide show
  1. package/CHANGELOG.md +146 -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 +3012 -0
  15. package/lib/browser/index.mjs.map +1 -0
  16. package/lib/index-S_qX2VLI.d.mts +1211 -0
  17. package/lib/index-S_qX2VLI.d.mts.map +1 -0
  18. package/lib/index-fKPuT65j.d.cts +1206 -0
  19. package/lib/index-fKPuT65j.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-BMGmmxH7.cjs +2895 -0
  33. package/lib/testing-BMGmmxH7.cjs.map +1 -0
  34. package/lib/testing-DCXz8AJD.mjs +2655 -0
  35. package/lib/testing-DCXz8AJD.mjs.map +1 -0
  36. package/package.json +23 -1
  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__/errors.spec.mts +6 -6
  44. package/src/__tests__/factory.spec.mts +1 -1
  45. package/src/__tests__/get-injectors.spec.mts +1 -1
  46. package/src/__tests__/injectable.spec.mts +1 -1
  47. package/src/__tests__/injection-token.spec.mts +1 -1
  48. package/src/__tests__/library-findings.spec.mts +563 -0
  49. package/src/__tests__/registry.spec.mts +2 -2
  50. package/src/__tests__/request-scope.spec.mts +266 -274
  51. package/src/__tests__/service-instantiator.spec.mts +18 -17
  52. package/src/__tests__/service-locator-event-bus.spec.mts +9 -9
  53. package/src/__tests__/service-locator-manager.spec.mts +15 -15
  54. package/src/__tests__/service-locator.spec.mts +167 -244
  55. package/src/__tests__/unified-api.spec.mts +27 -27
  56. package/src/__type-tests__/factory.spec-d.mts +2 -2
  57. package/src/__type-tests__/inject.spec-d.mts +2 -2
  58. package/src/__type-tests__/injectable.spec-d.mts +1 -1
  59. package/src/browser.mts +16 -0
  60. package/src/container/container.mts +319 -0
  61. package/src/container/index.mts +2 -0
  62. package/src/container/scoped-container.mts +350 -0
  63. package/src/decorators/factory.decorator.mts +4 -4
  64. package/src/decorators/injectable.decorator.mts +5 -5
  65. package/src/errors/di-error.mts +13 -7
  66. package/src/errors/index.mts +0 -8
  67. package/src/index.mts +156 -15
  68. package/src/interfaces/container.interface.mts +82 -0
  69. package/src/interfaces/factory.interface.mts +2 -2
  70. package/src/interfaces/index.mts +1 -0
  71. package/src/internal/context/async-local-storage.mts +120 -0
  72. package/src/internal/context/factory-context.mts +18 -0
  73. package/src/internal/context/index.mts +3 -0
  74. package/src/{request-context-holder.mts → internal/context/request-context.mts} +40 -27
  75. package/src/internal/context/resolution-context.mts +63 -0
  76. package/src/internal/context/sync-local-storage.mts +51 -0
  77. package/src/internal/core/index.mts +5 -0
  78. package/src/internal/core/instance-resolver.mts +641 -0
  79. package/src/{service-instantiator.mts → internal/core/instantiator.mts} +31 -27
  80. package/src/internal/core/invalidator.mts +437 -0
  81. package/src/internal/core/service-locator.mts +202 -0
  82. package/src/{token-processor.mts → internal/core/token-processor.mts} +79 -60
  83. package/src/{base-instance-holder-manager.mts → internal/holder/base-holder-manager.mts} +91 -21
  84. package/src/internal/holder/holder-manager.mts +85 -0
  85. package/src/internal/holder/holder-storage.interface.mts +116 -0
  86. package/src/internal/holder/index.mts +6 -0
  87. package/src/internal/holder/instance-holder.mts +109 -0
  88. package/src/internal/holder/request-storage.mts +134 -0
  89. package/src/internal/holder/singleton-storage.mts +105 -0
  90. package/src/internal/index.mts +4 -0
  91. package/src/internal/lifecycle/circular-detector.mts +77 -0
  92. package/src/internal/lifecycle/index.mts +2 -0
  93. package/src/{service-locator-event-bus.mts → internal/lifecycle/lifecycle-event-bus.mts} +11 -4
  94. package/src/testing/__tests__/test-container.spec.mts +2 -2
  95. package/src/testing/test-container.mts +4 -4
  96. package/src/token/index.mts +2 -0
  97. package/src/{injection-token.mts → token/injection-token.mts} +1 -1
  98. package/src/{registry.mts → token/registry.mts} +1 -1
  99. package/src/utils/get-injectable-token.mts +1 -1
  100. package/src/utils/get-injectors.mts +32 -15
  101. package/src/utils/types.mts +1 -1
  102. package/tsdown.config.mts +67 -0
  103. package/lib/_tsup-dts-rollup.d.mts +0 -1283
  104. package/lib/_tsup-dts-rollup.d.ts +0 -1283
  105. package/lib/chunk-2M576LCC.mjs +0 -2043
  106. package/lib/chunk-2M576LCC.mjs.map +0 -1
  107. package/lib/index.d.ts +0 -78
  108. package/lib/index.js +0 -2127
  109. package/lib/index.js.map +0 -1
  110. package/lib/testing/index.d.ts +0 -2
  111. package/lib/testing/index.js +0 -2060
  112. package/lib/testing/index.js.map +0 -1
  113. package/lib/testing/index.mjs.map +0 -1
  114. package/src/container.mts +0 -227
  115. package/src/factory-context.mts +0 -8
  116. package/src/instance-resolver.mts +0 -559
  117. package/src/request-context-manager.mts +0 -149
  118. package/src/service-invalidator.mts +0 -429
  119. package/src/service-locator-instance-holder.mts +0 -70
  120. package/src/service-locator-manager.mts +0 -85
  121. package/src/service-locator.mts +0 -246
  122. package/tsup.config.mts +0 -12
  123. /package/src/{injector.mts → injectors.mts} +0 -0
package/docs/scopes.md CHANGED
@@ -215,11 +215,7 @@ import { Injectable, InjectableScope } from '@navios/di'
215
215
  class RequestContext {
216
216
  private readonly requestId = Math.random().toString(36)
217
217
  private readonly startTime = Date.now()
218
- private readonly userId: string
219
-
220
- constructor(userId: string) {
221
- this.userId = userId
222
- }
218
+ userId?: string
223
219
 
224
220
  getRequestId() {
225
221
  return this.requestId
@@ -228,32 +224,44 @@ class RequestContext {
228
224
  getDuration() {
229
225
  return Date.now() - this.startTime
230
226
  }
231
-
232
- getUserId() {
233
- return this.userId
234
- }
235
227
  }
236
228
  ```
237
229
 
238
230
  ### Request Context Management
239
231
 
232
+ Request-scoped services require using `ScopedContainer`, which is created via `container.beginRequest()`:
233
+
240
234
  ```typescript
241
235
  import { Container } from '@navios/di'
242
236
 
243
237
  const container = new Container()
244
238
 
245
- // Begin a request context
246
- const requestId = 'req-123'
247
- container.beginRequest(requestId, { userId: 'user123' })
239
+ // Begin a request context - returns a ScopedContainer
240
+ const scopedContainer = container.beginRequest('req-123', { userId: 'user123' })
248
241
 
249
242
  // All injections within this request will share the same Request-scoped instances
250
- const context1 = await container.get(RequestContext)
251
- const context2 = await container.get(RequestContext)
243
+ const context1 = await scopedContainer.get(RequestContext)
244
+ const context2 = await scopedContainer.get(RequestContext)
252
245
 
253
246
  console.log(context1 === context2) // true - same instance within request
254
247
 
248
+ // Access metadata
249
+ const userId = scopedContainer.getMetadata('userId')
250
+
255
251
  // End the request context (cleans up all request-scoped instances)
256
- await container.endRequest(requestId)
252
+ await scopedContainer.endRequest()
253
+ ```
254
+
255
+ **Important**: You cannot resolve request-scoped services directly from the main `Container`. Attempting to do so will throw an error:
256
+
257
+ ```typescript
258
+ // ❌ This will throw an error
259
+ const context = await container.get(RequestContext)
260
+ // Error: Cannot resolve request-scoped service from Container
261
+
262
+ // ✅ Use ScopedContainer instead
263
+ const scoped = container.beginRequest('req-123')
264
+ const context = await scoped.get(RequestContext)
257
265
  ```
258
266
 
259
267
  ### Request Scope with Dependencies
@@ -272,15 +280,10 @@ class LoggerService {
272
280
  class UserSession {
273
281
  private readonly logger = inject(LoggerService)
274
282
  private readonly sessionId = Math.random().toString(36)
275
- private readonly userId: string
276
-
277
- constructor(userId: string) {
278
- this.userId = userId
279
- }
283
+ userId?: string
280
284
 
281
- async logActivity(activity: string) {
282
- const logger = await this.logger
283
- logger.log(`User ${this.userId}: ${activity}`)
285
+ logActivity(activity: string) {
286
+ this.logger.log(`User ${this.userId}: ${activity}`)
284
287
  }
285
288
 
286
289
  getSessionId() {
@@ -293,43 +296,77 @@ class OrderService {
293
296
  private readonly userSession = inject(UserSession)
294
297
  private orders: string[] = []
295
298
 
296
- async createOrder(productName: string) {
297
- const session = await this.userSession
299
+ createOrder(productName: string) {
298
300
  const orderId = `order_${Math.random().toString(36)}`
299
301
 
300
302
  this.orders.push(orderId)
301
- await session.logActivity(`Created order ${orderId} for ${productName}`)
303
+ this.userSession.logActivity(`Created order ${orderId} for ${productName}`)
302
304
 
303
- return { orderId, userId: session.getSessionId() }
305
+ return { orderId, sessionId: this.userSession.getSessionId() }
304
306
  }
305
307
  }
306
308
  ```
307
309
 
308
- ### Request Context Switching
310
+ ### Multiple Concurrent Requests
309
311
 
310
- You can manage multiple request contexts and switch between them:
312
+ Each request gets its own isolated `ScopedContainer`:
311
313
 
312
314
  ```typescript
313
315
  const container = new Container()
314
316
 
315
- // Start multiple requests
316
- container.beginRequest('req-1', { userId: 'user1' })
317
- container.beginRequest('req-2', { userId: 'user2' })
317
+ // Start multiple concurrent requests
318
+ const scoped1 = container.beginRequest('req-1', { userId: 'user1' })
319
+ const scoped2 = container.beginRequest('req-2', { userId: 'user2' })
318
320
 
319
- // Switch to request 1
320
- container.setCurrentRequestContext('req-1')
321
- const context1 = await container.get(RequestContext)
322
-
323
- // Switch to request 2
324
- container.setCurrentRequestContext('req-2')
325
- const context2 = await container.get(RequestContext)
321
+ // Each scoped container has its own request-scoped instances
322
+ const context1 = await scoped1.get(RequestContext)
323
+ const context2 = await scoped2.get(RequestContext)
326
324
 
327
325
  // Different instances for different requests
328
326
  console.log(context1 !== context2) // true
329
327
 
330
- // Clean up
331
- await container.endRequest('req-1')
332
- await container.endRequest('req-2')
328
+ // Singletons are shared across all requests
329
+ const logger1 = await scoped1.get(LoggerService)
330
+ const logger2 = await scoped2.get(LoggerService)
331
+ console.log(logger1 === logger2) // true - same singleton
332
+
333
+ // Clean up each request independently
334
+ await scoped1.endRequest()
335
+ await scoped2.endRequest()
336
+ ```
337
+
338
+ ### Cross-Storage Dependency Invalidation
339
+
340
+ When a request-scoped service is destroyed (via `endRequest()`), any singleton services that depend on it are automatically invalidated:
341
+
342
+ ```typescript
343
+ @Injectable({ scope: InjectableScope.Request })
344
+ class RequestData {
345
+ data = 'request-specific'
346
+ }
347
+
348
+ @Injectable({ scope: InjectableScope.Singleton })
349
+ class SingletonConsumer {
350
+ private requestData = inject(RequestData)
351
+
352
+ getData() {
353
+ return this.requestData.data
354
+ }
355
+ }
356
+
357
+ const container = new Container()
358
+ const scoped = container.beginRequest('req-1')
359
+
360
+ const singleton = await scoped.get(SingletonConsumer)
361
+ await singleton.getData() // Works fine
362
+
363
+ await scoped.endRequest()
364
+ // SingletonConsumer is also invalidated because it depends on RequestData
365
+
366
+ // Next request gets fresh instances
367
+ const scoped2 = container.beginRequest('req-2')
368
+ const singleton2 = await scoped2.get(SingletonConsumer)
369
+ console.log(singleton !== singleton2) // true - new instance
333
370
  ```
334
371
 
335
372
  ## Scope Compatibility