@navios/di 0.4.2 → 0.5.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.
- package/README.md +211 -1
- package/coverage/clover.xml +1912 -1277
- package/coverage/coverage-final.json +37 -28
- package/coverage/docs/examples/basic-usage.mts.html +1 -1
- package/coverage/docs/examples/factory-pattern.mts.html +1 -1
- package/coverage/docs/examples/index.html +1 -1
- package/coverage/docs/examples/injection-tokens.mts.html +1 -1
- package/coverage/docs/examples/request-scope-example.mts.html +1 -1
- package/coverage/docs/examples/service-lifecycle.mts.html +1 -1
- package/coverage/index.html +71 -41
- package/coverage/lib/_tsup-dts-rollup.d.mts.html +682 -43
- package/coverage/lib/index.d.mts.html +7 -4
- package/coverage/lib/index.html +5 -5
- package/coverage/lib/testing/index.d.mts.html +91 -0
- package/coverage/lib/testing/index.html +116 -0
- package/coverage/src/base-instance-holder-manager.mts.html +589 -0
- package/coverage/src/container.mts.html +257 -74
- package/coverage/src/decorators/factory.decorator.mts.html +1 -1
- package/coverage/src/decorators/index.html +1 -1
- package/coverage/src/decorators/index.mts.html +1 -1
- package/coverage/src/decorators/injectable.decorator.mts.html +20 -20
- package/coverage/src/enums/index.html +1 -1
- package/coverage/src/enums/index.mts.html +1 -1
- package/coverage/src/enums/injectable-scope.enum.mts.html +1 -1
- package/coverage/src/enums/injectable-type.enum.mts.html +1 -1
- package/coverage/src/errors/di-error.mts.html +292 -0
- package/coverage/src/errors/errors.enum.mts.html +30 -21
- package/coverage/src/errors/factory-not-found.mts.html +31 -22
- package/coverage/src/errors/factory-token-not-resolved.mts.html +29 -26
- package/coverage/src/errors/index.html +56 -41
- package/coverage/src/errors/index.mts.html +15 -9
- package/coverage/src/errors/instance-destroying.mts.html +31 -22
- package/coverage/src/errors/instance-expired.mts.html +31 -22
- package/coverage/src/errors/instance-not-found.mts.html +31 -22
- package/coverage/src/errors/unknown-error.mts.html +31 -43
- package/coverage/src/event-emitter.mts.html +14 -14
- package/coverage/src/factory-context.mts.html +1 -1
- package/coverage/src/index.html +121 -46
- package/coverage/src/index.mts.html +7 -4
- package/coverage/src/injection-token.mts.html +28 -28
- package/coverage/src/injector.mts.html +1 -1
- package/coverage/src/instance-resolver.mts.html +1762 -0
- package/coverage/src/interfaces/factory.interface.mts.html +1 -1
- package/coverage/src/interfaces/index.html +1 -1
- package/coverage/src/interfaces/index.mts.html +1 -1
- package/coverage/src/interfaces/on-service-destroy.interface.mts.html +1 -1
- package/coverage/src/interfaces/on-service-init.interface.mts.html +1 -1
- package/coverage/src/registry.mts.html +28 -28
- package/coverage/src/request-context-holder.mts.html +183 -102
- package/coverage/src/request-context-manager.mts.html +532 -0
- package/coverage/src/service-instantiator.mts.html +49 -49
- package/coverage/src/service-invalidator.mts.html +1372 -0
- package/coverage/src/service-locator-event-bus.mts.html +48 -48
- package/coverage/src/service-locator-instance-holder.mts.html +2 -14
- package/coverage/src/service-locator-manager.mts.html +71 -335
- package/coverage/src/service-locator.mts.html +240 -2328
- package/coverage/src/symbols/index.html +1 -1
- package/coverage/src/symbols/index.mts.html +1 -1
- package/coverage/src/symbols/injectable-token.mts.html +1 -1
- package/coverage/src/testing/index.html +131 -0
- package/coverage/src/testing/index.mts.html +88 -0
- package/coverage/src/testing/test-container.mts.html +445 -0
- package/coverage/src/token-processor.mts.html +607 -0
- package/coverage/src/utils/defer.mts.html +28 -214
- package/coverage/src/utils/get-injectable-token.mts.html +7 -7
- package/coverage/src/utils/get-injectors.mts.html +99 -99
- package/coverage/src/utils/index.html +15 -15
- package/coverage/src/utils/index.mts.html +4 -7
- package/coverage/src/utils/types.mts.html +1 -1
- package/docs/injectable.md +51 -11
- package/docs/scopes.md +63 -29
- package/lib/_tsup-dts-rollup.d.mts +376 -213
- package/lib/_tsup-dts-rollup.d.ts +376 -213
- package/lib/{chunk-3NLYPYBY.mjs → chunk-2M576LCC.mjs} +1024 -608
- package/lib/chunk-2M576LCC.mjs.map +1 -0
- package/lib/index.d.mts +6 -4
- package/lib/index.d.ts +6 -4
- package/lib/index.js +1189 -773
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +2 -2
- package/lib/testing/index.js +1261 -843
- package/lib/testing/index.js.map +1 -1
- package/lib/testing/index.mjs +1 -1
- package/package.json +4 -4
- package/src/__tests__/container.spec.mts +47 -13
- package/src/__tests__/errors.spec.mts +53 -27
- package/src/__tests__/injectable.spec.mts +73 -0
- package/src/__tests__/request-scope.spec.mts +0 -2
- package/src/__tests__/service-instantiator.spec.mts +1 -0
- package/src/__tests__/service-locator-manager.spec.mts +12 -82
- package/src/__tests__/service-locator.spec.mts +1009 -1
- package/src/__type-tests__/inject.spec-d.mts +30 -7
- package/src/__type-tests__/injectable.spec-d.mts +76 -37
- package/src/base-instance-holder-manager.mts +2 -9
- package/src/container.mts +61 -9
- package/src/decorators/injectable.decorator.mts +29 -5
- package/src/errors/di-error.mts +69 -0
- package/src/errors/index.mts +9 -7
- package/src/injection-token.mts +1 -0
- package/src/injector.mts +2 -0
- package/src/instance-resolver.mts +559 -0
- package/src/request-context-holder.mts +0 -2
- package/src/request-context-manager.mts +149 -0
- package/src/service-invalidator.mts +429 -0
- package/src/service-locator-event-bus.mts +1 -1
- package/src/service-locator-instance-holder.mts +0 -4
- package/src/service-locator-manager.mts +10 -40
- package/src/service-locator.mts +86 -782
- package/src/token-processor.mts +174 -0
- package/src/utils/get-injectors.mts +161 -24
- package/src/utils/index.mts +0 -1
- package/src/utils/types.mts +12 -8
- package/lib/chunk-3NLYPYBY.mjs.map +0 -1
- package/src/__tests__/defer.spec.mts +0 -166
- package/src/errors/errors.enum.mts +0 -8
- package/src/errors/factory-not-found.mts +0 -8
- package/src/errors/factory-token-not-resolved.mts +0 -10
- package/src/errors/instance-destroying.mts +0 -8
- package/src/errors/instance-expired.mts +0 -8
- package/src/errors/instance-not-found.mts +0 -8
- package/src/errors/unknown-error.mts +0 -15
- package/src/utils/defer.mts +0 -73
package/src/service-locator.mts
CHANGED
|
@@ -2,46 +2,33 @@
|
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-empty-object-type */
|
|
3
3
|
import type { z, ZodObject, ZodOptional } from 'zod/v4'
|
|
4
4
|
|
|
5
|
-
import type { FactoryContext } from './factory-context.mjs'
|
|
6
5
|
import type {
|
|
7
6
|
AnyInjectableType,
|
|
8
|
-
BaseInjectionTokenSchemaType,
|
|
9
7
|
InjectionTokenSchemaType,
|
|
10
|
-
InjectionTokenType,
|
|
11
|
-
OptionalInjectionTokenSchemaType,
|
|
12
8
|
} from './injection-token.mjs'
|
|
13
9
|
import type { Registry } from './registry.mjs'
|
|
14
10
|
import type { RequestContextHolder } from './request-context-holder.mjs'
|
|
15
|
-
import type {
|
|
11
|
+
import type { ClearAllOptions } from './service-invalidator.mjs'
|
|
16
12
|
import type { Injectors } from './utils/index.mjs'
|
|
17
13
|
|
|
18
|
-
import { InjectableScope } from './enums/index.mjs'
|
|
19
|
-
import {
|
|
20
|
-
ErrorsEnum,
|
|
21
|
-
FactoryNotFound,
|
|
22
|
-
FactoryTokenNotResolved,
|
|
23
|
-
UnknownError,
|
|
24
|
-
} from './errors/index.mjs'
|
|
25
|
-
import {
|
|
26
|
-
BoundInjectionToken,
|
|
27
|
-
FactoryInjectionToken,
|
|
28
|
-
InjectionToken,
|
|
29
|
-
} from './injection-token.mjs'
|
|
30
14
|
import { defaultInjectors } from './injector.mjs'
|
|
15
|
+
import { InstanceResolver } from './instance-resolver.mjs'
|
|
31
16
|
import { globalRegistry } from './registry.mjs'
|
|
32
|
-
import {
|
|
17
|
+
import { RequestContextManager } from './request-context-manager.mjs'
|
|
33
18
|
import { ServiceInstantiator } from './service-instantiator.mjs'
|
|
19
|
+
import { ServiceInvalidator } from './service-invalidator.mjs'
|
|
34
20
|
import { ServiceLocatorEventBus } from './service-locator-event-bus.mjs'
|
|
35
|
-
import { ServiceLocatorInstanceHolderStatus } from './service-locator-instance-holder.mjs'
|
|
36
21
|
import { ServiceLocatorManager } from './service-locator-manager.mjs'
|
|
37
|
-
import {
|
|
22
|
+
import { TokenProcessor } from './token-processor.mjs'
|
|
38
23
|
|
|
39
24
|
export class ServiceLocator {
|
|
40
25
|
private readonly eventBus: ServiceLocatorEventBus
|
|
41
26
|
private readonly manager: ServiceLocatorManager
|
|
42
27
|
private readonly serviceInstantiator: ServiceInstantiator
|
|
43
|
-
private readonly
|
|
44
|
-
private
|
|
28
|
+
private readonly tokenProcessor: TokenProcessor
|
|
29
|
+
private readonly requestContextManager: RequestContextManager
|
|
30
|
+
private readonly serviceInvalidator: ServiceInvalidator
|
|
31
|
+
private readonly instanceResolver: InstanceResolver
|
|
45
32
|
|
|
46
33
|
constructor(
|
|
47
34
|
private readonly registry: Registry = globalRegistry,
|
|
@@ -51,6 +38,22 @@ export class ServiceLocator {
|
|
|
51
38
|
this.eventBus = new ServiceLocatorEventBus(logger)
|
|
52
39
|
this.manager = new ServiceLocatorManager(logger)
|
|
53
40
|
this.serviceInstantiator = new ServiceInstantiator(injectors)
|
|
41
|
+
this.tokenProcessor = new TokenProcessor(logger)
|
|
42
|
+
this.requestContextManager = new RequestContextManager(logger)
|
|
43
|
+
this.serviceInvalidator = new ServiceInvalidator(
|
|
44
|
+
this.manager,
|
|
45
|
+
this.requestContextManager,
|
|
46
|
+
this.eventBus,
|
|
47
|
+
logger,
|
|
48
|
+
)
|
|
49
|
+
this.instanceResolver = new InstanceResolver(
|
|
50
|
+
this.registry,
|
|
51
|
+
this.manager,
|
|
52
|
+
this.serviceInstantiator,
|
|
53
|
+
this.tokenProcessor,
|
|
54
|
+
logger,
|
|
55
|
+
this,
|
|
56
|
+
)
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
// ============================================================================
|
|
@@ -65,13 +68,25 @@ export class ServiceLocator {
|
|
|
65
68
|
return this.manager
|
|
66
69
|
}
|
|
67
70
|
|
|
71
|
+
getRequestContexts() {
|
|
72
|
+
return this.requestContextManager.getRequestContexts()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
getRequestContextManager() {
|
|
76
|
+
return this.requestContextManager
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
getServiceInvalidator() {
|
|
80
|
+
return this.serviceInvalidator
|
|
81
|
+
}
|
|
82
|
+
|
|
68
83
|
public getInstanceIdentifier(token: AnyInjectableType, args?: any): string {
|
|
69
84
|
const [err, { actualToken, validatedArgs }] =
|
|
70
|
-
this.validateAndResolveTokenArgs(token, args)
|
|
85
|
+
this.tokenProcessor.validateAndResolveTokenArgs(token, args)
|
|
71
86
|
if (err) {
|
|
72
87
|
throw err
|
|
73
88
|
}
|
|
74
|
-
return this.generateInstanceName(actualToken, validatedArgs)
|
|
89
|
+
return this.tokenProcessor.generateInstanceName(actualToken, validatedArgs)
|
|
75
90
|
}
|
|
76
91
|
|
|
77
92
|
public async getInstance(
|
|
@@ -79,30 +94,30 @@ export class ServiceLocator {
|
|
|
79
94
|
args?: any,
|
|
80
95
|
onPrepare?: (data: {
|
|
81
96
|
instanceName: string
|
|
82
|
-
actualToken:
|
|
97
|
+
actualToken: any
|
|
83
98
|
validatedArgs?: any
|
|
84
99
|
}) => void,
|
|
85
100
|
) {
|
|
86
|
-
const [err, data] = await this.
|
|
101
|
+
const [err, data] = await this.instanceResolver.resolveInstance(
|
|
87
102
|
token,
|
|
88
103
|
args,
|
|
104
|
+
this.requestContextManager.getCurrentRequestContext() || undefined,
|
|
89
105
|
)
|
|
90
106
|
if (err) {
|
|
91
107
|
return [err]
|
|
92
108
|
}
|
|
93
109
|
|
|
94
|
-
|
|
95
|
-
onPrepare
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (error) {
|
|
103
|
-
return [error]
|
|
110
|
+
// Call onPrepare callback if provided
|
|
111
|
+
if (onPrepare) {
|
|
112
|
+
const instanceName = this.getInstanceIdentifier(token, args)
|
|
113
|
+
const [tokenErr, { actualToken, validatedArgs }] =
|
|
114
|
+
this.tokenProcessor.validateAndResolveTokenArgs(token, args)
|
|
115
|
+
if (!tokenErr) {
|
|
116
|
+
onPrepare({ instanceName, actualToken, validatedArgs })
|
|
117
|
+
}
|
|
104
118
|
}
|
|
105
|
-
|
|
119
|
+
|
|
120
|
+
return [undefined, data]
|
|
106
121
|
}
|
|
107
122
|
|
|
108
123
|
public async getOrThrowInstance<Instance>(
|
|
@@ -127,126 +142,27 @@ export class ServiceLocator {
|
|
|
127
142
|
? z.input<Schema> | undefined
|
|
128
143
|
: undefined,
|
|
129
144
|
): Instance | null {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
const instanceName = this.generateInstanceName(actualToken, validatedArgs)
|
|
136
|
-
|
|
137
|
-
// Try request context first
|
|
138
|
-
if (this.currentRequestContext) {
|
|
139
|
-
const requestHolder = this.currentRequestContext.get(instanceName)
|
|
140
|
-
if (requestHolder) {
|
|
141
|
-
return requestHolder.instance as Instance
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Try singleton manager
|
|
146
|
-
const [error, holder] = this.manager.get(instanceName)
|
|
147
|
-
if (error) {
|
|
148
|
-
return null
|
|
149
|
-
}
|
|
150
|
-
return holder.instance as Instance
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
invalidate(service: string, round = 1): Promise<any> {
|
|
154
|
-
this.logger?.log(
|
|
155
|
-
`[ServiceLocator] Starting invalidation process for ${service}`,
|
|
156
|
-
)
|
|
157
|
-
const toInvalidate = this.manager.filter(
|
|
158
|
-
(holder) => holder.name === service || holder.deps.has(service),
|
|
159
|
-
)
|
|
160
|
-
const promises = []
|
|
161
|
-
for (const [key, holder] of toInvalidate.entries()) {
|
|
162
|
-
promises.push(this.invalidateHolder(key, holder, round))
|
|
163
|
-
}
|
|
164
|
-
return Promise.all(promises)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Invalidates a single holder based on its current status.
|
|
169
|
-
*/
|
|
170
|
-
private async invalidateHolder(
|
|
171
|
-
key: string,
|
|
172
|
-
holder: ServiceLocatorInstanceHolder<any>,
|
|
173
|
-
round: number,
|
|
174
|
-
): Promise<void> {
|
|
175
|
-
switch (holder.status) {
|
|
176
|
-
case ServiceLocatorInstanceHolderStatus.Destroying:
|
|
177
|
-
this.logger?.trace(`[ServiceLocator] ${key} is already being destroyed`)
|
|
178
|
-
await holder.destroyPromise
|
|
179
|
-
break
|
|
180
|
-
|
|
181
|
-
case ServiceLocatorInstanceHolderStatus.Creating:
|
|
182
|
-
this.logger?.trace(
|
|
183
|
-
`[ServiceLocator] ${key} is being created, waiting...`,
|
|
184
|
-
)
|
|
185
|
-
await holder.creationPromise
|
|
186
|
-
if (round > 3) {
|
|
187
|
-
this.logger?.error(
|
|
188
|
-
`[ServiceLocator] ${key} creation triggered too many invalidation rounds`,
|
|
189
|
-
)
|
|
190
|
-
return
|
|
191
|
-
}
|
|
192
|
-
await this.invalidate(key, round + 1)
|
|
193
|
-
break
|
|
194
|
-
|
|
195
|
-
default:
|
|
196
|
-
await this.destroyHolder(key, holder)
|
|
197
|
-
break
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Destroys a holder and cleans up its resources.
|
|
203
|
-
*/
|
|
204
|
-
private async destroyHolder(
|
|
205
|
-
key: string,
|
|
206
|
-
holder: ServiceLocatorInstanceHolder<any>,
|
|
207
|
-
): Promise<void> {
|
|
208
|
-
holder.status = ServiceLocatorInstanceHolderStatus.Destroying
|
|
209
|
-
this.logger?.log(
|
|
210
|
-
`[ServiceLocator] Invalidating ${key} and notifying listeners`,
|
|
145
|
+
return this.instanceResolver.getSyncInstance(
|
|
146
|
+
token,
|
|
147
|
+
args as any,
|
|
148
|
+
this.requestContextManager.getCurrentRequestContext(),
|
|
211
149
|
)
|
|
212
|
-
|
|
213
|
-
holder.destroyPromise = Promise.all(
|
|
214
|
-
holder.destroyListeners.map((listener) => listener()),
|
|
215
|
-
).then(async () => {
|
|
216
|
-
this.manager.delete(key)
|
|
217
|
-
await this.emitInstanceEvent(key, 'destroy')
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
await holder.destroyPromise
|
|
221
150
|
}
|
|
222
151
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
([, holder]) => holder,
|
|
226
|
-
)
|
|
227
|
-
await Promise.all(
|
|
228
|
-
holders.map((holder) => this.waitForHolderToSettle(holder)),
|
|
229
|
-
)
|
|
152
|
+
invalidate(service: string, round = 1): Promise<any> {
|
|
153
|
+
return this.serviceInvalidator.invalidate(service, round)
|
|
230
154
|
}
|
|
231
155
|
|
|
232
156
|
/**
|
|
233
|
-
*
|
|
157
|
+
* Gracefully clears all services in the ServiceLocator using invalidation logic.
|
|
158
|
+
* This method respects service dependencies and ensures proper cleanup order.
|
|
159
|
+
* Services that depend on others will be invalidated first, then their dependencies.
|
|
160
|
+
*
|
|
161
|
+
* @param options Optional configuration for the clearing process
|
|
162
|
+
* @returns Promise that resolves when all services have been cleared
|
|
234
163
|
*/
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
): Promise<void> {
|
|
238
|
-
switch (holder.status) {
|
|
239
|
-
case ServiceLocatorInstanceHolderStatus.Creating:
|
|
240
|
-
await holder.creationPromise
|
|
241
|
-
break
|
|
242
|
-
case ServiceLocatorInstanceHolderStatus.Destroying:
|
|
243
|
-
await holder.destroyPromise
|
|
244
|
-
break
|
|
245
|
-
// Already settled states
|
|
246
|
-
case ServiceLocatorInstanceHolderStatus.Created:
|
|
247
|
-
case ServiceLocatorInstanceHolderStatus.Error:
|
|
248
|
-
break
|
|
249
|
-
}
|
|
164
|
+
async clearAll(options: ClearAllOptions = {}): Promise<void> {
|
|
165
|
+
return this.serviceInvalidator.clearAll(options)
|
|
250
166
|
}
|
|
251
167
|
|
|
252
168
|
// ============================================================================
|
|
@@ -265,22 +181,11 @@ export class ServiceLocator {
|
|
|
265
181
|
metadata?: Record<string, any>,
|
|
266
182
|
priority: number = 100,
|
|
267
183
|
): RequestContextHolder {
|
|
268
|
-
|
|
269
|
-
throw new Error(
|
|
270
|
-
`[ServiceLocator] Request context ${requestId} already exists`,
|
|
271
|
-
)
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const contextHolder = new DefaultRequestContextHolder(
|
|
184
|
+
return this.requestContextManager.beginRequest(
|
|
275
185
|
requestId,
|
|
276
|
-
priority,
|
|
277
186
|
metadata,
|
|
187
|
+
priority,
|
|
278
188
|
)
|
|
279
|
-
this.requestContexts.set(requestId, contextHolder)
|
|
280
|
-
this.currentRequestContext = contextHolder
|
|
281
|
-
|
|
282
|
-
this.logger?.log(`[ServiceLocator] Started request context: ${requestId}`)
|
|
283
|
-
return contextHolder
|
|
284
189
|
}
|
|
285
190
|
|
|
286
191
|
/**
|
|
@@ -288,39 +193,7 @@ export class ServiceLocator {
|
|
|
288
193
|
* @param requestId The request ID to end
|
|
289
194
|
*/
|
|
290
195
|
async endRequest(requestId: string): Promise<void> {
|
|
291
|
-
|
|
292
|
-
if (!contextHolder) {
|
|
293
|
-
this.logger?.warn(
|
|
294
|
-
`[ServiceLocator] Request context ${requestId} not found`,
|
|
295
|
-
)
|
|
296
|
-
return
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
this.logger?.log(`[ServiceLocator] Ending request context: ${requestId}`)
|
|
300
|
-
|
|
301
|
-
// Clean up all request-scoped instances
|
|
302
|
-
const cleanupPromises: Promise<any>[] = []
|
|
303
|
-
for (const [, holder] of contextHolder.holders) {
|
|
304
|
-
if (holder.destroyListeners.length > 0) {
|
|
305
|
-
cleanupPromises.push(
|
|
306
|
-
Promise.all(holder.destroyListeners.map((listener) => listener())),
|
|
307
|
-
)
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
await Promise.all(cleanupPromises)
|
|
312
|
-
|
|
313
|
-
// Clear the context
|
|
314
|
-
contextHolder.clear()
|
|
315
|
-
this.requestContexts.delete(requestId)
|
|
316
|
-
|
|
317
|
-
// Reset current context if it was the one being ended
|
|
318
|
-
if (this.currentRequestContext === contextHolder) {
|
|
319
|
-
this.currentRequestContext =
|
|
320
|
-
Array.from(this.requestContexts.values()).at(-1) ?? null
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
this.logger?.log(`[ServiceLocator] Request context ${requestId} ended`)
|
|
196
|
+
return this.requestContextManager.endRequest(requestId)
|
|
324
197
|
}
|
|
325
198
|
|
|
326
199
|
/**
|
|
@@ -328,7 +201,7 @@ export class ServiceLocator {
|
|
|
328
201
|
* @returns The current request context holder or null
|
|
329
202
|
*/
|
|
330
203
|
getCurrentRequestContext(): RequestContextHolder | null {
|
|
331
|
-
return this.
|
|
204
|
+
return this.requestContextManager.getCurrentRequestContext()
|
|
332
205
|
}
|
|
333
206
|
|
|
334
207
|
/**
|
|
@@ -336,607 +209,38 @@ export class ServiceLocator {
|
|
|
336
209
|
* @param requestId The request ID to set as current
|
|
337
210
|
*/
|
|
338
211
|
setCurrentRequestContext(requestId: string): void {
|
|
339
|
-
|
|
340
|
-
if (!contextHolder) {
|
|
341
|
-
throw new Error(`[ServiceLocator] Request context ${requestId} not found`)
|
|
342
|
-
}
|
|
343
|
-
this.currentRequestContext = contextHolder
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// ============================================================================
|
|
347
|
-
// PRIVATE METHODS
|
|
348
|
-
// ============================================================================
|
|
349
|
-
|
|
350
|
-
/**
|
|
351
|
-
* Validates and resolves token arguments, handling factory token resolution and validation.
|
|
352
|
-
*/
|
|
353
|
-
private validateAndResolveTokenArgs(
|
|
354
|
-
token: AnyInjectableType,
|
|
355
|
-
args?: any,
|
|
356
|
-
): [
|
|
357
|
-
FactoryTokenNotResolved | UnknownError | undefined,
|
|
358
|
-
{ actualToken: InjectionTokenType; validatedArgs?: any },
|
|
359
|
-
] {
|
|
360
|
-
let actualToken = token as InjectionToken<any, any>
|
|
361
|
-
if (typeof token === 'function') {
|
|
362
|
-
actualToken = getInjectableToken(token)
|
|
363
|
-
}
|
|
364
|
-
let realArgs = args
|
|
365
|
-
if (actualToken instanceof BoundInjectionToken) {
|
|
366
|
-
realArgs = actualToken.value
|
|
367
|
-
} else if (actualToken instanceof FactoryInjectionToken) {
|
|
368
|
-
if (actualToken.resolved) {
|
|
369
|
-
realArgs = actualToken.value
|
|
370
|
-
} else {
|
|
371
|
-
return [new FactoryTokenNotResolved(token.name), { actualToken }]
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
if (!actualToken.schema) {
|
|
375
|
-
return [undefined, { actualToken, validatedArgs: realArgs }]
|
|
376
|
-
}
|
|
377
|
-
const validatedArgs = actualToken.schema?.safeParse(realArgs)
|
|
378
|
-
if (validatedArgs && !validatedArgs.success) {
|
|
379
|
-
this.logger?.error(
|
|
380
|
-
`[ServiceLocator]#validateAndResolveTokenArgs(): Error validating args for ${actualToken.name.toString()}`,
|
|
381
|
-
validatedArgs.error,
|
|
382
|
-
)
|
|
383
|
-
return [new UnknownError(validatedArgs.error), { actualToken }]
|
|
384
|
-
}
|
|
385
|
-
return [undefined, { actualToken, validatedArgs: validatedArgs?.data }]
|
|
212
|
+
return this.requestContextManager.setCurrentRequestContext(requestId)
|
|
386
213
|
}
|
|
387
214
|
|
|
388
215
|
/**
|
|
389
|
-
*
|
|
390
|
-
* Handles factory token resolution and validation.
|
|
216
|
+
* Waits for all services to settle (either created, destroyed, or error state).
|
|
391
217
|
*/
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
args?: any,
|
|
395
|
-
): Promise<
|
|
396
|
-
| [
|
|
397
|
-
undefined,
|
|
398
|
-
{
|
|
399
|
-
instanceName: string
|
|
400
|
-
validatedArgs: any
|
|
401
|
-
actualToken: InjectionTokenType
|
|
402
|
-
realToken: InjectionToken<any, any>
|
|
403
|
-
},
|
|
404
|
-
]
|
|
405
|
-
| [UnknownError | FactoryTokenNotResolved]
|
|
406
|
-
> {
|
|
407
|
-
const [err, { actualToken, validatedArgs }] =
|
|
408
|
-
this.validateAndResolveTokenArgs(token, args)
|
|
409
|
-
if (err instanceof UnknownError) {
|
|
410
|
-
return [err]
|
|
411
|
-
} else if (
|
|
412
|
-
(err as any) instanceof FactoryTokenNotResolved &&
|
|
413
|
-
actualToken instanceof FactoryInjectionToken
|
|
414
|
-
) {
|
|
415
|
-
this.logger?.log(
|
|
416
|
-
`[ServiceLocator]#resolveTokenAndPrepareInstanceName() Factory token not resolved, resolving it`,
|
|
417
|
-
)
|
|
418
|
-
await actualToken.resolve(this.createFactoryContext())
|
|
419
|
-
return this.resolveTokenAndPrepareInstanceName(token)
|
|
420
|
-
}
|
|
421
|
-
const instanceName = this.generateInstanceName(actualToken, validatedArgs)
|
|
422
|
-
// Determine the real token (the actual InjectionToken that will be used for resolution)
|
|
423
|
-
const realToken =
|
|
424
|
-
actualToken instanceof BoundInjectionToken ||
|
|
425
|
-
actualToken instanceof FactoryInjectionToken
|
|
426
|
-
? actualToken.token
|
|
427
|
-
: actualToken
|
|
428
|
-
return [undefined, { instanceName, validatedArgs, actualToken, realToken }]
|
|
218
|
+
async ready(): Promise<void> {
|
|
219
|
+
return this.serviceInvalidator.ready()
|
|
429
220
|
}
|
|
430
221
|
|
|
431
222
|
/**
|
|
432
|
-
*
|
|
223
|
+
* Helper method for TokenProcessor to access pre-prepared instances.
|
|
224
|
+
* This is needed for the factory context creation.
|
|
433
225
|
*/
|
|
434
|
-
|
|
435
|
-
instanceName: string,
|
|
436
|
-
realToken: InjectionToken<any, any>,
|
|
437
|
-
realArgs: any,
|
|
438
|
-
): Promise<
|
|
439
|
-
| [undefined, ServiceLocatorInstanceHolder<any>]
|
|
440
|
-
| [UnknownError | FactoryNotFound]
|
|
441
|
-
> {
|
|
442
|
-
// Try to get existing instance (handles both request-scoped and singleton)
|
|
443
|
-
const existingHolder = await this.tryGetExistingInstance(
|
|
444
|
-
instanceName,
|
|
445
|
-
realToken,
|
|
446
|
-
)
|
|
447
|
-
if (existingHolder) {
|
|
448
|
-
return existingHolder
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// No existing instance found, create a new one
|
|
452
|
-
const result = await this.createNewInstance(
|
|
453
|
-
instanceName,
|
|
454
|
-
realToken,
|
|
455
|
-
realArgs,
|
|
456
|
-
)
|
|
457
|
-
if (result[0]) {
|
|
458
|
-
return [result[0]]
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
const [, holder] = result
|
|
462
|
-
return this.waitForInstanceReady(holder)
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
/**
|
|
466
|
-
* Attempts to retrieve an existing instance, handling request-scoped and singleton instances.
|
|
467
|
-
* Returns null if no instance exists and a new one should be created.
|
|
468
|
-
*/
|
|
469
|
-
private async tryGetExistingInstance(
|
|
470
|
-
instanceName: string,
|
|
471
|
-
realToken: InjectionToken<any, any>,
|
|
472
|
-
): Promise<
|
|
473
|
-
[undefined, ServiceLocatorInstanceHolder<any>] | [UnknownError] | null
|
|
474
|
-
> {
|
|
475
|
-
// Check request-scoped instances first
|
|
476
|
-
const requestResult = await this.tryGetRequestScopedInstance(
|
|
477
|
-
instanceName,
|
|
478
|
-
realToken,
|
|
479
|
-
)
|
|
480
|
-
if (requestResult) {
|
|
481
|
-
return requestResult
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
// Check singleton instances
|
|
485
|
-
return this.tryGetSingletonInstance(instanceName)
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
/**
|
|
489
|
-
* Attempts to get a request-scoped instance if applicable.
|
|
490
|
-
*/
|
|
491
|
-
private async tryGetRequestScopedInstance(
|
|
492
|
-
instanceName: string,
|
|
493
|
-
realToken: InjectionToken<any, any>,
|
|
494
|
-
): Promise<
|
|
495
|
-
[undefined, ServiceLocatorInstanceHolder<any>] | [UnknownError] | null
|
|
496
|
-
> {
|
|
497
|
-
if (!this.registry.has(realToken)) {
|
|
498
|
-
return null
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
const record = this.registry.get(realToken)
|
|
502
|
-
if (record.scope !== InjectableScope.Request) {
|
|
503
|
-
return null
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
if (!this.currentRequestContext) {
|
|
507
|
-
this.logger?.log(
|
|
508
|
-
`[ServiceLocator] No current request context available for request-scoped service ${instanceName}`,
|
|
509
|
-
)
|
|
510
|
-
return [new UnknownError(ErrorsEnum.InstanceNotFound)]
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
const requestHolder = this.currentRequestContext.get(instanceName)
|
|
514
|
-
if (!requestHolder) {
|
|
515
|
-
return null
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
return this.waitForInstanceReady(requestHolder)
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
/**
|
|
522
|
-
* Attempts to get a singleton instance from the manager.
|
|
523
|
-
*/
|
|
524
|
-
private async tryGetSingletonInstance(
|
|
525
|
-
instanceName: string,
|
|
526
|
-
): Promise<
|
|
527
|
-
[undefined, ServiceLocatorInstanceHolder<any>] | [UnknownError] | null
|
|
528
|
-
> {
|
|
529
|
-
const [error, holder] = this.manager.get(instanceName)
|
|
530
|
-
|
|
531
|
-
if (!error) {
|
|
532
|
-
return this.waitForInstanceReady(holder)
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
// Handle recovery scenarios
|
|
536
|
-
switch (error.code) {
|
|
537
|
-
case ErrorsEnum.InstanceDestroying:
|
|
538
|
-
this.logger?.log(
|
|
539
|
-
`[ServiceLocator] Instance ${instanceName} is being destroyed, waiting...`,
|
|
540
|
-
)
|
|
541
|
-
await holder?.destroyPromise
|
|
542
|
-
// Retry after destruction is complete
|
|
543
|
-
return this.tryGetSingletonInstance(instanceName)
|
|
544
|
-
|
|
545
|
-
case ErrorsEnum.InstanceExpired:
|
|
546
|
-
this.logger?.log(
|
|
547
|
-
`[ServiceLocator] Instance ${instanceName} expired, invalidating...`,
|
|
548
|
-
)
|
|
549
|
-
await this.invalidate(instanceName)
|
|
550
|
-
// Retry after invalidation
|
|
551
|
-
return this.tryGetSingletonInstance(instanceName)
|
|
552
|
-
|
|
553
|
-
case ErrorsEnum.InstanceNotFound:
|
|
554
|
-
return null // Instance doesn't exist, should create new one
|
|
555
|
-
|
|
556
|
-
default:
|
|
557
|
-
return [error]
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
/**
|
|
562
|
-
* Waits for an instance holder to be ready and returns the appropriate result.
|
|
563
|
-
*/
|
|
564
|
-
private async waitForInstanceReady<T>(
|
|
565
|
-
holder: ServiceLocatorInstanceHolder<T>,
|
|
566
|
-
): Promise<[undefined, ServiceLocatorInstanceHolder<T>] | [UnknownError]> {
|
|
567
|
-
switch (holder.status) {
|
|
568
|
-
case ServiceLocatorInstanceHolderStatus.Creating:
|
|
569
|
-
await holder.creationPromise
|
|
570
|
-
return this.waitForInstanceReady(holder)
|
|
571
|
-
|
|
572
|
-
case ServiceLocatorInstanceHolderStatus.Destroying:
|
|
573
|
-
return [new UnknownError(ErrorsEnum.InstanceDestroying)]
|
|
574
|
-
|
|
575
|
-
case ServiceLocatorInstanceHolderStatus.Error:
|
|
576
|
-
return [holder.instance as UnknownError]
|
|
577
|
-
|
|
578
|
-
case ServiceLocatorInstanceHolderStatus.Created:
|
|
579
|
-
return [undefined, holder]
|
|
580
|
-
|
|
581
|
-
default:
|
|
582
|
-
return [new UnknownError(ErrorsEnum.InstanceNotFound)]
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
/**
|
|
587
|
-
* Emits events to listeners for instance lifecycle events.
|
|
588
|
-
*/
|
|
589
|
-
private emitInstanceEvent(
|
|
590
|
-
name: string,
|
|
591
|
-
event: 'create' | 'destroy' = 'create',
|
|
592
|
-
) {
|
|
593
|
-
this.logger?.log(
|
|
594
|
-
`[ServiceLocator]#emitInstanceEvent() Notifying listeners for ${name} with event ${event}`,
|
|
595
|
-
)
|
|
596
|
-
return this.eventBus.emit(name, event)
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
/**
|
|
600
|
-
* Creates a new instance for the given token and arguments.
|
|
601
|
-
*/
|
|
602
|
-
private async createNewInstance<
|
|
603
|
-
Instance,
|
|
604
|
-
Schema extends InjectionTokenSchemaType | undefined,
|
|
605
|
-
>(
|
|
606
|
-
instanceName: string,
|
|
607
|
-
realToken: InjectionToken<Instance, Schema>,
|
|
608
|
-
args: Schema extends ZodObject
|
|
609
|
-
? z.output<Schema>
|
|
610
|
-
: Schema extends ZodOptional<ZodObject>
|
|
611
|
-
? z.output<Schema> | undefined
|
|
612
|
-
: undefined,
|
|
613
|
-
): Promise<
|
|
614
|
-
| [undefined, ServiceLocatorInstanceHolder<Instance>]
|
|
615
|
-
| [FactoryNotFound | UnknownError]
|
|
616
|
-
> {
|
|
617
|
-
this.logger?.log(
|
|
618
|
-
`[ServiceLocator]#createNewInstance() Creating instance for ${instanceName}`,
|
|
619
|
-
)
|
|
620
|
-
if (this.registry.has(realToken)) {
|
|
621
|
-
return this.instantiateServiceFromRegistry<Instance, Schema, any>(
|
|
622
|
-
instanceName,
|
|
623
|
-
realToken,
|
|
624
|
-
args,
|
|
625
|
-
)
|
|
626
|
-
} else {
|
|
627
|
-
return [new FactoryNotFound(realToken.name.toString())]
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
/**
|
|
632
|
-
* Instantiates a service from the registry using the service instantiator.
|
|
633
|
-
*/
|
|
634
|
-
private instantiateServiceFromRegistry<
|
|
635
|
-
Instance,
|
|
636
|
-
Schema extends InjectionTokenSchemaType | undefined,
|
|
637
|
-
Args extends Schema extends BaseInjectionTokenSchemaType
|
|
638
|
-
? z.output<Schema>
|
|
639
|
-
: Schema extends OptionalInjectionTokenSchemaType
|
|
640
|
-
? z.output<Schema> | undefined
|
|
641
|
-
: undefined,
|
|
642
|
-
>(
|
|
643
|
-
instanceName: string,
|
|
644
|
-
token: InjectionToken<Instance, Schema>,
|
|
645
|
-
args: Args,
|
|
646
|
-
): Promise<[undefined, ServiceLocatorInstanceHolder<Instance>]> {
|
|
647
|
-
this.logger?.log(
|
|
648
|
-
`[ServiceLocator]#instantiateServiceFromRegistry(): Creating instance for ${instanceName} from abstract factory`,
|
|
649
|
-
)
|
|
650
|
-
const ctx = this.createFactoryContext(
|
|
651
|
-
this.currentRequestContext || undefined,
|
|
652
|
-
)
|
|
653
|
-
let record = this.registry.get<Instance, Schema>(token)
|
|
654
|
-
let { scope, type } = record
|
|
655
|
-
|
|
656
|
-
// Use createCreatingHolder from manager
|
|
657
|
-
const [deferred, holder] = this.manager.createCreatingHolder<Instance>(
|
|
658
|
-
instanceName,
|
|
659
|
-
type,
|
|
660
|
-
scope,
|
|
661
|
-
ctx.deps,
|
|
662
|
-
Infinity,
|
|
663
|
-
)
|
|
664
|
-
|
|
665
|
-
// Start the instantiation process
|
|
666
|
-
this.serviceInstantiator
|
|
667
|
-
.instantiateService(ctx, record, args)
|
|
668
|
-
.then(async ([error, instance]) => {
|
|
669
|
-
await this.handleInstantiationResult(
|
|
670
|
-
instanceName,
|
|
671
|
-
holder,
|
|
672
|
-
ctx,
|
|
673
|
-
deferred,
|
|
674
|
-
scope,
|
|
675
|
-
error,
|
|
676
|
-
instance,
|
|
677
|
-
)
|
|
678
|
-
})
|
|
679
|
-
.catch(async (error) => {
|
|
680
|
-
await this.handleInstantiationError(
|
|
681
|
-
instanceName,
|
|
682
|
-
holder,
|
|
683
|
-
deferred,
|
|
684
|
-
scope,
|
|
685
|
-
error,
|
|
686
|
-
)
|
|
687
|
-
})
|
|
688
|
-
|
|
689
|
-
this.storeInstanceByScope(scope, instanceName, holder)
|
|
690
|
-
// @ts-expect-error TS2322 This is correct type
|
|
691
|
-
return [undefined, holder]
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
/**
|
|
695
|
-
* Handles the result of service instantiation.
|
|
696
|
-
*/
|
|
697
|
-
private async handleInstantiationResult(
|
|
698
|
-
instanceName: string,
|
|
699
|
-
holder: ServiceLocatorInstanceHolder<any>,
|
|
700
|
-
ctx: any,
|
|
701
|
-
deferred: any,
|
|
702
|
-
scope: InjectableScope,
|
|
703
|
-
error: any,
|
|
704
|
-
instance: any,
|
|
705
|
-
): Promise<void> {
|
|
706
|
-
holder.destroyListeners = ctx.getDestroyListeners()
|
|
707
|
-
holder.creationPromise = null
|
|
708
|
-
|
|
709
|
-
if (error) {
|
|
710
|
-
await this.handleInstantiationError(
|
|
711
|
-
instanceName,
|
|
712
|
-
holder,
|
|
713
|
-
deferred,
|
|
714
|
-
scope,
|
|
715
|
-
error,
|
|
716
|
-
)
|
|
717
|
-
} else {
|
|
718
|
-
await this.handleInstantiationSuccess(
|
|
719
|
-
instanceName,
|
|
720
|
-
holder,
|
|
721
|
-
ctx,
|
|
722
|
-
deferred,
|
|
723
|
-
instance,
|
|
724
|
-
)
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
/**
|
|
729
|
-
* Handles successful service instantiation.
|
|
730
|
-
*/
|
|
731
|
-
private async handleInstantiationSuccess(
|
|
732
|
-
instanceName: string,
|
|
733
|
-
holder: ServiceLocatorInstanceHolder<any>,
|
|
734
|
-
ctx: any,
|
|
735
|
-
deferred: any,
|
|
736
|
-
instance: any,
|
|
737
|
-
): Promise<void> {
|
|
738
|
-
holder.instance = instance
|
|
739
|
-
holder.status = ServiceLocatorInstanceHolderStatus.Created
|
|
740
|
-
|
|
741
|
-
// Set up dependency invalidation listeners
|
|
742
|
-
if (ctx.deps.size > 0) {
|
|
743
|
-
ctx.deps.forEach((dependency: string) => {
|
|
744
|
-
holder.destroyListeners.push(
|
|
745
|
-
this.eventBus.on(dependency, 'destroy', () =>
|
|
746
|
-
this.invalidate(instanceName),
|
|
747
|
-
),
|
|
748
|
-
)
|
|
749
|
-
})
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
await this.emitInstanceEvent(instanceName)
|
|
753
|
-
deferred.resolve([undefined, instance])
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
/**
|
|
757
|
-
* Handles service instantiation errors.
|
|
758
|
-
*/
|
|
759
|
-
private async handleInstantiationError(
|
|
760
|
-
instanceName: string,
|
|
761
|
-
holder: ServiceLocatorInstanceHolder<any>,
|
|
762
|
-
deferred: any,
|
|
763
|
-
scope: InjectableScope,
|
|
764
|
-
error: any,
|
|
765
|
-
): Promise<void> {
|
|
766
|
-
this.logger?.error(
|
|
767
|
-
`[ServiceLocator] Error creating instance for ${instanceName}`,
|
|
768
|
-
error,
|
|
769
|
-
)
|
|
770
|
-
|
|
771
|
-
holder.status = ServiceLocatorInstanceHolderStatus.Error
|
|
772
|
-
holder.instance = error
|
|
773
|
-
holder.creationPromise = null
|
|
774
|
-
|
|
775
|
-
if (scope === InjectableScope.Singleton) {
|
|
776
|
-
setTimeout(() => this.invalidate(instanceName), 10)
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
deferred.reject(error)
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
/**
|
|
783
|
-
* Stores an instance holder based on its scope.
|
|
784
|
-
*/
|
|
785
|
-
private storeInstanceByScope(
|
|
786
|
-
scope: InjectableScope,
|
|
787
|
-
instanceName: string,
|
|
788
|
-
holder: ServiceLocatorInstanceHolder<any>,
|
|
789
|
-
): void {
|
|
790
|
-
switch (scope) {
|
|
791
|
-
case InjectableScope.Singleton:
|
|
792
|
-
this.logger?.debug(
|
|
793
|
-
`[ServiceLocator] Setting singleton instance for ${instanceName}`,
|
|
794
|
-
)
|
|
795
|
-
this.manager.set(instanceName, holder)
|
|
796
|
-
break
|
|
797
|
-
|
|
798
|
-
case InjectableScope.Request:
|
|
799
|
-
if (this.currentRequestContext) {
|
|
800
|
-
this.logger?.debug(
|
|
801
|
-
`[ServiceLocator] Setting request-scoped instance for ${instanceName}`,
|
|
802
|
-
)
|
|
803
|
-
this.currentRequestContext.addInstance(
|
|
804
|
-
instanceName,
|
|
805
|
-
holder.instance,
|
|
806
|
-
holder,
|
|
807
|
-
)
|
|
808
|
-
}
|
|
809
|
-
break
|
|
810
|
-
|
|
811
|
-
case InjectableScope.Transient:
|
|
812
|
-
// Transient instances are not stored anywhere
|
|
813
|
-
break
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
/**
|
|
818
|
-
* Tries to get a pre-prepared instance from request contexts.
|
|
819
|
-
*/
|
|
820
|
-
private tryGetPrePreparedInstance(
|
|
226
|
+
tryGetPrePreparedInstance(
|
|
821
227
|
instanceName: string,
|
|
822
228
|
contextHolder: RequestContextHolder | undefined,
|
|
823
229
|
deps: Set<string>,
|
|
824
230
|
): any {
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
if (prePreparedInstance !== undefined) {
|
|
829
|
-
this.logger?.debug(
|
|
830
|
-
`[ServiceLocator] Using pre-prepared instance ${instanceName} from request context ${contextHolder.requestId}`,
|
|
831
|
-
)
|
|
832
|
-
deps.add(instanceName)
|
|
833
|
-
return prePreparedInstance
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
// Check current request context (if different from provided contextHolder)
|
|
838
|
-
if (
|
|
839
|
-
this.currentRequestContext &&
|
|
840
|
-
this.currentRequestContext !== contextHolder
|
|
841
|
-
) {
|
|
842
|
-
const prePreparedInstance =
|
|
843
|
-
this.currentRequestContext.get(instanceName)?.instance
|
|
844
|
-
if (prePreparedInstance !== undefined) {
|
|
845
|
-
this.logger?.debug(
|
|
846
|
-
`[ServiceLocator] Using pre-prepared instance ${instanceName} from current request context ${this.currentRequestContext.requestId}`,
|
|
847
|
-
)
|
|
848
|
-
deps.add(instanceName)
|
|
849
|
-
return prePreparedInstance
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
return undefined
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
/**
|
|
857
|
-
* Creates a factory context for dependency injection during service instantiation.
|
|
858
|
-
* @param contextHolder Optional request context holder for priority-based resolution
|
|
859
|
-
*/
|
|
860
|
-
private createFactoryContext(
|
|
861
|
-
contextHolder?: RequestContextHolder,
|
|
862
|
-
): FactoryContext & {
|
|
863
|
-
getDestroyListeners: () => (() => void)[]
|
|
864
|
-
deps: Set<string>
|
|
865
|
-
} {
|
|
866
|
-
const destroyListeners = new Set<() => void>()
|
|
867
|
-
const deps = new Set<string>()
|
|
868
|
-
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
869
|
-
const self = this
|
|
870
|
-
|
|
871
|
-
function addDestroyListener(listener: () => void) {
|
|
872
|
-
destroyListeners.add(listener)
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
function getDestroyListeners() {
|
|
876
|
-
return Array.from(destroyListeners)
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
return {
|
|
880
|
-
// @ts-expect-error This is correct type
|
|
881
|
-
async inject(token, args) {
|
|
882
|
-
const instanceName = self.generateInstanceName(token, args)
|
|
883
|
-
|
|
884
|
-
// Check request contexts for pre-prepared instances
|
|
885
|
-
const prePreparedInstance = self.tryGetPrePreparedInstance(
|
|
886
|
-
instanceName,
|
|
887
|
-
contextHolder,
|
|
888
|
-
deps,
|
|
889
|
-
)
|
|
890
|
-
if (prePreparedInstance !== undefined) {
|
|
891
|
-
return prePreparedInstance
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
// Fall back to normal resolution
|
|
895
|
-
const [error, instance] = await self.getInstance(
|
|
896
|
-
token,
|
|
897
|
-
args,
|
|
898
|
-
({ instanceName }) => {
|
|
899
|
-
deps.add(instanceName)
|
|
900
|
-
},
|
|
901
|
-
)
|
|
902
|
-
if (error) {
|
|
903
|
-
throw error
|
|
904
|
-
}
|
|
905
|
-
return instance
|
|
906
|
-
},
|
|
907
|
-
addDestroyListener,
|
|
908
|
-
getDestroyListeners,
|
|
909
|
-
locator: self,
|
|
231
|
+
return this.tokenProcessor.tryGetPrePreparedInstance(
|
|
232
|
+
instanceName,
|
|
233
|
+
contextHolder,
|
|
910
234
|
deps,
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
/**
|
|
915
|
-
* Generates a unique instance name based on token and arguments.
|
|
916
|
-
*/
|
|
917
|
-
private generateInstanceName(token: InjectionTokenType, args: any) {
|
|
918
|
-
if (!args) {
|
|
919
|
-
return token.toString()
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
const formattedArgs = Object.entries(args)
|
|
923
|
-
.sort(([keyA], [keyB]) => keyA.localeCompare(keyB))
|
|
924
|
-
.map(([key, value]) => `${key}=${this.formatArgValue(value)}`)
|
|
925
|
-
.join(',')
|
|
926
|
-
|
|
927
|
-
return `${token.toString()}:${formattedArgs.replaceAll(/"/g, '').replaceAll(/:/g, '=')}`
|
|
235
|
+
this.requestContextManager.getCurrentRequestContext(),
|
|
236
|
+
)
|
|
928
237
|
}
|
|
929
238
|
|
|
930
239
|
/**
|
|
931
|
-
*
|
|
240
|
+
* Helper method for InstanceResolver to generate instance names.
|
|
241
|
+
* This is needed for the factory context creation.
|
|
932
242
|
*/
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
return `fn_${value.name}(${value.length})`
|
|
936
|
-
}
|
|
937
|
-
if (typeof value === 'symbol') {
|
|
938
|
-
return value.toString()
|
|
939
|
-
}
|
|
940
|
-
return JSON.stringify(value).slice(0, 40)
|
|
243
|
+
generateInstanceName(token: any, args: any): string {
|
|
244
|
+
return this.tokenProcessor.generateInstanceName(token, args)
|
|
941
245
|
}
|
|
942
246
|
}
|