@navios/di 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +146 -0
- package/README.md +196 -219
- package/docs/README.md +69 -11
- package/docs/api-reference.md +281 -117
- package/docs/container.md +220 -56
- package/docs/examples/request-scope-example.mts +2 -2
- package/docs/factory.md +3 -8
- package/docs/getting-started.md +37 -8
- package/docs/migration.md +318 -37
- package/docs/request-contexts.md +263 -175
- package/docs/scopes.md +79 -42
- package/lib/browser/index.d.mts +1577 -0
- package/lib/browser/index.d.mts.map +1 -0
- package/lib/browser/index.mjs +3012 -0
- package/lib/browser/index.mjs.map +1 -0
- package/lib/index-S_qX2VLI.d.mts +1211 -0
- package/lib/index-S_qX2VLI.d.mts.map +1 -0
- package/lib/index-fKPuT65j.d.cts +1206 -0
- package/lib/index-fKPuT65j.d.cts.map +1 -0
- package/lib/index.cjs +389 -0
- package/lib/index.cjs.map +1 -0
- package/lib/index.d.cts +376 -0
- package/lib/index.d.cts.map +1 -0
- package/lib/index.d.mts +371 -78
- package/lib/index.d.mts.map +1 -0
- package/lib/index.mjs +325 -63
- package/lib/index.mjs.map +1 -1
- package/lib/testing/index.cjs +9 -0
- package/lib/testing/index.d.cts +2 -0
- package/lib/testing/index.d.mts +2 -2
- package/lib/testing/index.mjs +2 -72
- package/lib/testing-BMGmmxH7.cjs +2895 -0
- package/lib/testing-BMGmmxH7.cjs.map +1 -0
- package/lib/testing-DCXz8AJD.mjs +2655 -0
- package/lib/testing-DCXz8AJD.mjs.map +1 -0
- package/package.json +23 -1
- package/project.json +2 -2
- package/src/__tests__/async-local-storage.browser.spec.mts +240 -0
- package/src/__tests__/async-local-storage.spec.mts +333 -0
- package/src/__tests__/container.spec.mts +30 -25
- package/src/__tests__/e2e.browser.spec.mts +790 -0
- package/src/__tests__/e2e.spec.mts +1222 -0
- package/src/__tests__/errors.spec.mts +6 -6
- package/src/__tests__/factory.spec.mts +1 -1
- package/src/__tests__/get-injectors.spec.mts +1 -1
- package/src/__tests__/injectable.spec.mts +1 -1
- package/src/__tests__/injection-token.spec.mts +1 -1
- package/src/__tests__/library-findings.spec.mts +563 -0
- package/src/__tests__/registry.spec.mts +2 -2
- package/src/__tests__/request-scope.spec.mts +266 -274
- package/src/__tests__/service-instantiator.spec.mts +18 -17
- package/src/__tests__/service-locator-event-bus.spec.mts +9 -9
- package/src/__tests__/service-locator-manager.spec.mts +15 -15
- package/src/__tests__/service-locator.spec.mts +167 -244
- package/src/__tests__/unified-api.spec.mts +27 -27
- package/src/__type-tests__/factory.spec-d.mts +2 -2
- package/src/__type-tests__/inject.spec-d.mts +2 -2
- package/src/__type-tests__/injectable.spec-d.mts +1 -1
- package/src/browser.mts +16 -0
- package/src/container/container.mts +319 -0
- package/src/container/index.mts +2 -0
- package/src/container/scoped-container.mts +350 -0
- package/src/decorators/factory.decorator.mts +4 -4
- package/src/decorators/injectable.decorator.mts +5 -5
- package/src/errors/di-error.mts +13 -7
- package/src/errors/index.mts +0 -8
- package/src/index.mts +156 -15
- package/src/interfaces/container.interface.mts +82 -0
- package/src/interfaces/factory.interface.mts +2 -2
- package/src/interfaces/index.mts +1 -0
- package/src/internal/context/async-local-storage.mts +120 -0
- package/src/internal/context/factory-context.mts +18 -0
- package/src/internal/context/index.mts +3 -0
- package/src/{request-context-holder.mts → internal/context/request-context.mts} +40 -27
- package/src/internal/context/resolution-context.mts +63 -0
- package/src/internal/context/sync-local-storage.mts +51 -0
- package/src/internal/core/index.mts +5 -0
- package/src/internal/core/instance-resolver.mts +641 -0
- package/src/{service-instantiator.mts → internal/core/instantiator.mts} +31 -27
- package/src/internal/core/invalidator.mts +437 -0
- package/src/internal/core/service-locator.mts +202 -0
- package/src/{token-processor.mts → internal/core/token-processor.mts} +79 -60
- package/src/{base-instance-holder-manager.mts → internal/holder/base-holder-manager.mts} +91 -21
- package/src/internal/holder/holder-manager.mts +85 -0
- package/src/internal/holder/holder-storage.interface.mts +116 -0
- package/src/internal/holder/index.mts +6 -0
- package/src/internal/holder/instance-holder.mts +109 -0
- package/src/internal/holder/request-storage.mts +134 -0
- package/src/internal/holder/singleton-storage.mts +105 -0
- package/src/internal/index.mts +4 -0
- package/src/internal/lifecycle/circular-detector.mts +77 -0
- package/src/internal/lifecycle/index.mts +2 -0
- package/src/{service-locator-event-bus.mts → internal/lifecycle/lifecycle-event-bus.mts} +11 -4
- package/src/testing/__tests__/test-container.spec.mts +2 -2
- package/src/testing/test-container.mts +4 -4
- package/src/token/index.mts +2 -0
- package/src/{injection-token.mts → token/injection-token.mts} +1 -1
- package/src/{registry.mts → token/registry.mts} +1 -1
- package/src/utils/get-injectable-token.mts +1 -1
- package/src/utils/get-injectors.mts +32 -15
- package/src/utils/types.mts +1 -1
- package/tsdown.config.mts +67 -0
- package/lib/_tsup-dts-rollup.d.mts +0 -1283
- package/lib/_tsup-dts-rollup.d.ts +0 -1283
- package/lib/chunk-2M576LCC.mjs +0 -2043
- package/lib/chunk-2M576LCC.mjs.map +0 -1
- package/lib/index.d.ts +0 -78
- package/lib/index.js +0 -2127
- package/lib/index.js.map +0 -1
- package/lib/testing/index.d.ts +0 -2
- package/lib/testing/index.js +0 -2060
- package/lib/testing/index.js.map +0 -1
- package/lib/testing/index.mjs.map +0 -1
- package/src/container.mts +0 -227
- package/src/factory-context.mts +0 -8
- package/src/instance-resolver.mts +0 -559
- package/src/request-context-manager.mts +0 -149
- package/src/service-invalidator.mts +0 -429
- package/src/service-locator-instance-holder.mts +0 -70
- package/src/service-locator-manager.mts +0 -85
- package/src/service-locator.mts +0 -246
- package/tsup.config.mts +0 -12
- /package/src/{injector.mts → injectors.mts} +0 -0
|
@@ -1,429 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import type { RequestContextManager } from './request-context-manager.mjs'
|
|
3
|
-
import type { ServiceLocatorEventBus } from './service-locator-event-bus.mjs'
|
|
4
|
-
import type { ServiceLocatorInstanceHolder } from './service-locator-instance-holder.mjs'
|
|
5
|
-
import type { ServiceLocatorManager } from './service-locator-manager.mjs'
|
|
6
|
-
|
|
7
|
-
import { ServiceLocatorInstanceHolderStatus } from './service-locator-instance-holder.mjs'
|
|
8
|
-
|
|
9
|
-
export interface ClearAllOptions {
|
|
10
|
-
/** Whether to also clear request contexts (default: true) */
|
|
11
|
-
clearRequestContexts?: boolean
|
|
12
|
-
/** Maximum number of invalidation rounds to prevent infinite loops (default: 10) */
|
|
13
|
-
maxRounds?: number
|
|
14
|
-
/** Whether to wait for all services to settle before starting (default: true) */
|
|
15
|
-
waitForSettlement?: boolean
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* ServiceInvalidator handles service invalidation, cleanup, and graceful clearing.
|
|
20
|
-
* Extracted from ServiceLocator to improve separation of concerns.
|
|
21
|
-
*/
|
|
22
|
-
export class ServiceInvalidator {
|
|
23
|
-
constructor(
|
|
24
|
-
private readonly manager: ServiceLocatorManager,
|
|
25
|
-
private readonly requestContextManager: RequestContextManager,
|
|
26
|
-
private readonly eventBus: ServiceLocatorEventBus,
|
|
27
|
-
private readonly logger: Console | null = null,
|
|
28
|
-
) {}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Invalidates a service and all its dependencies.
|
|
32
|
-
*/
|
|
33
|
-
invalidate(service: string, round = 1): Promise<any> {
|
|
34
|
-
this.logger?.log(
|
|
35
|
-
`[ServiceInvalidator] Starting invalidation process for ${service}`,
|
|
36
|
-
)
|
|
37
|
-
const [, toInvalidate] = this.manager.get(service)
|
|
38
|
-
|
|
39
|
-
const promises = []
|
|
40
|
-
if (toInvalidate) {
|
|
41
|
-
promises.push(this.invalidateHolder(service, toInvalidate, round))
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Also invalidate request-scoped instances that depend on the service or match the service name
|
|
45
|
-
const requestContexts = this.requestContextManager.getRequestContexts()
|
|
46
|
-
for (const [requestId, requestContext] of requestContexts.entries()) {
|
|
47
|
-
const holder = requestContext.get(service)
|
|
48
|
-
if (holder) {
|
|
49
|
-
this.logger?.log(
|
|
50
|
-
`[ServiceInvalidator] Invalidating request-scoped instance ${service} in request ${requestId}`,
|
|
51
|
-
)
|
|
52
|
-
promises.push(
|
|
53
|
-
this.invalidateRequestHolder(requestId, service, holder, round),
|
|
54
|
-
)
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return Promise.all(promises)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Gracefully clears all services in the ServiceLocator using invalidation logic.
|
|
63
|
-
* This method respects service dependencies and ensures proper cleanup order.
|
|
64
|
-
* Services that depend on others will be invalidated first, then their dependencies.
|
|
65
|
-
*/
|
|
66
|
-
async clearAll(options: ClearAllOptions = {}): Promise<void> {
|
|
67
|
-
const {
|
|
68
|
-
clearRequestContexts = true,
|
|
69
|
-
maxRounds = 10,
|
|
70
|
-
waitForSettlement = true,
|
|
71
|
-
} = options
|
|
72
|
-
|
|
73
|
-
this.logger?.log(
|
|
74
|
-
'[ServiceInvalidator] Starting graceful clearing of all services',
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
// Wait for all services to settle if requested
|
|
78
|
-
if (waitForSettlement) {
|
|
79
|
-
this.logger?.log(
|
|
80
|
-
'[ServiceInvalidator] Waiting for all services to settle...',
|
|
81
|
-
)
|
|
82
|
-
await this.ready()
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Get all service names that need to be cleared
|
|
86
|
-
const allServiceNames = this.getAllServiceNames()
|
|
87
|
-
|
|
88
|
-
if (allServiceNames.length === 0) {
|
|
89
|
-
this.logger?.log('[ServiceInvalidator] No singleton services to clear')
|
|
90
|
-
} else {
|
|
91
|
-
this.logger?.log(
|
|
92
|
-
`[ServiceInvalidator] Found ${allServiceNames.length} services to clear: ${allServiceNames.join(', ')}`,
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
// Clear services using dependency-aware invalidation
|
|
96
|
-
await this.clearServicesWithDependencyAwareness(
|
|
97
|
-
allServiceNames,
|
|
98
|
-
maxRounds,
|
|
99
|
-
)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Clear request contexts if requested
|
|
103
|
-
if (clearRequestContexts) {
|
|
104
|
-
await this.requestContextManager.clearAllRequestContexts()
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
this.logger?.log('[ServiceInvalidator] Graceful clearing completed')
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Waits for all services to settle (either created, destroyed, or error state).
|
|
112
|
-
*/
|
|
113
|
-
async ready(): Promise<void> {
|
|
114
|
-
const holders = Array.from(this.manager.filter(() => true)).map(
|
|
115
|
-
([, holder]) => holder,
|
|
116
|
-
)
|
|
117
|
-
await Promise.all(
|
|
118
|
-
holders.map((holder) => this.waitForHolderToSettle(holder)),
|
|
119
|
-
)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Invalidates a single holder based on its current status.
|
|
124
|
-
*/
|
|
125
|
-
private async invalidateHolder(
|
|
126
|
-
key: string,
|
|
127
|
-
holder: ServiceLocatorInstanceHolder<any>,
|
|
128
|
-
round: number,
|
|
129
|
-
): Promise<void> {
|
|
130
|
-
await this.invalidateHolderByStatus(holder, round, {
|
|
131
|
-
context: key,
|
|
132
|
-
isRequestScoped: false,
|
|
133
|
-
onCreationError: () =>
|
|
134
|
-
this.logger?.error(
|
|
135
|
-
`[ServiceInvalidator] ${key} creation triggered too many invalidation rounds`,
|
|
136
|
-
),
|
|
137
|
-
onRecursiveInvalidate: () => this.invalidate(key, round + 1),
|
|
138
|
-
onDestroy: () => this.destroyHolder(key, holder),
|
|
139
|
-
})
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Invalidates a request-scoped holder based on its current status.
|
|
144
|
-
*/
|
|
145
|
-
private async invalidateRequestHolder(
|
|
146
|
-
requestId: string,
|
|
147
|
-
instanceName: string,
|
|
148
|
-
holder: ServiceLocatorInstanceHolder<any>,
|
|
149
|
-
round: number,
|
|
150
|
-
): Promise<void> {
|
|
151
|
-
await this.invalidateHolderByStatus(holder, round, {
|
|
152
|
-
context: `Request-scoped ${instanceName} in ${requestId}`,
|
|
153
|
-
isRequestScoped: true,
|
|
154
|
-
onCreationError: () =>
|
|
155
|
-
this.logger?.error(
|
|
156
|
-
`[ServiceInvalidator] Request-scoped ${instanceName} in ${requestId} creation triggered too many invalidation rounds`,
|
|
157
|
-
),
|
|
158
|
-
onRecursiveInvalidate: () =>
|
|
159
|
-
this.invalidateRequestHolder(
|
|
160
|
-
requestId,
|
|
161
|
-
instanceName,
|
|
162
|
-
holder,
|
|
163
|
-
round + 1,
|
|
164
|
-
),
|
|
165
|
-
onDestroy: () =>
|
|
166
|
-
this.destroyRequestHolder(requestId, instanceName, holder),
|
|
167
|
-
})
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Common invalidation logic for holders based on their status.
|
|
172
|
-
*/
|
|
173
|
-
private async invalidateHolderByStatus(
|
|
174
|
-
holder: ServiceLocatorInstanceHolder<any>,
|
|
175
|
-
round: number,
|
|
176
|
-
options: {
|
|
177
|
-
context: string
|
|
178
|
-
isRequestScoped: boolean
|
|
179
|
-
onCreationError: () => void
|
|
180
|
-
onRecursiveInvalidate: () => Promise<void>
|
|
181
|
-
onDestroy: () => Promise<void>
|
|
182
|
-
},
|
|
183
|
-
): Promise<void> {
|
|
184
|
-
switch (holder.status) {
|
|
185
|
-
case ServiceLocatorInstanceHolderStatus.Destroying:
|
|
186
|
-
await holder.destroyPromise
|
|
187
|
-
break
|
|
188
|
-
|
|
189
|
-
case ServiceLocatorInstanceHolderStatus.Creating:
|
|
190
|
-
await holder.creationPromise
|
|
191
|
-
if (round > 3) {
|
|
192
|
-
options.onCreationError()
|
|
193
|
-
return
|
|
194
|
-
}
|
|
195
|
-
await options.onRecursiveInvalidate()
|
|
196
|
-
break
|
|
197
|
-
|
|
198
|
-
default:
|
|
199
|
-
await options.onDestroy()
|
|
200
|
-
break
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Destroys a holder and cleans up its resources.
|
|
206
|
-
*/
|
|
207
|
-
private async destroyHolder(
|
|
208
|
-
key: string,
|
|
209
|
-
holder: ServiceLocatorInstanceHolder<any>,
|
|
210
|
-
): Promise<void> {
|
|
211
|
-
await this.destroyHolderWithCleanup(holder, {
|
|
212
|
-
context: key,
|
|
213
|
-
logMessage: `[ServiceInvalidator] Invalidating ${key} and notifying listeners`,
|
|
214
|
-
cleanup: () => this.manager.delete(key),
|
|
215
|
-
eventName: key,
|
|
216
|
-
})
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Destroys a request-scoped holder and cleans up its resources.
|
|
221
|
-
*/
|
|
222
|
-
private async destroyRequestHolder(
|
|
223
|
-
requestId: string,
|
|
224
|
-
instanceName: string,
|
|
225
|
-
holder: ServiceLocatorInstanceHolder<any>,
|
|
226
|
-
): Promise<void> {
|
|
227
|
-
await this.destroyHolderWithCleanup(holder, {
|
|
228
|
-
context: `Request-scoped ${instanceName} in ${requestId}`,
|
|
229
|
-
logMessage: `[ServiceInvalidator] Invalidating request-scoped ${instanceName} in ${requestId} and notifying listeners`,
|
|
230
|
-
cleanup: () => {
|
|
231
|
-
const requestContext = this.requestContextManager
|
|
232
|
-
.getRequestContexts()
|
|
233
|
-
.get(requestId)
|
|
234
|
-
if (requestContext) {
|
|
235
|
-
requestContext.delete(instanceName)
|
|
236
|
-
}
|
|
237
|
-
},
|
|
238
|
-
eventName: instanceName,
|
|
239
|
-
})
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Common destroy logic for holders with customizable cleanup.
|
|
244
|
-
*/
|
|
245
|
-
private async destroyHolderWithCleanup(
|
|
246
|
-
holder: ServiceLocatorInstanceHolder<any>,
|
|
247
|
-
options: {
|
|
248
|
-
context: string
|
|
249
|
-
logMessage: string
|
|
250
|
-
cleanup: () => void
|
|
251
|
-
eventName: string
|
|
252
|
-
},
|
|
253
|
-
): Promise<void> {
|
|
254
|
-
holder.status = ServiceLocatorInstanceHolderStatus.Destroying
|
|
255
|
-
this.logger?.log(options.logMessage)
|
|
256
|
-
|
|
257
|
-
holder.destroyPromise = Promise.all(
|
|
258
|
-
holder.destroyListeners.map((listener) => listener()),
|
|
259
|
-
).then(async () => {
|
|
260
|
-
holder.destroyListeners = []
|
|
261
|
-
holder.deps.clear()
|
|
262
|
-
options.cleanup()
|
|
263
|
-
await this.emitInstanceEvent(options.eventName, 'destroy')
|
|
264
|
-
})
|
|
265
|
-
|
|
266
|
-
await holder.destroyPromise
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Waits for a holder to settle (either created, destroyed, or error state).
|
|
271
|
-
*/
|
|
272
|
-
private async waitForHolderToSettle(
|
|
273
|
-
holder: ServiceLocatorInstanceHolder<any>,
|
|
274
|
-
): Promise<void> {
|
|
275
|
-
switch (holder.status) {
|
|
276
|
-
case ServiceLocatorInstanceHolderStatus.Creating:
|
|
277
|
-
await holder.creationPromise
|
|
278
|
-
break
|
|
279
|
-
case ServiceLocatorInstanceHolderStatus.Destroying:
|
|
280
|
-
await holder.destroyPromise
|
|
281
|
-
break
|
|
282
|
-
// Already settled states
|
|
283
|
-
case ServiceLocatorInstanceHolderStatus.Created:
|
|
284
|
-
case ServiceLocatorInstanceHolderStatus.Error:
|
|
285
|
-
break
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Clears services with dependency awareness, ensuring proper cleanup order.
|
|
291
|
-
* Services with no dependencies are cleared first, then services that depend on them.
|
|
292
|
-
*/
|
|
293
|
-
private async clearServicesWithDependencyAwareness(
|
|
294
|
-
serviceNames: string[],
|
|
295
|
-
maxRounds: number,
|
|
296
|
-
): Promise<void> {
|
|
297
|
-
const clearedServices = new Set<string>()
|
|
298
|
-
let round = 1
|
|
299
|
-
|
|
300
|
-
while (clearedServices.size < serviceNames.length && round <= maxRounds) {
|
|
301
|
-
this.logger?.log(
|
|
302
|
-
`[ServiceInvalidator] Clearing round ${round}/${maxRounds}, ${clearedServices.size}/${serviceNames.length} services cleared`,
|
|
303
|
-
)
|
|
304
|
-
|
|
305
|
-
// Find services that can be cleared in this round
|
|
306
|
-
const servicesToClearThisRound = this.findServicesReadyForClearing(
|
|
307
|
-
serviceNames,
|
|
308
|
-
clearedServices,
|
|
309
|
-
)
|
|
310
|
-
|
|
311
|
-
if (servicesToClearThisRound.length === 0) {
|
|
312
|
-
// If no services can be cleared, try to clear remaining services anyway
|
|
313
|
-
// This handles circular dependencies or other edge cases
|
|
314
|
-
const remainingServices = serviceNames.filter(
|
|
315
|
-
(name) => !clearedServices.has(name),
|
|
316
|
-
)
|
|
317
|
-
|
|
318
|
-
if (remainingServices.length > 0) {
|
|
319
|
-
this.logger?.warn(
|
|
320
|
-
`[ServiceInvalidator] No services ready for clearing, forcing cleanup of remaining: ${remainingServices.join(', ')}`,
|
|
321
|
-
)
|
|
322
|
-
await this.forceClearServices(remainingServices)
|
|
323
|
-
remainingServices.forEach((name) => clearedServices.add(name))
|
|
324
|
-
}
|
|
325
|
-
break
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// Clear services in this round
|
|
329
|
-
const clearPromises = servicesToClearThisRound.map(
|
|
330
|
-
async (serviceName) => {
|
|
331
|
-
try {
|
|
332
|
-
await this.invalidate(serviceName, round)
|
|
333
|
-
clearedServices.add(serviceName)
|
|
334
|
-
this.logger?.log(
|
|
335
|
-
`[ServiceInvalidator] Successfully cleared service: ${serviceName}`,
|
|
336
|
-
)
|
|
337
|
-
} catch (error) {
|
|
338
|
-
this.logger?.error(
|
|
339
|
-
`[ServiceInvalidator] Error clearing service ${serviceName}:`,
|
|
340
|
-
error,
|
|
341
|
-
)
|
|
342
|
-
// Still mark as cleared to avoid infinite loops
|
|
343
|
-
clearedServices.add(serviceName)
|
|
344
|
-
}
|
|
345
|
-
},
|
|
346
|
-
)
|
|
347
|
-
|
|
348
|
-
await Promise.all(clearPromises)
|
|
349
|
-
round++
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
if (clearedServices.size < serviceNames.length) {
|
|
353
|
-
this.logger?.warn(
|
|
354
|
-
`[ServiceInvalidator] Clearing completed after ${maxRounds} rounds, but ${serviceNames.length - clearedServices.size} services may not have been properly cleared`,
|
|
355
|
-
)
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/**
|
|
360
|
-
* Finds services that are ready to be cleared in the current round.
|
|
361
|
-
* A service is ready if all its dependencies have already been cleared.
|
|
362
|
-
*/
|
|
363
|
-
private findServicesReadyForClearing(
|
|
364
|
-
allServiceNames: string[],
|
|
365
|
-
clearedServices: Set<string>,
|
|
366
|
-
): string[] {
|
|
367
|
-
return allServiceNames.filter((serviceName) => {
|
|
368
|
-
if (clearedServices.has(serviceName)) {
|
|
369
|
-
return false // Already cleared
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// Check if this service has any dependencies that haven't been cleared yet
|
|
373
|
-
const [error, holder] = this.manager.get(serviceName)
|
|
374
|
-
if (error) {
|
|
375
|
-
return true // Service not found or in error state, can be cleared
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// Check if all dependencies have been cleared
|
|
379
|
-
const hasUnclearedDependencies = Array.from(holder.deps).some(
|
|
380
|
-
(dep) => !clearedServices.has(dep),
|
|
381
|
-
)
|
|
382
|
-
|
|
383
|
-
return !hasUnclearedDependencies
|
|
384
|
-
})
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Force clears services that couldn't be cleared through normal dependency resolution.
|
|
389
|
-
* This handles edge cases like circular dependencies.
|
|
390
|
-
*/
|
|
391
|
-
private async forceClearServices(serviceNames: string[]): Promise<void> {
|
|
392
|
-
const promises = serviceNames.map(async (serviceName) => {
|
|
393
|
-
try {
|
|
394
|
-
// Directly destroy the holder without going through normal invalidation
|
|
395
|
-
const [error, holder] = this.manager.get(serviceName)
|
|
396
|
-
if (!error && holder) {
|
|
397
|
-
await this.destroyHolder(serviceName, holder)
|
|
398
|
-
}
|
|
399
|
-
} catch (error) {
|
|
400
|
-
this.logger?.error(
|
|
401
|
-
`[ServiceInvalidator] Error force clearing service ${serviceName}:`,
|
|
402
|
-
error,
|
|
403
|
-
)
|
|
404
|
-
}
|
|
405
|
-
})
|
|
406
|
-
|
|
407
|
-
await Promise.all(promises)
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
/**
|
|
411
|
-
* Gets all service names currently managed by the ServiceLocator.
|
|
412
|
-
*/
|
|
413
|
-
private getAllServiceNames(): string[] {
|
|
414
|
-
return this.manager.getAllNames()
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
/**
|
|
418
|
-
* Emits events to listeners for instance lifecycle events.
|
|
419
|
-
*/
|
|
420
|
-
private emitInstanceEvent(
|
|
421
|
-
name: string,
|
|
422
|
-
event: 'create' | 'destroy' = 'create',
|
|
423
|
-
) {
|
|
424
|
-
this.logger?.log(
|
|
425
|
-
`[ServiceInvalidator]#emitInstanceEvent() Notifying listeners for ${name} with event ${event}`,
|
|
426
|
-
)
|
|
427
|
-
return this.eventBus.emit(name, event)
|
|
428
|
-
}
|
|
429
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import type { InjectableScope, InjectableType } from './enums/index.mjs'
|
|
2
|
-
|
|
3
|
-
/* eslint-disable @typescript-eslint/no-empty-object-type */
|
|
4
|
-
export enum ServiceLocatorInstanceHolderStatus {
|
|
5
|
-
Created = 'created',
|
|
6
|
-
Creating = 'creating',
|
|
7
|
-
Destroying = 'destroying',
|
|
8
|
-
Error = 'error',
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export type ServiceLocatorInstanceEffect = () => void
|
|
12
|
-
export type ServiceLocatorInstanceDestroyListener = () => void | Promise<void>
|
|
13
|
-
|
|
14
|
-
export interface ServiceLocatorInstanceHolderCreating<Instance> {
|
|
15
|
-
status: ServiceLocatorInstanceHolderStatus.Creating
|
|
16
|
-
name: string
|
|
17
|
-
instance: null
|
|
18
|
-
creationPromise: Promise<[undefined, Instance]> | null
|
|
19
|
-
destroyPromise: null
|
|
20
|
-
type: InjectableType
|
|
21
|
-
scope: InjectableScope
|
|
22
|
-
deps: Set<string>
|
|
23
|
-
destroyListeners: ServiceLocatorInstanceDestroyListener[]
|
|
24
|
-
createdAt: number
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface ServiceLocatorInstanceHolderCreated<Instance> {
|
|
28
|
-
status: ServiceLocatorInstanceHolderStatus.Created
|
|
29
|
-
name: string
|
|
30
|
-
instance: Instance
|
|
31
|
-
creationPromise: null
|
|
32
|
-
destroyPromise: null
|
|
33
|
-
type: InjectableType
|
|
34
|
-
scope: InjectableScope
|
|
35
|
-
deps: Set<string>
|
|
36
|
-
destroyListeners: ServiceLocatorInstanceDestroyListener[]
|
|
37
|
-
createdAt: number
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface ServiceLocatorInstanceHolderDestroying<Instance> {
|
|
41
|
-
status: ServiceLocatorInstanceHolderStatus.Destroying
|
|
42
|
-
name: string
|
|
43
|
-
instance: Instance | null
|
|
44
|
-
creationPromise: null
|
|
45
|
-
destroyPromise: Promise<void>
|
|
46
|
-
type: InjectableType
|
|
47
|
-
scope: InjectableScope
|
|
48
|
-
deps: Set<string>
|
|
49
|
-
destroyListeners: ServiceLocatorInstanceDestroyListener[]
|
|
50
|
-
createdAt: number
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export interface ServiceLocatorInstanceHolderError {
|
|
54
|
-
status: ServiceLocatorInstanceHolderStatus.Error
|
|
55
|
-
name: string
|
|
56
|
-
instance: Error
|
|
57
|
-
creationPromise: null
|
|
58
|
-
destroyPromise: null
|
|
59
|
-
type: InjectableType
|
|
60
|
-
scope: InjectableScope
|
|
61
|
-
deps: Set<string>
|
|
62
|
-
destroyListeners: ServiceLocatorInstanceDestroyListener[]
|
|
63
|
-
createdAt: number
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export type ServiceLocatorInstanceHolder<Instance = unknown> =
|
|
67
|
-
| ServiceLocatorInstanceHolderCreating<Instance>
|
|
68
|
-
| ServiceLocatorInstanceHolderCreated<Instance>
|
|
69
|
-
| ServiceLocatorInstanceHolderDestroying<Instance>
|
|
70
|
-
| ServiceLocatorInstanceHolderError
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-empty-object-type */
|
|
2
|
-
import type { ServiceLocatorInstanceHolder } from './service-locator-instance-holder.mjs'
|
|
3
|
-
|
|
4
|
-
import { BaseInstanceHolderManager } from './base-instance-holder-manager.mjs'
|
|
5
|
-
import { InjectableScope, InjectableType } from './enums/index.mjs'
|
|
6
|
-
import { DIError, DIErrorCode } from './errors/index.mjs'
|
|
7
|
-
import { ServiceLocatorInstanceHolderStatus } from './service-locator-instance-holder.mjs'
|
|
8
|
-
|
|
9
|
-
export class ServiceLocatorManager extends BaseInstanceHolderManager {
|
|
10
|
-
constructor(logger: Console | null = null) {
|
|
11
|
-
super(logger)
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
get(
|
|
15
|
-
name: string,
|
|
16
|
-
):
|
|
17
|
-
| [DIError, ServiceLocatorInstanceHolder]
|
|
18
|
-
| [DIError]
|
|
19
|
-
| [undefined, ServiceLocatorInstanceHolder] {
|
|
20
|
-
const holder = this._holders.get(name)
|
|
21
|
-
if (holder) {
|
|
22
|
-
if (holder.status === ServiceLocatorInstanceHolderStatus.Destroying) {
|
|
23
|
-
this.logger?.log(
|
|
24
|
-
`[ServiceLocatorManager]#getInstanceHolder() Instance ${holder.name} is destroying`,
|
|
25
|
-
)
|
|
26
|
-
return [DIError.instanceDestroying(holder.name), holder]
|
|
27
|
-
} else if (holder.status === ServiceLocatorInstanceHolderStatus.Error) {
|
|
28
|
-
this.logger?.log(
|
|
29
|
-
`[ServiceLocatorManager]#getInstanceHolder() Instance ${holder.name} is in error state`,
|
|
30
|
-
)
|
|
31
|
-
return [holder.instance as DIError, holder]
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return [undefined, holder]
|
|
35
|
-
} else {
|
|
36
|
-
this.logger?.log(
|
|
37
|
-
`[ServiceLocatorManager]#getInstanceHolder() Instance ${name} not found`,
|
|
38
|
-
)
|
|
39
|
-
return [DIError.instanceNotFound(name)]
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
set(name: string, holder: ServiceLocatorInstanceHolder): 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 BaseInstanceHolderManager
|
|
59
|
-
|
|
60
|
-
// createCreatingHolder method is inherited from BaseInstanceHolderManager
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Creates a new holder with Created status and an actual instance.
|
|
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
|
-
): ServiceLocatorInstanceHolder<Instance> {
|
|
79
|
-
const holder = this.createCreatedHolder(name, instance, type, scope, deps)
|
|
80
|
-
|
|
81
|
-
this._holders.set(name, holder)
|
|
82
|
-
|
|
83
|
-
return holder
|
|
84
|
-
}
|
|
85
|
-
}
|