@navios/di 0.5.1 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +145 -0
- package/README.md +196 -219
- package/docs/README.md +69 -11
- package/docs/api-reference.md +281 -117
- package/docs/container.md +220 -56
- package/docs/examples/request-scope-example.mts +2 -2
- package/docs/factory.md +3 -8
- package/docs/getting-started.md +37 -8
- package/docs/migration.md +318 -37
- package/docs/request-contexts.md +263 -175
- package/docs/scopes.md +79 -42
- package/lib/browser/index.d.mts +1577 -0
- package/lib/browser/index.d.mts.map +1 -0
- package/lib/browser/index.mjs +3013 -0
- package/lib/browser/index.mjs.map +1 -0
- package/lib/index-7jfWsiG4.d.mts +1211 -0
- package/lib/index-7jfWsiG4.d.mts.map +1 -0
- package/lib/index-DW3K5sOX.d.cts +1206 -0
- package/lib/index-DW3K5sOX.d.cts.map +1 -0
- package/lib/index.cjs +389 -0
- package/lib/index.cjs.map +1 -0
- package/lib/index.d.cts +376 -0
- package/lib/index.d.cts.map +1 -0
- package/lib/index.d.mts +371 -78
- package/lib/index.d.mts.map +1 -0
- package/lib/index.mjs +325 -63
- package/lib/index.mjs.map +1 -1
- package/lib/testing/index.cjs +9 -0
- package/lib/testing/index.d.cts +2 -0
- package/lib/testing/index.d.mts +2 -2
- package/lib/testing/index.mjs +2 -72
- package/lib/testing-BG_fa9TJ.mjs +2656 -0
- package/lib/testing-BG_fa9TJ.mjs.map +1 -0
- package/lib/testing-DIaIRiJz.cjs +2896 -0
- package/lib/testing-DIaIRiJz.cjs.map +1 -0
- package/package.json +29 -7
- package/project.json +2 -2
- package/src/__tests__/async-local-storage.browser.spec.mts +240 -0
- package/src/__tests__/async-local-storage.spec.mts +333 -0
- package/src/__tests__/container.spec.mts +30 -25
- package/src/__tests__/e2e.browser.spec.mts +790 -0
- package/src/__tests__/e2e.spec.mts +1222 -0
- package/src/__tests__/factory.spec.mts +1 -1
- package/src/__tests__/get-injectors.spec.mts +1 -1
- package/src/__tests__/injectable.spec.mts +1 -1
- package/src/__tests__/injection-token.spec.mts +1 -1
- package/src/__tests__/library-findings.spec.mts +563 -0
- package/src/__tests__/registry.spec.mts +2 -2
- package/src/__tests__/request-scope.spec.mts +266 -274
- package/src/__tests__/service-instantiator.spec.mts +18 -17
- package/src/__tests__/service-locator-event-bus.spec.mts +9 -9
- package/src/__tests__/service-locator-manager.spec.mts +15 -15
- package/src/__tests__/service-locator.spec.mts +167 -244
- package/src/__tests__/unified-api.spec.mts +27 -27
- package/src/__type-tests__/factory.spec-d.mts +2 -2
- package/src/__type-tests__/inject.spec-d.mts +2 -2
- package/src/__type-tests__/injectable.spec-d.mts +1 -1
- package/src/browser.mts +16 -0
- package/src/container/container.mts +319 -0
- package/src/container/index.mts +2 -0
- package/src/container/scoped-container.mts +350 -0
- package/src/decorators/factory.decorator.mts +4 -4
- package/src/decorators/injectable.decorator.mts +5 -5
- package/src/errors/di-error.mts +12 -5
- package/src/errors/index.mts +0 -8
- package/src/index.mts +156 -15
- package/src/interfaces/container.interface.mts +82 -0
- package/src/interfaces/factory.interface.mts +2 -2
- package/src/interfaces/index.mts +1 -0
- package/src/internal/context/async-local-storage.mts +120 -0
- package/src/internal/context/factory-context.mts +18 -0
- package/src/internal/context/index.mts +3 -0
- package/src/{request-context-holder.mts → internal/context/request-context.mts} +40 -27
- package/src/internal/context/resolution-context.mts +63 -0
- package/src/internal/context/sync-local-storage.mts +51 -0
- package/src/internal/core/index.mts +5 -0
- package/src/internal/core/instance-resolver.mts +641 -0
- package/src/{service-instantiator.mts → internal/core/instantiator.mts} +31 -27
- package/src/internal/core/invalidator.mts +437 -0
- package/src/internal/core/service-locator.mts +202 -0
- package/src/{token-processor.mts → internal/core/token-processor.mts} +79 -60
- package/src/{base-instance-holder-manager.mts → internal/holder/base-holder-manager.mts} +91 -21
- package/src/internal/holder/holder-manager.mts +85 -0
- package/src/internal/holder/holder-storage.interface.mts +116 -0
- package/src/internal/holder/index.mts +6 -0
- package/src/internal/holder/instance-holder.mts +109 -0
- package/src/internal/holder/request-storage.mts +134 -0
- package/src/internal/holder/singleton-storage.mts +105 -0
- package/src/internal/index.mts +4 -0
- package/src/internal/lifecycle/circular-detector.mts +77 -0
- package/src/internal/lifecycle/index.mts +2 -0
- package/src/{service-locator-event-bus.mts → internal/lifecycle/lifecycle-event-bus.mts} +11 -4
- package/src/testing/__tests__/test-container.spec.mts +2 -2
- package/src/testing/test-container.mts +4 -4
- package/src/token/index.mts +2 -0
- package/src/{injection-token.mts → token/injection-token.mts} +1 -1
- package/src/{registry.mts → token/registry.mts} +1 -1
- package/src/utils/get-injectable-token.mts +1 -1
- package/src/utils/get-injectors.mts +32 -15
- package/src/utils/types.mts +1 -1
- package/tsdown.config.mts +67 -0
- package/lib/_tsup-dts-rollup.d.mts +0 -1283
- package/lib/_tsup-dts-rollup.d.ts +0 -1283
- package/lib/chunk-2M576LCC.mjs +0 -2043
- package/lib/chunk-2M576LCC.mjs.map +0 -1
- package/lib/index.d.ts +0 -78
- package/lib/index.js +0 -2127
- package/lib/index.js.map +0 -1
- package/lib/testing/index.d.ts +0 -2
- package/lib/testing/index.js +0 -2060
- package/lib/testing/index.js.map +0 -1
- package/lib/testing/index.mjs.map +0 -1
- package/src/container.mts +0 -227
- package/src/factory-context.mts +0 -8
- package/src/instance-resolver.mts +0 -559
- package/src/request-context-manager.mts +0 -149
- package/src/service-invalidator.mts +0 -429
- package/src/service-locator-instance-holder.mts +0 -70
- package/src/service-locator-manager.mts +0 -85
- package/src/service-locator.mts +0 -246
- package/tsup.config.mts +0 -12
- /package/src/{injector.mts → injectors.mts} +0 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-empty-object-type */
|
|
3
|
+
import type { z, ZodObject, ZodOptional } from 'zod/v4'
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
AnyInjectableType,
|
|
7
|
+
InjectionTokenSchemaType,
|
|
8
|
+
} from '../../token/injection-token.mjs'
|
|
9
|
+
import type { IContainer } from '../../interfaces/container.interface.mjs'
|
|
10
|
+
import type { Registry } from '../../token/registry.mjs'
|
|
11
|
+
import type { ScopedContainer } from '../../container/scoped-container.mjs'
|
|
12
|
+
import type { ClearAllOptions } from './invalidator.mjs'
|
|
13
|
+
import type { Injectors } from '../../utils/index.mjs'
|
|
14
|
+
|
|
15
|
+
import { defaultInjectors } from '../../injectors.mjs'
|
|
16
|
+
import { InstanceResolver } from './instance-resolver.mjs'
|
|
17
|
+
import { globalRegistry } from '../../token/registry.mjs'
|
|
18
|
+
import { Instantiator } from './instantiator.mjs'
|
|
19
|
+
import { Invalidator } from './invalidator.mjs'
|
|
20
|
+
import { LifecycleEventBus } from '../lifecycle/lifecycle-event-bus.mjs'
|
|
21
|
+
import { HolderManager } from '../holder/holder-manager.mjs'
|
|
22
|
+
import { TokenProcessor } from './token-processor.mjs'
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Core DI engine that coordinates service instantiation, resolution, and lifecycle.
|
|
26
|
+
*
|
|
27
|
+
* Acts as the central orchestrator for dependency injection operations,
|
|
28
|
+
* delegating to specialized components (InstanceResolver, Instantiator, Invalidator)
|
|
29
|
+
* for specific tasks.
|
|
30
|
+
*/
|
|
31
|
+
export class ServiceLocator {
|
|
32
|
+
private readonly eventBus: LifecycleEventBus
|
|
33
|
+
private readonly manager: HolderManager
|
|
34
|
+
private readonly instantiator: Instantiator
|
|
35
|
+
private readonly tokenProcessor: TokenProcessor
|
|
36
|
+
private readonly invalidator: Invalidator
|
|
37
|
+
private readonly instanceResolver: InstanceResolver
|
|
38
|
+
|
|
39
|
+
constructor(
|
|
40
|
+
private readonly registry: Registry = globalRegistry,
|
|
41
|
+
private readonly logger: Console | null = null,
|
|
42
|
+
private readonly injectors: Injectors = defaultInjectors,
|
|
43
|
+
) {
|
|
44
|
+
this.eventBus = new LifecycleEventBus(logger)
|
|
45
|
+
this.manager = new HolderManager(logger)
|
|
46
|
+
this.instantiator = new Instantiator(injectors)
|
|
47
|
+
this.tokenProcessor = new TokenProcessor(logger)
|
|
48
|
+
this.invalidator = new Invalidator(
|
|
49
|
+
this.manager,
|
|
50
|
+
this.eventBus,
|
|
51
|
+
logger,
|
|
52
|
+
)
|
|
53
|
+
this.instanceResolver = new InstanceResolver(
|
|
54
|
+
this.registry,
|
|
55
|
+
this.manager,
|
|
56
|
+
this.instantiator,
|
|
57
|
+
this.tokenProcessor,
|
|
58
|
+
logger,
|
|
59
|
+
this,
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ============================================================================
|
|
64
|
+
// PUBLIC METHODS
|
|
65
|
+
// ============================================================================
|
|
66
|
+
|
|
67
|
+
getEventBus() {
|
|
68
|
+
return this.eventBus
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
getManager() {
|
|
72
|
+
return this.manager
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
getInvalidator() {
|
|
76
|
+
return this.invalidator
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
getTokenProcessor() {
|
|
80
|
+
return this.tokenProcessor
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public getInstanceIdentifier(token: AnyInjectableType, args?: any): string {
|
|
84
|
+
const [err, { actualToken, validatedArgs }] =
|
|
85
|
+
this.tokenProcessor.validateAndResolveTokenArgs(token, args)
|
|
86
|
+
if (err) {
|
|
87
|
+
throw err
|
|
88
|
+
}
|
|
89
|
+
return this.tokenProcessor.generateInstanceName(actualToken, validatedArgs)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Gets or creates an instance for the given token.
|
|
94
|
+
* @param token The injection token
|
|
95
|
+
* @param args Optional arguments
|
|
96
|
+
* @param contextContainer The container to use for creating FactoryContext
|
|
97
|
+
*/
|
|
98
|
+
public async getInstance(
|
|
99
|
+
token: AnyInjectableType,
|
|
100
|
+
args: any,
|
|
101
|
+
contextContainer: IContainer,
|
|
102
|
+
) {
|
|
103
|
+
const [err, data] = await this.instanceResolver.resolveInstance(
|
|
104
|
+
token,
|
|
105
|
+
args,
|
|
106
|
+
contextContainer,
|
|
107
|
+
)
|
|
108
|
+
if (err) {
|
|
109
|
+
return [err]
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return [undefined, data]
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Gets or throws an instance for the given token.
|
|
117
|
+
* @param token The injection token
|
|
118
|
+
* @param args Optional arguments
|
|
119
|
+
* @param contextContainer The container to use for creating FactoryContext
|
|
120
|
+
*/
|
|
121
|
+
public async getOrThrowInstance<Instance>(
|
|
122
|
+
token: AnyInjectableType,
|
|
123
|
+
args: any,
|
|
124
|
+
contextContainer: IContainer,
|
|
125
|
+
): Promise<Instance> {
|
|
126
|
+
const [error, instance] = await this.getInstance(token, args, contextContainer)
|
|
127
|
+
if (error) {
|
|
128
|
+
throw error
|
|
129
|
+
}
|
|
130
|
+
return instance
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Resolves a request-scoped service for a ScopedContainer.
|
|
135
|
+
* The service will be stored in the ScopedContainer's request context.
|
|
136
|
+
*
|
|
137
|
+
* @param token The injection token
|
|
138
|
+
* @param args Optional arguments
|
|
139
|
+
* @param scopedContainer The ScopedContainer that owns the request context
|
|
140
|
+
*/
|
|
141
|
+
public async resolveRequestScoped(
|
|
142
|
+
token: AnyInjectableType,
|
|
143
|
+
args: any,
|
|
144
|
+
scopedContainer: ScopedContainer,
|
|
145
|
+
): Promise<any> {
|
|
146
|
+
const [err, data] = await this.instanceResolver.resolveRequestScopedInstance(
|
|
147
|
+
token,
|
|
148
|
+
args,
|
|
149
|
+
scopedContainer,
|
|
150
|
+
)
|
|
151
|
+
if (err) {
|
|
152
|
+
throw err
|
|
153
|
+
}
|
|
154
|
+
return data
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
public getSyncInstance<
|
|
158
|
+
Instance,
|
|
159
|
+
Schema extends InjectionTokenSchemaType | undefined,
|
|
160
|
+
>(
|
|
161
|
+
token: AnyInjectableType,
|
|
162
|
+
args: Schema extends ZodObject
|
|
163
|
+
? z.input<Schema>
|
|
164
|
+
: Schema extends ZodOptional<ZodObject>
|
|
165
|
+
? z.input<Schema> | undefined
|
|
166
|
+
: undefined,
|
|
167
|
+
contextContainer: IContainer,
|
|
168
|
+
): Instance | null {
|
|
169
|
+
return this.instanceResolver.getSyncInstance(token, args as any, contextContainer)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
invalidate(service: string, round = 1): Promise<any> {
|
|
173
|
+
return this.invalidator.invalidate(service, round)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Gracefully clears all services in the ServiceLocator using invalidation logic.
|
|
178
|
+
* This method respects service dependencies and ensures proper cleanup order.
|
|
179
|
+
* Services that depend on others will be invalidated first, then their dependencies.
|
|
180
|
+
*
|
|
181
|
+
* @param options Optional configuration for the clearing process
|
|
182
|
+
* @returns Promise that resolves when all services have been cleared
|
|
183
|
+
*/
|
|
184
|
+
async clearAll(options: ClearAllOptions = {}): Promise<void> {
|
|
185
|
+
return this.invalidator.clearAll(options)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Waits for all services to settle (either created, destroyed, or error state).
|
|
190
|
+
*/
|
|
191
|
+
async ready(): Promise<void> {
|
|
192
|
+
return this.invalidator.ready()
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Helper method for InstanceResolver to generate instance names.
|
|
197
|
+
* This is needed for the factory context creation.
|
|
198
|
+
*/
|
|
199
|
+
generateInstanceName(token: any, args: any): string {
|
|
200
|
+
return this.tokenProcessor.generateInstanceName(token, args)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -1,29 +1,81 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-empty-object-type */
|
|
3
3
|
|
|
4
|
-
import type { FactoryContext } from '
|
|
4
|
+
import type { FactoryContext } from '../context/factory-context.mjs'
|
|
5
5
|
import type {
|
|
6
6
|
AnyInjectableType,
|
|
7
7
|
InjectionTokenType,
|
|
8
|
-
} from '
|
|
9
|
-
import type {
|
|
10
|
-
import type { ServiceLocator } from './service-locator.mjs'
|
|
8
|
+
} from '../../token/injection-token.mjs'
|
|
9
|
+
import type { IContainer } from '../../interfaces/container.interface.mjs'
|
|
11
10
|
|
|
12
|
-
import { DIError } from '
|
|
11
|
+
import { DIError } from '../../errors/index.mjs'
|
|
13
12
|
import {
|
|
14
13
|
BoundInjectionToken,
|
|
15
14
|
FactoryInjectionToken,
|
|
16
15
|
InjectionToken,
|
|
17
|
-
} from '
|
|
18
|
-
import { getInjectableToken } from '
|
|
16
|
+
} from '../../token/injection-token.mjs'
|
|
17
|
+
import { getInjectableToken } from '../../utils/index.mjs'
|
|
19
18
|
|
|
20
19
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
20
|
+
* Handles token validation, normalization, and instance name generation.
|
|
21
|
+
*
|
|
22
|
+
* Provides utilities for resolving tokens to their underlying InjectionToken,
|
|
23
|
+
* validating arguments against schemas, and generating unique instance identifiers.
|
|
23
24
|
*/
|
|
24
25
|
export class TokenProcessor {
|
|
25
26
|
constructor(private readonly logger: Console | null = null) {}
|
|
26
27
|
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// TOKEN NORMALIZATION
|
|
30
|
+
// ============================================================================
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Normalizes a token to an InjectionToken.
|
|
34
|
+
* Handles class constructors by getting their injectable token.
|
|
35
|
+
*
|
|
36
|
+
* @param token A class constructor, InjectionToken, BoundInjectionToken, or FactoryInjectionToken
|
|
37
|
+
* @returns The normalized InjectionTokenType
|
|
38
|
+
*/
|
|
39
|
+
normalizeToken(token: AnyInjectableType): InjectionTokenType {
|
|
40
|
+
if (typeof token === 'function') {
|
|
41
|
+
return getInjectableToken(token)
|
|
42
|
+
}
|
|
43
|
+
return token as InjectionTokenType
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Gets the underlying "real" token from wrapped tokens.
|
|
48
|
+
* For BoundInjectionToken and FactoryInjectionToken, returns the wrapped token.
|
|
49
|
+
* For other tokens, returns the token itself.
|
|
50
|
+
*
|
|
51
|
+
* @param token The token to unwrap
|
|
52
|
+
* @returns The underlying InjectionToken
|
|
53
|
+
*/
|
|
54
|
+
getRealToken<T = unknown>(token: InjectionTokenType): InjectionToken<T> {
|
|
55
|
+
if (
|
|
56
|
+
token instanceof BoundInjectionToken ||
|
|
57
|
+
token instanceof FactoryInjectionToken
|
|
58
|
+
) {
|
|
59
|
+
return token.token as InjectionToken<T>
|
|
60
|
+
}
|
|
61
|
+
return token as InjectionToken<T>
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Convenience method that normalizes a token and then gets the real token.
|
|
66
|
+
* Useful for checking registry entries where you need the actual registered token.
|
|
67
|
+
*
|
|
68
|
+
* @param token Any injectable type
|
|
69
|
+
* @returns The underlying InjectionToken
|
|
70
|
+
*/
|
|
71
|
+
getRegistryToken<T = unknown>(token: AnyInjectableType): InjectionToken<T> {
|
|
72
|
+
return this.getRealToken(this.normalizeToken(token))
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ============================================================================
|
|
76
|
+
// TOKEN VALIDATION
|
|
77
|
+
// ============================================================================
|
|
78
|
+
|
|
27
79
|
/**
|
|
28
80
|
* Validates and resolves token arguments, handling factory token resolution and validation.
|
|
29
81
|
*/
|
|
@@ -93,10 +145,12 @@ export class TokenProcessor {
|
|
|
93
145
|
|
|
94
146
|
/**
|
|
95
147
|
* Creates a factory context for dependency injection during service instantiation.
|
|
96
|
-
* @param
|
|
148
|
+
* @param container The container instance (Container or ScopedContainer) for dependency resolution
|
|
149
|
+
* @param onDependencyResolved Callback when a dependency is resolved, receives the instance name
|
|
97
150
|
*/
|
|
98
151
|
createFactoryContext(
|
|
99
|
-
|
|
152
|
+
container: IContainer,
|
|
153
|
+
onDependencyResolved?: (instanceName: string) => void,
|
|
100
154
|
): FactoryContext & {
|
|
101
155
|
getDestroyListeners: () => (() => void)[]
|
|
102
156
|
deps: Set<string>
|
|
@@ -112,63 +166,28 @@ export class TokenProcessor {
|
|
|
112
166
|
return Array.from(destroyListeners)
|
|
113
167
|
}
|
|
114
168
|
|
|
169
|
+
const self = this
|
|
170
|
+
|
|
115
171
|
return {
|
|
116
172
|
// @ts-expect-error This is correct type
|
|
117
173
|
async inject(token, args) {
|
|
118
|
-
//
|
|
119
|
-
const
|
|
120
|
-
token
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (error) {
|
|
127
|
-
throw error
|
|
174
|
+
// Get the instance name for dependency tracking
|
|
175
|
+
const actualToken =
|
|
176
|
+
typeof token === 'function' ? getInjectableToken(token) : token
|
|
177
|
+
const instanceName = self.generateInstanceName(actualToken, args)
|
|
178
|
+
deps.add(instanceName)
|
|
179
|
+
|
|
180
|
+
if (onDependencyResolved) {
|
|
181
|
+
onDependencyResolved(instanceName)
|
|
128
182
|
}
|
|
129
|
-
|
|
183
|
+
|
|
184
|
+
// Use the container's get method for resolution
|
|
185
|
+
return container.get(token, args)
|
|
130
186
|
},
|
|
131
187
|
addDestroyListener,
|
|
132
188
|
getDestroyListeners,
|
|
133
|
-
|
|
189
|
+
container,
|
|
134
190
|
deps,
|
|
135
191
|
}
|
|
136
192
|
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Tries to get a pre-prepared instance from request contexts.
|
|
140
|
-
*/
|
|
141
|
-
tryGetPrePreparedInstance(
|
|
142
|
-
instanceName: string,
|
|
143
|
-
contextHolder: RequestContextHolder | undefined,
|
|
144
|
-
deps: Set<string>,
|
|
145
|
-
currentRequestContext: RequestContextHolder | null,
|
|
146
|
-
): any {
|
|
147
|
-
// Check provided context holder first (if has higher priority)
|
|
148
|
-
if (contextHolder && contextHolder.priority > 0) {
|
|
149
|
-
const prePreparedInstance = contextHolder.get(instanceName)?.instance
|
|
150
|
-
if (prePreparedInstance !== undefined) {
|
|
151
|
-
this.logger?.debug(
|
|
152
|
-
`[TokenProcessor] Using pre-prepared instance ${instanceName} from request context ${contextHolder.requestId}`,
|
|
153
|
-
)
|
|
154
|
-
deps.add(instanceName)
|
|
155
|
-
return prePreparedInstance
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Check current request context (if different from provided contextHolder)
|
|
160
|
-
if (currentRequestContext && currentRequestContext !== contextHolder) {
|
|
161
|
-
const prePreparedInstance =
|
|
162
|
-
currentRequestContext.get(instanceName)?.instance
|
|
163
|
-
if (prePreparedInstance !== undefined) {
|
|
164
|
-
this.logger?.debug(
|
|
165
|
-
`[TokenProcessor] Using pre-prepared instance ${instanceName} from current request context ${currentRequestContext.requestId}`,
|
|
166
|
-
)
|
|
167
|
-
deps.add(instanceName)
|
|
168
|
-
return prePreparedInstance
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return undefined
|
|
173
|
-
}
|
|
174
193
|
}
|
|
@@ -1,14 +1,24 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { InstanceHolder } from './instance-holder.mjs'
|
|
2
2
|
|
|
3
|
-
import { InjectableScope, InjectableType } from '
|
|
4
|
-
import {
|
|
3
|
+
import { InjectableScope, InjectableType } from '../../enums/index.mjs'
|
|
4
|
+
import { DIError } from '../../errors/index.mjs'
|
|
5
|
+
import { CircularDetector } from '../lifecycle/circular-detector.mjs'
|
|
6
|
+
import { InstanceStatus } from './instance-holder.mjs'
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
+
* Result type for waitForHolderReady.
|
|
10
|
+
* Returns either [undefined, holder] on success or [error] on failure.
|
|
9
11
|
*/
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
+
export type HolderReadyResult<T> = [undefined, InstanceHolder<T>] | [DIError]
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Abstract base class providing common functionality for managing InstanceHolder objects.
|
|
16
|
+
*
|
|
17
|
+
* Provides shared patterns for holder storage, creation, and lifecycle management
|
|
18
|
+
* used by both singleton (HolderManager) and request-scoped (RequestContext) managers.
|
|
19
|
+
*/
|
|
20
|
+
export abstract class BaseHolderManager {
|
|
21
|
+
protected readonly _holders: Map<string, InstanceHolder>
|
|
12
22
|
|
|
13
23
|
constructor(protected readonly logger: Console | null = null) {
|
|
14
24
|
this._holders = new Map()
|
|
@@ -17,7 +27,7 @@ export abstract class BaseInstanceHolderManager {
|
|
|
17
27
|
/**
|
|
18
28
|
* Protected getter for accessing the holders map from subclasses.
|
|
19
29
|
*/
|
|
20
|
-
protected get holders(): Map<string,
|
|
30
|
+
protected get holders(): Map<string, InstanceHolder> {
|
|
21
31
|
return this._holders
|
|
22
32
|
}
|
|
23
33
|
|
|
@@ -30,7 +40,7 @@ export abstract class BaseInstanceHolderManager {
|
|
|
30
40
|
/**
|
|
31
41
|
* Abstract method to set a holder by name. Each implementation may have different validation logic.
|
|
32
42
|
*/
|
|
33
|
-
abstract set(name: string, holder:
|
|
43
|
+
abstract set(name: string, holder: InstanceHolder): void
|
|
34
44
|
|
|
35
45
|
/**
|
|
36
46
|
* Abstract method to check if a holder exists. Each implementation may have different validation logic.
|
|
@@ -52,11 +62,8 @@ export abstract class BaseInstanceHolderManager {
|
|
|
52
62
|
* @returns A new Map containing only the holders that match the predicate
|
|
53
63
|
*/
|
|
54
64
|
filter(
|
|
55
|
-
predicate: (
|
|
56
|
-
|
|
57
|
-
key: string,
|
|
58
|
-
) => boolean,
|
|
59
|
-
): Map<string, ServiceLocatorInstanceHolder> {
|
|
65
|
+
predicate: (value: InstanceHolder<any>, key: string) => boolean,
|
|
66
|
+
): Map<string, InstanceHolder> {
|
|
60
67
|
return new Map(
|
|
61
68
|
[...this._holders].filter(([key, value]) => predicate(value, key)),
|
|
62
69
|
)
|
|
@@ -92,12 +99,12 @@ export abstract class BaseInstanceHolderManager {
|
|
|
92
99
|
deps: Set<string> = new Set(),
|
|
93
100
|
): [
|
|
94
101
|
ReturnType<typeof Promise.withResolvers<[undefined, Instance]>>,
|
|
95
|
-
|
|
102
|
+
InstanceHolder<Instance>,
|
|
96
103
|
] {
|
|
97
104
|
const deferred = Promise.withResolvers<[undefined, Instance]>()
|
|
98
105
|
|
|
99
|
-
const holder:
|
|
100
|
-
status:
|
|
106
|
+
const holder: InstanceHolder<Instance> = {
|
|
107
|
+
status: InstanceStatus.Creating,
|
|
101
108
|
name,
|
|
102
109
|
instance: null,
|
|
103
110
|
creationPromise: deferred.promise,
|
|
@@ -107,6 +114,7 @@ export abstract class BaseInstanceHolderManager {
|
|
|
107
114
|
deps,
|
|
108
115
|
destroyListeners: [],
|
|
109
116
|
createdAt: Date.now(),
|
|
117
|
+
waitingFor: new Set(),
|
|
110
118
|
}
|
|
111
119
|
|
|
112
120
|
return [deferred, holder]
|
|
@@ -128,9 +136,9 @@ export abstract class BaseInstanceHolderManager {
|
|
|
128
136
|
type: InjectableType,
|
|
129
137
|
scope: InjectableScope,
|
|
130
138
|
deps: Set<string> = new Set(),
|
|
131
|
-
):
|
|
132
|
-
const holder:
|
|
133
|
-
status:
|
|
139
|
+
): InstanceHolder<Instance> {
|
|
140
|
+
const holder: InstanceHolder<Instance> = {
|
|
141
|
+
status: InstanceStatus.Created,
|
|
134
142
|
name,
|
|
135
143
|
instance,
|
|
136
144
|
creationPromise: null,
|
|
@@ -140,6 +148,7 @@ export abstract class BaseInstanceHolderManager {
|
|
|
140
148
|
deps,
|
|
141
149
|
destroyListeners: [],
|
|
142
150
|
createdAt: Date.now(),
|
|
151
|
+
waitingFor: new Set(),
|
|
143
152
|
}
|
|
144
153
|
|
|
145
154
|
return holder
|
|
@@ -155,7 +164,7 @@ export abstract class BaseInstanceHolderManager {
|
|
|
155
164
|
/**
|
|
156
165
|
* Gets all holders currently managed.
|
|
157
166
|
*/
|
|
158
|
-
getAllHolders():
|
|
167
|
+
getAllHolders(): InstanceHolder[] {
|
|
159
168
|
return Array.from(this._holders.values())
|
|
160
169
|
}
|
|
161
170
|
|
|
@@ -165,4 +174,65 @@ export abstract class BaseInstanceHolderManager {
|
|
|
165
174
|
isEmpty(): boolean {
|
|
166
175
|
return this._holders.size === 0
|
|
167
176
|
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Waits for a holder to be ready and returns the appropriate result.
|
|
180
|
+
* This is a shared utility used by both singleton and request-scoped resolution.
|
|
181
|
+
*
|
|
182
|
+
* @param holder The holder to wait for
|
|
183
|
+
* @param waiterHolder Optional holder that is doing the waiting (for circular dependency detection)
|
|
184
|
+
* @param getHolder Optional function to retrieve holders by name (required if waiterHolder is provided)
|
|
185
|
+
* @returns A promise that resolves with [undefined, holder] on success or [DIError] on failure
|
|
186
|
+
*/
|
|
187
|
+
static async waitForHolderReady<T>(
|
|
188
|
+
holder: InstanceHolder<T>,
|
|
189
|
+
waiterHolder?: InstanceHolder,
|
|
190
|
+
getHolder?: (name: string) => InstanceHolder | undefined,
|
|
191
|
+
): Promise<HolderReadyResult<T>> {
|
|
192
|
+
switch (holder.status) {
|
|
193
|
+
case InstanceStatus.Creating: {
|
|
194
|
+
// Check for circular dependency before waiting
|
|
195
|
+
if (waiterHolder && getHolder) {
|
|
196
|
+
const cycle = CircularDetector.detectCycle(
|
|
197
|
+
waiterHolder.name,
|
|
198
|
+
holder.name,
|
|
199
|
+
getHolder,
|
|
200
|
+
)
|
|
201
|
+
if (cycle) {
|
|
202
|
+
return [DIError.circularDependency(cycle)]
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Track the waiting relationship
|
|
206
|
+
waiterHolder.waitingFor.add(holder.name)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
await holder.creationPromise
|
|
211
|
+
} finally {
|
|
212
|
+
// Clean up the waiting relationship
|
|
213
|
+
if (waiterHolder) {
|
|
214
|
+
waiterHolder.waitingFor.delete(holder.name)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return BaseHolderManager.waitForHolderReady(
|
|
219
|
+
holder,
|
|
220
|
+
waiterHolder,
|
|
221
|
+
getHolder,
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
case InstanceStatus.Destroying:
|
|
226
|
+
return [DIError.instanceDestroying(holder.name)]
|
|
227
|
+
|
|
228
|
+
case InstanceStatus.Error:
|
|
229
|
+
return [holder.instance as unknown as DIError]
|
|
230
|
+
|
|
231
|
+
case InstanceStatus.Created:
|
|
232
|
+
return [undefined, holder]
|
|
233
|
+
|
|
234
|
+
default:
|
|
235
|
+
return [DIError.instanceNotFound('unknown')]
|
|
236
|
+
}
|
|
237
|
+
}
|
|
168
238
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { InstanceHolder } from './instance-holder.mjs'
|
|
2
|
+
|
|
3
|
+
import { InjectableScope, InjectableType } from '../../enums/index.mjs'
|
|
4
|
+
import { DIError, DIErrorCode } from '../../errors/index.mjs'
|
|
5
|
+
import { BaseHolderManager } from './base-holder-manager.mjs'
|
|
6
|
+
import { InstanceStatus } from './instance-holder.mjs'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Manages the storage and retrieval of singleton instance holders.
|
|
10
|
+
*
|
|
11
|
+
* Provides CRUD operations and filtering for the holder map.
|
|
12
|
+
* Handles holder state validation (destroying, error states) on retrieval.
|
|
13
|
+
*/
|
|
14
|
+
export class HolderManager extends BaseHolderManager {
|
|
15
|
+
constructor(logger: Console | null = null) {
|
|
16
|
+
super(logger)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get(
|
|
20
|
+
name: string,
|
|
21
|
+
): [DIError, InstanceHolder] | [DIError] | [undefined, InstanceHolder] {
|
|
22
|
+
const holder = this._holders.get(name)
|
|
23
|
+
if (holder) {
|
|
24
|
+
if (holder.status === InstanceStatus.Destroying) {
|
|
25
|
+
this.logger?.log(
|
|
26
|
+
`[HolderManager]#get() Instance ${holder.name} is destroying`,
|
|
27
|
+
)
|
|
28
|
+
return [DIError.instanceDestroying(holder.name), holder]
|
|
29
|
+
} else if (holder.status === InstanceStatus.Error) {
|
|
30
|
+
this.logger?.log(
|
|
31
|
+
`[HolderManager]#get() Instance ${holder.name} is in error state`,
|
|
32
|
+
)
|
|
33
|
+
return [holder.instance as unknown as DIError, holder]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return [undefined, holder]
|
|
37
|
+
} else {
|
|
38
|
+
this.logger?.log(`[HolderManager]#get() Instance ${name} not found`)
|
|
39
|
+
return [DIError.instanceNotFound(name)]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
set(name: string, holder: InstanceHolder): void {
|
|
44
|
+
this._holders.set(name, holder)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
has(name: string): [DIError] | [undefined, boolean] {
|
|
48
|
+
const [error, holder] = this.get(name)
|
|
49
|
+
if (!error) {
|
|
50
|
+
return [undefined, true]
|
|
51
|
+
}
|
|
52
|
+
if (error.code === DIErrorCode.InstanceDestroying) {
|
|
53
|
+
return [error]
|
|
54
|
+
}
|
|
55
|
+
return [undefined, !!holder]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// delete and filter methods are inherited from BaseHolderManager
|
|
59
|
+
|
|
60
|
+
// createCreatingHolder method is inherited from BaseHolderManager
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Creates a new holder with Created status and stores it.
|
|
64
|
+
* This is useful for creating holders that already have their instance ready.
|
|
65
|
+
* @param name The name of the instance
|
|
66
|
+
* @param instance The actual instance to store
|
|
67
|
+
* @param type The injectable type
|
|
68
|
+
* @param scope The injectable scope
|
|
69
|
+
* @param deps Optional set of dependencies
|
|
70
|
+
* @returns The created holder
|
|
71
|
+
*/
|
|
72
|
+
storeCreatedHolder<Instance>(
|
|
73
|
+
name: string,
|
|
74
|
+
instance: Instance,
|
|
75
|
+
type: InjectableType,
|
|
76
|
+
scope: InjectableScope,
|
|
77
|
+
deps: Set<string> = new Set(),
|
|
78
|
+
): InstanceHolder<Instance> {
|
|
79
|
+
const holder = this.createCreatedHolder(name, instance, type, scope, deps)
|
|
80
|
+
|
|
81
|
+
this._holders.set(name, holder)
|
|
82
|
+
|
|
83
|
+
return holder
|
|
84
|
+
}
|
|
85
|
+
}
|