@navios/di 0.3.1 → 0.4.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 +67 -6
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/clover.xml +2659 -0
- package/coverage/coverage-final.json +46 -0
- package/coverage/docs/examples/basic-usage.mts.html +376 -0
- package/coverage/docs/examples/factory-pattern.mts.html +1039 -0
- package/coverage/docs/examples/index.html +176 -0
- package/coverage/docs/examples/injection-tokens.mts.html +760 -0
- package/coverage/docs/examples/request-scope-example.mts.html +847 -0
- package/coverage/docs/examples/service-lifecycle.mts.html +1162 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +236 -0
- package/coverage/lib/_tsup-dts-rollup.d.mts.html +2806 -0
- package/coverage/lib/index.d.mts.html +310 -0
- package/coverage/lib/index.html +131 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +196 -0
- package/coverage/src/container.mts.html +586 -0
- package/coverage/src/decorators/factory.decorator.mts.html +322 -0
- package/coverage/src/decorators/index.html +146 -0
- package/coverage/src/decorators/index.mts.html +91 -0
- package/coverage/src/decorators/injectable.decorator.mts.html +394 -0
- package/coverage/src/enums/index.html +146 -0
- package/coverage/src/enums/index.mts.html +91 -0
- package/coverage/src/enums/injectable-scope.enum.mts.html +127 -0
- package/coverage/src/enums/injectable-type.enum.mts.html +97 -0
- package/coverage/src/errors/errors.enum.mts.html +109 -0
- package/coverage/src/errors/factory-not-found.mts.html +109 -0
- package/coverage/src/errors/factory-token-not-resolved.mts.html +115 -0
- package/coverage/src/errors/index.html +221 -0
- package/coverage/src/errors/index.mts.html +106 -0
- package/coverage/src/errors/instance-destroying.mts.html +109 -0
- package/coverage/src/errors/instance-expired.mts.html +109 -0
- package/coverage/src/errors/instance-not-found.mts.html +109 -0
- package/coverage/src/errors/unknown-error.mts.html +130 -0
- package/coverage/src/event-emitter.mts.html +400 -0
- package/coverage/src/factory-context.mts.html +109 -0
- package/coverage/src/index.html +296 -0
- package/coverage/src/index.mts.html +139 -0
- package/coverage/src/injection-token.mts.html +571 -0
- package/coverage/src/injector.mts.html +133 -0
- package/coverage/src/interfaces/factory.interface.mts.html +121 -0
- package/coverage/src/interfaces/index.html +161 -0
- package/coverage/src/interfaces/index.mts.html +94 -0
- package/coverage/src/interfaces/on-service-destroy.interface.mts.html +94 -0
- package/coverage/src/interfaces/on-service-init.interface.mts.html +94 -0
- package/coverage/src/registry.mts.html +247 -0
- package/coverage/src/request-context-holder.mts.html +607 -0
- package/coverage/src/service-instantiator.mts.html +559 -0
- package/coverage/src/service-locator-event-bus.mts.html +289 -0
- package/coverage/src/service-locator-instance-holder.mts.html +307 -0
- package/coverage/src/service-locator-manager.mts.html +604 -0
- package/coverage/src/service-locator.mts.html +2911 -0
- package/coverage/src/symbols/index.html +131 -0
- package/coverage/src/symbols/index.mts.html +88 -0
- package/coverage/src/symbols/injectable-token.mts.html +88 -0
- package/coverage/src/utils/defer.mts.html +304 -0
- package/coverage/src/utils/get-injectable-token.mts.html +142 -0
- package/coverage/src/utils/get-injectors.mts.html +691 -0
- package/coverage/src/utils/index.html +176 -0
- package/coverage/src/utils/index.mts.html +97 -0
- package/coverage/src/utils/types.mts.html +241 -0
- package/docs/README.md +5 -2
- package/docs/api-reference.md +38 -0
- package/docs/container.md +75 -0
- package/docs/getting-started.md +4 -3
- package/docs/injectable.md +4 -3
- package/docs/migration.md +177 -0
- package/docs/request-contexts.md +364 -0
- package/lib/_tsup-dts-rollup.d.mts +182 -41
- package/lib/_tsup-dts-rollup.d.ts +182 -41
- package/lib/index.d.mts +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +480 -294
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +480 -295
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/defer.spec.mts +166 -0
- package/src/__tests__/errors.spec.mts +61 -0
- package/src/__tests__/event-emitter.spec.mts +163 -0
- package/src/__tests__/get-injectors.spec.mts +70 -0
- package/src/__tests__/registry.spec.mts +335 -0
- package/src/__tests__/request-scope.spec.mts +34 -35
- package/src/__tests__/service-instantiator.spec.mts +408 -0
- package/src/__tests__/service-locator-event-bus.spec.mts +242 -0
- package/src/__tests__/service-locator-manager.spec.mts +370 -0
- package/src/__tests__/unified-api.spec.mts +130 -0
- package/src/base-instance-holder-manager.mts +175 -0
- package/src/event-emitter.mts +5 -5
- package/src/index.mts +1 -0
- package/src/request-context-holder.mts +73 -44
- package/src/service-locator-manager.mts +12 -70
- package/src/service-locator.mts +421 -226
package/src/service-locator.mts
CHANGED
|
@@ -90,10 +90,10 @@ export class ServiceLocator {
|
|
|
90
90
|
if (err) {
|
|
91
91
|
return [err]
|
|
92
92
|
}
|
|
93
|
+
|
|
93
94
|
const { instanceName, validatedArgs, actualToken, realToken } = data
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
95
|
+
onPrepare?.({ instanceName, actualToken, validatedArgs })
|
|
96
|
+
|
|
97
97
|
const [error, holder] = await this.retrieveOrCreateInstanceByInstanceName(
|
|
98
98
|
instanceName,
|
|
99
99
|
realToken,
|
|
@@ -133,12 +133,16 @@ export class ServiceLocator {
|
|
|
133
133
|
return null
|
|
134
134
|
}
|
|
135
135
|
const instanceName = this.generateInstanceName(actualToken, validatedArgs)
|
|
136
|
+
|
|
137
|
+
// Try request context first
|
|
136
138
|
if (this.currentRequestContext) {
|
|
137
|
-
const requestHolder = this.currentRequestContext.
|
|
139
|
+
const requestHolder = this.currentRequestContext.get(instanceName)
|
|
138
140
|
if (requestHolder) {
|
|
139
141
|
return requestHolder.instance as Instance
|
|
140
142
|
}
|
|
141
143
|
}
|
|
144
|
+
|
|
145
|
+
// Try singleton manager
|
|
142
146
|
const [error, holder] = this.manager.get(instanceName)
|
|
143
147
|
if (error) {
|
|
144
148
|
return null
|
|
@@ -148,66 +152,101 @@ export class ServiceLocator {
|
|
|
148
152
|
|
|
149
153
|
invalidate(service: string, round = 1): Promise<any> {
|
|
150
154
|
this.logger?.log(
|
|
151
|
-
`[ServiceLocator]
|
|
155
|
+
`[ServiceLocator] Starting invalidation process for ${service}`,
|
|
152
156
|
)
|
|
153
157
|
const toInvalidate = this.manager.filter(
|
|
154
158
|
(holder) => holder.name === service || holder.deps.has(service),
|
|
155
159
|
)
|
|
156
160
|
const promises = []
|
|
157
161
|
for (const [key, holder] of toInvalidate.entries()) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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:
|
|
166
182
|
this.logger?.trace(
|
|
167
|
-
`[ServiceLocator]
|
|
168
|
-
)
|
|
169
|
-
promises.push(
|
|
170
|
-
holder.creationPromise?.then(() => {
|
|
171
|
-
if (round > 3) {
|
|
172
|
-
this.logger?.error(
|
|
173
|
-
`[ServiceLocator]#invalidate(): ${key} creation is triggering a new invalidation round, but it is still not created`,
|
|
174
|
-
)
|
|
175
|
-
return
|
|
176
|
-
}
|
|
177
|
-
return this.invalidate(key, round + 1)
|
|
178
|
-
}),
|
|
183
|
+
`[ServiceLocator] ${key} is being created, waiting...`,
|
|
179
184
|
)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
this.
|
|
192
|
-
|
|
193
|
-
})
|
|
194
|
-
promises.push(holder.destroyPromise)
|
|
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
|
|
195
198
|
}
|
|
196
|
-
|
|
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`,
|
|
211
|
+
)
|
|
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
|
|
197
221
|
}
|
|
198
222
|
|
|
199
223
|
async ready() {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
224
|
+
const holders = Array.from(this.manager.filter(() => true)).map(
|
|
225
|
+
([, holder]) => holder,
|
|
226
|
+
)
|
|
227
|
+
await Promise.all(
|
|
228
|
+
holders.map((holder) => this.waitForHolderToSettle(holder)),
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Waits for a holder to settle (either created, destroyed, or error state).
|
|
234
|
+
*/
|
|
235
|
+
private async waitForHolderToSettle(
|
|
236
|
+
holder: ServiceLocatorInstanceHolder<any>,
|
|
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
|
+
}
|
|
211
250
|
}
|
|
212
251
|
|
|
213
252
|
// ============================================================================
|
|
@@ -400,99 +439,148 @@ export class ServiceLocator {
|
|
|
400
439
|
| [undefined, ServiceLocatorInstanceHolder<any>]
|
|
401
440
|
| [UnknownError | FactoryNotFound]
|
|
402
441
|
> {
|
|
403
|
-
//
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
)
|
|
411
|
-
return [new UnknownError(ErrorsEnum.InstanceNotFound)]
|
|
412
|
-
}
|
|
413
|
-
const requestHolder = this.currentRequestContext.getHolder(instanceName)
|
|
414
|
-
if (requestHolder) {
|
|
415
|
-
if (
|
|
416
|
-
requestHolder.status === ServiceLocatorInstanceHolderStatus.Creating
|
|
417
|
-
) {
|
|
418
|
-
await requestHolder.creationPromise
|
|
419
|
-
return this.retrieveOrCreateInstanceByInstanceName(
|
|
420
|
-
instanceName,
|
|
421
|
-
realToken,
|
|
422
|
-
realArgs,
|
|
423
|
-
)
|
|
424
|
-
} else if (
|
|
425
|
-
requestHolder.status ===
|
|
426
|
-
ServiceLocatorInstanceHolderStatus.Destroying
|
|
427
|
-
) {
|
|
428
|
-
return [new UnknownError(ErrorsEnum.InstanceDestroying)]
|
|
429
|
-
}
|
|
430
|
-
return [undefined, requestHolder]
|
|
431
|
-
}
|
|
432
|
-
}
|
|
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
|
|
433
449
|
}
|
|
434
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
|
+
> {
|
|
435
529
|
const [error, holder] = this.manager.get(instanceName)
|
|
530
|
+
|
|
436
531
|
if (!error) {
|
|
437
|
-
|
|
438
|
-
await holder.creationPromise
|
|
439
|
-
return this.retrieveOrCreateInstanceByInstanceName(
|
|
440
|
-
instanceName,
|
|
441
|
-
realToken,
|
|
442
|
-
realArgs,
|
|
443
|
-
)
|
|
444
|
-
} else if (
|
|
445
|
-
holder.status === ServiceLocatorInstanceHolderStatus.Destroying
|
|
446
|
-
) {
|
|
447
|
-
// Should never happen
|
|
448
|
-
return [new UnknownError(ErrorsEnum.InstanceDestroying)]
|
|
449
|
-
}
|
|
450
|
-
return [undefined, holder]
|
|
532
|
+
return this.waitForInstanceReady(holder)
|
|
451
533
|
}
|
|
534
|
+
|
|
535
|
+
// Handle recovery scenarios
|
|
452
536
|
switch (error.code) {
|
|
453
537
|
case ErrorsEnum.InstanceDestroying:
|
|
454
538
|
this.logger?.log(
|
|
455
|
-
`[ServiceLocator]
|
|
539
|
+
`[ServiceLocator] Instance ${instanceName} is being destroyed, waiting...`,
|
|
456
540
|
)
|
|
457
541
|
await holder?.destroyPromise
|
|
458
|
-
//
|
|
459
|
-
return this.
|
|
460
|
-
instanceName,
|
|
461
|
-
realToken,
|
|
462
|
-
realArgs,
|
|
463
|
-
)
|
|
542
|
+
// Retry after destruction is complete
|
|
543
|
+
return this.tryGetSingletonInstance(instanceName)
|
|
464
544
|
|
|
465
545
|
case ErrorsEnum.InstanceExpired:
|
|
466
546
|
this.logger?.log(
|
|
467
|
-
`[ServiceLocator]
|
|
547
|
+
`[ServiceLocator] Instance ${instanceName} expired, invalidating...`,
|
|
468
548
|
)
|
|
469
549
|
await this.invalidate(instanceName)
|
|
470
|
-
//
|
|
471
|
-
return this.
|
|
472
|
-
|
|
473
|
-
realToken,
|
|
474
|
-
realArgs,
|
|
475
|
-
)
|
|
550
|
+
// Retry after invalidation
|
|
551
|
+
return this.tryGetSingletonInstance(instanceName)
|
|
552
|
+
|
|
476
553
|
case ErrorsEnum.InstanceNotFound:
|
|
477
|
-
|
|
554
|
+
return null // Instance doesn't exist, should create new one
|
|
555
|
+
|
|
478
556
|
default:
|
|
479
557
|
return [error]
|
|
480
558
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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)]
|
|
494
583
|
}
|
|
495
|
-
return [undefined, result[1]]
|
|
496
584
|
}
|
|
497
585
|
|
|
498
586
|
/**
|
|
@@ -578,71 +666,191 @@ export class ServiceLocator {
|
|
|
578
666
|
this.serviceInstantiator
|
|
579
667
|
.instantiateService(ctx, record, args)
|
|
580
668
|
.then(async ([error, instance]) => {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
if (scope === InjectableScope.Singleton) {
|
|
591
|
-
setTimeout(() => this.invalidate(instanceName), 10)
|
|
592
|
-
}
|
|
593
|
-
deferred.reject(error)
|
|
594
|
-
} else {
|
|
595
|
-
holder.instance = instance
|
|
596
|
-
holder.status = ServiceLocatorInstanceHolderStatus.Created
|
|
597
|
-
if (ctx.deps.size > 0) {
|
|
598
|
-
ctx.deps.forEach((dependency) => {
|
|
599
|
-
holder.destroyListeners.push(
|
|
600
|
-
this.eventBus.on(dependency, 'destroy', () =>
|
|
601
|
-
this.invalidate(instanceName),
|
|
602
|
-
),
|
|
603
|
-
)
|
|
604
|
-
})
|
|
605
|
-
}
|
|
606
|
-
await this.emitInstanceEvent(instanceName)
|
|
607
|
-
deferred.resolve([undefined, instance])
|
|
608
|
-
}
|
|
669
|
+
await this.handleInstantiationResult(
|
|
670
|
+
instanceName,
|
|
671
|
+
holder,
|
|
672
|
+
ctx,
|
|
673
|
+
deferred,
|
|
674
|
+
scope,
|
|
675
|
+
error,
|
|
676
|
+
instance,
|
|
677
|
+
)
|
|
609
678
|
})
|
|
610
|
-
.catch((error) => {
|
|
611
|
-
this.
|
|
612
|
-
|
|
679
|
+
.catch(async (error) => {
|
|
680
|
+
await this.handleInstantiationError(
|
|
681
|
+
instanceName,
|
|
682
|
+
holder,
|
|
683
|
+
deferred,
|
|
684
|
+
scope,
|
|
613
685
|
error,
|
|
614
686
|
)
|
|
615
|
-
holder.status = ServiceLocatorInstanceHolderStatus.Error
|
|
616
|
-
holder.instance = error
|
|
617
|
-
holder.creationPromise = null
|
|
618
|
-
if (scope === InjectableScope.Singleton) {
|
|
619
|
-
setTimeout(() => this.invalidate(instanceName), 10)
|
|
620
|
-
}
|
|
621
|
-
deferred.reject(error)
|
|
622
687
|
})
|
|
623
688
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
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,
|
|
635
716
|
)
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
this.currentRequestContext.addInstance(
|
|
717
|
+
} else {
|
|
718
|
+
await this.handleInstantiationSuccess(
|
|
639
719
|
instanceName,
|
|
640
|
-
holder.instance,
|
|
641
720
|
holder,
|
|
721
|
+
ctx,
|
|
722
|
+
deferred,
|
|
723
|
+
instance,
|
|
642
724
|
)
|
|
643
725
|
}
|
|
644
|
-
|
|
645
|
-
|
|
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(
|
|
821
|
+
instanceName: string,
|
|
822
|
+
contextHolder: RequestContextHolder | undefined,
|
|
823
|
+
deps: Set<string>,
|
|
824
|
+
): any {
|
|
825
|
+
// Check provided context holder first (if has higher priority)
|
|
826
|
+
if (contextHolder && contextHolder.priority > 0) {
|
|
827
|
+
const prePreparedInstance = contextHolder.get(instanceName)?.instance
|
|
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
|
|
646
854
|
}
|
|
647
855
|
|
|
648
856
|
/**
|
|
@@ -671,37 +879,19 @@ export class ServiceLocator {
|
|
|
671
879
|
return {
|
|
672
880
|
// @ts-expect-error This is correct type
|
|
673
881
|
async inject(token, args) {
|
|
674
|
-
|
|
675
|
-
if (contextHolder && contextHolder.priority > 0) {
|
|
676
|
-
const instanceName = self.generateInstanceName(token, args)
|
|
677
|
-
const prePreparedInstance = contextHolder.getInstance(instanceName)
|
|
678
|
-
if (prePreparedInstance !== undefined) {
|
|
679
|
-
self.logger?.debug(
|
|
680
|
-
`[ServiceLocator] Using pre-prepared instance ${instanceName} from request context ${contextHolder.requestId}`,
|
|
681
|
-
)
|
|
682
|
-
deps.add(instanceName)
|
|
683
|
-
return prePreparedInstance
|
|
684
|
-
}
|
|
685
|
-
}
|
|
882
|
+
const instanceName = self.generateInstanceName(token, args)
|
|
686
883
|
|
|
687
|
-
//
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
if (prePreparedInstance !== undefined) {
|
|
696
|
-
self.logger?.debug(
|
|
697
|
-
`[ServiceLocator] Using pre-prepared instance ${instanceName} from current request context ${self.currentRequestContext.requestId}`,
|
|
698
|
-
)
|
|
699
|
-
deps.add(instanceName)
|
|
700
|
-
return prePreparedInstance
|
|
701
|
-
}
|
|
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
|
|
702
892
|
}
|
|
703
893
|
|
|
704
|
-
//
|
|
894
|
+
// Fall back to normal resolution
|
|
705
895
|
const [error, instance] = await self.getInstance(
|
|
706
896
|
token,
|
|
707
897
|
args,
|
|
@@ -725,23 +915,28 @@ export class ServiceLocator {
|
|
|
725
915
|
* Generates a unique instance name based on token and arguments.
|
|
726
916
|
*/
|
|
727
917
|
private generateInstanceName(token: InjectionTokenType, args: any) {
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
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, '=')}`
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* Formats a single argument value for instance name generation.
|
|
932
|
+
*/
|
|
933
|
+
private formatArgValue(value: any): string {
|
|
934
|
+
if (typeof value === 'function') {
|
|
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)
|
|
746
941
|
}
|
|
747
942
|
}
|