@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.
- package/CHANGELOG.md +146 -0
- package/README.md +196 -219
- package/docs/README.md +69 -11
- package/docs/api-reference.md +281 -117
- package/docs/container.md +220 -56
- package/docs/examples/request-scope-example.mts +2 -2
- package/docs/factory.md +3 -8
- package/docs/getting-started.md +37 -8
- package/docs/migration.md +318 -37
- package/docs/request-contexts.md +263 -175
- package/docs/scopes.md +79 -42
- package/lib/browser/index.d.mts +1577 -0
- package/lib/browser/index.d.mts.map +1 -0
- package/lib/browser/index.mjs +3012 -0
- package/lib/browser/index.mjs.map +1 -0
- package/lib/index-S_qX2VLI.d.mts +1211 -0
- package/lib/index-S_qX2VLI.d.mts.map +1 -0
- package/lib/index-fKPuT65j.d.cts +1206 -0
- package/lib/index-fKPuT65j.d.cts.map +1 -0
- package/lib/index.cjs +389 -0
- package/lib/index.cjs.map +1 -0
- package/lib/index.d.cts +376 -0
- package/lib/index.d.cts.map +1 -0
- package/lib/index.d.mts +371 -78
- package/lib/index.d.mts.map +1 -0
- package/lib/index.mjs +325 -63
- package/lib/index.mjs.map +1 -1
- package/lib/testing/index.cjs +9 -0
- package/lib/testing/index.d.cts +2 -0
- package/lib/testing/index.d.mts +2 -2
- package/lib/testing/index.mjs +2 -72
- package/lib/testing-BMGmmxH7.cjs +2895 -0
- package/lib/testing-BMGmmxH7.cjs.map +1 -0
- package/lib/testing-DCXz8AJD.mjs +2655 -0
- package/lib/testing-DCXz8AJD.mjs.map +1 -0
- package/package.json +23 -1
- package/project.json +2 -2
- package/src/__tests__/async-local-storage.browser.spec.mts +240 -0
- package/src/__tests__/async-local-storage.spec.mts +333 -0
- package/src/__tests__/container.spec.mts +30 -25
- package/src/__tests__/e2e.browser.spec.mts +790 -0
- package/src/__tests__/e2e.spec.mts +1222 -0
- package/src/__tests__/errors.spec.mts +6 -6
- package/src/__tests__/factory.spec.mts +1 -1
- package/src/__tests__/get-injectors.spec.mts +1 -1
- package/src/__tests__/injectable.spec.mts +1 -1
- package/src/__tests__/injection-token.spec.mts +1 -1
- package/src/__tests__/library-findings.spec.mts +563 -0
- package/src/__tests__/registry.spec.mts +2 -2
- package/src/__tests__/request-scope.spec.mts +266 -274
- package/src/__tests__/service-instantiator.spec.mts +18 -17
- package/src/__tests__/service-locator-event-bus.spec.mts +9 -9
- package/src/__tests__/service-locator-manager.spec.mts +15 -15
- package/src/__tests__/service-locator.spec.mts +167 -244
- package/src/__tests__/unified-api.spec.mts +27 -27
- package/src/__type-tests__/factory.spec-d.mts +2 -2
- package/src/__type-tests__/inject.spec-d.mts +2 -2
- package/src/__type-tests__/injectable.spec-d.mts +1 -1
- package/src/browser.mts +16 -0
- package/src/container/container.mts +319 -0
- package/src/container/index.mts +2 -0
- package/src/container/scoped-container.mts +350 -0
- package/src/decorators/factory.decorator.mts +4 -4
- package/src/decorators/injectable.decorator.mts +5 -5
- package/src/errors/di-error.mts +13 -7
- package/src/errors/index.mts +0 -8
- package/src/index.mts +156 -15
- package/src/interfaces/container.interface.mts +82 -0
- package/src/interfaces/factory.interface.mts +2 -2
- package/src/interfaces/index.mts +1 -0
- package/src/internal/context/async-local-storage.mts +120 -0
- package/src/internal/context/factory-context.mts +18 -0
- package/src/internal/context/index.mts +3 -0
- package/src/{request-context-holder.mts → internal/context/request-context.mts} +40 -27
- package/src/internal/context/resolution-context.mts +63 -0
- package/src/internal/context/sync-local-storage.mts +51 -0
- package/src/internal/core/index.mts +5 -0
- package/src/internal/core/instance-resolver.mts +641 -0
- package/src/{service-instantiator.mts → internal/core/instantiator.mts} +31 -27
- package/src/internal/core/invalidator.mts +437 -0
- package/src/internal/core/service-locator.mts +202 -0
- package/src/{token-processor.mts → internal/core/token-processor.mts} +79 -60
- package/src/{base-instance-holder-manager.mts → internal/holder/base-holder-manager.mts} +91 -21
- package/src/internal/holder/holder-manager.mts +85 -0
- package/src/internal/holder/holder-storage.interface.mts +116 -0
- package/src/internal/holder/index.mts +6 -0
- package/src/internal/holder/instance-holder.mts +109 -0
- package/src/internal/holder/request-storage.mts +134 -0
- package/src/internal/holder/singleton-storage.mts +105 -0
- package/src/internal/index.mts +4 -0
- package/src/internal/lifecycle/circular-detector.mts +77 -0
- package/src/internal/lifecycle/index.mts +2 -0
- package/src/{service-locator-event-bus.mts → internal/lifecycle/lifecycle-event-bus.mts} +11 -4
- package/src/testing/__tests__/test-container.spec.mts +2 -2
- package/src/testing/test-container.mts +4 -4
- package/src/token/index.mts +2 -0
- package/src/{injection-token.mts → token/injection-token.mts} +1 -1
- package/src/{registry.mts → token/registry.mts} +1 -1
- package/src/utils/get-injectable-token.mts +1 -1
- package/src/utils/get-injectors.mts +32 -15
- package/src/utils/types.mts +1 -1
- package/tsdown.config.mts +67 -0
- package/lib/_tsup-dts-rollup.d.mts +0 -1283
- package/lib/_tsup-dts-rollup.d.ts +0 -1283
- package/lib/chunk-2M576LCC.mjs +0 -2043
- package/lib/chunk-2M576LCC.mjs.map +0 -1
- package/lib/index.d.ts +0 -78
- package/lib/index.js +0 -2127
- package/lib/index.js.map +0 -1
- package/lib/testing/index.d.ts +0 -2
- package/lib/testing/index.js +0 -2060
- package/lib/testing/index.js.map +0 -1
- package/lib/testing/index.mjs.map +0 -1
- package/src/container.mts +0 -227
- package/src/factory-context.mts +0 -8
- package/src/instance-resolver.mts +0 -559
- package/src/request-context-manager.mts +0 -149
- package/src/service-invalidator.mts +0 -429
- package/src/service-locator-instance-holder.mts +0 -70
- package/src/service-locator-manager.mts +0 -85
- package/src/service-locator.mts +0 -246
- package/tsup.config.mts +0 -12
- /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
|
-
}
|