@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
@@ -1,53 +1,53 @@
1
1
  import { describe, expect, it } from 'vitest'
2
2
 
3
3
  import { InjectableScope, InjectableType } from '../enums/index.mjs'
4
- import { DefaultRequestContextHolder } from '../request-context-holder.mjs'
5
- import { ServiceLocatorManager } from '../service-locator-manager.mjs'
4
+ import { DefaultRequestContext } from '../internal/context/request-context.mjs'
5
+ import { HolderManager } from '../internal/holder/holder-manager.mjs'
6
6
 
7
7
  describe('Unified API', () => {
8
- describe('Common methods between ServiceLocatorManager and RequestContextHolder', () => {
8
+ describe('Common methods between HolderManager and RequestContext', () => {
9
9
  it('should have the same basic API surface', () => {
10
- const serviceManager = new ServiceLocatorManager()
11
- const requestContext = new DefaultRequestContextHolder(
10
+ const holderManager = new HolderManager()
11
+ const requestContext = new DefaultRequestContext(
12
12
  'test-request',
13
13
  100,
14
14
  )
15
15
 
16
16
  // Both should have the same common methods
17
- expect(typeof serviceManager.size).toBe('function')
17
+ expect(typeof holderManager.size).toBe('function')
18
18
  expect(typeof requestContext.size).toBe('function')
19
19
 
20
- expect(typeof serviceManager.isEmpty).toBe('function')
20
+ expect(typeof holderManager.isEmpty).toBe('function')
21
21
  expect(typeof requestContext.isEmpty).toBe('function')
22
22
 
23
- expect(typeof serviceManager.filter).toBe('function')
23
+ expect(typeof holderManager.filter).toBe('function')
24
24
  expect(typeof requestContext.filter).toBe('function')
25
25
 
26
- expect(typeof serviceManager.clear).toBe('function')
26
+ expect(typeof holderManager.clear).toBe('function')
27
27
  expect(typeof requestContext.clear).toBe('function')
28
28
 
29
- expect(typeof serviceManager.delete).toBe('function')
29
+ expect(typeof holderManager.delete).toBe('function')
30
30
  expect(typeof requestContext.delete).toBe('function')
31
31
 
32
- expect(typeof serviceManager.getAllNames).toBe('function')
32
+ expect(typeof holderManager.getAllNames).toBe('function')
33
33
  expect(typeof requestContext.getAllNames).toBe('function')
34
34
 
35
- expect(typeof serviceManager.getAllHolders).toBe('function')
35
+ expect(typeof holderManager.getAllHolders).toBe('function')
36
36
  expect(typeof requestContext.getAllHolders).toBe('function')
37
37
 
38
- expect(typeof serviceManager.createCreatingHolder).toBe('function')
38
+ expect(typeof holderManager.createCreatingHolder).toBe('function')
39
39
  expect(typeof requestContext.createCreatingHolder).toBe('function')
40
40
  })
41
41
 
42
42
  it('should work with the same holder creation patterns', () => {
43
- const serviceManager = new ServiceLocatorManager()
44
- const requestContext = new DefaultRequestContextHolder(
43
+ const holderManager = new HolderManager()
44
+ const requestContext = new DefaultRequestContext(
45
45
  'test-request',
46
46
  100,
47
47
  )
48
48
 
49
49
  // Both should be able to create holders the same way
50
- const [deferred1, holder1] = serviceManager.createCreatingHolder(
50
+ const [deferred1, holder1] = holderManager.createCreatingHolder(
51
51
  'Service1',
52
52
  InjectableType.Class,
53
53
  InjectableScope.Singleton,
@@ -65,22 +65,22 @@ describe('Unified API', () => {
65
65
  expect(holder2.scope).toBe(InjectableScope.Request)
66
66
 
67
67
  // Both should be able to store holders
68
- serviceManager.set('Service1', holder1)
68
+ holderManager.set('Service1', holder1)
69
69
  requestContext.set('Service2', holder2)
70
70
 
71
- expect(serviceManager.size()).toBe(1)
71
+ expect(holderManager.size()).toBe(1)
72
72
  expect(requestContext.size()).toBe(1)
73
73
  })
74
74
 
75
75
  it('should support the same filtering patterns', () => {
76
- const serviceManager = new ServiceLocatorManager()
77
- const requestContext = new DefaultRequestContextHolder(
76
+ const holderManager = new HolderManager()
77
+ const requestContext = new DefaultRequestContext(
78
78
  'test-request',
79
79
  100,
80
80
  )
81
81
 
82
82
  // Create and store different types of holders
83
- const singletonHolder = serviceManager.storeCreatedHolder(
83
+ const singletonHolder = holderManager.storeCreatedHolder(
84
84
  'Singleton',
85
85
  {},
86
86
  InjectableType.Class,
@@ -97,7 +97,7 @@ describe('Unified API', () => {
97
97
  const transientHolder = transientHolderTemp
98
98
 
99
99
  // Both should support the same filtering API
100
- const singletons = serviceManager.filter(
100
+ const singletons = holderManager.filter(
101
101
  (holder) => holder.scope === InjectableScope.Singleton,
102
102
  )
103
103
  const transients = requestContext.filter(
@@ -111,17 +111,17 @@ describe('Unified API', () => {
111
111
  })
112
112
 
113
113
  it('should maintain their specific behaviors while sharing common API', () => {
114
- const serviceManager = new ServiceLocatorManager()
115
- const requestContext = new DefaultRequestContextHolder(
114
+ const holderManager = new HolderManager()
115
+ const requestContext = new DefaultRequestContext(
116
116
  'test-request',
117
117
  100,
118
118
  )
119
119
 
120
- // ServiceLocatorManager has specific error handling
121
- const [notFound] = serviceManager.get('NonExistent')
120
+ // HolderManager has specific error handling
121
+ const [notFound] = holderManager.get('NonExistent')
122
122
  expect(notFound).toBeDefined()
123
123
 
124
- // RequestContextHolder has specific request features
124
+ // RequestContext has specific request features
125
125
  expect(requestContext.requestId).toBe('test-request')
126
126
  expect(requestContext.priority).toBe(100)
127
127
  expect(requestContext.metadata).toBeInstanceOf(Map)
@@ -4,8 +4,8 @@ import type { Factorable, FactorableWithArgs } from '../interfaces/index.mjs'
4
4
 
5
5
  import { Factory } from '../decorators/index.mjs'
6
6
  import { InjectableScope } from '../enums/index.mjs'
7
- import { InjectionToken } from '../injection-token.mjs'
8
- import { Registry } from '../registry.mjs'
7
+ import { InjectionToken } from '../token/injection-token.mjs'
8
+ import { Registry } from '../token/registry.mjs'
9
9
 
10
10
  // Test factory without arguments
11
11
  @Factory()
@@ -2,8 +2,8 @@ import { assertType, describe, test } from 'vitest'
2
2
  import { z } from 'zod/v4'
3
3
 
4
4
  import { Injectable } from '../decorators/index.mjs'
5
- import { InjectionToken } from '../injection-token.mjs'
6
- import { asyncInject } from '../injector.mjs'
5
+ import { InjectionToken } from '../token/injection-token.mjs'
6
+ import { asyncInject } from '../injectors.mjs'
7
7
 
8
8
  interface FooService {
9
9
  makeFoo(): string
@@ -3,7 +3,7 @@ import { expectTypeOf, test } from 'vitest'
3
3
  import { z } from 'zod/v4'
4
4
 
5
5
  import { Injectable } from '../decorators/index.mjs'
6
- import { InjectionToken } from '../injection-token.mjs'
6
+ import { InjectionToken } from '../token/injection-token.mjs'
7
7
 
8
8
  interface FooService {
9
9
  makeFoo(): string
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Browser-specific entry point for @navios/di.
3
+ *
4
+ * This entry point forces the use of SyncLocalStorage instead of
5
+ * Node's AsyncLocalStorage, making it safe for browser environments.
6
+ *
7
+ * The browser build is automatically selected by bundlers that respect
8
+ * the "browser" condition in package.json exports.
9
+ */
10
+
11
+ // Force sync mode before any other imports initialize the storage
12
+ import { __testing__ } from './internal/context/async-local-storage.mjs'
13
+ __testing__.forceSyncMode()
14
+
15
+ // Re-export everything from the main entry
16
+ export * from './index.mjs'
@@ -0,0 +1,319 @@
1
+ import type { z, ZodType } from 'zod/v4'
2
+
3
+ import type {
4
+ ClassType,
5
+ ClassTypeWithArgument,
6
+ InjectionToken,
7
+ InjectionTokenSchemaType,
8
+ } from '../token/injection-token.mjs'
9
+ import type { IContainer } from '../interfaces/container.interface.mjs'
10
+ import type { Factorable } from '../interfaces/factory.interface.mjs'
11
+ import type { Registry } from '../token/registry.mjs'
12
+ import type { InstanceHolder } from '../internal/holder/instance-holder.mjs'
13
+ import type { Injectors } from '../utils/index.mjs'
14
+ import type { Join, UnionToArray } from '../utils/types.mjs'
15
+
16
+ import { Injectable } from '../decorators/injectable.decorator.mjs'
17
+ import { InjectableScope, InjectableType } from '../enums/index.mjs'
18
+ import { DIError } from '../errors/index.mjs'
19
+ import {
20
+ BoundInjectionToken,
21
+ FactoryInjectionToken,
22
+ } from '../token/injection-token.mjs'
23
+ import { defaultInjectors } from '../injectors.mjs'
24
+ import { globalRegistry } from '../token/registry.mjs'
25
+ import { ScopedContainer } from './scoped-container.mjs'
26
+ import { ServiceLocator } from '../internal/core/service-locator.mjs'
27
+ import { getInjectableToken } from '../utils/get-injectable-token.mjs'
28
+
29
+ /**
30
+ * Main dependency injection container.
31
+ *
32
+ * Provides a simplified public API for dependency injection, wrapping
33
+ * a ServiceLocator instance. Handles singleton and transient services directly,
34
+ * while request-scoped services require using beginRequest() to create a ScopedContainer.
35
+ */
36
+ @Injectable()
37
+ export class Container implements IContainer {
38
+ private readonly serviceLocator: ServiceLocator
39
+ private readonly activeRequestIds = new Set<string>()
40
+
41
+ constructor(
42
+ protected readonly registry: Registry = globalRegistry,
43
+ protected readonly logger: Console | null = null,
44
+ protected readonly injectors: Injectors = defaultInjectors,
45
+ ) {
46
+ this.serviceLocator = new ServiceLocator(registry, logger, injectors)
47
+ this.registerSelf()
48
+ }
49
+
50
+ private registerSelf() {
51
+ const token = getInjectableToken(Container)
52
+ const instanceName = this.serviceLocator.getInstanceIdentifier(token)
53
+ this.serviceLocator
54
+ .getManager()
55
+ .storeCreatedHolder(
56
+ instanceName,
57
+ this,
58
+ InjectableType.Class,
59
+ InjectableScope.Singleton,
60
+ )
61
+ }
62
+
63
+ /**
64
+ * Gets an instance from the container.
65
+ * This method has the same type signature as the inject method from get-injectors.mts
66
+ *
67
+ * NOTE: Request-scoped services cannot be resolved directly from Container.
68
+ * Use beginRequest() to create a ScopedContainer for request-scoped services.
69
+ */
70
+ // #1 Simple class
71
+ get<T extends ClassType>(
72
+ token: T,
73
+ ): InstanceType<T> extends Factorable<infer R>
74
+ ? Promise<R>
75
+ : Promise<InstanceType<T>>
76
+ // #1.1 Simple class with args
77
+ get<T extends ClassTypeWithArgument<R>, R>(
78
+ token: T,
79
+ args: R,
80
+ ): Promise<InstanceType<T>>
81
+
82
+ // #2 Token with required Schema
83
+ get<T, S extends InjectionTokenSchemaType>(
84
+ token: InjectionToken<T, S>,
85
+ args: z.input<S>,
86
+ ): Promise<T>
87
+ // #3 Token with optional Schema
88
+ get<T, S extends InjectionTokenSchemaType, R extends boolean>(
89
+ token: InjectionToken<T, S, R>,
90
+ ): R extends false
91
+ ? Promise<T>
92
+ : S extends ZodType<infer Type>
93
+ ? `Error: Your token requires args: ${Join<
94
+ UnionToArray<keyof Type>,
95
+ ', '
96
+ >}`
97
+ : 'Error: Your token requires args'
98
+ // #4 Token with no Schema
99
+ get<T>(token: InjectionToken<T, undefined>): Promise<T>
100
+ get<T>(token: BoundInjectionToken<T, any>): Promise<T>
101
+ get<T>(token: FactoryInjectionToken<T, any>): Promise<T>
102
+
103
+ async get(
104
+ token:
105
+ | ClassType
106
+ | InjectionToken<any>
107
+ | BoundInjectionToken<any, any>
108
+ | FactoryInjectionToken<any, any>,
109
+ args?: unknown,
110
+ ) {
111
+ // Check if this is a request-scoped service
112
+ // Use TokenProcessor for consistent token normalization
113
+ const tokenProcessor = this.serviceLocator.getTokenProcessor()
114
+ const realToken = tokenProcessor.getRegistryToken(token)
115
+
116
+ if (this.registry.has(realToken)) {
117
+ const record = this.registry.get(realToken)
118
+ if (record.scope === InjectableScope.Request) {
119
+ throw DIError.unknown(
120
+ `Cannot resolve request-scoped service "${String(realToken.name)}" from Container. ` +
121
+ `Use beginRequest() to create a ScopedContainer for request-scoped services.`,
122
+ )
123
+ }
124
+ }
125
+
126
+ return this.serviceLocator.getOrThrowInstance(token, args as any, this)
127
+ }
128
+
129
+ /**
130
+ * Gets an instance with a specific container context.
131
+ * Used by ScopedContainer to delegate singleton/transient resolution
132
+ * while maintaining the correct container context for nested inject() calls.
133
+ *
134
+ * @internal
135
+ */
136
+ async getWithContext(
137
+ token:
138
+ | ClassType
139
+ | InjectionToken<any>
140
+ | BoundInjectionToken<any, any>
141
+ | FactoryInjectionToken<any, any>,
142
+ args: unknown,
143
+ contextContainer: IContainer,
144
+ ): Promise<any> {
145
+ return this.serviceLocator.getOrThrowInstance(
146
+ token,
147
+ args as any,
148
+ contextContainer,
149
+ )
150
+ }
151
+
152
+ /**
153
+ * Resolves a request-scoped service for a ScopedContainer.
154
+ * The service will be stored in the ScopedContainer's request context.
155
+ *
156
+ * @internal
157
+ */
158
+ async resolveForRequest(
159
+ token:
160
+ | ClassType
161
+ | InjectionToken<any>
162
+ | BoundInjectionToken<any, any>
163
+ | FactoryInjectionToken<any, any>,
164
+ args: unknown,
165
+ scopedContainer: ScopedContainer,
166
+ ): Promise<any> {
167
+ return this.serviceLocator.resolveRequestScoped(
168
+ token,
169
+ args as any,
170
+ scopedContainer,
171
+ )
172
+ }
173
+
174
+ /**
175
+ * Gets the underlying ServiceLocator instance for advanced usage
176
+ */
177
+ getServiceLocator(): ServiceLocator {
178
+ return this.serviceLocator
179
+ }
180
+
181
+ /**
182
+ * Gets the registry
183
+ */
184
+ getRegistry(): Registry {
185
+ return this.registry
186
+ }
187
+
188
+ /**
189
+ * Invalidates a service and its dependencies
190
+ */
191
+ async invalidate(service: unknown): Promise<void> {
192
+ const holder = this.getHolderByInstance(service)
193
+ if (holder) {
194
+ await this.serviceLocator.invalidate(holder.name)
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Gets a service holder by instance (reverse lookup)
200
+ */
201
+ private getHolderByInstance(
202
+ instance: unknown,
203
+ ): InstanceHolder | null {
204
+ const holderMap = Array.from(
205
+ this.serviceLocator
206
+ .getManager()
207
+ .filter((holder) => holder.instance === instance)
208
+ .values(),
209
+ )
210
+
211
+ return holderMap.length > 0 ? holderMap[0] : null
212
+ }
213
+
214
+ /**
215
+ * Checks if a service is registered in the container
216
+ */
217
+ isRegistered(token: any): boolean {
218
+ try {
219
+ return this.serviceLocator.getInstanceIdentifier(token) !== null
220
+ } catch {
221
+ return false
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Disposes the container and cleans up all resources
227
+ */
228
+ async dispose(): Promise<void> {
229
+ await this.serviceLocator.clearAll()
230
+ }
231
+
232
+ /**
233
+ * Waits for all pending operations to complete
234
+ */
235
+ async ready(): Promise<void> {
236
+ await this.serviceLocator.ready()
237
+ }
238
+
239
+ /**
240
+ * @internal
241
+ * Attempts to get an instance synchronously if it already exists.
242
+ * Returns null if the instance doesn't exist or is not ready.
243
+ */
244
+ tryGetSync<T>(token: any, args?: any): T | null {
245
+ return this.serviceLocator.getSyncInstance(token, args, this)
246
+ }
247
+
248
+ // ============================================================================
249
+ // REQUEST CONTEXT MANAGEMENT
250
+ // ============================================================================
251
+
252
+ /**
253
+ * Begins a new request context and returns a ScopedContainer.
254
+ *
255
+ * The ScopedContainer provides isolated request-scoped service resolution
256
+ * while delegating singleton and transient services to this Container.
257
+ *
258
+ * @param requestId Unique identifier for this request
259
+ * @param metadata Optional metadata for the request
260
+ * @param priority Priority for resolution (higher = more priority)
261
+ * @returns A ScopedContainer for this request
262
+ */
263
+ beginRequest(
264
+ requestId: string,
265
+ metadata?: Record<string, any>,
266
+ priority: number = 100,
267
+ ): ScopedContainer {
268
+ if (this.activeRequestIds.has(requestId)) {
269
+ throw DIError.unknown(
270
+ `Request context "${requestId}" already exists. Use a unique request ID.`,
271
+ )
272
+ }
273
+
274
+ this.activeRequestIds.add(requestId)
275
+
276
+ this.logger?.log(`[Container] Started request context: ${requestId}`)
277
+
278
+ return new ScopedContainer(
279
+ this,
280
+ this.registry,
281
+ requestId,
282
+ metadata,
283
+ priority,
284
+ )
285
+ }
286
+
287
+ /**
288
+ * Removes a request ID from the active set.
289
+ * Called by ScopedContainer when the request ends.
290
+ *
291
+ * @internal
292
+ */
293
+ removeActiveRequest(requestId: string): void {
294
+ this.activeRequestIds.delete(requestId)
295
+ this.logger?.log(`[Container] Ended request context: ${requestId}`)
296
+ }
297
+
298
+ /**
299
+ * Gets the set of active request IDs.
300
+ */
301
+ getActiveRequestIds(): ReadonlySet<string> {
302
+ return this.activeRequestIds
303
+ }
304
+
305
+ /**
306
+ * Checks if a request ID is currently active.
307
+ */
308
+ hasActiveRequest(requestId: string): boolean {
309
+ return this.activeRequestIds.has(requestId)
310
+ }
311
+
312
+ /**
313
+ * Clears all instances and bindings from the container.
314
+ * This is useful for testing or resetting the container state.
315
+ */
316
+ clear(): Promise<void> {
317
+ return this.serviceLocator.clearAll()
318
+ }
319
+ }
@@ -0,0 +1,2 @@
1
+ export * from './container.mjs'
2
+ export * from './scoped-container.mjs'