@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/README.md CHANGED
@@ -1,19 +1,19 @@
1
1
  # Navios DI
2
2
 
3
- A powerful, type-safe dependency injection library for TypeScript applications. Navios DI provides a modern, decorator-based approach to dependency injection with support for singletons, transients, factories, injection tokens, and service lifecycle management.
3
+ A powerful, type-safe dependency injection library for TypeScript applications. Navios DI provides a modern, decorator-based approach to dependency injection with support for singletons, transients, request-scoped services, factories, injection tokens, and service lifecycle management.
4
4
 
5
5
  ## Features
6
6
 
7
- - 🎯 **Type-safe**: Full TypeScript support with compile-time type checking
8
- - 🏗️ **Decorator-based**: Clean, declarative syntax using decorators
9
- - 🔄 **Lifecycle Management**: Built-in support for service initialization and cleanup
10
- - 🏭 **Factory Pattern**: Create instances using factory classes
11
- - 🎫 **Injection Tokens**: Flexible token-based dependency resolution
12
- - 📦 **Scoped Instances**: Singleton and transient scopes
13
- - **Async/Sync Injection**: Both synchronous and asynchronous dependency resolution
14
- - 🔧 **Container API**: Simple container-based API for dependency management
15
- - 🌐 **Request Context**: Manage request-scoped services with automatic cleanup
16
- - 🔀 **Priority Resolution**: Request contexts with priority-based resolution
7
+ - **Type-safe**: Full TypeScript support with compile-time type checking
8
+ - **Decorator-based**: Clean, declarative syntax using decorators
9
+ - **Lifecycle Management**: Built-in support for service initialization and cleanup
10
+ - **Factory Pattern**: Create instances using factory classes
11
+ - **Injection Tokens**: Flexible token-based dependency resolution
12
+ - **Scoped Instances**: Singleton, transient, and request scopes
13
+ - **Async/Sync Injection**: Both synchronous and asynchronous dependency resolution
14
+ - **Container API**: Simple container-based API for dependency management
15
+ - **Request Context**: Manage request-scoped services with automatic cleanup via ScopedContainer
16
+ - **Circular Dependency Detection**: Automatic detection and helpful error messages for circular dependencies
17
17
 
18
18
  ## Installation
19
19
 
@@ -74,11 +74,38 @@ await container.invalidate(service)
74
74
  // Wait for all pending operations
75
75
  await container.ready()
76
76
 
77
- // Request context management
78
- const context = container.beginRequest('req-123', { userId: 456 })
79
- container.setCurrentRequestContext('req-123')
80
- // ... do work within request context
81
- await container.endRequest('req-123')
77
+ // Clean up all resources
78
+ await container.dispose()
79
+ ```
80
+
81
+ ### ScopedContainer (Request Context)
82
+
83
+ For request-scoped services, use `ScopedContainer` which provides isolated service resolution:
84
+
85
+ ```typescript
86
+ import { Container, Injectable, InjectableScope } from '@navios/di'
87
+
88
+ @Injectable({ scope: InjectableScope.Request })
89
+ class RequestLogger {
90
+ constructor() {
91
+ console.log('New logger for this request')
92
+ }
93
+ }
94
+
95
+ const container = new Container()
96
+
97
+ // Begin a request context - returns a ScopedContainer
98
+ const scopedContainer = container.beginRequest('req-123', { userId: 456 })
99
+
100
+ // Use the scoped container for request-scoped services
101
+ const logger = await scopedContainer.get(RequestLogger)
102
+
103
+ // Access metadata
104
+ scopedContainer.setMetadata('correlationId', 'abc-123')
105
+ const corrId = scopedContainer.getMetadata('correlationId')
106
+
107
+ // End the request (cleanup all request-scoped instances)
108
+ await scopedContainer.endRequest()
82
109
  ```
83
110
 
84
111
  ### Injectable Decorator
@@ -87,8 +114,6 @@ The `@Injectable` decorator marks a class as injectable:
87
114
 
88
115
  ```typescript
89
116
  import { Injectable, InjectableScope } from '@navios/di'
90
-
91
- // With schema (for constructor arguments)
92
117
  import { z } from 'zod'
93
118
 
94
119
  // Singleton (default)
@@ -99,10 +124,15 @@ class SingletonService {}
99
124
  @Injectable({ scope: InjectableScope.Transient })
100
125
  class TransientService {}
101
126
 
127
+ // Request-scoped (new instance per request context)
128
+ @Injectable({ scope: InjectableScope.Request })
129
+ class RequestService {}
130
+
102
131
  // With custom injection token
103
132
  @Injectable({ token: MyToken })
104
133
  class TokenizedService {}
105
134
 
135
+ // With schema for constructor arguments
106
136
  const configSchema = z.object({
107
137
  host: z.string(),
108
138
  port: z.number(),
@@ -141,7 +171,7 @@ class NotificationService {
141
171
 
142
172
  #### `asyncInject` - Asynchronous Injection
143
173
 
144
- Use `asyncInject` for async dependency resolution:
174
+ Use `asyncInject` for async dependency resolution, especially useful for circular dependencies:
145
175
 
146
176
  ```typescript
147
177
  @Injectable()
@@ -155,21 +185,46 @@ class AsyncService {
155
185
  }
156
186
  ```
157
187
 
188
+ #### `optional` - Optional Injection
189
+
190
+ Use `optional` to inject a dependency only if it's available:
191
+
192
+ ```typescript
193
+ @Injectable()
194
+ class FeatureService {
195
+ private readonly analytics = optional(AnalyticsService)
196
+
197
+ track(event: string) {
198
+ // Only calls analytics if the service is available
199
+ this.analytics?.track(event)
200
+ }
201
+ }
202
+ ```
203
+
158
204
  ### Factory Decorator
159
205
 
160
206
  Create instances using factory classes:
161
207
 
162
208
  ```typescript
163
- import { Factory } from '@navios/di'
209
+ import { Factory, Factorable, FactoryContext } from '@navios/di'
164
210
 
165
211
  @Factory()
166
- class DatabaseConnectionFactory {
167
- create() {
168
- return {
169
- host: 'localhost',
170
- port: 5432,
212
+ class DatabaseConnectionFactory implements Factorable<Connection> {
213
+ async create(ctx?: FactoryContext) {
214
+ const config = await ctx?.inject(ConfigService)
215
+
216
+ const connection = {
217
+ host: config?.host ?? 'localhost',
218
+ port: config?.port ?? 5432,
171
219
  connected: true,
172
220
  }
221
+
222
+ // Register cleanup callback
223
+ ctx?.addDestroyListener(() => {
224
+ connection.connected = false
225
+ })
226
+
227
+ return connection
173
228
  }
174
229
  }
175
230
 
@@ -203,46 +258,9 @@ class DatabaseService implements OnServiceInit, OnServiceDestroy {
203
258
 
204
259
  private async connect() {
205
260
  // Database connection logic
206
- return { connected: true }
207
- }
208
- }
209
- ```
210
-
211
- ### Request Context Management
212
-
213
- Manage request-scoped services with automatic cleanup and priority-based resolution:
214
-
215
- ```typescript
216
- import { Container, Injectable, InjectionToken } from '@navios/di'
217
-
218
- const REQUEST_ID_TOKEN = InjectionToken.create<string>('REQUEST_ID')
219
-
220
- @Injectable()
221
- class RequestLogger {
222
- private readonly requestId = asyncInject(REQUEST_ID_TOKEN)
223
-
224
- async log(message: string) {
225
- const id = await this.requestId
226
- console.log(`[${id}] ${message}`)
261
+ return { connected: true, close: async () => {} }
227
262
  }
228
263
  }
229
-
230
- // Usage
231
- const container = new Container()
232
-
233
- // Begin a request context
234
- const context = container.beginRequest('req-123', { userId: 456 }, 100)
235
- context.addInstance(REQUEST_ID_TOKEN, 'req-123')
236
-
237
- // Set as current context
238
- container.setCurrentRequestContext('req-123')
239
-
240
- // Use services within the request context
241
- const logger = await container.get(RequestLogger)
242
- await logger.log('Processing request') // "[req-123] Processing request"
243
-
244
- // End the request context (automatically cleans up)
245
- await container.endRequest('req-123')
246
264
  ```
247
265
 
248
266
  ### Injection Tokens
@@ -253,7 +271,6 @@ Use injection tokens for flexible dependency resolution:
253
271
 
254
272
  ```typescript
255
273
  import { Container, Injectable, InjectionToken } from '@navios/di'
256
-
257
274
  import { z } from 'zod'
258
275
 
259
276
  const configSchema = z.object({
@@ -261,7 +278,7 @@ const configSchema = z.object({
261
278
  timeout: z.number(),
262
279
  })
263
280
 
264
- const CONFIG_TOKEN = InjectionToken.create<Config, typeof configSchema>(
281
+ const CONFIG_TOKEN = InjectionToken.create<z.infer<typeof configSchema>, typeof configSchema>(
265
282
  'APP_CONFIG',
266
283
  configSchema,
267
284
  )
@@ -295,7 +312,6 @@ const BoundConfig = InjectionToken.bound(CONFIG_TOKEN, {
295
312
 
296
313
  // No need to provide arguments
297
314
  const container = new Container()
298
-
299
315
  const config = await container.get(BoundConfig)
300
316
  ```
301
317
 
@@ -317,13 +333,10 @@ const config = await container.get(FactoryConfig)
317
333
 
318
334
  ### Injectable with Schema
319
335
 
320
- Instead of creating an injection token with a schema, you can directly provide a schema to the `@Injectable` decorator. This is a more concise way to define services that require constructor arguments with validation.
321
-
322
- #### Basic Schema Usage
336
+ Instead of creating an injection token with a schema, you can directly provide a schema to the `@Injectable` decorator:
323
337
 
324
338
  ```typescript
325
- import { getInjectableToken, Injectable, InjectionToken } from '@navios/di'
326
-
339
+ import { Injectable } from '@navios/di'
327
340
  import { z } from 'zod'
328
341
 
329
342
  const databaseConfigSchema = z.object({
@@ -342,7 +355,7 @@ class DatabaseConfig {
342
355
  }
343
356
  }
344
357
 
345
- // Usage with bound token
358
+ // Usage with arguments
346
359
  const container = new Container()
347
360
  const config = await container.get(DatabaseConfig, {
348
361
  host: 'localhost',
@@ -353,34 +366,6 @@ const config = await container.get(DatabaseConfig, {
353
366
  console.log(config.getConnectionString()) // "localhost:5432"
354
367
  ```
355
368
 
356
- #### Schema with Different Scopes
357
-
358
- ```typescript
359
- // Singleton with schema (default)
360
- @Injectable({ schema: apiConfigSchema })
361
- class ApiConfig {
362
- constructor(public readonly config: z.output<typeof apiConfigSchema>) {}
363
- }
364
-
365
- // Transient with schema
366
- @Injectable({
367
- schema: loggerConfigSchema,
368
- scope: InjectableScope.Transient,
369
- })
370
- class Logger {
371
- constructor(public readonly config: z.output<typeof loggerConfigSchema>) {}
372
- }
373
-
374
- // Request-scoped with schema
375
- @Injectable({
376
- schema: userContextSchema,
377
- scope: InjectableScope.Request,
378
- })
379
- class UserContext {
380
- constructor(public readonly context: z.output<typeof userContextSchema>) {}
381
- }
382
- ```
383
-
384
369
  #### Using Schema-based Services as Dependencies
385
370
 
386
371
  ```typescript
@@ -395,7 +380,7 @@ class DatabaseConfig {
395
380
 
396
381
  @Injectable()
397
382
  class DatabaseService {
398
- // Inject with bound token
383
+ // Inject with bound arguments
399
384
  private dbConfig = inject(DatabaseConfig, {
400
385
  connectionString: 'postgres://localhost:5432/myapp',
401
386
  })
@@ -404,114 +389,36 @@ class DatabaseService {
404
389
  return `Connecting to ${this.dbConfig.config.connectionString}`
405
390
  }
406
391
  }
407
-
408
- // Or with async injection
409
- @Injectable()
410
- class AsyncDatabaseService {
411
- private dbConfig = asyncInject(DatabaseConfig, {
412
- connectionString: 'postgres://localhost:5432/myapp',
413
- })
414
-
415
- async connect() {
416
- const config = await this.dbConfig
417
- return `Connecting to ${config.config.connectionString}`
418
- }
419
- }
420
392
  ```
421
393
 
422
- #### Complex Nested Schemas
423
-
424
- ```typescript
425
- const appConfigSchema = z.object({
426
- database: z.object({
427
- host: z.string(),
428
- port: z.number(),
429
- credentials: z.object({
430
- username: z.string(),
431
- password: z.string(),
432
- }),
433
- }),
434
- cache: z.object({
435
- enabled: z.boolean(),
436
- ttl: z.number(),
437
- }),
438
- api: z.object({
439
- baseUrl: z.string(),
440
- timeout: z.number(),
441
- }),
442
- })
394
+ ## Advanced Usage
443
395
 
444
- @Injectable({ schema: appConfigSchema })
445
- class AppConfig {
446
- constructor(public readonly config: z.output<typeof appConfigSchema>) {}
396
+ ### Circular Dependency Detection
447
397
 
448
- getDatabaseConfig() {
449
- return this.config.database
450
- }
398
+ The library automatically detects circular dependencies and provides helpful error messages:
451
399
 
452
- getCacheConfig() {
453
- return this.config.cache
454
- }
400
+ ```typescript
401
+ @Injectable()
402
+ class ServiceA {
403
+ // Use asyncInject to break circular dependency
404
+ private serviceB = asyncInject(ServiceB)
455
405
 
456
- getApiConfig() {
457
- return this.config.api
406
+ async doSomething() {
407
+ const b = await this.serviceB
408
+ return b.getValue()
458
409
  }
459
410
  }
460
411
 
461
- // Usage
462
- const container = new Container()
463
- const config = await container.get(AppConfig, {
464
- database: {
465
- host: 'db.example.com',
466
- port: 5432,
467
- credentials: {
468
- username: 'admin',
469
- password: 'secret',
470
- },
471
- },
472
- cache: {
473
- enabled: true,
474
- ttl: 300,
475
- },
476
- api: {
477
- baseUrl: 'https://api.example.com',
478
- timeout: 5000,
479
- },
480
- })
481
- ```
482
-
483
- #### Schema vs Token with Schema
484
-
485
- There are two ways to use schemas with `@Injectable`:
486
-
487
- **Option 1: Direct Schema (Recommended for simplicity)**
488
-
489
- ```typescript
490
- @Injectable({ schema: configSchema })
491
- class ConfigService {
492
- constructor(public readonly config: z.output<typeof configSchema>) {}
493
- }
494
- ```
495
-
496
- **Option 2: Token with Schema (Use when you need to share the token)**
497
-
498
- ```typescript
499
- const CONFIG_TOKEN = InjectionToken.create('CONFIG', configSchema)
412
+ @Injectable()
413
+ class ServiceB {
414
+ private serviceA = inject(ServiceA)
500
415
 
501
- @Injectable({ token: CONFIG_TOKEN })
502
- class ConfigService {
503
- constructor(public readonly config: z.output<typeof configSchema>) {}
416
+ getValue() {
417
+ return 'value from B'
418
+ }
504
419
  }
505
420
  ```
506
421
 
507
- The direct schema approach is simpler and creates the injection token automatically. Use the token approach when you need to:
508
-
509
- - Share the same token across multiple classes
510
- - Reference the token in multiple places
511
- - Have more control over the token name
512
-
513
- ## Advanced Usage
514
-
515
422
  ### Custom Registry
516
423
 
517
424
  ```typescript
@@ -524,10 +431,21 @@ const container = new Container(customRegistry)
524
431
  ### Error Handling
525
432
 
526
433
  ```typescript
434
+ import { DIError, DIErrorCode } from '@navios/di'
435
+
527
436
  try {
528
437
  const service = await container.get(NonExistentService)
529
438
  } catch (error) {
530
- console.error('Service not found:', error.message)
439
+ if (error instanceof DIError) {
440
+ switch (error.code) {
441
+ case DIErrorCode.FactoryNotFound:
442
+ console.error('Service not registered')
443
+ break
444
+ case DIErrorCode.InstanceDestroying:
445
+ console.error('Service is being destroyed')
446
+ break
447
+ }
448
+ }
531
449
  }
532
450
  ```
533
451
 
@@ -545,14 +463,32 @@ const newService = await container.get(MyService)
545
463
 
546
464
  ### Container
547
465
 
548
- - `get<T>(token: T): Promise<InstanceType<T>>` - Get an instance
466
+ - `get<T>(token: T, args?): Promise<T>` - Get an instance
549
467
  - `invalidate(service: unknown): Promise<void>` - Invalidate a service
550
468
  - `ready(): Promise<void>` - Wait for pending operations
469
+ - `dispose(): Promise<void>` - Clean up all resources
470
+ - `clear(): Promise<void>` - Clear all instances and bindings
471
+ - `isRegistered(token: any): boolean` - Check if service is registered
551
472
  - `getServiceLocator(): ServiceLocator` - Get underlying service locator
552
- - `beginRequest(requestId: string, metadata?, priority?): RequestContextHolder` - Begin request context
553
- - `endRequest(requestId: string): Promise<void>` - End request context
554
- - `getCurrentRequestContext(): RequestContextHolder | null` - Get current request context
555
- - `setCurrentRequestContext(requestId: string): void` - Set current request context
473
+ - `getRegistry(): Registry` - Get the registry
474
+ - `beginRequest(requestId: string, metadata?, priority?): ScopedContainer` - Begin request context
475
+ - `getActiveRequestIds(): ReadonlySet<string>` - Get active request IDs
476
+ - `hasActiveRequest(requestId: string): boolean` - Check if request is active
477
+
478
+ ### ScopedContainer
479
+
480
+ - `get<T>(token: T, args?): Promise<T>` - Get an instance (request-scoped or delegated)
481
+ - `invalidate(service: unknown): Promise<void>` - Invalidate a service
482
+ - `endRequest(): Promise<void>` - End request and cleanup
483
+ - `dispose(): Promise<void>` - Alias for endRequest()
484
+ - `ready(): Promise<void>` - Wait for pending operations
485
+ - `isRegistered(token: any): boolean` - Check if service is registered
486
+ - `getMetadata(key: string): any` - Get request metadata
487
+ - `setMetadata(key: string, value: any): void` - Set request metadata
488
+ - `addInstance(token: InjectionToken, instance: any): void` - Add pre-prepared instance
489
+ - `getRequestId(): string` - Get the request ID
490
+ - `getParent(): Container` - Get the parent container
491
+ - `getRequestContextHolder(): RequestContext` - Get the underlying request context
556
492
 
557
493
  ### Injectable Decorator
558
494
 
@@ -560,7 +496,7 @@ const newService = await container.get(MyService)
560
496
  - Options:
561
497
  - `scope?: InjectableScope` - Service scope (Singleton | Transient | Request)
562
498
  - `token?: InjectionToken` - Custom injection token
563
- - `schema?: ZodSchema` - Zod schema for constructor arguments (alternative to token)
499
+ - `schema?: ZodSchema` - Zod schema for constructor arguments
564
500
  - `registry?: Registry` - Custom registry
565
501
  - Note: Cannot use both `token` and `schema` options together
566
502
 
@@ -574,8 +510,11 @@ const newService = await container.get(MyService)
574
510
 
575
511
  ### Injection Methods
576
512
 
577
- - `inject<T>(token: T): T` - Synchronous injection
578
- - `asyncInject<T>(token: T): Promise<T>` - Asynchronous injection
513
+ - `inject<T>(token: T, args?): T` - Synchronous injection
514
+ - `asyncInject<T>(token: T, args?): Promise<T>` - Asynchronous injection
515
+ - `optional<T>(token: T, args?): T | null` - Optional injection
516
+ - `wrapSyncInit<T>(fn: () => T): T` - Wrap synchronous initialization
517
+ - `provideFactoryContext<T>(ctx: FactoryContext, fn: () => T): T` - Provide factory context
579
518
 
580
519
  ### Injection Tokens
581
520
 
@@ -589,24 +528,62 @@ const newService = await container.get(MyService)
589
528
  - `OnServiceInit` - Implement `onServiceInit(): Promise<void> | void`
590
529
  - `OnServiceDestroy` - Implement `onServiceDestroy(): Promise<void> | void`
591
530
 
592
- ### Request Context
531
+ ### RequestContext
532
+
533
+ - `requestId: string` - Unique request identifier
534
+ - `priority: number` - Priority for resolution
535
+ - `metadata: Map<string, any>` - Request metadata
536
+ - `createdAt: number` - Creation timestamp
537
+ - `addInstance(token, instance): void` - Add pre-prepared instance
538
+ - `get(instanceName): InstanceHolder | undefined` - Get instance holder
539
+ - `set(instanceName, holder): void` - Set instance holder
540
+ - `has(instanceName): boolean` - Check if instance exists
541
+ - `clear(): void` - Clear all instances
542
+ - `getMetadata(key): any` - Get metadata value
543
+ - `setMetadata(key, value): void` - Set metadata value
544
+
545
+ ## Testing
546
+
547
+ ### TestContainer
548
+
549
+ ```typescript
550
+ import { TestContainer } from '@navios/di/testing'
551
+
552
+ describe('UserService', () => {
553
+ let container: TestContainer
554
+
555
+ beforeEach(() => {
556
+ container = new TestContainer()
557
+ })
558
+
559
+ afterEach(async () => {
560
+ await container.dispose()
561
+ })
562
+
563
+ it('should create user', async () => {
564
+ // Bind mock
565
+ container.bind(DatabaseService).toValue({
566
+ save: vi.fn().mockResolvedValue({ id: '1' }),
567
+ })
593
568
 
594
- - `RequestContextHolder` - Interface for managing request-scoped instances
595
- - `beginRequest(requestId, metadata?, priority?)` - Create new request context
596
- - `endRequest(requestId)` - Clean up request context
597
- - `addInstance(token, instance)` - Add pre-prepared instance to context
598
- - `setMetadata(key, value)` - Set request-specific metadata
569
+ const userService = await container.get(UserService)
570
+ const user = await userService.create({ name: 'John' })
571
+
572
+ expect(user.id).toBe('1')
573
+ })
574
+ })
575
+ ```
599
576
 
600
577
  ## Best Practices
601
578
 
602
- 1. **Use `asyncInject` for most dependencies** - Safer than `inject` and handles async initialization
603
- 2. **Use `inject` only for immediate dependencies** - When you're certain the dependency is ready
604
- 3. **Implement lifecycle hooks** - For proper resource management
605
- 4. **Use injection tokens** - For configuration and interface-based dependencies
606
- 5. **Prefer singletons** - Unless you specifically need new instances each time
607
- 6. **Use factories** - For complex object creation logic
608
- 7. **Leverage request contexts** - For request-scoped data and cleanup
609
- 8. **Set appropriate priorities** - When using multiple request contexts
579
+ 1. **Use `asyncInject` for circular dependencies** - Breaks circular dependency cycles safely
580
+ 2. **Use `inject` for simple dependencies** - When you're certain the dependency is ready
581
+ 3. **Use `optional` for feature flags** - Dependencies that may not be available
582
+ 4. **Implement lifecycle hooks** - For proper resource management
583
+ 5. **Use injection tokens** - For configuration and interface-based dependencies
584
+ 6. **Prefer singletons** - Unless you specifically need new instances each time
585
+ 7. **Use factories** - For complex object creation logic
586
+ 8. **Leverage ScopedContainer** - For request-scoped data and cleanup
610
587
 
611
588
  ## License
612
589