@navios/di 0.7.1 → 0.9.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 +110 -0
- package/README.md +117 -17
- package/lib/browser/container/abstract-container.d.mts +112 -0
- package/lib/browser/container/abstract-container.d.mts.map +1 -0
- package/lib/browser/container/abstract-container.mjs +100 -0
- package/lib/browser/container/abstract-container.mjs.map +1 -0
- package/lib/browser/container/container.d.mts +100 -0
- package/lib/browser/container/container.d.mts.map +1 -0
- package/lib/browser/container/container.mjs +424 -0
- package/lib/browser/container/container.mjs.map +1 -0
- package/lib/browser/container/scoped-container.d.mts +93 -0
- package/lib/browser/container/scoped-container.d.mts.map +1 -0
- package/lib/browser/container/scoped-container.mjs +119 -0
- package/lib/browser/container/scoped-container.mjs.map +1 -0
- package/lib/browser/decorators/factory.decorator.d.mts +26 -0
- package/lib/browser/decorators/factory.decorator.d.mts.map +1 -0
- package/lib/browser/decorators/factory.decorator.mjs +20 -0
- package/lib/browser/decorators/factory.decorator.mjs.map +1 -0
- package/lib/browser/decorators/injectable.decorator.d.mts +38 -0
- package/lib/browser/decorators/injectable.decorator.d.mts.map +1 -0
- package/lib/browser/decorators/injectable.decorator.mjs +21 -0
- package/lib/browser/decorators/injectable.decorator.mjs.map +1 -0
- package/lib/browser/enums/injectable-scope.enum.d.mts +18 -0
- package/lib/browser/enums/injectable-scope.enum.d.mts.map +1 -0
- package/lib/browser/enums/injectable-scope.enum.mjs +20 -0
- package/lib/browser/enums/injectable-scope.enum.mjs.map +1 -0
- package/lib/browser/enums/injectable-type.enum.d.mts +8 -0
- package/lib/browser/enums/injectable-type.enum.d.mts.map +1 -0
- package/lib/browser/enums/injectable-type.enum.mjs +10 -0
- package/lib/browser/enums/injectable-type.enum.mjs.map +1 -0
- package/lib/browser/errors/di-error.d.mts +43 -0
- package/lib/browser/errors/di-error.d.mts.map +1 -0
- package/lib/browser/errors/di-error.mjs +98 -0
- package/lib/browser/errors/di-error.mjs.map +1 -0
- package/lib/browser/event-emitter.d.mts +16 -0
- package/lib/browser/event-emitter.d.mts.map +1 -0
- package/lib/browser/event-emitter.mjs +320 -0
- package/lib/browser/event-emitter.mjs.map +1 -0
- package/lib/browser/index.d.mts +37 -1508
- package/lib/browser/index.mjs +29 -2650
- package/lib/browser/interfaces/container.interface.d.mts +59 -0
- package/lib/browser/interfaces/container.interface.d.mts.map +1 -0
- package/lib/browser/interfaces/factory.interface.d.mts +14 -0
- package/lib/browser/interfaces/factory.interface.d.mts.map +1 -0
- package/lib/browser/interfaces/on-service-destroy.interface.d.mts +7 -0
- package/lib/browser/interfaces/on-service-destroy.interface.d.mts.map +1 -0
- package/lib/browser/interfaces/on-service-init.interface.d.mts +7 -0
- package/lib/browser/interfaces/on-service-init.interface.d.mts.map +1 -0
- package/lib/browser/internal/context/async-local-storage.browser.mjs +20 -0
- package/lib/browser/internal/context/async-local-storage.browser.mjs.map +1 -0
- package/lib/browser/internal/context/async-local-storage.d.mts +9 -0
- package/lib/browser/internal/context/async-local-storage.d.mts.map +1 -0
- package/lib/browser/internal/context/async-local-storage.types.d.mts +11 -0
- package/lib/browser/internal/context/async-local-storage.types.d.mts.map +1 -0
- package/lib/browser/internal/context/factory-context.d.mts +23 -0
- package/lib/browser/internal/context/factory-context.d.mts.map +1 -0
- package/lib/browser/internal/context/resolution-context.d.mts +43 -0
- package/lib/browser/internal/context/resolution-context.d.mts.map +1 -0
- package/lib/browser/internal/context/resolution-context.mjs +56 -0
- package/lib/browser/internal/context/resolution-context.mjs.map +1 -0
- package/lib/browser/internal/context/service-initialization-context.d.mts +48 -0
- package/lib/browser/internal/context/service-initialization-context.d.mts.map +1 -0
- package/lib/browser/internal/context/sync-local-storage.mjs +53 -0
- package/lib/browser/internal/context/sync-local-storage.mjs.map +1 -0
- package/lib/browser/internal/core/instance-resolver.d.mts +119 -0
- package/lib/browser/internal/core/instance-resolver.d.mts.map +1 -0
- package/lib/browser/internal/core/instance-resolver.mjs +306 -0
- package/lib/browser/internal/core/instance-resolver.mjs.map +1 -0
- package/lib/browser/internal/core/name-resolver.d.mts +52 -0
- package/lib/browser/internal/core/name-resolver.d.mts.map +1 -0
- package/lib/browser/internal/core/name-resolver.mjs +118 -0
- package/lib/browser/internal/core/name-resolver.mjs.map +1 -0
- package/lib/browser/internal/core/scope-tracker.d.mts +65 -0
- package/lib/browser/internal/core/scope-tracker.d.mts.map +1 -0
- package/lib/browser/internal/core/scope-tracker.mjs +120 -0
- package/lib/browser/internal/core/scope-tracker.mjs.map +1 -0
- package/lib/browser/internal/core/service-initializer.d.mts +44 -0
- package/lib/browser/internal/core/service-initializer.d.mts.map +1 -0
- package/lib/browser/internal/core/service-initializer.mjs +109 -0
- package/lib/browser/internal/core/service-initializer.mjs.map +1 -0
- package/lib/browser/internal/core/service-invalidator.d.mts +81 -0
- package/lib/browser/internal/core/service-invalidator.d.mts.map +1 -0
- package/lib/browser/internal/core/service-invalidator.mjs +142 -0
- package/lib/browser/internal/core/service-invalidator.mjs.map +1 -0
- package/lib/browser/internal/core/token-resolver.d.mts +54 -0
- package/lib/browser/internal/core/token-resolver.d.mts.map +1 -0
- package/lib/browser/internal/core/token-resolver.mjs +77 -0
- package/lib/browser/internal/core/token-resolver.mjs.map +1 -0
- package/lib/browser/internal/holder/holder-storage.interface.d.mts +99 -0
- package/lib/browser/internal/holder/holder-storage.interface.d.mts.map +1 -0
- package/lib/browser/internal/holder/instance-holder.d.mts +101 -0
- package/lib/browser/internal/holder/instance-holder.d.mts.map +1 -0
- package/lib/browser/internal/holder/instance-holder.mjs +19 -0
- package/lib/browser/internal/holder/instance-holder.mjs.map +1 -0
- package/lib/browser/internal/holder/unified-storage.d.mts +53 -0
- package/lib/browser/internal/holder/unified-storage.d.mts.map +1 -0
- package/lib/browser/internal/holder/unified-storage.mjs +144 -0
- package/lib/browser/internal/holder/unified-storage.mjs.map +1 -0
- package/lib/browser/internal/lifecycle/circular-detector.d.mts +39 -0
- package/lib/browser/internal/lifecycle/circular-detector.d.mts.map +1 -0
- package/lib/browser/internal/lifecycle/circular-detector.mjs +55 -0
- package/lib/browser/internal/lifecycle/circular-detector.mjs.map +1 -0
- package/lib/browser/internal/lifecycle/lifecycle-event-bus.d.mts +18 -0
- package/lib/browser/internal/lifecycle/lifecycle-event-bus.d.mts.map +1 -0
- package/lib/browser/internal/lifecycle/lifecycle-event-bus.mjs +43 -0
- package/lib/browser/internal/lifecycle/lifecycle-event-bus.mjs.map +1 -0
- package/lib/browser/internal/stub-factory-class.d.mts +14 -0
- package/lib/browser/internal/stub-factory-class.d.mts.map +1 -0
- package/lib/browser/internal/stub-factory-class.mjs +18 -0
- package/lib/browser/internal/stub-factory-class.mjs.map +1 -0
- package/lib/browser/symbols/injectable-token.d.mts +5 -0
- package/lib/browser/symbols/injectable-token.d.mts.map +1 -0
- package/lib/browser/symbols/injectable-token.mjs +6 -0
- package/lib/browser/symbols/injectable-token.mjs.map +1 -0
- package/lib/browser/token/injection-token.d.mts +55 -0
- package/lib/browser/token/injection-token.d.mts.map +1 -0
- package/lib/browser/token/injection-token.mjs +100 -0
- package/lib/browser/token/injection-token.mjs.map +1 -0
- package/lib/browser/token/registry.d.mts +37 -0
- package/lib/browser/token/registry.d.mts.map +1 -0
- package/lib/browser/token/registry.mjs +86 -0
- package/lib/browser/token/registry.mjs.map +1 -0
- package/lib/browser/utils/default-injectors.d.mts +12 -0
- package/lib/browser/utils/default-injectors.d.mts.map +1 -0
- package/lib/browser/utils/default-injectors.mjs +13 -0
- package/lib/browser/utils/default-injectors.mjs.map +1 -0
- package/lib/browser/utils/get-injectable-token.d.mts +9 -0
- package/lib/browser/utils/get-injectable-token.d.mts.map +1 -0
- package/lib/browser/utils/get-injectable-token.mjs +13 -0
- package/lib/browser/utils/get-injectable-token.mjs.map +1 -0
- package/lib/browser/utils/get-injectors.d.mts +55 -0
- package/lib/browser/utils/get-injectors.d.mts.map +1 -0
- package/lib/browser/utils/get-injectors.mjs +121 -0
- package/lib/browser/utils/get-injectors.mjs.map +1 -0
- package/lib/browser/utils/types.d.mts +23 -0
- package/lib/browser/utils/types.d.mts.map +1 -0
- package/lib/{container-Pb_Y4Z4x.mjs → container-8-z89TyQ.mjs} +1269 -1305
- package/lib/container-8-z89TyQ.mjs.map +1 -0
- package/lib/{container-BuAutHGg.d.mts → container-CNiqesCL.d.mts} +600 -569
- package/lib/container-CNiqesCL.d.mts.map +1 -0
- package/lib/{container-DnzgpfBe.cjs → container-CaY2fDuk.cjs} +1287 -1329
- package/lib/container-CaY2fDuk.cjs.map +1 -0
- package/lib/{container-oGTgX2iX.d.cts → container-D-0Ho3qL.d.cts} +601 -565
- package/lib/container-D-0Ho3qL.d.cts.map +1 -0
- package/lib/index.cjs +13 -15
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +58 -223
- package/lib/index.d.cts.map +1 -1
- package/lib/index.d.mts +62 -222
- package/lib/index.d.mts.map +1 -1
- package/lib/index.mjs +5 -6
- package/lib/index.mjs.map +1 -1
- package/lib/testing/index.cjs +569 -311
- package/lib/testing/index.cjs.map +1 -1
- package/lib/testing/index.d.cts +370 -41
- package/lib/testing/index.d.cts.map +1 -1
- package/lib/testing/index.d.mts +370 -41
- package/lib/testing/index.d.mts.map +1 -1
- package/lib/testing/index.mjs +568 -305
- package/lib/testing/index.mjs.map +1 -1
- package/package.json +2 -1
- package/src/__tests__/circular-detector.spec.mts +193 -0
- package/src/__tests__/concurrent.spec.mts +368 -0
- package/src/__tests__/container.spec.mts +32 -30
- package/src/__tests__/di-error.spec.mts +351 -0
- package/src/__tests__/e2e.browser.spec.mts +0 -4
- package/src/__tests__/e2e.spec.mts +10 -19
- package/src/__tests__/event-emitter.spec.mts +232 -109
- package/src/__tests__/get-injectors.spec.mts +250 -39
- package/src/__tests__/injection-token.spec.mts +293 -349
- package/src/__tests__/library-findings.spec.mts +8 -8
- package/src/__tests__/registry.spec.mts +358 -210
- package/src/__tests__/resolution-context.spec.mts +255 -0
- package/src/__tests__/scope-tracker.spec.mts +598 -0
- package/src/__tests__/scope-upgrade.spec.mts +808 -0
- package/src/__tests__/scoped-container.spec.mts +595 -0
- package/src/__tests__/test-container.spec.mts +293 -0
- package/src/__tests__/token-resolver.spec.mts +207 -0
- package/src/__tests__/unified-storage.spec.mts +535 -0
- package/src/__tests__/unit-test-container.spec.mts +405 -0
- package/src/__type-tests__/container.spec-d.mts +180 -0
- package/src/__type-tests__/factory.spec-d.mts +15 -3
- package/src/__type-tests__/inject.spec-d.mts +115 -20
- package/src/__type-tests__/injectable.spec-d.mts +69 -52
- package/src/__type-tests__/injection-token.spec-d.mts +176 -0
- package/src/__type-tests__/scoped-container.spec-d.mts +212 -0
- package/src/container/abstract-container.mts +327 -0
- package/src/container/container.mts +142 -170
- package/src/container/scoped-container.mts +126 -208
- package/src/decorators/factory.decorator.mts +16 -11
- package/src/decorators/injectable.decorator.mts +20 -16
- package/src/enums/index.mts +2 -2
- package/src/enums/injectable-scope.enum.mts +1 -0
- package/src/enums/injectable-type.enum.mts +1 -0
- package/src/errors/di-error.mts +96 -0
- package/src/event-emitter.mts +3 -27
- package/src/index.mts +6 -153
- package/src/interfaces/container.interface.mts +13 -0
- package/src/interfaces/factory.interface.mts +1 -1
- package/src/interfaces/index.mts +1 -1
- package/src/internal/context/async-local-storage.mts +3 -2
- package/src/internal/context/async-local-storage.types.mts +1 -0
- package/src/internal/context/factory-context.mts +1 -0
- package/src/internal/context/index.mts +3 -1
- package/src/internal/context/resolution-context.mts +1 -0
- package/src/internal/context/service-initialization-context.mts +43 -0
- package/src/internal/core/index.mts +5 -4
- package/src/internal/core/instance-resolver.mts +461 -292
- package/src/internal/core/name-resolver.mts +196 -0
- package/src/internal/core/scope-tracker.mts +242 -0
- package/src/internal/core/{instantiator.mts → service-initializer.mts} +51 -29
- package/src/internal/core/service-invalidator.mts +290 -0
- package/src/internal/core/{token-processor.mts → token-resolver.mts} +17 -88
- package/src/internal/holder/holder-storage.interface.mts +11 -5
- package/src/internal/holder/index.mts +2 -5
- package/src/internal/holder/instance-holder.mts +1 -3
- package/src/internal/holder/unified-storage.mts +245 -0
- package/src/internal/index.mts +2 -1
- package/src/internal/lifecycle/circular-detector.mts +1 -0
- package/src/internal/lifecycle/index.mts +1 -1
- package/src/internal/lifecycle/lifecycle-event-bus.mts +1 -0
- package/src/internal/stub-factory-class.mts +16 -0
- package/src/symbols/injectable-token.mts +3 -1
- package/src/testing/index.mts +2 -0
- package/src/testing/test-container.mts +546 -85
- package/src/testing/types.mts +117 -0
- package/src/testing/unit-test-container.mts +509 -0
- package/src/token/injection-token.mts +41 -4
- package/src/token/registry.mts +75 -9
- package/src/utils/default-injectors.mts +16 -0
- package/src/utils/get-injectable-token.mts +2 -3
- package/src/utils/get-injectors.mts +26 -15
- package/src/utils/index.mts +3 -1
- package/src/utils/types.mts +1 -0
- package/tsdown.config.mts +11 -1
- package/lib/browser/index.d.mts.map +0 -1
- package/lib/browser/index.mjs.map +0 -1
- package/lib/container-BuAutHGg.d.mts.map +0 -1
- package/lib/container-DnzgpfBe.cjs.map +0 -1
- package/lib/container-Pb_Y4Z4x.mjs.map +0 -1
- package/lib/container-oGTgX2iX.d.cts.map +0 -1
- package/src/__tests__/async-local-storage.browser.spec.mts +0 -166
- package/src/__tests__/async-local-storage.spec.mts +0 -333
- package/src/__tests__/errors.spec.mts +0 -87
- package/src/__tests__/factory.spec.mts +0 -137
- package/src/__tests__/injectable.spec.mts +0 -246
- package/src/__tests__/request-scope.spec.mts +0 -416
- package/src/__tests__/service-instantiator.spec.mts +0 -410
- package/src/__tests__/service-locator-event-bus.spec.mts +0 -242
- package/src/__tests__/service-locator-manager.spec.mts +0 -300
- package/src/__tests__/service-locator.spec.mts +0 -966
- package/src/__tests__/unified-api.spec.mts +0 -130
- package/src/browser.mts +0 -11
- package/src/injectors.mts +0 -18
- package/src/internal/context/request-context.mts +0 -214
- package/src/internal/core/invalidator.mts +0 -437
- package/src/internal/core/service-locator.mts +0 -202
- package/src/internal/holder/base-holder-manager.mts +0 -238
- package/src/internal/holder/holder-manager.mts +0 -85
- package/src/internal/holder/request-storage.mts +0 -134
- package/src/internal/holder/singleton-storage.mts +0 -105
- package/src/testing/README.md +0 -80
- package/src/testing/__tests__/test-container.spec.mts +0 -173
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import type { InjectionTokenType } from '../../token/injection-token.mjs'
|
|
2
|
+
|
|
3
|
+
import { InjectableScope } from '../../enums/index.mjs'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Simple LRU cache for instance name generation.
|
|
7
|
+
* Uses a Map which maintains insertion order for efficient LRU eviction.
|
|
8
|
+
*/
|
|
9
|
+
class InstanceNameCache {
|
|
10
|
+
private readonly cache = new Map<string, string>()
|
|
11
|
+
private readonly maxSize: number
|
|
12
|
+
|
|
13
|
+
constructor(maxSize = 1000) {
|
|
14
|
+
this.maxSize = maxSize
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
get(key: string): string | undefined {
|
|
18
|
+
const value = this.cache.get(key)
|
|
19
|
+
if (value !== undefined) {
|
|
20
|
+
// Move to end (most recently used)
|
|
21
|
+
this.cache.delete(key)
|
|
22
|
+
this.cache.set(key, value)
|
|
23
|
+
}
|
|
24
|
+
return value
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
set(key: string, value: string): void {
|
|
28
|
+
if (this.cache.has(key)) {
|
|
29
|
+
this.cache.delete(key)
|
|
30
|
+
} else if (this.cache.size >= this.maxSize) {
|
|
31
|
+
// Remove least recently used (first item)
|
|
32
|
+
const firstKey = this.cache.keys().next().value
|
|
33
|
+
if (firstKey !== undefined) {
|
|
34
|
+
this.cache.delete(firstKey)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
this.cache.set(key, value)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
clear(): void {
|
|
41
|
+
this.cache.clear()
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Simple hash function for deterministic hashing of arguments
|
|
47
|
+
*/
|
|
48
|
+
function hashArgs(args: any): string {
|
|
49
|
+
const str = JSON.stringify(args, Object.keys(args || {}).sort())
|
|
50
|
+
let hash = 0
|
|
51
|
+
for (let i = 0; i < str.length; i++) {
|
|
52
|
+
const char = str.charCodeAt(i)
|
|
53
|
+
hash = (hash << 5) - hash + char
|
|
54
|
+
hash = hash & hash // Convert to 32-bit integer
|
|
55
|
+
}
|
|
56
|
+
return Math.abs(hash).toString(36)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Handles instance name generation with support for requestId and scope.
|
|
61
|
+
*
|
|
62
|
+
* Generates unique instance identifiers based on token, arguments, and scope.
|
|
63
|
+
* Request-scoped services MUST include requestId in their name for proper isolation.
|
|
64
|
+
*/
|
|
65
|
+
export class NameResolver {
|
|
66
|
+
private readonly instanceNameCache = new InstanceNameCache()
|
|
67
|
+
|
|
68
|
+
constructor(private readonly logger: Console | null = null) {}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Generates a unique instance name based on token, arguments, requestId, and scope.
|
|
72
|
+
*
|
|
73
|
+
* Name formats:
|
|
74
|
+
* - Singleton/Transient without args: `${tokenId}`
|
|
75
|
+
* - Singleton/Transient with args: `${tokenId}:${argsHash}`
|
|
76
|
+
* - Request without args: `${tokenId}:requestId=${requestId}`
|
|
77
|
+
* - Request with args: `${tokenId}:requestId=${requestId}:${argsHash}`
|
|
78
|
+
*
|
|
79
|
+
* @param token The injection token
|
|
80
|
+
* @param args Optional arguments
|
|
81
|
+
* @param requestId Optional request ID (required for request-scoped services)
|
|
82
|
+
* @param scope Optional scope (used to determine if requestId should be included)
|
|
83
|
+
* @returns The generated instance name
|
|
84
|
+
*/
|
|
85
|
+
generateInstanceName(
|
|
86
|
+
token: InjectionTokenType,
|
|
87
|
+
args?: any,
|
|
88
|
+
requestId?: string,
|
|
89
|
+
scope?: InjectableScope,
|
|
90
|
+
): string {
|
|
91
|
+
const tokenStr = token.toString()
|
|
92
|
+
const isRequest = scope === InjectableScope.Request
|
|
93
|
+
|
|
94
|
+
// For request-scoped services, requestId is required
|
|
95
|
+
if (isRequest && !requestId) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`[NameResolver] requestId is required for request-scoped services`,
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Build cache key
|
|
102
|
+
const cacheKey = `${tokenStr}:${scope}:${requestId || ''}:${args ? JSON.stringify(args) : ''}`
|
|
103
|
+
|
|
104
|
+
// Check cache first
|
|
105
|
+
const cached = this.instanceNameCache.get(cacheKey)
|
|
106
|
+
if (cached !== undefined) {
|
|
107
|
+
return cached
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Generate the instance name
|
|
111
|
+
let result = tokenStr
|
|
112
|
+
|
|
113
|
+
// Add requestId for request-scoped services
|
|
114
|
+
if (isRequest && requestId) {
|
|
115
|
+
result = `${result}:requestId=${requestId}`
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Add args hash if args are provided
|
|
119
|
+
if (args) {
|
|
120
|
+
const argsHash = hashArgs(args)
|
|
121
|
+
result = `${result}:${argsHash}`
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Cache the result
|
|
125
|
+
this.instanceNameCache.set(cacheKey, result)
|
|
126
|
+
|
|
127
|
+
return result
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Upgrades an existing instance name to include requestId.
|
|
132
|
+
* Preserves any args hash that might already be in the name.
|
|
133
|
+
*
|
|
134
|
+
* Examples:
|
|
135
|
+
* - `TokenName` → `TokenName:requestId=req-123`
|
|
136
|
+
* - `TokenName:abc123` → `TokenName:requestId=req-123:abc123`
|
|
137
|
+
*
|
|
138
|
+
* @param existingName The existing instance name (without requestId)
|
|
139
|
+
* @param requestId The request ID to add
|
|
140
|
+
* @returns The upgraded instance name with requestId
|
|
141
|
+
*/
|
|
142
|
+
upgradeInstanceNameToRequest(
|
|
143
|
+
existingName: string,
|
|
144
|
+
requestId: string,
|
|
145
|
+
): string {
|
|
146
|
+
// Check if requestId is already in the name
|
|
147
|
+
if (existingName.includes(`:requestId=${requestId}`)) {
|
|
148
|
+
return existingName
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Find where to insert requestId
|
|
152
|
+
// Format: TokenName or TokenName:argsHash
|
|
153
|
+
// We want: TokenName:requestId=req-123 or TokenName:requestId=req-123:argsHash
|
|
154
|
+
|
|
155
|
+
// Check if there's an args hash (starts after first colon, but not requestId=)
|
|
156
|
+
const requestIdPattern = /:requestId=/
|
|
157
|
+
const hasRequestId = requestIdPattern.test(existingName)
|
|
158
|
+
|
|
159
|
+
if (hasRequestId) {
|
|
160
|
+
// Already has a requestId, don't upgrade
|
|
161
|
+
return existingName
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Find the token part (everything before first colon, or entire string if no colon)
|
|
165
|
+
const colonIndex = existingName.indexOf(':')
|
|
166
|
+
if (colonIndex === -1) {
|
|
167
|
+
// No colon, just token name: TokenName → TokenName:requestId=req-123
|
|
168
|
+
return `${existingName}:requestId=${requestId}`
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Has colon, means there's an args hash: TokenName:abc123 → TokenName:requestId=req-123:abc123
|
|
172
|
+
const tokenPart = existingName.substring(0, colonIndex)
|
|
173
|
+
const argsPart = existingName.substring(colonIndex + 1)
|
|
174
|
+
|
|
175
|
+
// Check if argsPart looks like an args hash (not requestId=)
|
|
176
|
+
if (argsPart.startsWith('requestId=')) {
|
|
177
|
+
// Already has requestId, return as is
|
|
178
|
+
return existingName
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return `${tokenPart}:requestId=${requestId}:${argsPart}`
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Formats a single argument value for instance name generation.
|
|
186
|
+
*/
|
|
187
|
+
formatArgValue(value: any): string {
|
|
188
|
+
if (typeof value === 'function') {
|
|
189
|
+
return `fn_${value.name}(${value.length})`
|
|
190
|
+
}
|
|
191
|
+
if (typeof value === 'symbol') {
|
|
192
|
+
return value.toString()
|
|
193
|
+
}
|
|
194
|
+
return JSON.stringify(value).slice(0, 40)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import type { InjectableScope } from '../../enums/index.mjs'
|
|
2
|
+
import type { InjectionToken } from '../../token/injection-token.mjs'
|
|
3
|
+
import type { IHolderStorage } from '../holder/holder-storage.interface.mjs'
|
|
4
|
+
|
|
5
|
+
import { InjectableScope as Scope } from '../../enums/index.mjs'
|
|
6
|
+
import { DIError } from '../../errors/index.mjs'
|
|
7
|
+
import { InstanceStatus } from '../holder/instance-holder.mjs'
|
|
8
|
+
import { Registry } from '../../token/registry.mjs'
|
|
9
|
+
import { NameResolver } from './name-resolver.mjs'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Component for tracking and handling scope upgrades.
|
|
13
|
+
*
|
|
14
|
+
* Detects when a Singleton service needs to be upgraded to Request scope
|
|
15
|
+
* and coordinates the scope upgrade process atomically.
|
|
16
|
+
*/
|
|
17
|
+
export class ScopeTracker {
|
|
18
|
+
constructor(
|
|
19
|
+
private readonly registry: Registry,
|
|
20
|
+
private readonly nameResolver: NameResolver,
|
|
21
|
+
private readonly logger: Console | null = null,
|
|
22
|
+
) {}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Checks if a dependency requires scope upgrade and performs it if needed.
|
|
26
|
+
* Called during service resolution when a dependency is resolved.
|
|
27
|
+
*
|
|
28
|
+
* @param currentServiceName - Name of the service being created
|
|
29
|
+
* @param currentServiceScope - Current scope of the service being created
|
|
30
|
+
* @param dependencyName - Name of the dependency being resolved
|
|
31
|
+
* @param dependencyScope - Scope of the dependency
|
|
32
|
+
* @param dependencyToken - Token of the dependency
|
|
33
|
+
* @param singletonStorage - Singleton storage instance
|
|
34
|
+
* @param requestStorage - Request storage instance (if in request context)
|
|
35
|
+
* @param requestId - Request ID (if in request context)
|
|
36
|
+
* @returns [needsUpgrade: boolean, newName?: string] - whether upgrade occurred and new name
|
|
37
|
+
*/
|
|
38
|
+
checkAndUpgradeScope(
|
|
39
|
+
currentServiceName: string,
|
|
40
|
+
currentServiceScope: InjectableScope,
|
|
41
|
+
dependencyName: string,
|
|
42
|
+
dependencyScope: InjectableScope,
|
|
43
|
+
dependencyToken: InjectionToken<any, any>,
|
|
44
|
+
singletonStorage: IHolderStorage,
|
|
45
|
+
requestStorage?: IHolderStorage,
|
|
46
|
+
requestId?: string,
|
|
47
|
+
): [boolean, string?] {
|
|
48
|
+
// Only upgrade if current service is Singleton and dependency is Request
|
|
49
|
+
if (
|
|
50
|
+
currentServiceScope !== Scope.Singleton ||
|
|
51
|
+
dependencyScope !== Scope.Request
|
|
52
|
+
) {
|
|
53
|
+
return [false]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Need request storage and requestId for upgrade
|
|
57
|
+
if (!requestStorage || !requestId) {
|
|
58
|
+
this.logger?.warn(
|
|
59
|
+
`[ScopeTracker] Cannot upgrade scope for ${currentServiceName}: missing requestStorage or requestId`,
|
|
60
|
+
)
|
|
61
|
+
return [false]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Perform the upgrade
|
|
65
|
+
this.logger?.log(
|
|
66
|
+
`[ScopeTracker] Upgrading ${currentServiceName} from Singleton to Request scope`,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const [success, newName] = this.upgradeScopeToRequestSync(
|
|
71
|
+
currentServiceName,
|
|
72
|
+
dependencyToken,
|
|
73
|
+
singletonStorage,
|
|
74
|
+
requestStorage,
|
|
75
|
+
requestId,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if (success && newName) {
|
|
79
|
+
return [true, newName]
|
|
80
|
+
}
|
|
81
|
+
} catch (error) {
|
|
82
|
+
this.logger?.error(
|
|
83
|
+
`[ScopeTracker] Error upgrading scope for ${currentServiceName}:`,
|
|
84
|
+
error,
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return [false]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Performs the actual scope upgrade from Singleton to Request.
|
|
93
|
+
* This is the core migration logic.
|
|
94
|
+
*
|
|
95
|
+
* @param serviceName - Current service name (without requestId)
|
|
96
|
+
* @param token - Service injection token
|
|
97
|
+
* @param singletonStorage - Source storage
|
|
98
|
+
* @param requestStorage - Target storage
|
|
99
|
+
* @param requestId - Request ID to include in new name
|
|
100
|
+
* @returns [success: boolean, newName?: string, error?: DIError]
|
|
101
|
+
*/
|
|
102
|
+
async upgradeScopeToRequest(
|
|
103
|
+
serviceName: string,
|
|
104
|
+
token: InjectionToken<any, any>,
|
|
105
|
+
singletonStorage: IHolderStorage,
|
|
106
|
+
requestStorage: IHolderStorage,
|
|
107
|
+
requestId: string,
|
|
108
|
+
): Promise<[boolean, string?, DIError?]> {
|
|
109
|
+
try {
|
|
110
|
+
const [success, newName] = this.upgradeScopeToRequestSync(
|
|
111
|
+
serviceName,
|
|
112
|
+
token,
|
|
113
|
+
singletonStorage,
|
|
114
|
+
requestStorage,
|
|
115
|
+
requestId,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
if (success && newName) {
|
|
119
|
+
return [true, newName]
|
|
120
|
+
}
|
|
121
|
+
return [
|
|
122
|
+
false,
|
|
123
|
+
undefined,
|
|
124
|
+
DIError.storageError(
|
|
125
|
+
'Scope upgrade failed',
|
|
126
|
+
'upgradeScopeToRequest',
|
|
127
|
+
serviceName,
|
|
128
|
+
),
|
|
129
|
+
]
|
|
130
|
+
} catch (error) {
|
|
131
|
+
return [
|
|
132
|
+
false,
|
|
133
|
+
undefined,
|
|
134
|
+
error instanceof DIError ? error : DIError.unknown(error as Error),
|
|
135
|
+
]
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Synchronous part of scope upgrade - handles immediate updates.
|
|
141
|
+
* Async operations (like waiting for holder creation) should be done separately.
|
|
142
|
+
*/
|
|
143
|
+
private upgradeScopeToRequestSync(
|
|
144
|
+
serviceName: string,
|
|
145
|
+
token: InjectionToken<any, any>,
|
|
146
|
+
singletonStorage: IHolderStorage,
|
|
147
|
+
requestStorage: IHolderStorage,
|
|
148
|
+
requestId: string,
|
|
149
|
+
): [boolean, string?] {
|
|
150
|
+
// 1. Upgrade existing instance name to include requestId
|
|
151
|
+
// This preserves any args hash that might be in the original name
|
|
152
|
+
const newName = this.nameResolver.upgradeInstanceNameToRequest(
|
|
153
|
+
serviceName,
|
|
154
|
+
requestId,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
// 2. Update Registry scope to Request (synchronous)
|
|
158
|
+
const updated = this.registry.updateScope(token, Scope.Request)
|
|
159
|
+
if (!updated) {
|
|
160
|
+
this.logger?.warn(
|
|
161
|
+
`[ScopeTracker] Could not update scope in registry for ${serviceName}`,
|
|
162
|
+
)
|
|
163
|
+
return [false]
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 3. Check if holder exists in singleton storage
|
|
167
|
+
const holderResult = singletonStorage.get(serviceName)
|
|
168
|
+
if (holderResult === null) {
|
|
169
|
+
// No holder exists yet - just update registry, future resolutions will use request storage
|
|
170
|
+
return [true, newName]
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const [error, holder] = holderResult
|
|
174
|
+
if (error) {
|
|
175
|
+
this.logger?.warn(
|
|
176
|
+
`[ScopeTracker] Holder for ${serviceName} is in error state: ${error.message}`,
|
|
177
|
+
)
|
|
178
|
+
return [false]
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!holder) {
|
|
182
|
+
return [false]
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// 4. If holder is in "Creating" state, we need to wait for it before migrating
|
|
186
|
+
// For now, we'll update the name and move it, but the caller should wait for creation
|
|
187
|
+
if (holder.status === InstanceStatus.Creating) {
|
|
188
|
+
// Update holder name
|
|
189
|
+
holder.name = newName
|
|
190
|
+
// Move to request storage
|
|
191
|
+
requestStorage.set(newName, holder)
|
|
192
|
+
// Remove from singleton storage
|
|
193
|
+
singletonStorage.delete(serviceName)
|
|
194
|
+
// Update parent dependencies
|
|
195
|
+
this.updateParentDependencies(
|
|
196
|
+
serviceName,
|
|
197
|
+
newName,
|
|
198
|
+
singletonStorage,
|
|
199
|
+
requestStorage,
|
|
200
|
+
)
|
|
201
|
+
return [true, newName]
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// 5. Move holder from singleton to request storage
|
|
205
|
+
holder.name = newName
|
|
206
|
+
requestStorage.set(newName, holder)
|
|
207
|
+
singletonStorage.delete(serviceName)
|
|
208
|
+
|
|
209
|
+
// 6. Update all parent dependencies
|
|
210
|
+
this.updateParentDependencies(
|
|
211
|
+
serviceName,
|
|
212
|
+
newName,
|
|
213
|
+
singletonStorage,
|
|
214
|
+
requestStorage,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
return [true, newName]
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Updates all parent dependencies to reference the new service name.
|
|
222
|
+
*
|
|
223
|
+
* @param oldName - Original service name
|
|
224
|
+
* @param newName - New service name with requestId
|
|
225
|
+
* @param singletonStorage - Singleton storage to check
|
|
226
|
+
* @param requestStorage - Request storage to check
|
|
227
|
+
*/
|
|
228
|
+
updateParentDependencies(
|
|
229
|
+
oldName: string,
|
|
230
|
+
newName: string,
|
|
231
|
+
singletonStorage: IHolderStorage,
|
|
232
|
+
requestStorage?: IHolderStorage,
|
|
233
|
+
): void {
|
|
234
|
+
// Update dependencies in singleton storage
|
|
235
|
+
singletonStorage.updateDependencyReference(oldName, newName)
|
|
236
|
+
|
|
237
|
+
// Update dependencies in request storage if provided
|
|
238
|
+
if (requestStorage) {
|
|
239
|
+
requestStorage.updateDependencyReference(oldName, newName)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FactoryRecord } from '../../token/registry.mjs'
|
|
2
|
-
import type { Injectors } from '../../utils/
|
|
3
|
-
import type {
|
|
2
|
+
import type { Injectors } from '../../utils/index.mjs'
|
|
3
|
+
import type { ServiceInitializationContext } from '../context/service-initialization-context.mjs'
|
|
4
4
|
|
|
5
5
|
import { InjectableType } from '../../enums/index.mjs'
|
|
6
6
|
import { DIError } from '../../errors/index.mjs'
|
|
@@ -9,10 +9,9 @@ import { DIError } from '../../errors/index.mjs'
|
|
|
9
9
|
* Creates service instances from registry records.
|
|
10
10
|
*
|
|
11
11
|
* Handles both class-based (@Injectable) and factory-based (@Factory) services,
|
|
12
|
-
* managing the instantiation lifecycle including
|
|
13
|
-
* and lifecycle hook invocation (onServiceInit, onServiceDestroy).
|
|
12
|
+
* managing the instantiation lifecycle including lifecycle hook invocation.
|
|
14
13
|
*/
|
|
15
|
-
export class
|
|
14
|
+
export class ServiceInitializer {
|
|
16
15
|
constructor(private readonly injectors: Injectors) {}
|
|
17
16
|
|
|
18
17
|
/**
|
|
@@ -23,7 +22,7 @@ export class Instantiator {
|
|
|
23
22
|
* @returns Promise resolving to [undefined, instance] or [error]
|
|
24
23
|
*/
|
|
25
24
|
async instantiateService<T>(
|
|
26
|
-
ctx:
|
|
25
|
+
ctx: ServiceInitializationContext,
|
|
27
26
|
record: FactoryRecord<T, any>,
|
|
28
27
|
args: any = undefined,
|
|
29
28
|
): Promise<[undefined, T] | [DIError]> {
|
|
@@ -35,11 +34,15 @@ export class Instantiator {
|
|
|
35
34
|
return this.instantiateFactory(ctx, record, args)
|
|
36
35
|
default:
|
|
37
36
|
throw DIError.unknown(
|
|
38
|
-
`[
|
|
37
|
+
`[ServiceInitializer] Unknown service type: ${record.type}`,
|
|
39
38
|
)
|
|
40
39
|
}
|
|
41
40
|
} catch (error) {
|
|
42
|
-
return [
|
|
41
|
+
return [
|
|
42
|
+
error instanceof DIError
|
|
43
|
+
? error
|
|
44
|
+
: DIError.initializationError(record.target.name, error as Error),
|
|
45
|
+
]
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
48
|
|
|
@@ -51,13 +54,15 @@ export class Instantiator {
|
|
|
51
54
|
* @returns Promise resolving to [undefined, instance] or [error]
|
|
52
55
|
*/
|
|
53
56
|
private async instantiateClass<T>(
|
|
54
|
-
ctx:
|
|
57
|
+
ctx: ServiceInitializationContext,
|
|
55
58
|
record: FactoryRecord<T, any>,
|
|
56
59
|
args: any,
|
|
57
60
|
): Promise<[undefined, T] | [DIError]> {
|
|
58
61
|
try {
|
|
59
62
|
const tryLoad = this.injectors.wrapSyncInit(() => {
|
|
60
|
-
const original = this.injectors.provideFactoryContext(
|
|
63
|
+
const original = this.injectors.provideFactoryContext(
|
|
64
|
+
ctx as ServiceInitializationContext,
|
|
65
|
+
)
|
|
61
66
|
let result = new record.target(...(args ? [args] : []))
|
|
62
67
|
this.injectors.provideFactoryContext(original)
|
|
63
68
|
return result
|
|
@@ -67,8 +72,9 @@ export class Instantiator {
|
|
|
67
72
|
if (promises.length > 0) {
|
|
68
73
|
const results = await Promise.allSettled(promises)
|
|
69
74
|
if (results.some((result) => result.status === 'rejected')) {
|
|
70
|
-
throw DIError.
|
|
71
|
-
|
|
75
|
+
throw DIError.initializationError(
|
|
76
|
+
record.target.name,
|
|
77
|
+
new Error('Service cannot be instantiated'),
|
|
72
78
|
)
|
|
73
79
|
}
|
|
74
80
|
const newRes = tryLoad(injectState)
|
|
@@ -77,13 +83,16 @@ export class Instantiator {
|
|
|
77
83
|
}
|
|
78
84
|
|
|
79
85
|
if (promises.length > 0) {
|
|
80
|
-
console.error(
|
|
86
|
+
console.error(
|
|
87
|
+
`[ServiceInitializer] ${record.target.name} has problem with it's definition.
|
|
81
88
|
|
|
82
|
-
One or more of the dependencies are registered as a InjectableScope.
|
|
89
|
+
One or more of the dependencies are registered as a InjectableScope.Transient and are used with inject.
|
|
83
90
|
|
|
84
|
-
Please use
|
|
85
|
-
|
|
86
|
-
|
|
91
|
+
Please use asyncInject instead of inject to load those dependencies.`,
|
|
92
|
+
)
|
|
93
|
+
throw DIError.initializationError(
|
|
94
|
+
record.target.name,
|
|
95
|
+
new Error('Service cannot be instantiated'),
|
|
87
96
|
)
|
|
88
97
|
}
|
|
89
98
|
|
|
@@ -99,7 +108,11 @@ export class Instantiator {
|
|
|
99
108
|
|
|
100
109
|
return [undefined, instance]
|
|
101
110
|
} catch (error) {
|
|
102
|
-
return [
|
|
111
|
+
return [
|
|
112
|
+
error instanceof DIError
|
|
113
|
+
? error
|
|
114
|
+
: DIError.initializationError(record.target.name, error as Error),
|
|
115
|
+
]
|
|
103
116
|
}
|
|
104
117
|
}
|
|
105
118
|
|
|
@@ -111,7 +124,7 @@ export class Instantiator {
|
|
|
111
124
|
* @returns Promise resolving to [undefined, instance] or [error]
|
|
112
125
|
*/
|
|
113
126
|
private async instantiateFactory<T>(
|
|
114
|
-
ctx:
|
|
127
|
+
ctx: ServiceInitializationContext,
|
|
115
128
|
record: FactoryRecord<T, any>,
|
|
116
129
|
args: any,
|
|
117
130
|
): Promise<[undefined, T] | [DIError]> {
|
|
@@ -127,8 +140,9 @@ export class Instantiator {
|
|
|
127
140
|
if (promises.length > 0) {
|
|
128
141
|
const results = await Promise.allSettled(promises)
|
|
129
142
|
if (results.some((result) => result.status === 'rejected')) {
|
|
130
|
-
throw DIError.
|
|
131
|
-
|
|
143
|
+
throw DIError.initializationError(
|
|
144
|
+
record.target.name,
|
|
145
|
+
new Error('Service cannot be instantiated'),
|
|
132
146
|
)
|
|
133
147
|
}
|
|
134
148
|
const newRes = tryLoad(injectState)
|
|
@@ -137,26 +151,34 @@ export class Instantiator {
|
|
|
137
151
|
}
|
|
138
152
|
|
|
139
153
|
if (promises.length > 0) {
|
|
140
|
-
console.error(
|
|
154
|
+
console.error(
|
|
155
|
+
`[ServiceInitializer] ${record.target.name} has problem with it's definition.
|
|
141
156
|
|
|
142
|
-
One or more of the dependencies are registered as a InjectableScope.
|
|
157
|
+
One or more of the dependencies are registered as a InjectableScope.Transient and are used with inject.
|
|
143
158
|
|
|
144
|
-
Please use asyncInject instead of inject to load those dependencies
|
|
145
|
-
|
|
146
|
-
|
|
159
|
+
Please use asyncInject instead of inject to load those dependencies.`,
|
|
160
|
+
)
|
|
161
|
+
throw DIError.initializationError(
|
|
162
|
+
record.target.name,
|
|
163
|
+
new Error('Service cannot be instantiated'),
|
|
147
164
|
)
|
|
148
165
|
}
|
|
149
166
|
|
|
150
167
|
if (typeof builder.create !== 'function') {
|
|
151
|
-
throw DIError.
|
|
152
|
-
|
|
168
|
+
throw DIError.initializationError(
|
|
169
|
+
record.target.name,
|
|
170
|
+
new Error('Factory does not implement the create method'),
|
|
153
171
|
)
|
|
154
172
|
}
|
|
155
173
|
|
|
156
174
|
const instance = await builder.create(ctx, args)
|
|
157
175
|
return [undefined, instance]
|
|
158
176
|
} catch (error) {
|
|
159
|
-
return [
|
|
177
|
+
return [
|
|
178
|
+
error instanceof DIError
|
|
179
|
+
? error
|
|
180
|
+
: DIError.initializationError(record.target.name, error as Error),
|
|
181
|
+
]
|
|
160
182
|
}
|
|
161
183
|
}
|
|
162
184
|
}
|