@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/README.md CHANGED
@@ -11,8 +11,7 @@ Welcome to the comprehensive documentation for Navios DI, a powerful dependency
11
11
  - [Injection Tokens](./injection-tokens.md) - Token-based dependency resolution
12
12
  - [Request Contexts](./request-contexts.md) - Request-scoped services and cleanup
13
13
  - [Service Lifecycle](./lifecycle.md) - Initialization and cleanup hooks
14
- - [Scopes](./scopes.md) - Singleton and transient service scopes
15
- - [Advanced Patterns](./advanced-patterns.md) - Complex usage scenarios
14
+ - [Scopes](./scopes.md) - Singleton, transient, and request service scopes
16
15
  - [API Reference](./api-reference.md) - Complete API documentation
17
16
  - [Migration Guide](./migration.md) - Upgrading from older versions
18
17
 
@@ -30,15 +29,17 @@ Welcome to the comprehensive documentation for Navios DI, a powerful dependency
30
29
 
31
30
  - **Type Safety** - Full TypeScript support with compile-time checking
32
31
  - **Lifecycle Management** - Built-in hooks for service initialization and cleanup
33
- - **Multiple Scopes** - Singleton and transient service lifetimes
32
+ - **Multiple Scopes** - Singleton, transient, and request service lifetimes
34
33
  - **Async/Sync Injection** - Both synchronous and asynchronous dependency resolution
34
+ - **Circular Dependency Detection** - Automatic detection with clear error messages
35
35
  - **Factory Pattern** - Complex object creation with factory classes
36
36
  - **Request Contexts** - Request-scoped services with priority resolution and automatic cleanup
37
+ - **Cross-Platform** - Works in Node.js, Bun, Deno, and browsers
37
38
 
38
39
  ### Getting Started
39
40
 
40
41
  ```typescript
41
- import { asyncInject, Container, Injectable } from '@navios/di'
42
+ import { inject, Container, Injectable } from '@navios/di'
42
43
 
43
44
  @Injectable()
44
45
  class DatabaseService {
@@ -49,11 +50,11 @@ class DatabaseService {
49
50
 
50
51
  @Injectable()
51
52
  class UserService {
52
- private readonly db = asyncInject(DatabaseService)
53
+ private readonly db = inject(DatabaseService)
53
54
 
54
55
  async getUsers() {
55
- const connection = await this.db.connect()
56
- return `Users from ${connection}`
56
+ const users = await this.db.query('SELECT * FROM users')
57
+ return users
57
58
  }
58
59
  }
59
60
 
@@ -68,10 +69,12 @@ console.log(await userService.getUsers())
68
69
  Navios DI follows a modern, decorator-based architecture:
69
70
 
70
71
  1. **Services** are marked with `@Injectable()` decorator
71
- 2. **Dependencies** are injected using `asyncInject()` or `inject()`
72
+ 2. **Dependencies** are injected using `inject()` or `asyncInject()`
72
73
  3. **Container** manages service instances and their lifecycle
73
- 4. **Injection Tokens** provide flexible dependency resolution
74
- 5. **Factories** handle complex object creation
74
+ 4. **ScopedContainer** provides request-scoped isolation
75
+ 5. **Injection Tokens** provide flexible dependency resolution
76
+ 6. **Factories** handle complex object creation
77
+ 7. **CircularDetector** prevents circular dependency deadlocks
75
78
 
76
79
  ## Design Principles
77
80
 
@@ -80,6 +83,16 @@ Navios DI follows a modern, decorator-based architecture:
80
83
  - **Flexible Resolution** - Support both class-based and token-based injection
81
84
  - **Lifecycle Awareness** - Built-in support for service initialization and cleanup
82
85
  - **Performance Optimized** - Efficient instance management and caching
86
+ - **Safe by Default** - Automatic circular dependency detection
87
+
88
+ ## Platform Support
89
+
90
+ | Platform | AsyncLocalStorage | Notes |
91
+ | -------- | ----------------- | ---------------------------------- |
92
+ | Node.js | Native | Full async tracking support |
93
+ | Bun | Native | Full async tracking support |
94
+ | Deno | Native | Via Node compatibility layer |
95
+ | Browser | Polyfill | Sync-only tracking (SyncLocalStorage) |
83
96
 
84
97
  ## Examples
85
98
 
@@ -149,9 +162,54 @@ class DatabaseService implements OnServiceInit, OnServiceDestroy {
149
162
  }
150
163
  ```
151
164
 
165
+ ### Handling Circular Dependencies
166
+
167
+ ```typescript
168
+ // Use asyncInject to break circular dependencies
169
+ @Injectable()
170
+ class ServiceA {
171
+ private serviceB = asyncInject(ServiceB) // Use asyncInject to break cycle
172
+
173
+ async doSomething() {
174
+ const b = await this.serviceB
175
+ return b.process()
176
+ }
177
+ }
178
+
179
+ @Injectable()
180
+ class ServiceB {
181
+ private serviceA = inject(ServiceA) // This side can use inject()
182
+
183
+ process() {
184
+ return 'processed'
185
+ }
186
+ }
187
+ ```
188
+
189
+ ### Request-Scoped Services
190
+
191
+ ```typescript
192
+ @Injectable({ scope: InjectableScope.Request })
193
+ class RequestContext {
194
+ userId?: string
195
+ correlationId?: string
196
+ }
197
+
198
+ // In HTTP middleware
199
+ app.use(async (req, res, next) => {
200
+ const scoped = container.beginRequest(req.id, { userId: req.userId })
201
+ req.container = scoped
202
+
203
+ try {
204
+ await next()
205
+ } finally {
206
+ await scoped.endRequest()
207
+ }
208
+ })
209
+ ```
210
+
152
211
  ## Need Help?
153
212
 
154
213
  - Check the [API Reference](./api-reference.md) for detailed method documentation
155
- - Look at [Advanced Patterns](./advanced-patterns.md) for complex scenarios
156
214
  - Review the [Migration Guide](./migration.md) if upgrading from older versions
157
215
  - See the [Examples](./examples/) folder for complete working examples
@@ -9,9 +9,14 @@ Complete API reference for Navios DI library.
9
9
  The main entry point for dependency injection.
10
10
 
11
11
  ```typescript
12
- class Container {
13
- constructor(registry?: Registry, logger?: Console | null)
14
-
12
+ class Container implements IContainer {
13
+ constructor(
14
+ registry?: Registry,
15
+ logger?: Console | null,
16
+ injectors?: Injectors
17
+ )
18
+
19
+ // Service resolution
15
20
  get<T>(token: T): Promise<InstanceType<T>>
16
21
  get<T, S extends InjectionTokenSchemaType>(
17
22
  token: InjectionToken<T, S>,
@@ -21,18 +26,26 @@ class Container {
21
26
  get<T>(token: BoundInjectionToken<T, any>): Promise<T>
22
27
  get<T>(token: FactoryInjectionToken<T, any>): Promise<T>
23
28
 
29
+ // Lifecycle
24
30
  invalidate(service: unknown): Promise<void>
25
31
  ready(): Promise<void>
32
+ dispose(): Promise<void>
33
+ clear(): Promise<void>
34
+
35
+ // Introspection
36
+ isRegistered(token: any): boolean
26
37
  getServiceLocator(): ServiceLocator
38
+ getRegistry(): Registry
39
+ tryGetSync<T>(token: any, args?: any): T | null
27
40
 
28
41
  // Request Context Management
29
42
  beginRequest(
30
43
  requestId: string,
31
44
  metadata?: Record<string, any>,
32
45
  priority?: number,
33
- ): RequestContextHolder
34
- endRequest(requestId: string): Promise<void>
35
- setCurrentRequestContext(requestId: string): void
46
+ ): ScopedContainer
47
+ getActiveRequestIds(): ReadonlySet<string>
48
+ hasActiveRequest(requestId: string): boolean
36
49
  }
37
50
  ```
38
51
 
@@ -40,20 +53,72 @@ class Container {
40
53
 
41
54
  - `registry?: Registry` - Optional registry instance (defaults to global registry)
42
55
  - `logger?: Console | null` - Optional logger for debugging
56
+ - `injectors?: Injectors` - Optional custom injectors
43
57
 
44
58
  **Methods:**
45
59
 
46
- - `get<T>(token: T)` - Get a service instance
60
+ - `get<T>(token: T)` - Get a service instance (throws error for request-scoped services)
47
61
  - `invalidate(service: unknown)` - Invalidate a service and its dependencies
48
62
  - `ready()` - Wait for all pending operations to complete
63
+ - `dispose()` - Clean up all resources
64
+ - `clear()` - Clear all instances and bindings
65
+ - `isRegistered(token: any)` - Check if a service is registered
49
66
  - `getServiceLocator()` - Get the underlying ServiceLocator instance
67
+ - `getRegistry()` - Get the registry
68
+ - `tryGetSync<T>(token, args?)` - Get instance synchronously if it exists
50
69
 
51
70
  **Request Context Management:**
52
71
 
53
- - `beginRequest(requestId: string, metadata?: Record<string, any>, priority?: number)` - Begin a new request context
54
- - `endRequest(requestId: string)` - End a request context and clean up instances
55
- - `getCurrentRequestContext()` - Get the current request context
56
- - `setCurrentRequestContext(requestId: string)` - Switch to a different request context
72
+ - `beginRequest(requestId, metadata?, priority?)` - Begin a new request context, returns `ScopedContainer`
73
+ - `getActiveRequestIds()` - Get set of active request IDs
74
+ - `hasActiveRequest(requestId)` - Check if a request is active
75
+
76
+ ### ScopedContainer
77
+
78
+ Request-scoped container for isolated request-scoped service resolution.
79
+
80
+ ```typescript
81
+ class ScopedContainer implements IContainer {
82
+ readonly requestId: string
83
+
84
+ // Service resolution
85
+ get<T>(token: T): Promise<InstanceType<T>>
86
+ get<T, S extends InjectionTokenSchemaType>(
87
+ token: InjectionToken<T, S>,
88
+ args: z.input<S>,
89
+ ): Promise<T>
90
+
91
+ // Lifecycle
92
+ invalidate(service: unknown): Promise<void>
93
+ endRequest(): Promise<void>
94
+ dispose(): Promise<void> // Alias for endRequest()
95
+ ready(): Promise<void>
96
+
97
+ // Introspection
98
+ isRegistered(token: any): boolean
99
+ getParent(): Container
100
+ getRequestId(): string
101
+ getRequestContextHolder(): RequestContext
102
+ getHolderStorage(): IHolderStorage
103
+ tryGetSync<T>(token: any, args?: any): T | null
104
+
105
+ // Metadata
106
+ getMetadata(key: string): any | undefined
107
+ setMetadata(key: string, value: any): void
108
+ addInstance(token: InjectionToken<any, undefined>, instance: any): void
109
+ }
110
+ ```
111
+
112
+ **Methods:**
113
+
114
+ - `get<T>(token: T)` - Get a service instance (request-scoped or delegated to parent)
115
+ - `invalidate(service: unknown)` - Invalidate a service
116
+ - `endRequest()` - End request and cleanup all request-scoped instances
117
+ - `dispose()` - Alias for `endRequest()`
118
+ - `ready()` - Wait for pending operations
119
+ - `getMetadata(key)` - Get request metadata
120
+ - `setMetadata(key, value)` - Set request metadata
121
+ - `addInstance(token, instance)` - Add pre-prepared instance to request context
57
122
 
58
123
  ### InjectionToken
59
124
 
@@ -317,85 +382,103 @@ interface FactorableWithArgs<T, S> {
317
382
  }
318
383
  ```
319
384
 
320
- ### RequestContextHolder
385
+ ### RequestContext
321
386
 
322
387
  Interface for managing request-scoped instances.
323
388
 
324
389
  ```typescript
325
- interface RequestContextHolder {
390
+ interface RequestContext {
326
391
  readonly requestId: string
327
- readonly holders: Map<string, ServiceLocatorInstanceHolder>
392
+ readonly holders: Map<string, InstanceHolder>
328
393
  readonly priority: number
329
394
  readonly metadata: Map<string, any>
330
395
  readonly createdAt: number
331
396
 
397
+ addInstance(token: InjectionToken<any, undefined>, instance: any): void
332
398
  addInstance(
333
399
  instanceName: string,
334
400
  instance: any,
335
- holder: ServiceLocatorInstanceHolder,
401
+ holder: InstanceHolder,
336
402
  ): void
337
- addInstance(token: InjectionToken<any, undefined>, instance: any): void
338
- get(instanceName: string): ServiceLocatorInstanceHolder | undefined
403
+ get(instanceName: string): InstanceHolder | undefined
404
+ set(instanceName: string, holder: InstanceHolder): void
339
405
  has(instanceName: string): boolean
340
406
  clear(): void
341
407
  getMetadata(key: string): any | undefined
342
408
  setMetadata(key: string, value: any): void
343
-
344
- // Inherited from BaseInstanceHolderManager
345
409
  filter(
346
- predicate: (
347
- value: ServiceLocatorInstanceHolder<any>,
348
- key: string,
349
- ) => boolean,
350
- ): Map<string, ServiceLocatorInstanceHolder>
410
+ predicate: (value: InstanceHolder<any>, key: string) => boolean,
411
+ ): Map<string, InstanceHolder>
351
412
  delete(name: string): boolean
352
413
  size(): number
353
414
  isEmpty(): boolean
354
415
  }
355
416
  ```
356
417
 
418
+ **Deprecated alias:** `RequestContextHolder`
419
+
357
420
  ### FactoryContext
358
421
 
359
422
  Context provided to factory methods.
360
423
 
361
424
  ```typescript
362
425
  interface FactoryContext {
363
- inject<T>(token: T): Promise<T>
426
+ inject: typeof asyncInject
364
427
  locator: ServiceLocator
365
- on(event: string, listener: Function): void
366
- getDependencies(): any[]
367
- invalidate(): Promise<void>
368
- addEffect(effect: Function): void
369
- setTtl(ttl: number): void
370
- getTtl(): number | null
428
+ addDestroyListener: (listener: () => void | Promise<void>) => void
371
429
  }
372
430
  ```
373
431
 
374
- ### RequestContextHolder
432
+ ### IContainer
375
433
 
376
- Request context holder for managing request-scoped instances.
434
+ Common interface for Container and ScopedContainer.
377
435
 
378
436
  ```typescript
379
- interface RequestContextHolder {
380
- readonly requestId: string
381
- readonly priority: number
382
- readonly createdAt: number
383
- readonly instances: Map<string, any>
384
- readonly metadata: Map<string, any>
437
+ interface IContainer {
438
+ get<T>(token: T, args?: any): Promise<T>
439
+ invalidate(service: unknown): Promise<void>
440
+ isRegistered(token: any): boolean
441
+ dispose(): Promise<void>
442
+ ready(): Promise<void>
443
+ tryGetSync<T>(token: any, args?: any): T | null
444
+ }
445
+ ```
385
446
 
386
- addInstance(
387
- instanceName: string,
388
- instance: any,
389
- holder: ServiceLocatorInstanceHolder,
390
- ): void
391
- getInstance(instanceName: string): any | undefined
392
- hasInstance(instanceName: string): boolean
393
- clear(): void
394
- getMetadata(key: string): any | undefined
395
- setMetadata(key: string, value: any): void
447
+ ### InstanceHolder
448
+
449
+ Represents a managed service instance with its lifecycle state.
450
+
451
+ ```typescript
452
+ interface InstanceHolder<T = unknown> {
453
+ name: string
454
+ instance: T | null
455
+ status: InstanceStatus
456
+ type: InjectableType
457
+ scope: InjectableScope
458
+ deps: Set<string> // Services this holder depends on
459
+ waitingFor: Set<string> // Services this holder is waiting for (cycle detection)
460
+ destroyListeners: InstanceDestroyListener[]
461
+ createdAt: number
462
+ creationPromise: Promise<[undefined, T]> | null
463
+ destroyPromise: Promise<void> | null
396
464
  }
397
465
  ```
398
466
 
467
+ **Deprecated alias:** `ServiceLocatorInstanceHolder`
468
+
469
+ ### InstanceStatus
470
+
471
+ ```typescript
472
+ enum InstanceStatus {
473
+ Creating = 'creating',
474
+ Created = 'created',
475
+ Destroying = 'destroying',
476
+ Error = 'error',
477
+ }
478
+ ```
479
+
480
+ **Deprecated alias:** `ServiceLocatorInstanceHolderStatus`
481
+
399
482
  ## Functions
400
483
 
401
484
  ### asyncInject
@@ -448,6 +531,66 @@ function inject<T>(token: BoundInjectionToken<T, any>): T
448
531
  function inject<T>(token: FactoryInjectionToken<T, any>): T
449
532
  ```
450
533
 
534
+ ### optional
535
+
536
+ Optional dependency injection (returns null if not registered).
537
+
538
+ ```typescript
539
+ function optional<T extends ClassType>(
540
+ token: T,
541
+ ): InstanceType<T> | null
542
+ function optional<T>(token: InjectionToken<T, any>): T | null
543
+ ```
544
+
545
+ ### wrapSyncInit
546
+
547
+ Wraps a synchronous initialization function.
548
+
549
+ ```typescript
550
+ function wrapSyncInit<T>(fn: () => T): T
551
+ ```
552
+
553
+ ### provideFactoryContext
554
+
555
+ Provides a factory context for the duration of a function execution.
556
+
557
+ ```typescript
558
+ function provideFactoryContext<T>(ctx: FactoryContext, fn: () => T): T
559
+ ```
560
+
561
+ ### withResolutionContext
562
+
563
+ Runs a function within a resolution context for cycle detection.
564
+
565
+ ```typescript
566
+ function withResolutionContext<T>(
567
+ waiterHolder: InstanceHolder,
568
+ getHolder: (name: string) => InstanceHolder | undefined,
569
+ fn: () => T
570
+ ): T
571
+ ```
572
+
573
+ ### getCurrentResolutionContext
574
+
575
+ Gets the current resolution context if any.
576
+
577
+ ```typescript
578
+ function getCurrentResolutionContext(): ResolutionContextData | undefined
579
+
580
+ interface ResolutionContextData {
581
+ waiterHolder: InstanceHolder
582
+ getHolder: (name: string) => InstanceHolder | undefined
583
+ }
584
+ ```
585
+
586
+ ### withoutResolutionContext
587
+
588
+ Runs a function outside of any resolution context (used by asyncInject).
589
+
590
+ ```typescript
591
+ function withoutResolutionContext<T>(fn: () => T): T
592
+ ```
593
+
451
594
  ## Types
452
595
 
453
596
  ### ClassType
@@ -553,73 +696,73 @@ type InjectionTokenType =
553
696
 
554
697
  ### Injection Method Compatibility
555
698
 
556
- | Scope | inject | asyncInject |
557
- | --------- | ---------------- | ------------ |
558
- | Singleton | ✅ Supported | ✅ Supported |
559
- | Transient | Not Supported | ✅ Supported |
560
- | Request | ✅ Supported | ✅ Supported |
561
-
562
- **Note:** The `inject` function only works with Singleton and Request scopes because it requires synchronous resolution. Transient services must use `asyncInject` since they create new instances on each injection.
563
-
564
- ## Error Classes
565
-
566
- ### InstanceNotFoundError
699
+ | Scope | inject | asyncInject | optional |
700
+ | --------- | ------------ | ------------ | ------------ |
701
+ | Singleton | ✅ Supported | ✅ Supported | ✅ Supported |
702
+ | Transient | Supported | ✅ Supported | ✅ Supported |
703
+ | Request | ✅ Supported | ✅ Supported | ✅ Supported |
567
704
 
568
- Thrown when a service instance is not found.
705
+ **Notes:**
706
+ - `inject()` works with all scopes but returns a proxy for dependencies not yet initialized
707
+ - `asyncInject()` is recommended for circular dependencies as it runs outside the resolution context
708
+ - `optional()` returns `null` if the service is not registered
569
709
 
570
- ```typescript
571
- class InstanceNotFoundError extends Error {
572
- constructor(message: string)
573
- }
574
- ```
710
+ ## Error Handling
575
711
 
576
- ### InstanceExpiredError
712
+ ### DIError
577
713
 
578
- Thrown when a service instance has expired.
714
+ Base error class for all DI-related errors.
579
715
 
580
716
  ```typescript
581
- class InstanceExpiredError extends Error {
582
- constructor(message: string)
583
- }
584
- ```
585
-
586
- ### InstanceDestroyingError
717
+ class DIError extends Error {
718
+ readonly code: DIErrorCode
587
719
 
588
- Thrown when trying to access a service being destroyed.
720
+ constructor(code: DIErrorCode, message: string)
589
721
 
590
- ```typescript
591
- class InstanceDestroyingError extends Error {
592
- constructor(message: string)
722
+ // Static factory methods
723
+ static factoryNotFound(message: string): DIError
724
+ static factoryTokenNotResolved(message: string): DIError
725
+ static instanceNotFound(message: string): DIError
726
+ static instanceDestroying(message: string): DIError
727
+ static circularDependency(message: string): DIError
728
+ static unknown(message: string): DIError
593
729
  }
594
730
  ```
595
731
 
596
- ### FactoryNotFoundError
597
-
598
- Thrown when a factory is not found.
732
+ ### DIErrorCode
599
733
 
600
734
  ```typescript
601
- class FactoryNotFoundError extends Error {
602
- constructor(message: string)
735
+ enum DIErrorCode {
736
+ FactoryNotFound = 'FACTORY_NOT_FOUND',
737
+ FactoryTokenNotResolved = 'FACTORY_TOKEN_NOT_RESOLVED',
738
+ InstanceNotFound = 'INSTANCE_NOT_FOUND',
739
+ InstanceDestroying = 'INSTANCE_DESTROYING',
740
+ CircularDependency = 'CIRCULAR_DEPENDENCY',
741
+ UnknownError = 'UNKNOWN_ERROR',
603
742
  }
604
743
  ```
605
744
 
606
- ### FactoryTokenNotResolvedError
607
-
608
- Thrown when a factory token cannot be resolved.
745
+ ### Error Handling Example
609
746
 
610
747
  ```typescript
611
- class FactoryTokenNotResolvedError extends Error {
612
- constructor(message: string)
613
- }
614
- ```
615
-
616
- ### UnknownError
748
+ import { DIError, DIErrorCode } from '@navios/di'
617
749
 
618
- Thrown for unexpected errors.
619
-
620
- ```typescript
621
- class UnknownError extends Error {
622
- constructor(message: string)
750
+ try {
751
+ const service = await container.get(UnregisteredService)
752
+ } catch (error) {
753
+ if (error instanceof DIError) {
754
+ switch (error.code) {
755
+ case DIErrorCode.FactoryNotFound:
756
+ console.error('Service not registered')
757
+ break
758
+ case DIErrorCode.InstanceDestroying:
759
+ console.error('Service is being destroyed')
760
+ break
761
+ case DIErrorCode.CircularDependency:
762
+ console.error('Circular dependency detected:', error.message)
763
+ break
764
+ }
765
+ }
623
766
  }
624
767
  ```
625
768
 
@@ -764,22 +907,13 @@ class RequestContext {
764
907
  @Injectable({ scope: InjectableScope.Request })
765
908
  class UserSession {
766
909
  private readonly context = inject(RequestContext)
767
- private readonly userId: string
768
-
769
- constructor(userId: string) {
770
- this.userId = userId
771
- }
772
-
773
- getUserId() {
774
- return this.userId
775
- }
910
+ userId?: string
776
911
 
777
- async getRequestInfo() {
778
- const ctx = await this.context
912
+ getRequestInfo() {
779
913
  return {
780
914
  userId: this.userId,
781
- requestId: ctx.getRequestId(),
782
- duration: ctx.getDuration(),
915
+ requestId: this.context.getRequestId(),
916
+ duration: this.context.getDuration(),
783
917
  }
784
918
  }
785
919
  }
@@ -787,15 +921,45 @@ class UserSession {
787
921
  // Usage
788
922
  const container = new Container()
789
923
 
790
- // Begin request context
791
- container.beginRequest('req-123', { userId: 'user123' })
924
+ // Begin request context - returns a ScopedContainer
925
+ const scoped = container.beginRequest('req-123', { userId: 'user123' })
792
926
 
793
- // Get request-scoped instances
794
- const session1 = await container.get(UserSession)
795
- const session2 = await container.get(UserSession)
927
+ // Get request-scoped instances from the ScopedContainer
928
+ const session1 = await scoped.get(UserSession)
929
+ const session2 = await scoped.get(UserSession)
796
930
 
797
931
  console.log(session1 === session2) // true - same instance within request
798
932
 
799
- // End request context
800
- await container.endRequest('req-123')
933
+ // End request context and cleanup
934
+ await scoped.endRequest()
935
+ ```
936
+
937
+ ### Circular Dependency Handling
938
+
939
+ ```typescript
940
+ import { asyncInject, inject, Injectable } from '@navios/di'
941
+
942
+ // Use asyncInject to break circular dependencies
943
+ @Injectable()
944
+ class ServiceA {
945
+ private serviceB = asyncInject(ServiceB) // Break cycle here
946
+
947
+ async doSomething() {
948
+ const b = await this.serviceB
949
+ return b.process()
950
+ }
951
+ }
952
+
953
+ @Injectable()
954
+ class ServiceB {
955
+ private serviceA = inject(ServiceA) // This side can use inject
956
+
957
+ process() {
958
+ return 'processed'
959
+ }
960
+ }
961
+
962
+ const container = new Container()
963
+ const serviceA = await container.get(ServiceA)
964
+ await serviceA.doSomething() // Works!
801
965
  ```