@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,559 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- /* eslint-disable @typescript-eslint/no-empty-object-type */
3
- import type { z, ZodObject, ZodOptional } from 'zod/v4'
4
-
5
- import type { FactoryContext } from './factory-context.mjs'
6
- import type {
7
- AnyInjectableType,
8
- BaseInjectionTokenSchemaType,
9
- InjectionTokenSchemaType,
10
- InjectionTokenType,
11
- OptionalInjectionTokenSchemaType,
12
- } from './injection-token.mjs'
13
- import type { Registry } from './registry.mjs'
14
- import type { RequestContextHolder } from './request-context-holder.mjs'
15
- import type { ServiceInstantiator } from './service-instantiator.mjs'
16
- import type { ServiceLocatorInstanceHolder } from './service-locator-instance-holder.mjs'
17
- import type { ServiceLocatorManager } from './service-locator-manager.mjs'
18
- import type { ServiceLocator } from './service-locator.mjs'
19
- import type { TokenProcessor } from './token-processor.mjs'
20
-
21
- import { InjectableScope } from './enums/index.mjs'
22
- import { DIError, DIErrorCode } from './errors/index.mjs'
23
- import {
24
- BoundInjectionToken,
25
- FactoryInjectionToken,
26
- InjectionToken,
27
- } from './injection-token.mjs'
28
- import { ServiceLocatorInstanceHolderStatus } from './service-locator-instance-holder.mjs'
29
-
30
- /**
31
- * InstanceResolver handles instance resolution, creation, and lifecycle management.
32
- * Extracted from ServiceLocator to improve separation of concerns.
33
- */
34
- export class InstanceResolver {
35
- constructor(
36
- private readonly registry: Registry,
37
- private readonly manager: ServiceLocatorManager,
38
- private readonly serviceInstantiator: ServiceInstantiator,
39
- private readonly tokenProcessor: TokenProcessor,
40
- private readonly logger: Console | null = null,
41
- private readonly serviceLocator: ServiceLocator,
42
- ) {}
43
-
44
- /**
45
- * Resolves an instance for the given token and arguments.
46
- */
47
- async resolveInstance(
48
- token: AnyInjectableType,
49
- args?: any,
50
- requestContext?: RequestContextHolder,
51
- ): Promise<[undefined, any] | [DIError]> {
52
- const [err, data] = await this.resolveTokenAndPrepareInstanceName(
53
- token,
54
- args,
55
- )
56
- if (err) {
57
- return [err]
58
- }
59
-
60
- const {
61
- instanceName,
62
- validatedArgs,
63
- actualToken: _actualToken,
64
- realToken,
65
- } = data
66
-
67
- const [error, holder] = await this.retrieveOrCreateInstanceByInstanceName(
68
- instanceName,
69
- realToken,
70
- validatedArgs,
71
- requestContext,
72
- )
73
- if (error) {
74
- return [error]
75
- }
76
- return [undefined, holder.instance]
77
- }
78
-
79
- /**
80
- * Gets a synchronous instance (for sync operations).
81
- */
82
- getSyncInstance<
83
- Instance,
84
- Schema extends InjectionTokenSchemaType | undefined,
85
- >(
86
- token: AnyInjectableType,
87
- args: Schema extends ZodObject
88
- ? z.input<Schema>
89
- : Schema extends ZodOptional<ZodObject>
90
- ? z.input<Schema> | undefined
91
- : undefined,
92
- currentRequestContext: RequestContextHolder | null,
93
- ): Instance | null {
94
- const [err, { actualToken, validatedArgs }] =
95
- this.tokenProcessor.validateAndResolveTokenArgs(token, args)
96
- if (err) {
97
- return null
98
- }
99
- const instanceName = this.tokenProcessor.generateInstanceName(
100
- actualToken,
101
- validatedArgs,
102
- )
103
-
104
- // Try request context first
105
- if (currentRequestContext) {
106
- const requestHolder = currentRequestContext.get(instanceName)
107
- if (requestHolder) {
108
- return requestHolder.instance as Instance
109
- }
110
- }
111
-
112
- // Try singleton manager
113
- const [error, holder] = this.manager.get(instanceName)
114
- if (error) {
115
- return null
116
- }
117
- return holder.instance as Instance
118
- }
119
-
120
- /**
121
- * Internal method to resolve token args and create instance name.
122
- * Handles factory token resolution and validation.
123
- */
124
- private async resolveTokenAndPrepareInstanceName(
125
- token: AnyInjectableType,
126
- args?: any,
127
- ): Promise<
128
- | [
129
- undefined,
130
- {
131
- instanceName: string
132
- validatedArgs: any
133
- actualToken: InjectionTokenType
134
- realToken: InjectionToken<any, any>
135
- },
136
- ]
137
- | [DIError]
138
- > {
139
- const [err, { actualToken, validatedArgs }] =
140
- this.tokenProcessor.validateAndResolveTokenArgs(token, args)
141
- if (err instanceof DIError && err.code === DIErrorCode.UnknownError) {
142
- return [err]
143
- } else if (
144
- err instanceof DIError &&
145
- err.code === DIErrorCode.FactoryTokenNotResolved &&
146
- actualToken instanceof FactoryInjectionToken
147
- ) {
148
- this.logger?.log(
149
- `[InstanceResolver]#resolveTokenAndPrepareInstanceName() Factory token not resolved, resolving it`,
150
- )
151
- await actualToken.resolve(this.createFactoryContext())
152
- return this.resolveTokenAndPrepareInstanceName(token)
153
- }
154
- const instanceName = this.tokenProcessor.generateInstanceName(
155
- actualToken,
156
- validatedArgs,
157
- )
158
- // Determine the real token (the actual InjectionToken that will be used for resolution)
159
- const realToken =
160
- actualToken instanceof BoundInjectionToken ||
161
- actualToken instanceof FactoryInjectionToken
162
- ? actualToken.token
163
- : actualToken
164
- return [undefined, { instanceName, validatedArgs, actualToken, realToken }]
165
- }
166
-
167
- /**
168
- * Gets an instance by its instance name, handling all the logic after instance name creation.
169
- */
170
- private async retrieveOrCreateInstanceByInstanceName(
171
- instanceName: string,
172
- realToken: InjectionToken<any, any>,
173
- realArgs: any,
174
- requestContext?: RequestContextHolder,
175
- ): Promise<[undefined, ServiceLocatorInstanceHolder<any>] | [DIError]> {
176
- // Try to get existing instance (handles both request-scoped and singleton)
177
- const existingHolder = await this.tryGetExistingInstance(
178
- instanceName,
179
- realToken,
180
- requestContext,
181
- )
182
- if (existingHolder) {
183
- return existingHolder
184
- }
185
-
186
- // No existing instance found, create a new one
187
- const result = await this.createNewInstance(
188
- instanceName,
189
- realToken,
190
- realArgs,
191
- requestContext,
192
- )
193
- if (result[0]) {
194
- return [result[0]]
195
- }
196
-
197
- const [, holder] = result
198
- return this.waitForInstanceReady(holder)
199
- }
200
-
201
- /**
202
- * Attempts to retrieve an existing instance, handling request-scoped and singleton instances.
203
- * Returns null if no instance exists and a new one should be created.
204
- */
205
- private async tryGetExistingInstance(
206
- instanceName: string,
207
- realToken: InjectionToken<any, any>,
208
- requestContext?: RequestContextHolder,
209
- ): Promise<
210
- [undefined, ServiceLocatorInstanceHolder<any>] | [DIError] | null
211
- > {
212
- // Check request-scoped instances first
213
- const requestResult = await this.tryGetRequestScopedInstance(
214
- instanceName,
215
- realToken,
216
- requestContext,
217
- )
218
- if (requestResult) {
219
- return requestResult
220
- }
221
-
222
- // Check singleton instances
223
- return this.tryGetSingletonInstance(instanceName)
224
- }
225
-
226
- /**
227
- * Attempts to get a request-scoped instance if applicable.
228
- */
229
- private async tryGetRequestScopedInstance(
230
- instanceName: string,
231
- realToken: InjectionToken<any, any>,
232
- requestContext?: RequestContextHolder,
233
- ): Promise<
234
- [undefined, ServiceLocatorInstanceHolder<any>] | [DIError] | null
235
- > {
236
- if (!this.registry.has(realToken)) {
237
- return null
238
- }
239
-
240
- const record = this.registry.get(realToken)
241
- if (record.scope !== InjectableScope.Request) {
242
- return null
243
- }
244
-
245
- if (!requestContext) {
246
- this.logger?.log(
247
- `[InstanceResolver] No current request context available for request-scoped service ${instanceName}`,
248
- )
249
- return [
250
- DIError.unknown(
251
- `No current request context available for request-scoped service ${instanceName}`,
252
- ),
253
- ]
254
- }
255
-
256
- const requestHolder = requestContext.get(instanceName)
257
- if (!requestHolder) {
258
- return null
259
- }
260
-
261
- return this.waitForInstanceReady(requestHolder)
262
- }
263
-
264
- /**
265
- * Attempts to get a singleton instance from the manager.
266
- */
267
- private async tryGetSingletonInstance(
268
- instanceName: string,
269
- ): Promise<
270
- [undefined, ServiceLocatorInstanceHolder<any>] | [DIError] | null
271
- > {
272
- const [error, holder] = this.manager.get(instanceName)
273
-
274
- if (!error) {
275
- return this.waitForInstanceReady(holder)
276
- }
277
-
278
- // Handle recovery scenarios
279
- switch (error.code) {
280
- case DIErrorCode.InstanceDestroying:
281
- this.logger?.log(
282
- `[InstanceResolver] Instance ${instanceName} is being destroyed, waiting...`,
283
- )
284
- await holder?.destroyPromise
285
- // Retry after destruction is complete
286
- return this.tryGetSingletonInstance(instanceName)
287
-
288
- case DIErrorCode.InstanceNotFound:
289
- return null // Instance doesn't exist, should create new one
290
-
291
- default:
292
- return [error]
293
- }
294
- }
295
-
296
- /**
297
- * Waits for an instance holder to be ready and returns the appropriate result.
298
- */
299
- private async waitForInstanceReady<T>(
300
- holder: ServiceLocatorInstanceHolder<T>,
301
- ): Promise<[undefined, ServiceLocatorInstanceHolder<T>] | [DIError]> {
302
- switch (holder.status) {
303
- case ServiceLocatorInstanceHolderStatus.Creating:
304
- await holder.creationPromise
305
- return this.waitForInstanceReady(holder)
306
-
307
- case ServiceLocatorInstanceHolderStatus.Destroying:
308
- return [DIError.instanceDestroying(holder.name)]
309
-
310
- case ServiceLocatorInstanceHolderStatus.Error:
311
- return [holder.instance as DIError]
312
-
313
- case ServiceLocatorInstanceHolderStatus.Created:
314
- return [undefined, holder]
315
-
316
- default:
317
- return [DIError.instanceNotFound('unknown')]
318
- }
319
- }
320
-
321
- /**
322
- * Creates a new instance for the given token and arguments.
323
- */
324
- private async createNewInstance<
325
- Instance,
326
- Schema extends InjectionTokenSchemaType | undefined,
327
- >(
328
- instanceName: string,
329
- realToken: InjectionToken<Instance, Schema>,
330
- args: Schema extends ZodObject
331
- ? z.output<Schema>
332
- : Schema extends ZodOptional<ZodObject>
333
- ? z.output<Schema> | undefined
334
- : undefined,
335
- requestContext?: RequestContextHolder,
336
- ): Promise<[undefined, ServiceLocatorInstanceHolder<Instance>] | [DIError]> {
337
- this.logger?.log(
338
- `[InstanceResolver]#createNewInstance() Creating instance for ${instanceName}`,
339
- )
340
- if (this.registry.has(realToken)) {
341
- return this.instantiateServiceFromRegistry<Instance, Schema, any>(
342
- instanceName,
343
- realToken,
344
- args,
345
- requestContext,
346
- )
347
- } else {
348
- return [DIError.factoryNotFound(realToken.name.toString())]
349
- }
350
- }
351
-
352
- /**
353
- * Instantiates a service from the registry using the service instantiator.
354
- */
355
- private instantiateServiceFromRegistry<
356
- Instance,
357
- Schema extends InjectionTokenSchemaType | undefined,
358
- Args extends Schema extends BaseInjectionTokenSchemaType
359
- ? z.output<Schema>
360
- : Schema extends OptionalInjectionTokenSchemaType
361
- ? z.output<Schema> | undefined
362
- : undefined,
363
- >(
364
- instanceName: string,
365
- token: InjectionToken<Instance, Schema>,
366
- args: Args,
367
- requestContext?: RequestContextHolder,
368
- ): Promise<[undefined, ServiceLocatorInstanceHolder<Instance>]> {
369
- this.logger?.log(
370
- `[InstanceResolver]#instantiateServiceFromRegistry(): Creating instance for ${instanceName} from abstract factory`,
371
- )
372
- const ctx = this.createFactoryContext()
373
- let record = this.registry.get<Instance, Schema>(token)
374
- let { scope, type } = record
375
-
376
- // Use createCreatingHolder from manager
377
- const [deferred, holder] = this.manager.createCreatingHolder<Instance>(
378
- instanceName,
379
- type,
380
- scope,
381
- ctx.deps,
382
- )
383
-
384
- // Start the instantiation process
385
- this.serviceInstantiator
386
- .instantiateService(ctx, record, args)
387
- .then(async ([error, instance]) => {
388
- await this.handleInstantiationResult(
389
- instanceName,
390
- holder,
391
- ctx,
392
- deferred,
393
- scope,
394
- error,
395
- instance,
396
- requestContext,
397
- )
398
- })
399
- .catch(async (error) => {
400
- await this.handleInstantiationError(
401
- instanceName,
402
- holder,
403
- deferred,
404
- scope,
405
- error,
406
- )
407
- })
408
-
409
- this.storeInstanceByScope(scope, instanceName, holder, requestContext)
410
- // @ts-expect-error TS2322 This is correct type
411
- return [undefined, holder]
412
- }
413
-
414
- /**
415
- * Handles the result of service instantiation.
416
- */
417
- private async handleInstantiationResult(
418
- instanceName: string,
419
- holder: ServiceLocatorInstanceHolder<any>,
420
- ctx: FactoryContext & {
421
- deps: Set<string>
422
- getDestroyListeners: () => (() => void)[]
423
- },
424
- deferred: any,
425
- scope: InjectableScope,
426
- error: any,
427
- instance: any,
428
- _requestContext?: RequestContextHolder,
429
- ): Promise<void> {
430
- holder.destroyListeners = ctx.getDestroyListeners()
431
- holder.creationPromise = null
432
-
433
- if (error) {
434
- await this.handleInstantiationError(
435
- instanceName,
436
- holder,
437
- deferred,
438
- scope,
439
- error,
440
- )
441
- } else {
442
- await this.handleInstantiationSuccess(
443
- instanceName,
444
- holder,
445
- ctx,
446
- deferred,
447
- instance,
448
- )
449
- }
450
- }
451
-
452
- /**
453
- * Handles successful service instantiation.
454
- */
455
- private async handleInstantiationSuccess(
456
- instanceName: string,
457
- holder: ServiceLocatorInstanceHolder<any>,
458
- ctx: FactoryContext & {
459
- deps: Set<string>
460
- getDestroyListeners: () => (() => void)[]
461
- },
462
- deferred: any,
463
- instance: any,
464
- ): Promise<void> {
465
- holder.instance = instance
466
- holder.status = ServiceLocatorInstanceHolderStatus.Created
467
-
468
- // Set up dependency invalidation listeners
469
- if (ctx.deps.size > 0) {
470
- ctx.deps.forEach((dependency: string) => {
471
- holder.destroyListeners.push(
472
- this.serviceLocator.getEventBus().on(dependency, 'destroy', () => {
473
- this.logger?.log(
474
- `[InstanceResolver] Dependency ${dependency} destroyed, invalidating ${instanceName}`,
475
- )
476
- this.serviceLocator.getServiceInvalidator().invalidate(instanceName)
477
- }),
478
- )
479
- })
480
- }
481
-
482
- // Note: Event emission would need access to the event bus
483
- this.logger?.log(
484
- `[InstanceResolver] Instance ${instanceName} created successfully`,
485
- )
486
- deferred.resolve([undefined, instance])
487
- }
488
-
489
- /**
490
- * Handles service instantiation errors.
491
- */
492
- private async handleInstantiationError(
493
- instanceName: string,
494
- holder: ServiceLocatorInstanceHolder<any>,
495
- deferred: any,
496
- scope: InjectableScope,
497
- error: any,
498
- ): Promise<void> {
499
- this.logger?.error(
500
- `[InstanceResolver] Error creating instance for ${instanceName}`,
501
- error,
502
- )
503
-
504
- holder.status = ServiceLocatorInstanceHolderStatus.Error
505
- holder.instance = error
506
- holder.creationPromise = null
507
-
508
- if (scope === InjectableScope.Singleton) {
509
- this.logger?.log(
510
- `[InstanceResolver] Singleton ${instanceName} failed, will be invalidated`,
511
- )
512
- this.serviceLocator.getServiceInvalidator().invalidate(instanceName)
513
- }
514
-
515
- deferred.reject(error)
516
- }
517
-
518
- /**
519
- * Stores an instance holder based on its scope.
520
- */
521
- private storeInstanceByScope(
522
- scope: InjectableScope,
523
- instanceName: string,
524
- holder: ServiceLocatorInstanceHolder<any>,
525
- requestContext?: RequestContextHolder,
526
- ): void {
527
- switch (scope) {
528
- case InjectableScope.Singleton:
529
- this.logger?.debug(
530
- `[InstanceResolver] Setting singleton instance for ${instanceName}`,
531
- )
532
- this.manager.set(instanceName, holder)
533
- break
534
-
535
- case InjectableScope.Request:
536
- if (requestContext) {
537
- this.logger?.debug(
538
- `[InstanceResolver] Setting request-scoped instance for ${instanceName}`,
539
- )
540
- requestContext.addInstance(instanceName, holder.instance, holder)
541
- }
542
- break
543
-
544
- case InjectableScope.Transient:
545
- // Transient instances are not stored anywhere
546
- break
547
- }
548
- }
549
-
550
- /**
551
- * Creates a factory context for dependency injection during service instantiation.
552
- */
553
- private createFactoryContext(): FactoryContext & {
554
- getDestroyListeners: () => (() => void)[]
555
- deps: Set<string>
556
- } {
557
- return this.tokenProcessor.createFactoryContext(this.serviceLocator)
558
- }
559
- }
@@ -1,149 +0,0 @@
1
- import type { RequestContextHolder } from './request-context-holder.mjs'
2
-
3
- import { DefaultRequestContextHolder } from './request-context-holder.mjs'
4
-
5
- /**
6
- * RequestContextManager handles request context lifecycle management.
7
- * Extracted from ServiceLocator to improve separation of concerns.
8
- */
9
- export class RequestContextManager {
10
- private readonly requestContexts = new Map<string, RequestContextHolder>()
11
- private currentRequestContext: RequestContextHolder | null = null
12
-
13
- constructor(private readonly logger: Console | null = null) {}
14
-
15
- /**
16
- * Begins a new request context with the given parameters.
17
- * @param requestId Unique identifier for this request
18
- * @param metadata Optional metadata for the request
19
- * @param priority Priority for resolution (higher = more priority)
20
- * @returns The created request context holder
21
- */
22
- beginRequest(
23
- requestId: string,
24
- metadata?: Record<string, any>,
25
- priority: number = 100,
26
- ): RequestContextHolder {
27
- if (this.requestContexts.has(requestId)) {
28
- throw new Error(
29
- `[RequestContextManager] Request context ${requestId} already exists`,
30
- )
31
- }
32
-
33
- const contextHolder = new DefaultRequestContextHolder(
34
- requestId,
35
- priority,
36
- metadata,
37
- )
38
- this.requestContexts.set(requestId, contextHolder)
39
- this.currentRequestContext = contextHolder
40
-
41
- this.logger?.log(
42
- `[RequestContextManager] Started request context: ${requestId}`,
43
- )
44
- return contextHolder
45
- }
46
-
47
- /**
48
- * Ends a request context and cleans up all associated instances.
49
- * @param requestId The request ID to end
50
- */
51
- async endRequest(requestId: string): Promise<void> {
52
- const contextHolder = this.requestContexts.get(requestId)
53
- if (!contextHolder) {
54
- this.logger?.warn(
55
- `[RequestContextManager] Request context ${requestId} not found`,
56
- )
57
- return
58
- }
59
-
60
- this.logger?.log(
61
- `[RequestContextManager] Ending request context: ${requestId}`,
62
- )
63
-
64
- // Clean up all request-scoped instances
65
- const cleanupPromises: Promise<any>[] = []
66
- for (const [, holder] of contextHolder.holders) {
67
- if (holder.destroyListeners.length > 0) {
68
- cleanupPromises.push(
69
- Promise.all(holder.destroyListeners.map((listener) => listener())),
70
- )
71
- }
72
- }
73
-
74
- await Promise.all(cleanupPromises)
75
-
76
- // Clear the context
77
- contextHolder.clear()
78
- this.requestContexts.delete(requestId)
79
-
80
- // Reset current context if it was the one being ended
81
- if (this.currentRequestContext === contextHolder) {
82
- this.currentRequestContext =
83
- Array.from(this.requestContexts.values()).at(-1) ?? null
84
- }
85
-
86
- this.logger?.log(
87
- `[RequestContextManager] Request context ${requestId} ended`,
88
- )
89
- }
90
-
91
- /**
92
- * Gets the current request context.
93
- * @returns The current request context holder or null
94
- */
95
- getCurrentRequestContext(): RequestContextHolder | null {
96
- return this.currentRequestContext
97
- }
98
-
99
- /**
100
- * Sets the current request context.
101
- * @param requestId The request ID to set as current
102
- */
103
- setCurrentRequestContext(requestId: string): void {
104
- const contextHolder = this.requestContexts.get(requestId)
105
- if (!contextHolder) {
106
- throw new Error(
107
- `[RequestContextManager] Request context ${requestId} not found`,
108
- )
109
- }
110
- this.currentRequestContext = contextHolder
111
- }
112
-
113
- /**
114
- * Gets all request contexts.
115
- * @returns Map of request contexts
116
- */
117
- getRequestContexts(): Map<string, RequestContextHolder> {
118
- return this.requestContexts
119
- }
120
-
121
- /**
122
- * Clears all request contexts.
123
- */
124
- async clearAllRequestContexts(): Promise<void> {
125
- const requestIds = Array.from(this.requestContexts.keys())
126
-
127
- if (requestIds.length === 0) {
128
- this.logger?.log('[RequestContextManager] No request contexts to clear')
129
- return
130
- }
131
-
132
- this.logger?.log(
133
- `[RequestContextManager] Clearing ${requestIds.length} request contexts: ${requestIds.join(', ')}`,
134
- )
135
-
136
- // Clear request contexts sequentially to avoid race conditions
137
- for (const requestId of requestIds) {
138
- try {
139
- await this.endRequest(requestId)
140
- } catch (error) {
141
- this.logger?.error(
142
- `[RequestContextManager] Error clearing request context ${requestId}:`,
143
- error,
144
- )
145
- // Continue with other request contexts even if one fails
146
- }
147
- }
148
- }
149
- }