@navios/di 0.4.2 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +211 -1
- package/coverage/clover.xml +1912 -1277
- package/coverage/coverage-final.json +37 -28
- package/coverage/docs/examples/basic-usage.mts.html +1 -1
- package/coverage/docs/examples/factory-pattern.mts.html +1 -1
- package/coverage/docs/examples/index.html +1 -1
- package/coverage/docs/examples/injection-tokens.mts.html +1 -1
- package/coverage/docs/examples/request-scope-example.mts.html +1 -1
- package/coverage/docs/examples/service-lifecycle.mts.html +1 -1
- package/coverage/index.html +71 -41
- package/coverage/lib/_tsup-dts-rollup.d.mts.html +682 -43
- package/coverage/lib/index.d.mts.html +7 -4
- package/coverage/lib/index.html +5 -5
- package/coverage/lib/testing/index.d.mts.html +91 -0
- package/coverage/lib/testing/index.html +116 -0
- package/coverage/src/base-instance-holder-manager.mts.html +589 -0
- package/coverage/src/container.mts.html +257 -74
- package/coverage/src/decorators/factory.decorator.mts.html +1 -1
- package/coverage/src/decorators/index.html +1 -1
- package/coverage/src/decorators/index.mts.html +1 -1
- package/coverage/src/decorators/injectable.decorator.mts.html +20 -20
- package/coverage/src/enums/index.html +1 -1
- package/coverage/src/enums/index.mts.html +1 -1
- package/coverage/src/enums/injectable-scope.enum.mts.html +1 -1
- package/coverage/src/enums/injectable-type.enum.mts.html +1 -1
- package/coverage/src/errors/di-error.mts.html +292 -0
- package/coverage/src/errors/errors.enum.mts.html +30 -21
- package/coverage/src/errors/factory-not-found.mts.html +31 -22
- package/coverage/src/errors/factory-token-not-resolved.mts.html +29 -26
- package/coverage/src/errors/index.html +56 -41
- package/coverage/src/errors/index.mts.html +15 -9
- package/coverage/src/errors/instance-destroying.mts.html +31 -22
- package/coverage/src/errors/instance-expired.mts.html +31 -22
- package/coverage/src/errors/instance-not-found.mts.html +31 -22
- package/coverage/src/errors/unknown-error.mts.html +31 -43
- package/coverage/src/event-emitter.mts.html +14 -14
- package/coverage/src/factory-context.mts.html +1 -1
- package/coverage/src/index.html +121 -46
- package/coverage/src/index.mts.html +7 -4
- package/coverage/src/injection-token.mts.html +28 -28
- package/coverage/src/injector.mts.html +1 -1
- package/coverage/src/instance-resolver.mts.html +1762 -0
- package/coverage/src/interfaces/factory.interface.mts.html +1 -1
- package/coverage/src/interfaces/index.html +1 -1
- package/coverage/src/interfaces/index.mts.html +1 -1
- package/coverage/src/interfaces/on-service-destroy.interface.mts.html +1 -1
- package/coverage/src/interfaces/on-service-init.interface.mts.html +1 -1
- package/coverage/src/registry.mts.html +28 -28
- package/coverage/src/request-context-holder.mts.html +183 -102
- package/coverage/src/request-context-manager.mts.html +532 -0
- package/coverage/src/service-instantiator.mts.html +49 -49
- package/coverage/src/service-invalidator.mts.html +1372 -0
- package/coverage/src/service-locator-event-bus.mts.html +48 -48
- package/coverage/src/service-locator-instance-holder.mts.html +2 -14
- package/coverage/src/service-locator-manager.mts.html +71 -335
- package/coverage/src/service-locator.mts.html +240 -2328
- package/coverage/src/symbols/index.html +1 -1
- package/coverage/src/symbols/index.mts.html +1 -1
- package/coverage/src/symbols/injectable-token.mts.html +1 -1
- package/coverage/src/testing/index.html +131 -0
- package/coverage/src/testing/index.mts.html +88 -0
- package/coverage/src/testing/test-container.mts.html +445 -0
- package/coverage/src/token-processor.mts.html +607 -0
- package/coverage/src/utils/defer.mts.html +28 -214
- package/coverage/src/utils/get-injectable-token.mts.html +7 -7
- package/coverage/src/utils/get-injectors.mts.html +99 -99
- package/coverage/src/utils/index.html +15 -15
- package/coverage/src/utils/index.mts.html +4 -7
- package/coverage/src/utils/types.mts.html +1 -1
- package/docs/injectable.md +51 -11
- package/docs/scopes.md +63 -29
- package/lib/_tsup-dts-rollup.d.mts +376 -213
- package/lib/_tsup-dts-rollup.d.ts +376 -213
- package/lib/{chunk-3NLYPYBY.mjs → chunk-2M576LCC.mjs} +1024 -608
- package/lib/chunk-2M576LCC.mjs.map +1 -0
- package/lib/index.d.mts +6 -4
- package/lib/index.d.ts +6 -4
- package/lib/index.js +1189 -773
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +2 -2
- package/lib/testing/index.js +1261 -843
- package/lib/testing/index.js.map +1 -1
- package/lib/testing/index.mjs +1 -1
- package/package.json +4 -4
- package/src/__tests__/container.spec.mts +47 -13
- package/src/__tests__/errors.spec.mts +53 -27
- package/src/__tests__/injectable.spec.mts +73 -0
- package/src/__tests__/request-scope.spec.mts +0 -2
- package/src/__tests__/service-instantiator.spec.mts +1 -0
- package/src/__tests__/service-locator-manager.spec.mts +12 -82
- package/src/__tests__/service-locator.spec.mts +1009 -1
- package/src/__type-tests__/inject.spec-d.mts +30 -7
- package/src/__type-tests__/injectable.spec-d.mts +76 -37
- package/src/base-instance-holder-manager.mts +2 -9
- package/src/container.mts +61 -9
- package/src/decorators/injectable.decorator.mts +29 -5
- package/src/errors/di-error.mts +69 -0
- package/src/errors/index.mts +9 -7
- package/src/injection-token.mts +1 -0
- package/src/injector.mts +2 -0
- package/src/instance-resolver.mts +559 -0
- package/src/request-context-holder.mts +0 -2
- package/src/request-context-manager.mts +149 -0
- package/src/service-invalidator.mts +429 -0
- package/src/service-locator-event-bus.mts +1 -1
- package/src/service-locator-instance-holder.mts +0 -4
- package/src/service-locator-manager.mts +10 -40
- package/src/service-locator.mts +86 -782
- package/src/token-processor.mts +174 -0
- package/src/utils/get-injectors.mts +161 -24
- package/src/utils/index.mts +0 -1
- package/src/utils/types.mts +12 -8
- package/lib/chunk-3NLYPYBY.mjs.map +0 -1
- package/src/__tests__/defer.spec.mts +0 -166
- package/src/errors/errors.enum.mts +0 -8
- package/src/errors/factory-not-found.mts +0 -8
- package/src/errors/factory-token-not-resolved.mts +0 -10
- package/src/errors/instance-destroying.mts +0 -8
- package/src/errors/instance-expired.mts +0 -8
- package/src/errors/instance-not-found.mts +0 -8
- package/src/errors/unknown-error.mts +0 -15
- package/src/utils/defer.mts +0 -73
package/lib/testing/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
var __create = Object.create;
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
5
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
-
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
|
|
6
|
+
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
|
|
7
7
|
var __typeError = (msg) => {
|
|
8
8
|
throw TypeError(msg);
|
|
9
9
|
};
|
|
@@ -143,12 +143,13 @@ var Registry = class {
|
|
|
143
143
|
var globalRegistry = new Registry();
|
|
144
144
|
|
|
145
145
|
// src/symbols/injectable-token.mts
|
|
146
|
-
var InjectableTokenMeta = Symbol.for("InjectableTokenMeta");
|
|
146
|
+
var InjectableTokenMeta = /* @__PURE__ */ Symbol.for("InjectableTokenMeta");
|
|
147
147
|
|
|
148
148
|
// src/decorators/injectable.decorator.mts
|
|
149
149
|
function Injectable({
|
|
150
150
|
scope = "Singleton" /* Singleton */,
|
|
151
151
|
token,
|
|
152
|
+
schema,
|
|
152
153
|
registry = globalRegistry
|
|
153
154
|
} = {}) {
|
|
154
155
|
return (target, context) => {
|
|
@@ -157,7 +158,12 @@ function Injectable({
|
|
|
157
158
|
"[ServiceLocator] @Injectable decorator can only be used on classes."
|
|
158
159
|
);
|
|
159
160
|
}
|
|
160
|
-
|
|
161
|
+
if (schema && token) {
|
|
162
|
+
throw new Error(
|
|
163
|
+
"[ServiceLocator] @Injectable decorator cannot have both a token and a schema"
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
let injectableToken = token ?? InjectionToken.create(target, schema);
|
|
161
167
|
registry.set(injectableToken, scope, target, "Class" /* Class */);
|
|
162
168
|
target[InjectableTokenMeta] = injectableToken;
|
|
163
169
|
return target;
|
|
@@ -182,29 +188,58 @@ function getInjectors() {
|
|
|
182
188
|
}
|
|
183
189
|
let promiseCollector = null;
|
|
184
190
|
let injectState = null;
|
|
185
|
-
function
|
|
191
|
+
function getRequest(token, args) {
|
|
186
192
|
if (!injectState) {
|
|
187
193
|
throw new Error(
|
|
188
|
-
"[Injector] Trying to
|
|
194
|
+
"[Injector] Trying to make a request outside of a injectable context"
|
|
189
195
|
);
|
|
190
196
|
}
|
|
191
197
|
if (injectState.isFrozen) {
|
|
192
198
|
const idx = injectState.currentIndex++;
|
|
193
|
-
const
|
|
194
|
-
if (
|
|
199
|
+
const request2 = injectState.requests[idx];
|
|
200
|
+
if (request2.token !== token) {
|
|
195
201
|
throw new Error(
|
|
196
|
-
`[Injector] Wrong token order. Expected ${
|
|
202
|
+
`[Injector] Wrong token order. Expected ${request2.token.toString()} but got ${token.toString()}`
|
|
197
203
|
);
|
|
198
204
|
}
|
|
199
|
-
return
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
+
return request2;
|
|
206
|
+
}
|
|
207
|
+
let result = null;
|
|
208
|
+
let error = null;
|
|
209
|
+
const promise = getFactoryContext().inject(token, args).then((r) => {
|
|
210
|
+
result = r;
|
|
211
|
+
return r;
|
|
212
|
+
}).catch((e) => {
|
|
213
|
+
error = e;
|
|
205
214
|
});
|
|
215
|
+
const request = {
|
|
216
|
+
token,
|
|
217
|
+
promise,
|
|
218
|
+
get result() {
|
|
219
|
+
return result;
|
|
220
|
+
},
|
|
221
|
+
get error() {
|
|
222
|
+
return error;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
injectState.requests.push(request);
|
|
206
226
|
injectState.currentIndex++;
|
|
207
|
-
return
|
|
227
|
+
return request;
|
|
228
|
+
}
|
|
229
|
+
function asyncInject2(token, args) {
|
|
230
|
+
if (!injectState) {
|
|
231
|
+
throw new Error(
|
|
232
|
+
"[Injector] Trying to access inject outside of a injectable context"
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
const realToken = token[InjectableTokenMeta] ?? token;
|
|
236
|
+
const request = getRequest(realToken, args);
|
|
237
|
+
return request.promise.then((result) => {
|
|
238
|
+
if (request.error) {
|
|
239
|
+
throw request.error;
|
|
240
|
+
}
|
|
241
|
+
return result;
|
|
242
|
+
});
|
|
208
243
|
}
|
|
209
244
|
function wrapSyncInit2(cb) {
|
|
210
245
|
return (previousState) => {
|
|
@@ -234,23 +269,31 @@ function getInjectors() {
|
|
|
234
269
|
}
|
|
235
270
|
function inject2(token, args) {
|
|
236
271
|
const realToken = token[InjectableTokenMeta] ?? token;
|
|
272
|
+
if (!injectState) {
|
|
273
|
+
throw new Error(
|
|
274
|
+
"[Injector] Trying to access inject outside of a injectable context"
|
|
275
|
+
);
|
|
276
|
+
}
|
|
237
277
|
const instance = getFactoryContext().locator.getSyncInstance(
|
|
238
278
|
realToken,
|
|
239
279
|
args
|
|
240
280
|
);
|
|
241
281
|
if (!instance) {
|
|
282
|
+
const request = getRequest(realToken, args);
|
|
283
|
+
if (request.error) {
|
|
284
|
+
throw request.error;
|
|
285
|
+
} else if (request.result) {
|
|
286
|
+
return request.result;
|
|
287
|
+
}
|
|
242
288
|
if (promiseCollector) {
|
|
243
|
-
|
|
244
|
-
promiseCollector(promise);
|
|
245
|
-
} else {
|
|
246
|
-
throw new Error(`[Injector] Cannot initiate ${realToken.toString()}`);
|
|
289
|
+
promiseCollector(request.promise);
|
|
247
290
|
}
|
|
248
291
|
return new Proxy(
|
|
249
292
|
{},
|
|
250
293
|
{
|
|
251
294
|
get() {
|
|
252
295
|
throw new Error(
|
|
253
|
-
`[Injector] Trying to access ${realToken.toString()} before it's initialized, please
|
|
296
|
+
`[Injector] Trying to access ${realToken.toString()} before it's initialized, please move the code to a onServiceInit method`
|
|
254
297
|
);
|
|
255
298
|
}
|
|
256
299
|
}
|
|
@@ -258,9 +301,17 @@ function getInjectors() {
|
|
|
258
301
|
}
|
|
259
302
|
return instance;
|
|
260
303
|
}
|
|
304
|
+
function optional2(token, args) {
|
|
305
|
+
try {
|
|
306
|
+
return inject2(token, args);
|
|
307
|
+
} catch {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
261
311
|
const injectors = {
|
|
262
312
|
asyncInject: asyncInject2,
|
|
263
313
|
inject: inject2,
|
|
314
|
+
optional: optional2,
|
|
264
315
|
wrapSyncInit: wrapSyncInit2,
|
|
265
316
|
provideFactoryContext: provideFactoryContext2
|
|
266
317
|
};
|
|
@@ -278,235 +329,504 @@ function getInjectableToken(target) {
|
|
|
278
329
|
return token;
|
|
279
330
|
}
|
|
280
331
|
|
|
281
|
-
// src/utils/defer.mts
|
|
282
|
-
var Deferred = class {
|
|
283
|
-
promise;
|
|
284
|
-
_resolve;
|
|
285
|
-
_reject;
|
|
286
|
-
_isResolved = false;
|
|
287
|
-
_isRejected = false;
|
|
288
|
-
constructor() {
|
|
289
|
-
this.promise = new Promise((resolve, reject) => {
|
|
290
|
-
this._resolve = resolve;
|
|
291
|
-
this._reject = reject;
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
/**
|
|
295
|
-
* Resolves the deferred promise with the given value.
|
|
296
|
-
* @param value The value to resolve with
|
|
297
|
-
* @throws Error if the promise has already been resolved or rejected
|
|
298
|
-
*/
|
|
299
|
-
resolve(value) {
|
|
300
|
-
if (this._isResolved || this._isRejected) {
|
|
301
|
-
throw new Error("Deferred promise has already been resolved or rejected");
|
|
302
|
-
}
|
|
303
|
-
this._isResolved = true;
|
|
304
|
-
this._resolve(value);
|
|
305
|
-
}
|
|
306
|
-
/**
|
|
307
|
-
* Rejects the deferred promise with the given reason.
|
|
308
|
-
* @param reason The reason for rejection
|
|
309
|
-
* @throws Error if the promise has already been resolved or rejected
|
|
310
|
-
*/
|
|
311
|
-
reject(reason) {
|
|
312
|
-
if (this._isResolved || this._isRejected) {
|
|
313
|
-
throw new Error("Deferred promise has already been resolved or rejected");
|
|
314
|
-
}
|
|
315
|
-
this._isRejected = true;
|
|
316
|
-
this._reject(reason);
|
|
317
|
-
}
|
|
318
|
-
/**
|
|
319
|
-
* Returns true if the promise has been resolved.
|
|
320
|
-
*/
|
|
321
|
-
get isResolved() {
|
|
322
|
-
return this._isResolved;
|
|
323
|
-
}
|
|
324
|
-
/**
|
|
325
|
-
* Returns true if the promise has been rejected.
|
|
326
|
-
*/
|
|
327
|
-
get isRejected() {
|
|
328
|
-
return this._isRejected;
|
|
329
|
-
}
|
|
330
|
-
/**
|
|
331
|
-
* Returns true if the promise has been settled (resolved or rejected).
|
|
332
|
-
*/
|
|
333
|
-
get isSettled() {
|
|
334
|
-
return this._isResolved || this._isRejected;
|
|
335
|
-
}
|
|
336
|
-
};
|
|
337
|
-
function createDeferred() {
|
|
338
|
-
return new Deferred();
|
|
339
|
-
}
|
|
340
|
-
|
|
341
332
|
// src/injector.mts
|
|
342
333
|
var defaultInjectors = getInjectors();
|
|
343
334
|
|
|
344
|
-
// src/errors/
|
|
345
|
-
var
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
335
|
+
// src/errors/di-error.mts
|
|
336
|
+
var DIError = class _DIError extends Error {
|
|
337
|
+
code;
|
|
338
|
+
context;
|
|
339
|
+
constructor(code, message, context) {
|
|
340
|
+
super(message);
|
|
341
|
+
this.name = "DIError";
|
|
342
|
+
this.code = code;
|
|
343
|
+
this.context = context;
|
|
344
|
+
}
|
|
345
|
+
// Static factory methods for common error types
|
|
346
|
+
static factoryNotFound(name) {
|
|
347
|
+
return new _DIError(
|
|
348
|
+
"FactoryNotFound" /* FactoryNotFound */,
|
|
349
|
+
`Factory ${name} not found`,
|
|
350
|
+
{ name }
|
|
351
|
+
);
|
|
358
352
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
this.name = name;
|
|
353
|
+
static factoryTokenNotResolved(token) {
|
|
354
|
+
return new _DIError(
|
|
355
|
+
"FactoryTokenNotResolved" /* FactoryTokenNotResolved */,
|
|
356
|
+
`Factory token not resolved: ${token?.toString() ?? "unknown"}`,
|
|
357
|
+
{ token }
|
|
358
|
+
);
|
|
366
359
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
super(`Instance ${name} expired`);
|
|
374
|
-
this.name = name;
|
|
360
|
+
static instanceNotFound(name) {
|
|
361
|
+
return new _DIError(
|
|
362
|
+
"InstanceNotFound" /* InstanceNotFound */,
|
|
363
|
+
`Instance ${name} not found`,
|
|
364
|
+
{ name }
|
|
365
|
+
);
|
|
375
366
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
super(`Instance ${name} not found`);
|
|
383
|
-
this.name = name;
|
|
367
|
+
static instanceDestroying(name) {
|
|
368
|
+
return new _DIError(
|
|
369
|
+
"InstanceDestroying" /* InstanceDestroying */,
|
|
370
|
+
`Instance ${name} destroying`,
|
|
371
|
+
{ name }
|
|
372
|
+
);
|
|
384
373
|
}
|
|
385
|
-
|
|
386
|
-
};
|
|
387
|
-
|
|
388
|
-
// src/errors/unknown-error.mts
|
|
389
|
-
var UnknownError = class extends Error {
|
|
390
|
-
code = "UnknownError" /* UnknownError */;
|
|
391
|
-
parent;
|
|
392
|
-
constructor(message) {
|
|
374
|
+
static unknown(message, context) {
|
|
393
375
|
if (message instanceof Error) {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
376
|
+
return new _DIError("UnknownError" /* UnknownError */, message.message, {
|
|
377
|
+
...context,
|
|
378
|
+
parent: message
|
|
379
|
+
});
|
|
397
380
|
}
|
|
398
|
-
|
|
381
|
+
return new _DIError("UnknownError" /* UnknownError */, message, context);
|
|
399
382
|
}
|
|
400
383
|
};
|
|
401
384
|
|
|
402
|
-
// src/
|
|
403
|
-
var
|
|
404
|
-
constructor(logger = null) {
|
|
385
|
+
// src/instance-resolver.mts
|
|
386
|
+
var InstanceResolver = class {
|
|
387
|
+
constructor(registry, manager, serviceInstantiator, tokenProcessor, logger = null, serviceLocator) {
|
|
388
|
+
this.registry = registry;
|
|
389
|
+
this.manager = manager;
|
|
390
|
+
this.serviceInstantiator = serviceInstantiator;
|
|
391
|
+
this.tokenProcessor = tokenProcessor;
|
|
405
392
|
this.logger = logger;
|
|
406
|
-
this.
|
|
407
|
-
}
|
|
408
|
-
_holders;
|
|
409
|
-
/**
|
|
410
|
-
* Protected getter for accessing the holders map from subclasses.
|
|
411
|
-
*/
|
|
412
|
-
get holders() {
|
|
413
|
-
return this._holders;
|
|
393
|
+
this.serviceLocator = serviceLocator;
|
|
414
394
|
}
|
|
415
395
|
/**
|
|
416
|
-
*
|
|
417
|
-
* @param name The name of the holder to delete
|
|
418
|
-
* @returns true if the holder was deleted, false if it didn't exist
|
|
396
|
+
* Resolves an instance for the given token and arguments.
|
|
419
397
|
*/
|
|
420
|
-
|
|
421
|
-
|
|
398
|
+
async resolveInstance(token, args, requestContext) {
|
|
399
|
+
const [err, data] = await this.resolveTokenAndPrepareInstanceName(
|
|
400
|
+
token,
|
|
401
|
+
args
|
|
402
|
+
);
|
|
403
|
+
if (err) {
|
|
404
|
+
return [err];
|
|
405
|
+
}
|
|
406
|
+
const {
|
|
407
|
+
instanceName,
|
|
408
|
+
validatedArgs,
|
|
409
|
+
realToken
|
|
410
|
+
} = data;
|
|
411
|
+
const [error, holder] = await this.retrieveOrCreateInstanceByInstanceName(
|
|
412
|
+
instanceName,
|
|
413
|
+
realToken,
|
|
414
|
+
validatedArgs,
|
|
415
|
+
requestContext
|
|
416
|
+
);
|
|
417
|
+
if (error) {
|
|
418
|
+
return [error];
|
|
419
|
+
}
|
|
420
|
+
return [void 0, holder.instance];
|
|
422
421
|
}
|
|
423
422
|
/**
|
|
424
|
-
*
|
|
425
|
-
* @param predicate Function to test each holder
|
|
426
|
-
* @returns A new Map containing only the holders that match the predicate
|
|
423
|
+
* Gets a synchronous instance (for sync operations).
|
|
427
424
|
*/
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
425
|
+
getSyncInstance(token, args, currentRequestContext) {
|
|
426
|
+
const [err, { actualToken, validatedArgs }] = this.tokenProcessor.validateAndResolveTokenArgs(token, args);
|
|
427
|
+
if (err) {
|
|
428
|
+
return null;
|
|
429
|
+
}
|
|
430
|
+
const instanceName = this.tokenProcessor.generateInstanceName(
|
|
431
|
+
actualToken,
|
|
432
|
+
validatedArgs
|
|
431
433
|
);
|
|
434
|
+
if (currentRequestContext) {
|
|
435
|
+
const requestHolder = currentRequestContext.get(instanceName);
|
|
436
|
+
if (requestHolder) {
|
|
437
|
+
return requestHolder.instance;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
const [error, holder] = this.manager.get(instanceName);
|
|
441
|
+
if (error) {
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
return holder.instance;
|
|
432
445
|
}
|
|
433
446
|
/**
|
|
434
|
-
*
|
|
447
|
+
* Internal method to resolve token args and create instance name.
|
|
448
|
+
* Handles factory token resolution and validation.
|
|
435
449
|
*/
|
|
436
|
-
|
|
437
|
-
this.
|
|
450
|
+
async resolveTokenAndPrepareInstanceName(token, args) {
|
|
451
|
+
const [err, { actualToken, validatedArgs }] = this.tokenProcessor.validateAndResolveTokenArgs(token, args);
|
|
452
|
+
if (err instanceof DIError && err.code === "UnknownError" /* UnknownError */) {
|
|
453
|
+
return [err];
|
|
454
|
+
} else if (err instanceof DIError && err.code === "FactoryTokenNotResolved" /* FactoryTokenNotResolved */ && actualToken instanceof FactoryInjectionToken) {
|
|
455
|
+
this.logger?.log(
|
|
456
|
+
`[InstanceResolver]#resolveTokenAndPrepareInstanceName() Factory token not resolved, resolving it`
|
|
457
|
+
);
|
|
458
|
+
await actualToken.resolve(this.createFactoryContext());
|
|
459
|
+
return this.resolveTokenAndPrepareInstanceName(token);
|
|
460
|
+
}
|
|
461
|
+
const instanceName = this.tokenProcessor.generateInstanceName(
|
|
462
|
+
actualToken,
|
|
463
|
+
validatedArgs
|
|
464
|
+
);
|
|
465
|
+
const realToken = actualToken instanceof BoundInjectionToken || actualToken instanceof FactoryInjectionToken ? actualToken.token : actualToken;
|
|
466
|
+
return [void 0, { instanceName, validatedArgs, actualToken, realToken }];
|
|
438
467
|
}
|
|
439
468
|
/**
|
|
440
|
-
* Gets the
|
|
469
|
+
* Gets an instance by its instance name, handling all the logic after instance name creation.
|
|
441
470
|
*/
|
|
442
|
-
|
|
443
|
-
|
|
471
|
+
async retrieveOrCreateInstanceByInstanceName(instanceName, realToken, realArgs, requestContext) {
|
|
472
|
+
const existingHolder = await this.tryGetExistingInstance(
|
|
473
|
+
instanceName,
|
|
474
|
+
realToken,
|
|
475
|
+
requestContext
|
|
476
|
+
);
|
|
477
|
+
if (existingHolder) {
|
|
478
|
+
return existingHolder;
|
|
479
|
+
}
|
|
480
|
+
const result = await this.createNewInstance(
|
|
481
|
+
instanceName,
|
|
482
|
+
realToken,
|
|
483
|
+
realArgs,
|
|
484
|
+
requestContext
|
|
485
|
+
);
|
|
486
|
+
if (result[0]) {
|
|
487
|
+
return [result[0]];
|
|
488
|
+
}
|
|
489
|
+
const [, holder] = result;
|
|
490
|
+
return this.waitForInstanceReady(holder);
|
|
444
491
|
}
|
|
445
492
|
/**
|
|
446
|
-
*
|
|
447
|
-
*
|
|
448
|
-
* @param name The name of the instance
|
|
449
|
-
* @param type The injectable type
|
|
450
|
-
* @param scope The injectable scope
|
|
451
|
-
* @param deps Optional set of dependencies
|
|
452
|
-
* @param ttl Optional time-to-live in milliseconds (defaults to Infinity)
|
|
453
|
-
* @returns A tuple containing the deferred promise and the holder
|
|
493
|
+
* Attempts to retrieve an existing instance, handling request-scoped and singleton instances.
|
|
494
|
+
* Returns null if no instance exists and a new one should be created.
|
|
454
495
|
*/
|
|
455
|
-
|
|
456
|
-
const
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
deps,
|
|
466
|
-
destroyListeners: [],
|
|
467
|
-
createdAt: Date.now(),
|
|
468
|
-
ttl
|
|
469
|
-
};
|
|
470
|
-
return [deferred, holder];
|
|
496
|
+
async tryGetExistingInstance(instanceName, realToken, requestContext) {
|
|
497
|
+
const requestResult = await this.tryGetRequestScopedInstance(
|
|
498
|
+
instanceName,
|
|
499
|
+
realToken,
|
|
500
|
+
requestContext
|
|
501
|
+
);
|
|
502
|
+
if (requestResult) {
|
|
503
|
+
return requestResult;
|
|
504
|
+
}
|
|
505
|
+
return this.tryGetSingletonInstance(instanceName);
|
|
471
506
|
}
|
|
472
507
|
/**
|
|
473
|
-
*
|
|
474
|
-
* This is useful for creating holders that already have their instance ready.
|
|
475
|
-
* @param name The name of the instance
|
|
476
|
-
* @param instance The actual instance to store
|
|
477
|
-
* @param type The injectable type
|
|
478
|
-
* @param scope The injectable scope
|
|
479
|
-
* @param deps Optional set of dependencies
|
|
480
|
-
* @param ttl Optional time-to-live in milliseconds (defaults to Infinity)
|
|
481
|
-
* @returns The created holder
|
|
508
|
+
* Attempts to get a request-scoped instance if applicable.
|
|
482
509
|
*/
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
510
|
+
async tryGetRequestScopedInstance(instanceName, realToken, requestContext) {
|
|
511
|
+
if (!this.registry.has(realToken)) {
|
|
512
|
+
return null;
|
|
513
|
+
}
|
|
514
|
+
const record = this.registry.get(realToken);
|
|
515
|
+
if (record.scope !== "Request" /* Request */) {
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
if (!requestContext) {
|
|
519
|
+
this.logger?.log(
|
|
520
|
+
`[InstanceResolver] No current request context available for request-scoped service ${instanceName}`
|
|
521
|
+
);
|
|
522
|
+
return [
|
|
523
|
+
DIError.unknown(
|
|
524
|
+
`No current request context available for request-scoped service ${instanceName}`
|
|
525
|
+
)
|
|
526
|
+
];
|
|
527
|
+
}
|
|
528
|
+
const requestHolder = requestContext.get(instanceName);
|
|
529
|
+
if (!requestHolder) {
|
|
530
|
+
return null;
|
|
531
|
+
}
|
|
532
|
+
return this.waitForInstanceReady(requestHolder);
|
|
498
533
|
}
|
|
499
534
|
/**
|
|
500
|
-
*
|
|
535
|
+
* Attempts to get a singleton instance from the manager.
|
|
501
536
|
*/
|
|
502
|
-
|
|
503
|
-
|
|
537
|
+
async tryGetSingletonInstance(instanceName) {
|
|
538
|
+
const [error, holder] = this.manager.get(instanceName);
|
|
539
|
+
if (!error) {
|
|
540
|
+
return this.waitForInstanceReady(holder);
|
|
541
|
+
}
|
|
542
|
+
switch (error.code) {
|
|
543
|
+
case "InstanceDestroying" /* InstanceDestroying */:
|
|
544
|
+
this.logger?.log(
|
|
545
|
+
`[InstanceResolver] Instance ${instanceName} is being destroyed, waiting...`
|
|
546
|
+
);
|
|
547
|
+
await holder?.destroyPromise;
|
|
548
|
+
return this.tryGetSingletonInstance(instanceName);
|
|
549
|
+
case "InstanceNotFound" /* InstanceNotFound */:
|
|
550
|
+
return null;
|
|
551
|
+
// Instance doesn't exist, should create new one
|
|
552
|
+
default:
|
|
553
|
+
return [error];
|
|
554
|
+
}
|
|
504
555
|
}
|
|
505
556
|
/**
|
|
506
|
-
*
|
|
557
|
+
* Waits for an instance holder to be ready and returns the appropriate result.
|
|
507
558
|
*/
|
|
508
|
-
|
|
509
|
-
|
|
559
|
+
async waitForInstanceReady(holder) {
|
|
560
|
+
switch (holder.status) {
|
|
561
|
+
case "creating" /* Creating */:
|
|
562
|
+
await holder.creationPromise;
|
|
563
|
+
return this.waitForInstanceReady(holder);
|
|
564
|
+
case "destroying" /* Destroying */:
|
|
565
|
+
return [DIError.instanceDestroying(holder.name)];
|
|
566
|
+
case "error" /* Error */:
|
|
567
|
+
return [holder.instance];
|
|
568
|
+
case "created" /* Created */:
|
|
569
|
+
return [void 0, holder];
|
|
570
|
+
default:
|
|
571
|
+
return [DIError.instanceNotFound("unknown")];
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Creates a new instance for the given token and arguments.
|
|
576
|
+
*/
|
|
577
|
+
async createNewInstance(instanceName, realToken, args, requestContext) {
|
|
578
|
+
this.logger?.log(
|
|
579
|
+
`[InstanceResolver]#createNewInstance() Creating instance for ${instanceName}`
|
|
580
|
+
);
|
|
581
|
+
if (this.registry.has(realToken)) {
|
|
582
|
+
return this.instantiateServiceFromRegistry(
|
|
583
|
+
instanceName,
|
|
584
|
+
realToken,
|
|
585
|
+
args,
|
|
586
|
+
requestContext
|
|
587
|
+
);
|
|
588
|
+
} else {
|
|
589
|
+
return [DIError.factoryNotFound(realToken.name.toString())];
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Instantiates a service from the registry using the service instantiator.
|
|
594
|
+
*/
|
|
595
|
+
instantiateServiceFromRegistry(instanceName, token, args, requestContext) {
|
|
596
|
+
this.logger?.log(
|
|
597
|
+
`[InstanceResolver]#instantiateServiceFromRegistry(): Creating instance for ${instanceName} from abstract factory`
|
|
598
|
+
);
|
|
599
|
+
const ctx = this.createFactoryContext();
|
|
600
|
+
let record = this.registry.get(token);
|
|
601
|
+
let { scope, type } = record;
|
|
602
|
+
const [deferred, holder] = this.manager.createCreatingHolder(
|
|
603
|
+
instanceName,
|
|
604
|
+
type,
|
|
605
|
+
scope,
|
|
606
|
+
ctx.deps
|
|
607
|
+
);
|
|
608
|
+
this.serviceInstantiator.instantiateService(ctx, record, args).then(async ([error, instance]) => {
|
|
609
|
+
await this.handleInstantiationResult(
|
|
610
|
+
instanceName,
|
|
611
|
+
holder,
|
|
612
|
+
ctx,
|
|
613
|
+
deferred,
|
|
614
|
+
scope,
|
|
615
|
+
error,
|
|
616
|
+
instance,
|
|
617
|
+
requestContext
|
|
618
|
+
);
|
|
619
|
+
}).catch(async (error) => {
|
|
620
|
+
await this.handleInstantiationError(
|
|
621
|
+
instanceName,
|
|
622
|
+
holder,
|
|
623
|
+
deferred,
|
|
624
|
+
scope,
|
|
625
|
+
error
|
|
626
|
+
);
|
|
627
|
+
});
|
|
628
|
+
this.storeInstanceByScope(scope, instanceName, holder, requestContext);
|
|
629
|
+
return [void 0, holder];
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Handles the result of service instantiation.
|
|
633
|
+
*/
|
|
634
|
+
async handleInstantiationResult(instanceName, holder, ctx, deferred, scope, error, instance, _requestContext) {
|
|
635
|
+
holder.destroyListeners = ctx.getDestroyListeners();
|
|
636
|
+
holder.creationPromise = null;
|
|
637
|
+
if (error) {
|
|
638
|
+
await this.handleInstantiationError(
|
|
639
|
+
instanceName,
|
|
640
|
+
holder,
|
|
641
|
+
deferred,
|
|
642
|
+
scope,
|
|
643
|
+
error
|
|
644
|
+
);
|
|
645
|
+
} else {
|
|
646
|
+
await this.handleInstantiationSuccess(
|
|
647
|
+
instanceName,
|
|
648
|
+
holder,
|
|
649
|
+
ctx,
|
|
650
|
+
deferred,
|
|
651
|
+
instance
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Handles successful service instantiation.
|
|
657
|
+
*/
|
|
658
|
+
async handleInstantiationSuccess(instanceName, holder, ctx, deferred, instance) {
|
|
659
|
+
holder.instance = instance;
|
|
660
|
+
holder.status = "created" /* Created */;
|
|
661
|
+
if (ctx.deps.size > 0) {
|
|
662
|
+
ctx.deps.forEach((dependency) => {
|
|
663
|
+
holder.destroyListeners.push(
|
|
664
|
+
this.serviceLocator.getEventBus().on(dependency, "destroy", () => {
|
|
665
|
+
this.logger?.log(
|
|
666
|
+
`[InstanceResolver] Dependency ${dependency} destroyed, invalidating ${instanceName}`
|
|
667
|
+
);
|
|
668
|
+
this.serviceLocator.getServiceInvalidator().invalidate(instanceName);
|
|
669
|
+
})
|
|
670
|
+
);
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
this.logger?.log(
|
|
674
|
+
`[InstanceResolver] Instance ${instanceName} created successfully`
|
|
675
|
+
);
|
|
676
|
+
deferred.resolve([void 0, instance]);
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Handles service instantiation errors.
|
|
680
|
+
*/
|
|
681
|
+
async handleInstantiationError(instanceName, holder, deferred, scope, error) {
|
|
682
|
+
this.logger?.error(
|
|
683
|
+
`[InstanceResolver] Error creating instance for ${instanceName}`,
|
|
684
|
+
error
|
|
685
|
+
);
|
|
686
|
+
holder.status = "error" /* Error */;
|
|
687
|
+
holder.instance = error;
|
|
688
|
+
holder.creationPromise = null;
|
|
689
|
+
if (scope === "Singleton" /* Singleton */) {
|
|
690
|
+
this.logger?.log(
|
|
691
|
+
`[InstanceResolver] Singleton ${instanceName} failed, will be invalidated`
|
|
692
|
+
);
|
|
693
|
+
this.serviceLocator.getServiceInvalidator().invalidate(instanceName);
|
|
694
|
+
}
|
|
695
|
+
deferred.reject(error);
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Stores an instance holder based on its scope.
|
|
699
|
+
*/
|
|
700
|
+
storeInstanceByScope(scope, instanceName, holder, requestContext) {
|
|
701
|
+
switch (scope) {
|
|
702
|
+
case "Singleton" /* Singleton */:
|
|
703
|
+
this.logger?.debug(
|
|
704
|
+
`[InstanceResolver] Setting singleton instance for ${instanceName}`
|
|
705
|
+
);
|
|
706
|
+
this.manager.set(instanceName, holder);
|
|
707
|
+
break;
|
|
708
|
+
case "Request" /* Request */:
|
|
709
|
+
if (requestContext) {
|
|
710
|
+
this.logger?.debug(
|
|
711
|
+
`[InstanceResolver] Setting request-scoped instance for ${instanceName}`
|
|
712
|
+
);
|
|
713
|
+
requestContext.addInstance(instanceName, holder.instance, holder);
|
|
714
|
+
}
|
|
715
|
+
break;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Creates a factory context for dependency injection during service instantiation.
|
|
720
|
+
*/
|
|
721
|
+
createFactoryContext() {
|
|
722
|
+
return this.tokenProcessor.createFactoryContext(this.serviceLocator);
|
|
723
|
+
}
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
// src/base-instance-holder-manager.mts
|
|
727
|
+
var BaseInstanceHolderManager = class {
|
|
728
|
+
constructor(logger = null) {
|
|
729
|
+
this.logger = logger;
|
|
730
|
+
this._holders = /* @__PURE__ */ new Map();
|
|
731
|
+
}
|
|
732
|
+
_holders;
|
|
733
|
+
/**
|
|
734
|
+
* Protected getter for accessing the holders map from subclasses.
|
|
735
|
+
*/
|
|
736
|
+
get holders() {
|
|
737
|
+
return this._holders;
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Deletes a holder by name.
|
|
741
|
+
* @param name The name of the holder to delete
|
|
742
|
+
* @returns true if the holder was deleted, false if it didn't exist
|
|
743
|
+
*/
|
|
744
|
+
delete(name) {
|
|
745
|
+
return this._holders.delete(name);
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Filters holders based on a predicate function.
|
|
749
|
+
* @param predicate Function to test each holder
|
|
750
|
+
* @returns A new Map containing only the holders that match the predicate
|
|
751
|
+
*/
|
|
752
|
+
filter(predicate) {
|
|
753
|
+
return new Map(
|
|
754
|
+
[...this._holders].filter(([key, value]) => predicate(value, key))
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Clears all holders from this manager.
|
|
759
|
+
*/
|
|
760
|
+
clear() {
|
|
761
|
+
this._holders.clear();
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Gets the number of holders currently managed.
|
|
765
|
+
*/
|
|
766
|
+
size() {
|
|
767
|
+
return this._holders.size;
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Creates a new holder with Creating status and a deferred creation promise.
|
|
771
|
+
* This is useful for creating placeholder holders that can be fulfilled later.
|
|
772
|
+
* @param name The name of the instance
|
|
773
|
+
* @param type The injectable type
|
|
774
|
+
* @param scope The injectable scope
|
|
775
|
+
* @param deps Optional set of dependencies
|
|
776
|
+
* @returns A tuple containing the deferred promise and the holder
|
|
777
|
+
*/
|
|
778
|
+
createCreatingHolder(name, type, scope, deps = /* @__PURE__ */ new Set()) {
|
|
779
|
+
const deferred = Promise.withResolvers();
|
|
780
|
+
const holder = {
|
|
781
|
+
status: "creating" /* Creating */,
|
|
782
|
+
name,
|
|
783
|
+
instance: null,
|
|
784
|
+
creationPromise: deferred.promise,
|
|
785
|
+
destroyPromise: null,
|
|
786
|
+
type,
|
|
787
|
+
scope,
|
|
788
|
+
deps,
|
|
789
|
+
destroyListeners: [],
|
|
790
|
+
createdAt: Date.now()
|
|
791
|
+
};
|
|
792
|
+
return [deferred, holder];
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Creates a new holder with Created status and an actual instance.
|
|
796
|
+
* This is useful for creating holders that already have their instance ready.
|
|
797
|
+
* @param name The name of the instance
|
|
798
|
+
* @param instance The actual instance to store
|
|
799
|
+
* @param type The injectable type
|
|
800
|
+
* @param scope The injectable scope
|
|
801
|
+
* @param deps Optional set of dependencies
|
|
802
|
+
* @returns The created holder
|
|
803
|
+
*/
|
|
804
|
+
createCreatedHolder(name, instance, type, scope, deps = /* @__PURE__ */ new Set()) {
|
|
805
|
+
const holder = {
|
|
806
|
+
status: "created" /* Created */,
|
|
807
|
+
name,
|
|
808
|
+
instance,
|
|
809
|
+
creationPromise: null,
|
|
810
|
+
destroyPromise: null,
|
|
811
|
+
type,
|
|
812
|
+
scope,
|
|
813
|
+
deps,
|
|
814
|
+
destroyListeners: [],
|
|
815
|
+
createdAt: Date.now()
|
|
816
|
+
};
|
|
817
|
+
return holder;
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Gets all holder names currently managed.
|
|
821
|
+
*/
|
|
822
|
+
getAllNames() {
|
|
823
|
+
return Array.from(this._holders.keys());
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Gets all holders currently managed.
|
|
827
|
+
*/
|
|
828
|
+
getAllHolders() {
|
|
829
|
+
return Array.from(this._holders.values());
|
|
510
830
|
}
|
|
511
831
|
/**
|
|
512
832
|
* Checks if this manager has any holders.
|
|
@@ -562,8 +882,7 @@ var DefaultRequestContextHolder = class extends BaseInstanceHolderManager {
|
|
|
562
882
|
instance,
|
|
563
883
|
"Class" /* Class */,
|
|
564
884
|
"Singleton" /* Singleton */,
|
|
565
|
-
/* @__PURE__ */ new Set()
|
|
566
|
-
Infinity
|
|
885
|
+
/* @__PURE__ */ new Set()
|
|
567
886
|
);
|
|
568
887
|
this._holders.set(name, createdHolder);
|
|
569
888
|
} else {
|
|
@@ -585,20 +904,137 @@ var DefaultRequestContextHolder = class extends BaseInstanceHolderManager {
|
|
|
585
904
|
}
|
|
586
905
|
};
|
|
587
906
|
|
|
588
|
-
// src/
|
|
589
|
-
var
|
|
590
|
-
constructor(
|
|
591
|
-
this.
|
|
907
|
+
// src/request-context-manager.mts
|
|
908
|
+
var RequestContextManager = class {
|
|
909
|
+
constructor(logger = null) {
|
|
910
|
+
this.logger = logger;
|
|
592
911
|
}
|
|
912
|
+
requestContexts = /* @__PURE__ */ new Map();
|
|
913
|
+
currentRequestContext = null;
|
|
593
914
|
/**
|
|
594
|
-
*
|
|
595
|
-
* @param
|
|
596
|
-
* @param
|
|
597
|
-
* @param
|
|
598
|
-
* @returns
|
|
915
|
+
* Begins a new request context with the given parameters.
|
|
916
|
+
* @param requestId Unique identifier for this request
|
|
917
|
+
* @param metadata Optional metadata for the request
|
|
918
|
+
* @param priority Priority for resolution (higher = more priority)
|
|
919
|
+
* @returns The created request context holder
|
|
599
920
|
*/
|
|
600
|
-
|
|
601
|
-
|
|
921
|
+
beginRequest(requestId, metadata, priority = 100) {
|
|
922
|
+
if (this.requestContexts.has(requestId)) {
|
|
923
|
+
throw new Error(
|
|
924
|
+
`[RequestContextManager] Request context ${requestId} already exists`
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
const contextHolder = new DefaultRequestContextHolder(
|
|
928
|
+
requestId,
|
|
929
|
+
priority,
|
|
930
|
+
metadata
|
|
931
|
+
);
|
|
932
|
+
this.requestContexts.set(requestId, contextHolder);
|
|
933
|
+
this.currentRequestContext = contextHolder;
|
|
934
|
+
this.logger?.log(
|
|
935
|
+
`[RequestContextManager] Started request context: ${requestId}`
|
|
936
|
+
);
|
|
937
|
+
return contextHolder;
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Ends a request context and cleans up all associated instances.
|
|
941
|
+
* @param requestId The request ID to end
|
|
942
|
+
*/
|
|
943
|
+
async endRequest(requestId) {
|
|
944
|
+
const contextHolder = this.requestContexts.get(requestId);
|
|
945
|
+
if (!contextHolder) {
|
|
946
|
+
this.logger?.warn(
|
|
947
|
+
`[RequestContextManager] Request context ${requestId} not found`
|
|
948
|
+
);
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
this.logger?.log(
|
|
952
|
+
`[RequestContextManager] Ending request context: ${requestId}`
|
|
953
|
+
);
|
|
954
|
+
const cleanupPromises = [];
|
|
955
|
+
for (const [, holder] of contextHolder.holders) {
|
|
956
|
+
if (holder.destroyListeners.length > 0) {
|
|
957
|
+
cleanupPromises.push(
|
|
958
|
+
Promise.all(holder.destroyListeners.map((listener) => listener()))
|
|
959
|
+
);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
await Promise.all(cleanupPromises);
|
|
963
|
+
contextHolder.clear();
|
|
964
|
+
this.requestContexts.delete(requestId);
|
|
965
|
+
if (this.currentRequestContext === contextHolder) {
|
|
966
|
+
this.currentRequestContext = Array.from(this.requestContexts.values()).at(-1) ?? null;
|
|
967
|
+
}
|
|
968
|
+
this.logger?.log(
|
|
969
|
+
`[RequestContextManager] Request context ${requestId} ended`
|
|
970
|
+
);
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Gets the current request context.
|
|
974
|
+
* @returns The current request context holder or null
|
|
975
|
+
*/
|
|
976
|
+
getCurrentRequestContext() {
|
|
977
|
+
return this.currentRequestContext;
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* Sets the current request context.
|
|
981
|
+
* @param requestId The request ID to set as current
|
|
982
|
+
*/
|
|
983
|
+
setCurrentRequestContext(requestId) {
|
|
984
|
+
const contextHolder = this.requestContexts.get(requestId);
|
|
985
|
+
if (!contextHolder) {
|
|
986
|
+
throw new Error(
|
|
987
|
+
`[RequestContextManager] Request context ${requestId} not found`
|
|
988
|
+
);
|
|
989
|
+
}
|
|
990
|
+
this.currentRequestContext = contextHolder;
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Gets all request contexts.
|
|
994
|
+
* @returns Map of request contexts
|
|
995
|
+
*/
|
|
996
|
+
getRequestContexts() {
|
|
997
|
+
return this.requestContexts;
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* Clears all request contexts.
|
|
1001
|
+
*/
|
|
1002
|
+
async clearAllRequestContexts() {
|
|
1003
|
+
const requestIds = Array.from(this.requestContexts.keys());
|
|
1004
|
+
if (requestIds.length === 0) {
|
|
1005
|
+
this.logger?.log("[RequestContextManager] No request contexts to clear");
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
this.logger?.log(
|
|
1009
|
+
`[RequestContextManager] Clearing ${requestIds.length} request contexts: ${requestIds.join(", ")}`
|
|
1010
|
+
);
|
|
1011
|
+
for (const requestId of requestIds) {
|
|
1012
|
+
try {
|
|
1013
|
+
await this.endRequest(requestId);
|
|
1014
|
+
} catch (error) {
|
|
1015
|
+
this.logger?.error(
|
|
1016
|
+
`[RequestContextManager] Error clearing request context ${requestId}:`,
|
|
1017
|
+
error
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
};
|
|
1023
|
+
|
|
1024
|
+
// src/service-instantiator.mts
|
|
1025
|
+
var ServiceInstantiator = class {
|
|
1026
|
+
constructor(injectors) {
|
|
1027
|
+
this.injectors = injectors;
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* Instantiates a service based on its registry record.
|
|
1031
|
+
* @param ctx The factory context for dependency injection
|
|
1032
|
+
* @param record The factory record from the registry
|
|
1033
|
+
* @param args Optional arguments for the service
|
|
1034
|
+
* @returns Promise resolving to [undefined, instance] or [error]
|
|
1035
|
+
*/
|
|
1036
|
+
async instantiateService(ctx, record, args = void 0) {
|
|
1037
|
+
try {
|
|
602
1038
|
switch (record.type) {
|
|
603
1039
|
case "Class" /* Class */:
|
|
604
1040
|
return this.instantiateClass(ctx, record, args);
|
|
@@ -713,6 +1149,308 @@ var ServiceInstantiator = class {
|
|
|
713
1149
|
}
|
|
714
1150
|
};
|
|
715
1151
|
|
|
1152
|
+
// src/service-invalidator.mts
|
|
1153
|
+
var ServiceInvalidator = class {
|
|
1154
|
+
constructor(manager, requestContextManager, eventBus, logger = null) {
|
|
1155
|
+
this.manager = manager;
|
|
1156
|
+
this.requestContextManager = requestContextManager;
|
|
1157
|
+
this.eventBus = eventBus;
|
|
1158
|
+
this.logger = logger;
|
|
1159
|
+
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Invalidates a service and all its dependencies.
|
|
1162
|
+
*/
|
|
1163
|
+
invalidate(service, round = 1) {
|
|
1164
|
+
this.logger?.log(
|
|
1165
|
+
`[ServiceInvalidator] Starting invalidation process for ${service}`
|
|
1166
|
+
);
|
|
1167
|
+
const [, toInvalidate] = this.manager.get(service);
|
|
1168
|
+
const promises = [];
|
|
1169
|
+
if (toInvalidate) {
|
|
1170
|
+
promises.push(this.invalidateHolder(service, toInvalidate, round));
|
|
1171
|
+
}
|
|
1172
|
+
const requestContexts = this.requestContextManager.getRequestContexts();
|
|
1173
|
+
for (const [requestId, requestContext] of requestContexts.entries()) {
|
|
1174
|
+
const holder = requestContext.get(service);
|
|
1175
|
+
if (holder) {
|
|
1176
|
+
this.logger?.log(
|
|
1177
|
+
`[ServiceInvalidator] Invalidating request-scoped instance ${service} in request ${requestId}`
|
|
1178
|
+
);
|
|
1179
|
+
promises.push(
|
|
1180
|
+
this.invalidateRequestHolder(requestId, service, holder, round)
|
|
1181
|
+
);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
return Promise.all(promises);
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* Gracefully clears all services in the ServiceLocator using invalidation logic.
|
|
1188
|
+
* This method respects service dependencies and ensures proper cleanup order.
|
|
1189
|
+
* Services that depend on others will be invalidated first, then their dependencies.
|
|
1190
|
+
*/
|
|
1191
|
+
async clearAll(options = {}) {
|
|
1192
|
+
const {
|
|
1193
|
+
clearRequestContexts = true,
|
|
1194
|
+
maxRounds = 10,
|
|
1195
|
+
waitForSettlement = true
|
|
1196
|
+
} = options;
|
|
1197
|
+
this.logger?.log(
|
|
1198
|
+
"[ServiceInvalidator] Starting graceful clearing of all services"
|
|
1199
|
+
);
|
|
1200
|
+
if (waitForSettlement) {
|
|
1201
|
+
this.logger?.log(
|
|
1202
|
+
"[ServiceInvalidator] Waiting for all services to settle..."
|
|
1203
|
+
);
|
|
1204
|
+
await this.ready();
|
|
1205
|
+
}
|
|
1206
|
+
const allServiceNames = this.getAllServiceNames();
|
|
1207
|
+
if (allServiceNames.length === 0) {
|
|
1208
|
+
this.logger?.log("[ServiceInvalidator] No singleton services to clear");
|
|
1209
|
+
} else {
|
|
1210
|
+
this.logger?.log(
|
|
1211
|
+
`[ServiceInvalidator] Found ${allServiceNames.length} services to clear: ${allServiceNames.join(", ")}`
|
|
1212
|
+
);
|
|
1213
|
+
await this.clearServicesWithDependencyAwareness(
|
|
1214
|
+
allServiceNames,
|
|
1215
|
+
maxRounds
|
|
1216
|
+
);
|
|
1217
|
+
}
|
|
1218
|
+
if (clearRequestContexts) {
|
|
1219
|
+
await this.requestContextManager.clearAllRequestContexts();
|
|
1220
|
+
}
|
|
1221
|
+
this.logger?.log("[ServiceInvalidator] Graceful clearing completed");
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* Waits for all services to settle (either created, destroyed, or error state).
|
|
1225
|
+
*/
|
|
1226
|
+
async ready() {
|
|
1227
|
+
const holders = Array.from(this.manager.filter(() => true)).map(
|
|
1228
|
+
([, holder]) => holder
|
|
1229
|
+
);
|
|
1230
|
+
await Promise.all(
|
|
1231
|
+
holders.map((holder) => this.waitForHolderToSettle(holder))
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1235
|
+
* Invalidates a single holder based on its current status.
|
|
1236
|
+
*/
|
|
1237
|
+
async invalidateHolder(key, holder, round) {
|
|
1238
|
+
await this.invalidateHolderByStatus(holder, round, {
|
|
1239
|
+
context: key,
|
|
1240
|
+
isRequestScoped: false,
|
|
1241
|
+
onCreationError: () => this.logger?.error(
|
|
1242
|
+
`[ServiceInvalidator] ${key} creation triggered too many invalidation rounds`
|
|
1243
|
+
),
|
|
1244
|
+
onRecursiveInvalidate: () => this.invalidate(key, round + 1),
|
|
1245
|
+
onDestroy: () => this.destroyHolder(key, holder)
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Invalidates a request-scoped holder based on its current status.
|
|
1250
|
+
*/
|
|
1251
|
+
async invalidateRequestHolder(requestId, instanceName, holder, round) {
|
|
1252
|
+
await this.invalidateHolderByStatus(holder, round, {
|
|
1253
|
+
context: `Request-scoped ${instanceName} in ${requestId}`,
|
|
1254
|
+
isRequestScoped: true,
|
|
1255
|
+
onCreationError: () => this.logger?.error(
|
|
1256
|
+
`[ServiceInvalidator] Request-scoped ${instanceName} in ${requestId} creation triggered too many invalidation rounds`
|
|
1257
|
+
),
|
|
1258
|
+
onRecursiveInvalidate: () => this.invalidateRequestHolder(
|
|
1259
|
+
requestId,
|
|
1260
|
+
instanceName,
|
|
1261
|
+
holder,
|
|
1262
|
+
round + 1
|
|
1263
|
+
),
|
|
1264
|
+
onDestroy: () => this.destroyRequestHolder(requestId, instanceName, holder)
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
/**
|
|
1268
|
+
* Common invalidation logic for holders based on their status.
|
|
1269
|
+
*/
|
|
1270
|
+
async invalidateHolderByStatus(holder, round, options) {
|
|
1271
|
+
switch (holder.status) {
|
|
1272
|
+
case "destroying" /* Destroying */:
|
|
1273
|
+
await holder.destroyPromise;
|
|
1274
|
+
break;
|
|
1275
|
+
case "creating" /* Creating */:
|
|
1276
|
+
await holder.creationPromise;
|
|
1277
|
+
if (round > 3) {
|
|
1278
|
+
options.onCreationError();
|
|
1279
|
+
return;
|
|
1280
|
+
}
|
|
1281
|
+
await options.onRecursiveInvalidate();
|
|
1282
|
+
break;
|
|
1283
|
+
default:
|
|
1284
|
+
await options.onDestroy();
|
|
1285
|
+
break;
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
/**
|
|
1289
|
+
* Destroys a holder and cleans up its resources.
|
|
1290
|
+
*/
|
|
1291
|
+
async destroyHolder(key, holder) {
|
|
1292
|
+
await this.destroyHolderWithCleanup(holder, {
|
|
1293
|
+
context: key,
|
|
1294
|
+
logMessage: `[ServiceInvalidator] Invalidating ${key} and notifying listeners`,
|
|
1295
|
+
cleanup: () => this.manager.delete(key),
|
|
1296
|
+
eventName: key
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* Destroys a request-scoped holder and cleans up its resources.
|
|
1301
|
+
*/
|
|
1302
|
+
async destroyRequestHolder(requestId, instanceName, holder) {
|
|
1303
|
+
await this.destroyHolderWithCleanup(holder, {
|
|
1304
|
+
context: `Request-scoped ${instanceName} in ${requestId}`,
|
|
1305
|
+
logMessage: `[ServiceInvalidator] Invalidating request-scoped ${instanceName} in ${requestId} and notifying listeners`,
|
|
1306
|
+
cleanup: () => {
|
|
1307
|
+
const requestContext = this.requestContextManager.getRequestContexts().get(requestId);
|
|
1308
|
+
if (requestContext) {
|
|
1309
|
+
requestContext.delete(instanceName);
|
|
1310
|
+
}
|
|
1311
|
+
},
|
|
1312
|
+
eventName: instanceName
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
/**
|
|
1316
|
+
* Common destroy logic for holders with customizable cleanup.
|
|
1317
|
+
*/
|
|
1318
|
+
async destroyHolderWithCleanup(holder, options) {
|
|
1319
|
+
holder.status = "destroying" /* Destroying */;
|
|
1320
|
+
this.logger?.log(options.logMessage);
|
|
1321
|
+
holder.destroyPromise = Promise.all(
|
|
1322
|
+
holder.destroyListeners.map((listener) => listener())
|
|
1323
|
+
).then(async () => {
|
|
1324
|
+
holder.destroyListeners = [];
|
|
1325
|
+
holder.deps.clear();
|
|
1326
|
+
options.cleanup();
|
|
1327
|
+
await this.emitInstanceEvent(options.eventName, "destroy");
|
|
1328
|
+
});
|
|
1329
|
+
await holder.destroyPromise;
|
|
1330
|
+
}
|
|
1331
|
+
/**
|
|
1332
|
+
* Waits for a holder to settle (either created, destroyed, or error state).
|
|
1333
|
+
*/
|
|
1334
|
+
async waitForHolderToSettle(holder) {
|
|
1335
|
+
switch (holder.status) {
|
|
1336
|
+
case "creating" /* Creating */:
|
|
1337
|
+
await holder.creationPromise;
|
|
1338
|
+
break;
|
|
1339
|
+
case "destroying" /* Destroying */:
|
|
1340
|
+
await holder.destroyPromise;
|
|
1341
|
+
break;
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
/**
|
|
1345
|
+
* Clears services with dependency awareness, ensuring proper cleanup order.
|
|
1346
|
+
* Services with no dependencies are cleared first, then services that depend on them.
|
|
1347
|
+
*/
|
|
1348
|
+
async clearServicesWithDependencyAwareness(serviceNames, maxRounds) {
|
|
1349
|
+
const clearedServices = /* @__PURE__ */ new Set();
|
|
1350
|
+
let round = 1;
|
|
1351
|
+
while (clearedServices.size < serviceNames.length && round <= maxRounds) {
|
|
1352
|
+
this.logger?.log(
|
|
1353
|
+
`[ServiceInvalidator] Clearing round ${round}/${maxRounds}, ${clearedServices.size}/${serviceNames.length} services cleared`
|
|
1354
|
+
);
|
|
1355
|
+
const servicesToClearThisRound = this.findServicesReadyForClearing(
|
|
1356
|
+
serviceNames,
|
|
1357
|
+
clearedServices
|
|
1358
|
+
);
|
|
1359
|
+
if (servicesToClearThisRound.length === 0) {
|
|
1360
|
+
const remainingServices = serviceNames.filter(
|
|
1361
|
+
(name) => !clearedServices.has(name)
|
|
1362
|
+
);
|
|
1363
|
+
if (remainingServices.length > 0) {
|
|
1364
|
+
this.logger?.warn(
|
|
1365
|
+
`[ServiceInvalidator] No services ready for clearing, forcing cleanup of remaining: ${remainingServices.join(", ")}`
|
|
1366
|
+
);
|
|
1367
|
+
await this.forceClearServices(remainingServices);
|
|
1368
|
+
remainingServices.forEach((name) => clearedServices.add(name));
|
|
1369
|
+
}
|
|
1370
|
+
break;
|
|
1371
|
+
}
|
|
1372
|
+
const clearPromises = servicesToClearThisRound.map(
|
|
1373
|
+
async (serviceName) => {
|
|
1374
|
+
try {
|
|
1375
|
+
await this.invalidate(serviceName, round);
|
|
1376
|
+
clearedServices.add(serviceName);
|
|
1377
|
+
this.logger?.log(
|
|
1378
|
+
`[ServiceInvalidator] Successfully cleared service: ${serviceName}`
|
|
1379
|
+
);
|
|
1380
|
+
} catch (error) {
|
|
1381
|
+
this.logger?.error(
|
|
1382
|
+
`[ServiceInvalidator] Error clearing service ${serviceName}:`,
|
|
1383
|
+
error
|
|
1384
|
+
);
|
|
1385
|
+
clearedServices.add(serviceName);
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
);
|
|
1389
|
+
await Promise.all(clearPromises);
|
|
1390
|
+
round++;
|
|
1391
|
+
}
|
|
1392
|
+
if (clearedServices.size < serviceNames.length) {
|
|
1393
|
+
this.logger?.warn(
|
|
1394
|
+
`[ServiceInvalidator] Clearing completed after ${maxRounds} rounds, but ${serviceNames.length - clearedServices.size} services may not have been properly cleared`
|
|
1395
|
+
);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
/**
|
|
1399
|
+
* Finds services that are ready to be cleared in the current round.
|
|
1400
|
+
* A service is ready if all its dependencies have already been cleared.
|
|
1401
|
+
*/
|
|
1402
|
+
findServicesReadyForClearing(allServiceNames, clearedServices) {
|
|
1403
|
+
return allServiceNames.filter((serviceName) => {
|
|
1404
|
+
if (clearedServices.has(serviceName)) {
|
|
1405
|
+
return false;
|
|
1406
|
+
}
|
|
1407
|
+
const [error, holder] = this.manager.get(serviceName);
|
|
1408
|
+
if (error) {
|
|
1409
|
+
return true;
|
|
1410
|
+
}
|
|
1411
|
+
const hasUnclearedDependencies = Array.from(holder.deps).some(
|
|
1412
|
+
(dep) => !clearedServices.has(dep)
|
|
1413
|
+
);
|
|
1414
|
+
return !hasUnclearedDependencies;
|
|
1415
|
+
});
|
|
1416
|
+
}
|
|
1417
|
+
/**
|
|
1418
|
+
* Force clears services that couldn't be cleared through normal dependency resolution.
|
|
1419
|
+
* This handles edge cases like circular dependencies.
|
|
1420
|
+
*/
|
|
1421
|
+
async forceClearServices(serviceNames) {
|
|
1422
|
+
const promises = serviceNames.map(async (serviceName) => {
|
|
1423
|
+
try {
|
|
1424
|
+
const [error, holder] = this.manager.get(serviceName);
|
|
1425
|
+
if (!error && holder) {
|
|
1426
|
+
await this.destroyHolder(serviceName, holder);
|
|
1427
|
+
}
|
|
1428
|
+
} catch (error) {
|
|
1429
|
+
this.logger?.error(
|
|
1430
|
+
`[ServiceInvalidator] Error force clearing service ${serviceName}:`,
|
|
1431
|
+
error
|
|
1432
|
+
);
|
|
1433
|
+
}
|
|
1434
|
+
});
|
|
1435
|
+
await Promise.all(promises);
|
|
1436
|
+
}
|
|
1437
|
+
/**
|
|
1438
|
+
* Gets all service names currently managed by the ServiceLocator.
|
|
1439
|
+
*/
|
|
1440
|
+
getAllServiceNames() {
|
|
1441
|
+
return this.manager.getAllNames();
|
|
1442
|
+
}
|
|
1443
|
+
/**
|
|
1444
|
+
* Emits events to listeners for instance lifecycle events.
|
|
1445
|
+
*/
|
|
1446
|
+
emitInstanceEvent(name, event = "create") {
|
|
1447
|
+
this.logger?.log(
|
|
1448
|
+
`[ServiceInvalidator]#emitInstanceEvent() Notifying listeners for ${name} with event ${event}`
|
|
1449
|
+
);
|
|
1450
|
+
return this.eventBus.emit(name, event);
|
|
1451
|
+
}
|
|
1452
|
+
};
|
|
1453
|
+
|
|
716
1454
|
// src/service-locator-event-bus.mts
|
|
717
1455
|
var ServiceLocatorEventBus = class {
|
|
718
1456
|
constructor(logger = null) {
|
|
@@ -730,7 +1468,7 @@ var ServiceLocatorEventBus = class {
|
|
|
730
1468
|
}
|
|
731
1469
|
nsEvents.get(event).add(listener);
|
|
732
1470
|
return () => {
|
|
733
|
-
nsEvents.get(event)
|
|
1471
|
+
nsEvents.get(event)?.delete(listener);
|
|
734
1472
|
if (nsEvents.get(event)?.size === 0) {
|
|
735
1473
|
nsEvents.delete(event);
|
|
736
1474
|
}
|
|
@@ -772,19 +1510,11 @@ var ServiceLocatorManager = class extends BaseInstanceHolderManager {
|
|
|
772
1510
|
get(name) {
|
|
773
1511
|
const holder = this._holders.get(name);
|
|
774
1512
|
if (holder) {
|
|
775
|
-
if (holder.
|
|
776
|
-
const now = Date.now();
|
|
777
|
-
if (now - holder.createdAt > holder.ttl) {
|
|
778
|
-
this.logger?.log(
|
|
779
|
-
`[ServiceLocatorManager]#getInstanceHolder() TTL expired for ${holder.name}`
|
|
780
|
-
);
|
|
781
|
-
return [new InstanceExpired(holder.name), holder];
|
|
782
|
-
}
|
|
783
|
-
} else if (holder.status === "destroying" /* Destroying */) {
|
|
1513
|
+
if (holder.status === "destroying" /* Destroying */) {
|
|
784
1514
|
this.logger?.log(
|
|
785
1515
|
`[ServiceLocatorManager]#getInstanceHolder() Instance ${holder.name} is destroying`
|
|
786
1516
|
);
|
|
787
|
-
return [
|
|
1517
|
+
return [DIError.instanceDestroying(holder.name), holder];
|
|
788
1518
|
} else if (holder.status === "error" /* Error */) {
|
|
789
1519
|
this.logger?.log(
|
|
790
1520
|
`[ServiceLocatorManager]#getInstanceHolder() Instance ${holder.name} is in error state`
|
|
@@ -796,7 +1526,7 @@ var ServiceLocatorManager = class extends BaseInstanceHolderManager {
|
|
|
796
1526
|
this.logger?.log(
|
|
797
1527
|
`[ServiceLocatorManager]#getInstanceHolder() Instance ${name} not found`
|
|
798
1528
|
);
|
|
799
|
-
return [
|
|
1529
|
+
return [DIError.instanceNotFound(name)];
|
|
800
1530
|
}
|
|
801
1531
|
}
|
|
802
1532
|
set(name, holder) {
|
|
@@ -807,9 +1537,7 @@ var ServiceLocatorManager = class extends BaseInstanceHolderManager {
|
|
|
807
1537
|
if (!error) {
|
|
808
1538
|
return [void 0, true];
|
|
809
1539
|
}
|
|
810
|
-
if (
|
|
811
|
-
error.code
|
|
812
|
-
)) {
|
|
1540
|
+
if (error.code === "InstanceDestroying" /* InstanceDestroying */) {
|
|
813
1541
|
return [error];
|
|
814
1542
|
}
|
|
815
1543
|
return [void 0, !!holder];
|
|
@@ -821,596 +1549,83 @@ var ServiceLocatorManager = class extends BaseInstanceHolderManager {
|
|
|
821
1549
|
* This is useful for creating holders that already have their instance ready.
|
|
822
1550
|
* @param name The name of the instance
|
|
823
1551
|
* @param instance The actual instance to store
|
|
824
|
-
* @param type The injectable type
|
|
825
|
-
* @param scope The injectable scope
|
|
826
|
-
* @param deps Optional set of dependencies
|
|
827
|
-
* @
|
|
828
|
-
* @returns The created holder
|
|
829
|
-
*/
|
|
830
|
-
storeCreatedHolder(name, instance, type, scope, deps = /* @__PURE__ */ new Set(), ttl = Infinity) {
|
|
831
|
-
const holder = this.createCreatedHolder(
|
|
832
|
-
name,
|
|
833
|
-
instance,
|
|
834
|
-
type,
|
|
835
|
-
scope,
|
|
836
|
-
deps,
|
|
837
|
-
ttl
|
|
838
|
-
);
|
|
839
|
-
this._holders.set(name, holder);
|
|
840
|
-
return holder;
|
|
841
|
-
}
|
|
842
|
-
};
|
|
843
|
-
|
|
844
|
-
// src/service-locator.mts
|
|
845
|
-
var ServiceLocator = class {
|
|
846
|
-
constructor(registry = globalRegistry, logger = null, injectors = defaultInjectors) {
|
|
847
|
-
this.registry = registry;
|
|
848
|
-
this.logger = logger;
|
|
849
|
-
this.injectors = injectors;
|
|
850
|
-
this.eventBus = new ServiceLocatorEventBus(logger);
|
|
851
|
-
this.manager = new ServiceLocatorManager(logger);
|
|
852
|
-
this.serviceInstantiator = new ServiceInstantiator(injectors);
|
|
853
|
-
}
|
|
854
|
-
eventBus;
|
|
855
|
-
manager;
|
|
856
|
-
serviceInstantiator;
|
|
857
|
-
requestContexts = /* @__PURE__ */ new Map();
|
|
858
|
-
currentRequestContext = null;
|
|
859
|
-
// ============================================================================
|
|
860
|
-
// PUBLIC METHODS
|
|
861
|
-
// ============================================================================
|
|
862
|
-
getEventBus() {
|
|
863
|
-
return this.eventBus;
|
|
864
|
-
}
|
|
865
|
-
getManager() {
|
|
866
|
-
return this.manager;
|
|
867
|
-
}
|
|
868
|
-
getInstanceIdentifier(token, args) {
|
|
869
|
-
const [err, { actualToken, validatedArgs }] = this.validateAndResolveTokenArgs(token, args);
|
|
870
|
-
if (err) {
|
|
871
|
-
throw err;
|
|
872
|
-
}
|
|
873
|
-
return this.generateInstanceName(actualToken, validatedArgs);
|
|
874
|
-
}
|
|
875
|
-
async getInstance(token, args, onPrepare) {
|
|
876
|
-
const [err, data] = await this.resolveTokenAndPrepareInstanceName(
|
|
877
|
-
token,
|
|
878
|
-
args
|
|
879
|
-
);
|
|
880
|
-
if (err) {
|
|
881
|
-
return [err];
|
|
882
|
-
}
|
|
883
|
-
const { instanceName, validatedArgs, actualToken, realToken } = data;
|
|
884
|
-
onPrepare?.({ instanceName, actualToken, validatedArgs });
|
|
885
|
-
const [error, holder] = await this.retrieveOrCreateInstanceByInstanceName(
|
|
886
|
-
instanceName,
|
|
887
|
-
realToken,
|
|
888
|
-
validatedArgs
|
|
889
|
-
);
|
|
890
|
-
if (error) {
|
|
891
|
-
return [error];
|
|
892
|
-
}
|
|
893
|
-
return [void 0, holder.instance];
|
|
894
|
-
}
|
|
895
|
-
async getOrThrowInstance(token, args) {
|
|
896
|
-
const [error, instance] = await this.getInstance(token, args);
|
|
897
|
-
if (error) {
|
|
898
|
-
throw error;
|
|
899
|
-
}
|
|
900
|
-
return instance;
|
|
901
|
-
}
|
|
902
|
-
getSyncInstance(token, args) {
|
|
903
|
-
const [err, { actualToken, validatedArgs }] = this.validateAndResolveTokenArgs(token, args);
|
|
904
|
-
if (err) {
|
|
905
|
-
return null;
|
|
906
|
-
}
|
|
907
|
-
const instanceName = this.generateInstanceName(actualToken, validatedArgs);
|
|
908
|
-
if (this.currentRequestContext) {
|
|
909
|
-
const requestHolder = this.currentRequestContext.get(instanceName);
|
|
910
|
-
if (requestHolder) {
|
|
911
|
-
return requestHolder.instance;
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
const [error, holder] = this.manager.get(instanceName);
|
|
915
|
-
if (error) {
|
|
916
|
-
return null;
|
|
917
|
-
}
|
|
918
|
-
return holder.instance;
|
|
919
|
-
}
|
|
920
|
-
invalidate(service, round = 1) {
|
|
921
|
-
this.logger?.log(
|
|
922
|
-
`[ServiceLocator] Starting invalidation process for ${service}`
|
|
923
|
-
);
|
|
924
|
-
const toInvalidate = this.manager.filter(
|
|
925
|
-
(holder) => holder.name === service || holder.deps.has(service)
|
|
926
|
-
);
|
|
927
|
-
const promises = [];
|
|
928
|
-
for (const [key, holder] of toInvalidate.entries()) {
|
|
929
|
-
promises.push(this.invalidateHolder(key, holder, round));
|
|
930
|
-
}
|
|
931
|
-
return Promise.all(promises);
|
|
932
|
-
}
|
|
933
|
-
/**
|
|
934
|
-
* Invalidates a single holder based on its current status.
|
|
935
|
-
*/
|
|
936
|
-
async invalidateHolder(key, holder, round) {
|
|
937
|
-
switch (holder.status) {
|
|
938
|
-
case "destroying" /* Destroying */:
|
|
939
|
-
this.logger?.trace(`[ServiceLocator] ${key} is already being destroyed`);
|
|
940
|
-
await holder.destroyPromise;
|
|
941
|
-
break;
|
|
942
|
-
case "creating" /* Creating */:
|
|
943
|
-
this.logger?.trace(
|
|
944
|
-
`[ServiceLocator] ${key} is being created, waiting...`
|
|
945
|
-
);
|
|
946
|
-
await holder.creationPromise;
|
|
947
|
-
if (round > 3) {
|
|
948
|
-
this.logger?.error(
|
|
949
|
-
`[ServiceLocator] ${key} creation triggered too many invalidation rounds`
|
|
950
|
-
);
|
|
951
|
-
return;
|
|
952
|
-
}
|
|
953
|
-
await this.invalidate(key, round + 1);
|
|
954
|
-
break;
|
|
955
|
-
default:
|
|
956
|
-
await this.destroyHolder(key, holder);
|
|
957
|
-
break;
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
/**
|
|
961
|
-
* Destroys a holder and cleans up its resources.
|
|
962
|
-
*/
|
|
963
|
-
async destroyHolder(key, holder) {
|
|
964
|
-
holder.status = "destroying" /* Destroying */;
|
|
965
|
-
this.logger?.log(
|
|
966
|
-
`[ServiceLocator] Invalidating ${key} and notifying listeners`
|
|
967
|
-
);
|
|
968
|
-
holder.destroyPromise = Promise.all(
|
|
969
|
-
holder.destroyListeners.map((listener) => listener())
|
|
970
|
-
).then(async () => {
|
|
971
|
-
this.manager.delete(key);
|
|
972
|
-
await this.emitInstanceEvent(key, "destroy");
|
|
973
|
-
});
|
|
974
|
-
await holder.destroyPromise;
|
|
975
|
-
}
|
|
976
|
-
async ready() {
|
|
977
|
-
const holders = Array.from(this.manager.filter(() => true)).map(
|
|
978
|
-
([, holder]) => holder
|
|
979
|
-
);
|
|
980
|
-
await Promise.all(
|
|
981
|
-
holders.map((holder) => this.waitForHolderToSettle(holder))
|
|
982
|
-
);
|
|
983
|
-
}
|
|
984
|
-
/**
|
|
985
|
-
* Waits for a holder to settle (either created, destroyed, or error state).
|
|
986
|
-
*/
|
|
987
|
-
async waitForHolderToSettle(holder) {
|
|
988
|
-
switch (holder.status) {
|
|
989
|
-
case "creating" /* Creating */:
|
|
990
|
-
await holder.creationPromise;
|
|
991
|
-
break;
|
|
992
|
-
case "destroying" /* Destroying */:
|
|
993
|
-
await holder.destroyPromise;
|
|
994
|
-
break;
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
// ============================================================================
|
|
998
|
-
// REQUEST CONTEXT MANAGEMENT
|
|
999
|
-
// ============================================================================
|
|
1000
|
-
/**
|
|
1001
|
-
* Begins a new request context with the given parameters.
|
|
1002
|
-
* @param requestId Unique identifier for this request
|
|
1003
|
-
* @param metadata Optional metadata for the request
|
|
1004
|
-
* @param priority Priority for resolution (higher = more priority)
|
|
1005
|
-
* @returns The created request context holder
|
|
1006
|
-
*/
|
|
1007
|
-
beginRequest(requestId, metadata, priority = 100) {
|
|
1008
|
-
if (this.requestContexts.has(requestId)) {
|
|
1009
|
-
throw new Error(
|
|
1010
|
-
`[ServiceLocator] Request context ${requestId} already exists`
|
|
1011
|
-
);
|
|
1012
|
-
}
|
|
1013
|
-
const contextHolder = new DefaultRequestContextHolder(
|
|
1014
|
-
requestId,
|
|
1015
|
-
priority,
|
|
1016
|
-
metadata
|
|
1017
|
-
);
|
|
1018
|
-
this.requestContexts.set(requestId, contextHolder);
|
|
1019
|
-
this.currentRequestContext = contextHolder;
|
|
1020
|
-
this.logger?.log(`[ServiceLocator] Started request context: ${requestId}`);
|
|
1021
|
-
return contextHolder;
|
|
1022
|
-
}
|
|
1023
|
-
/**
|
|
1024
|
-
* Ends a request context and cleans up all associated instances.
|
|
1025
|
-
* @param requestId The request ID to end
|
|
1026
|
-
*/
|
|
1027
|
-
async endRequest(requestId) {
|
|
1028
|
-
const contextHolder = this.requestContexts.get(requestId);
|
|
1029
|
-
if (!contextHolder) {
|
|
1030
|
-
this.logger?.warn(
|
|
1031
|
-
`[ServiceLocator] Request context ${requestId} not found`
|
|
1032
|
-
);
|
|
1033
|
-
return;
|
|
1034
|
-
}
|
|
1035
|
-
this.logger?.log(`[ServiceLocator] Ending request context: ${requestId}`);
|
|
1036
|
-
const cleanupPromises = [];
|
|
1037
|
-
for (const [, holder] of contextHolder.holders) {
|
|
1038
|
-
if (holder.destroyListeners.length > 0) {
|
|
1039
|
-
cleanupPromises.push(
|
|
1040
|
-
Promise.all(holder.destroyListeners.map((listener) => listener()))
|
|
1041
|
-
);
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
await Promise.all(cleanupPromises);
|
|
1045
|
-
contextHolder.clear();
|
|
1046
|
-
this.requestContexts.delete(requestId);
|
|
1047
|
-
if (this.currentRequestContext === contextHolder) {
|
|
1048
|
-
this.currentRequestContext = Array.from(this.requestContexts.values()).at(-1) ?? null;
|
|
1049
|
-
}
|
|
1050
|
-
this.logger?.log(`[ServiceLocator] Request context ${requestId} ended`);
|
|
1051
|
-
}
|
|
1052
|
-
/**
|
|
1053
|
-
* Gets the current request context.
|
|
1054
|
-
* @returns The current request context holder or null
|
|
1055
|
-
*/
|
|
1056
|
-
getCurrentRequestContext() {
|
|
1057
|
-
return this.currentRequestContext;
|
|
1058
|
-
}
|
|
1059
|
-
/**
|
|
1060
|
-
* Sets the current request context.
|
|
1061
|
-
* @param requestId The request ID to set as current
|
|
1062
|
-
*/
|
|
1063
|
-
setCurrentRequestContext(requestId) {
|
|
1064
|
-
const contextHolder = this.requestContexts.get(requestId);
|
|
1065
|
-
if (!contextHolder) {
|
|
1066
|
-
throw new Error(`[ServiceLocator] Request context ${requestId} not found`);
|
|
1067
|
-
}
|
|
1068
|
-
this.currentRequestContext = contextHolder;
|
|
1069
|
-
}
|
|
1070
|
-
// ============================================================================
|
|
1071
|
-
// PRIVATE METHODS
|
|
1072
|
-
// ============================================================================
|
|
1073
|
-
/**
|
|
1074
|
-
* Validates and resolves token arguments, handling factory token resolution and validation.
|
|
1075
|
-
*/
|
|
1076
|
-
validateAndResolveTokenArgs(token, args) {
|
|
1077
|
-
let actualToken = token;
|
|
1078
|
-
if (typeof token === "function") {
|
|
1079
|
-
actualToken = getInjectableToken(token);
|
|
1080
|
-
}
|
|
1081
|
-
let realArgs = args;
|
|
1082
|
-
if (actualToken instanceof BoundInjectionToken) {
|
|
1083
|
-
realArgs = actualToken.value;
|
|
1084
|
-
} else if (actualToken instanceof FactoryInjectionToken) {
|
|
1085
|
-
if (actualToken.resolved) {
|
|
1086
|
-
realArgs = actualToken.value;
|
|
1087
|
-
} else {
|
|
1088
|
-
return [new FactoryTokenNotResolved(token.name), { actualToken }];
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
if (!actualToken.schema) {
|
|
1092
|
-
return [void 0, { actualToken, validatedArgs: realArgs }];
|
|
1093
|
-
}
|
|
1094
|
-
const validatedArgs = actualToken.schema?.safeParse(realArgs);
|
|
1095
|
-
if (validatedArgs && !validatedArgs.success) {
|
|
1096
|
-
this.logger?.error(
|
|
1097
|
-
`[ServiceLocator]#validateAndResolveTokenArgs(): Error validating args for ${actualToken.name.toString()}`,
|
|
1098
|
-
validatedArgs.error
|
|
1099
|
-
);
|
|
1100
|
-
return [new UnknownError(validatedArgs.error), { actualToken }];
|
|
1101
|
-
}
|
|
1102
|
-
return [void 0, { actualToken, validatedArgs: validatedArgs?.data }];
|
|
1103
|
-
}
|
|
1104
|
-
/**
|
|
1105
|
-
* Internal method to resolve token args and create instance name.
|
|
1106
|
-
* Handles factory token resolution and validation.
|
|
1107
|
-
*/
|
|
1108
|
-
async resolveTokenAndPrepareInstanceName(token, args) {
|
|
1109
|
-
const [err, { actualToken, validatedArgs }] = this.validateAndResolveTokenArgs(token, args);
|
|
1110
|
-
if (err instanceof UnknownError) {
|
|
1111
|
-
return [err];
|
|
1112
|
-
} else if (err instanceof FactoryTokenNotResolved && actualToken instanceof FactoryInjectionToken) {
|
|
1113
|
-
this.logger?.log(
|
|
1114
|
-
`[ServiceLocator]#resolveTokenAndPrepareInstanceName() Factory token not resolved, resolving it`
|
|
1115
|
-
);
|
|
1116
|
-
await actualToken.resolve(this.createFactoryContext());
|
|
1117
|
-
return this.resolveTokenAndPrepareInstanceName(token);
|
|
1118
|
-
}
|
|
1119
|
-
const instanceName = this.generateInstanceName(actualToken, validatedArgs);
|
|
1120
|
-
const realToken = actualToken instanceof BoundInjectionToken || actualToken instanceof FactoryInjectionToken ? actualToken.token : actualToken;
|
|
1121
|
-
return [void 0, { instanceName, validatedArgs, actualToken, realToken }];
|
|
1122
|
-
}
|
|
1123
|
-
/**
|
|
1124
|
-
* Gets an instance by its instance name, handling all the logic after instance name creation.
|
|
1125
|
-
*/
|
|
1126
|
-
async retrieveOrCreateInstanceByInstanceName(instanceName, realToken, realArgs) {
|
|
1127
|
-
const existingHolder = await this.tryGetExistingInstance(
|
|
1128
|
-
instanceName,
|
|
1129
|
-
realToken
|
|
1130
|
-
);
|
|
1131
|
-
if (existingHolder) {
|
|
1132
|
-
return existingHolder;
|
|
1133
|
-
}
|
|
1134
|
-
const result = await this.createNewInstance(
|
|
1135
|
-
instanceName,
|
|
1136
|
-
realToken,
|
|
1137
|
-
realArgs
|
|
1138
|
-
);
|
|
1139
|
-
if (result[0]) {
|
|
1140
|
-
return [result[0]];
|
|
1141
|
-
}
|
|
1142
|
-
const [, holder] = result;
|
|
1143
|
-
return this.waitForInstanceReady(holder);
|
|
1144
|
-
}
|
|
1145
|
-
/**
|
|
1146
|
-
* Attempts to retrieve an existing instance, handling request-scoped and singleton instances.
|
|
1147
|
-
* Returns null if no instance exists and a new one should be created.
|
|
1148
|
-
*/
|
|
1149
|
-
async tryGetExistingInstance(instanceName, realToken) {
|
|
1150
|
-
const requestResult = await this.tryGetRequestScopedInstance(
|
|
1151
|
-
instanceName,
|
|
1152
|
-
realToken
|
|
1153
|
-
);
|
|
1154
|
-
if (requestResult) {
|
|
1155
|
-
return requestResult;
|
|
1156
|
-
}
|
|
1157
|
-
return this.tryGetSingletonInstance(instanceName);
|
|
1158
|
-
}
|
|
1159
|
-
/**
|
|
1160
|
-
* Attempts to get a request-scoped instance if applicable.
|
|
1161
|
-
*/
|
|
1162
|
-
async tryGetRequestScopedInstance(instanceName, realToken) {
|
|
1163
|
-
if (!this.registry.has(realToken)) {
|
|
1164
|
-
return null;
|
|
1165
|
-
}
|
|
1166
|
-
const record = this.registry.get(realToken);
|
|
1167
|
-
if (record.scope !== "Request" /* Request */) {
|
|
1168
|
-
return null;
|
|
1169
|
-
}
|
|
1170
|
-
if (!this.currentRequestContext) {
|
|
1171
|
-
this.logger?.log(
|
|
1172
|
-
`[ServiceLocator] No current request context available for request-scoped service ${instanceName}`
|
|
1173
|
-
);
|
|
1174
|
-
return [new UnknownError("InstanceNotFound" /* InstanceNotFound */)];
|
|
1175
|
-
}
|
|
1176
|
-
const requestHolder = this.currentRequestContext.get(instanceName);
|
|
1177
|
-
if (!requestHolder) {
|
|
1178
|
-
return null;
|
|
1179
|
-
}
|
|
1180
|
-
return this.waitForInstanceReady(requestHolder);
|
|
1181
|
-
}
|
|
1182
|
-
/**
|
|
1183
|
-
* Attempts to get a singleton instance from the manager.
|
|
1184
|
-
*/
|
|
1185
|
-
async tryGetSingletonInstance(instanceName) {
|
|
1186
|
-
const [error, holder] = this.manager.get(instanceName);
|
|
1187
|
-
if (!error) {
|
|
1188
|
-
return this.waitForInstanceReady(holder);
|
|
1189
|
-
}
|
|
1190
|
-
switch (error.code) {
|
|
1191
|
-
case "InstanceDestroying" /* InstanceDestroying */:
|
|
1192
|
-
this.logger?.log(
|
|
1193
|
-
`[ServiceLocator] Instance ${instanceName} is being destroyed, waiting...`
|
|
1194
|
-
);
|
|
1195
|
-
await holder?.destroyPromise;
|
|
1196
|
-
return this.tryGetSingletonInstance(instanceName);
|
|
1197
|
-
case "InstanceExpired" /* InstanceExpired */:
|
|
1198
|
-
this.logger?.log(
|
|
1199
|
-
`[ServiceLocator] Instance ${instanceName} expired, invalidating...`
|
|
1200
|
-
);
|
|
1201
|
-
await this.invalidate(instanceName);
|
|
1202
|
-
return this.tryGetSingletonInstance(instanceName);
|
|
1203
|
-
case "InstanceNotFound" /* InstanceNotFound */:
|
|
1204
|
-
return null;
|
|
1205
|
-
// Instance doesn't exist, should create new one
|
|
1206
|
-
default:
|
|
1207
|
-
return [error];
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
/**
|
|
1211
|
-
* Waits for an instance holder to be ready and returns the appropriate result.
|
|
1212
|
-
*/
|
|
1213
|
-
async waitForInstanceReady(holder) {
|
|
1214
|
-
switch (holder.status) {
|
|
1215
|
-
case "creating" /* Creating */:
|
|
1216
|
-
await holder.creationPromise;
|
|
1217
|
-
return this.waitForInstanceReady(holder);
|
|
1218
|
-
case "destroying" /* Destroying */:
|
|
1219
|
-
return [new UnknownError("InstanceDestroying" /* InstanceDestroying */)];
|
|
1220
|
-
case "error" /* Error */:
|
|
1221
|
-
return [holder.instance];
|
|
1222
|
-
case "created" /* Created */:
|
|
1223
|
-
return [void 0, holder];
|
|
1224
|
-
default:
|
|
1225
|
-
return [new UnknownError("InstanceNotFound" /* InstanceNotFound */)];
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
/**
|
|
1229
|
-
* Emits events to listeners for instance lifecycle events.
|
|
1230
|
-
*/
|
|
1231
|
-
emitInstanceEvent(name, event = "create") {
|
|
1232
|
-
this.logger?.log(
|
|
1233
|
-
`[ServiceLocator]#emitInstanceEvent() Notifying listeners for ${name} with event ${event}`
|
|
1234
|
-
);
|
|
1235
|
-
return this.eventBus.emit(name, event);
|
|
1236
|
-
}
|
|
1237
|
-
/**
|
|
1238
|
-
* Creates a new instance for the given token and arguments.
|
|
1239
|
-
*/
|
|
1240
|
-
async createNewInstance(instanceName, realToken, args) {
|
|
1241
|
-
this.logger?.log(
|
|
1242
|
-
`[ServiceLocator]#createNewInstance() Creating instance for ${instanceName}`
|
|
1243
|
-
);
|
|
1244
|
-
if (this.registry.has(realToken)) {
|
|
1245
|
-
return this.instantiateServiceFromRegistry(
|
|
1246
|
-
instanceName,
|
|
1247
|
-
realToken,
|
|
1248
|
-
args
|
|
1249
|
-
);
|
|
1250
|
-
} else {
|
|
1251
|
-
return [new FactoryNotFound(realToken.name.toString())];
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
/**
|
|
1255
|
-
* Instantiates a service from the registry using the service instantiator.
|
|
1256
|
-
*/
|
|
1257
|
-
instantiateServiceFromRegistry(instanceName, token, args) {
|
|
1258
|
-
this.logger?.log(
|
|
1259
|
-
`[ServiceLocator]#instantiateServiceFromRegistry(): Creating instance for ${instanceName} from abstract factory`
|
|
1260
|
-
);
|
|
1261
|
-
const ctx = this.createFactoryContext(
|
|
1262
|
-
this.currentRequestContext || void 0
|
|
1263
|
-
);
|
|
1264
|
-
let record = this.registry.get(token);
|
|
1265
|
-
let { scope, type } = record;
|
|
1266
|
-
const [deferred, holder] = this.manager.createCreatingHolder(
|
|
1267
|
-
instanceName,
|
|
1268
|
-
type,
|
|
1269
|
-
scope,
|
|
1270
|
-
ctx.deps,
|
|
1271
|
-
Infinity
|
|
1272
|
-
);
|
|
1273
|
-
this.serviceInstantiator.instantiateService(ctx, record, args).then(async ([error, instance]) => {
|
|
1274
|
-
await this.handleInstantiationResult(
|
|
1275
|
-
instanceName,
|
|
1276
|
-
holder,
|
|
1277
|
-
ctx,
|
|
1278
|
-
deferred,
|
|
1279
|
-
scope,
|
|
1280
|
-
error,
|
|
1281
|
-
instance
|
|
1282
|
-
);
|
|
1283
|
-
}).catch(async (error) => {
|
|
1284
|
-
await this.handleInstantiationError(
|
|
1285
|
-
instanceName,
|
|
1286
|
-
holder,
|
|
1287
|
-
deferred,
|
|
1288
|
-
scope,
|
|
1289
|
-
error
|
|
1290
|
-
);
|
|
1291
|
-
});
|
|
1292
|
-
this.storeInstanceByScope(scope, instanceName, holder);
|
|
1293
|
-
return [void 0, holder];
|
|
1294
|
-
}
|
|
1295
|
-
/**
|
|
1296
|
-
* Handles the result of service instantiation.
|
|
1297
|
-
*/
|
|
1298
|
-
async handleInstantiationResult(instanceName, holder, ctx, deferred, scope, error, instance) {
|
|
1299
|
-
holder.destroyListeners = ctx.getDestroyListeners();
|
|
1300
|
-
holder.creationPromise = null;
|
|
1301
|
-
if (error) {
|
|
1302
|
-
await this.handleInstantiationError(
|
|
1303
|
-
instanceName,
|
|
1304
|
-
holder,
|
|
1305
|
-
deferred,
|
|
1306
|
-
scope,
|
|
1307
|
-
error
|
|
1308
|
-
);
|
|
1309
|
-
} else {
|
|
1310
|
-
await this.handleInstantiationSuccess(
|
|
1311
|
-
instanceName,
|
|
1312
|
-
holder,
|
|
1313
|
-
ctx,
|
|
1314
|
-
deferred,
|
|
1315
|
-
instance
|
|
1316
|
-
);
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
/**
|
|
1320
|
-
* Handles successful service instantiation.
|
|
1552
|
+
* @param type The injectable type
|
|
1553
|
+
* @param scope The injectable scope
|
|
1554
|
+
* @param deps Optional set of dependencies
|
|
1555
|
+
* @returns The created holder
|
|
1321
1556
|
*/
|
|
1322
|
-
|
|
1323
|
-
holder.instance
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
);
|
|
1334
|
-
});
|
|
1335
|
-
}
|
|
1336
|
-
await this.emitInstanceEvent(instanceName);
|
|
1337
|
-
deferred.resolve([void 0, instance]);
|
|
1557
|
+
storeCreatedHolder(name, instance, type, scope, deps = /* @__PURE__ */ new Set()) {
|
|
1558
|
+
const holder = this.createCreatedHolder(name, instance, type, scope, deps);
|
|
1559
|
+
this._holders.set(name, holder);
|
|
1560
|
+
return holder;
|
|
1561
|
+
}
|
|
1562
|
+
};
|
|
1563
|
+
|
|
1564
|
+
// src/token-processor.mts
|
|
1565
|
+
var TokenProcessor = class {
|
|
1566
|
+
constructor(logger = null) {
|
|
1567
|
+
this.logger = logger;
|
|
1338
1568
|
}
|
|
1339
1569
|
/**
|
|
1340
|
-
*
|
|
1570
|
+
* Validates and resolves token arguments, handling factory token resolution and validation.
|
|
1341
1571
|
*/
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
);
|
|
1347
|
-
holder.status = "error" /* Error */;
|
|
1348
|
-
holder.instance = error;
|
|
1349
|
-
holder.creationPromise = null;
|
|
1350
|
-
if (scope === "Singleton" /* Singleton */) {
|
|
1351
|
-
setTimeout(() => this.invalidate(instanceName), 10);
|
|
1572
|
+
validateAndResolveTokenArgs(token, args) {
|
|
1573
|
+
let actualToken = token;
|
|
1574
|
+
if (typeof token === "function") {
|
|
1575
|
+
actualToken = getInjectableToken(token);
|
|
1352
1576
|
}
|
|
1353
|
-
|
|
1577
|
+
let realArgs = args;
|
|
1578
|
+
if (actualToken instanceof BoundInjectionToken) {
|
|
1579
|
+
realArgs = actualToken.value;
|
|
1580
|
+
} else if (actualToken instanceof FactoryInjectionToken) {
|
|
1581
|
+
if (actualToken.resolved) {
|
|
1582
|
+
realArgs = actualToken.value;
|
|
1583
|
+
} else {
|
|
1584
|
+
return [DIError.factoryTokenNotResolved(token.name), { actualToken }];
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
if (!actualToken.schema) {
|
|
1588
|
+
return [void 0, { actualToken, validatedArgs: realArgs }];
|
|
1589
|
+
}
|
|
1590
|
+
const validatedArgs = actualToken.schema?.safeParse(realArgs);
|
|
1591
|
+
if (validatedArgs && !validatedArgs.success) {
|
|
1592
|
+
this.logger?.error(
|
|
1593
|
+
`[TokenProcessor]#validateAndResolveTokenArgs(): Error validating args for ${actualToken.name.toString()}`,
|
|
1594
|
+
validatedArgs.error
|
|
1595
|
+
);
|
|
1596
|
+
return [DIError.unknown(validatedArgs.error), { actualToken }];
|
|
1597
|
+
}
|
|
1598
|
+
return [void 0, { actualToken, validatedArgs: validatedArgs?.data }];
|
|
1354
1599
|
}
|
|
1355
1600
|
/**
|
|
1356
|
-
*
|
|
1601
|
+
* Generates a unique instance name based on token and arguments.
|
|
1357
1602
|
*/
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
this.logger?.debug(
|
|
1362
|
-
`[ServiceLocator] Setting singleton instance for ${instanceName}`
|
|
1363
|
-
);
|
|
1364
|
-
this.manager.set(instanceName, holder);
|
|
1365
|
-
break;
|
|
1366
|
-
case "Request" /* Request */:
|
|
1367
|
-
if (this.currentRequestContext) {
|
|
1368
|
-
this.logger?.debug(
|
|
1369
|
-
`[ServiceLocator] Setting request-scoped instance for ${instanceName}`
|
|
1370
|
-
);
|
|
1371
|
-
this.currentRequestContext.addInstance(
|
|
1372
|
-
instanceName,
|
|
1373
|
-
holder.instance,
|
|
1374
|
-
holder
|
|
1375
|
-
);
|
|
1376
|
-
}
|
|
1377
|
-
break;
|
|
1603
|
+
generateInstanceName(token, args) {
|
|
1604
|
+
if (!args) {
|
|
1605
|
+
return token.toString();
|
|
1378
1606
|
}
|
|
1607
|
+
const formattedArgs = Object.entries(args).sort(([keyA], [keyB]) => keyA.localeCompare(keyB)).map(([key, value]) => `${key}=${this.formatArgValue(value)}`).join(",");
|
|
1608
|
+
return `${token.toString()}:${formattedArgs.replaceAll(/"/g, "").replaceAll(/:/g, "=")}`;
|
|
1379
1609
|
}
|
|
1380
1610
|
/**
|
|
1381
|
-
*
|
|
1611
|
+
* Formats a single argument value for instance name generation.
|
|
1382
1612
|
*/
|
|
1383
|
-
|
|
1384
|
-
if (
|
|
1385
|
-
|
|
1386
|
-
if (prePreparedInstance !== void 0) {
|
|
1387
|
-
this.logger?.debug(
|
|
1388
|
-
`[ServiceLocator] Using pre-prepared instance ${instanceName} from request context ${contextHolder.requestId}`
|
|
1389
|
-
);
|
|
1390
|
-
deps.add(instanceName);
|
|
1391
|
-
return prePreparedInstance;
|
|
1392
|
-
}
|
|
1613
|
+
formatArgValue(value) {
|
|
1614
|
+
if (typeof value === "function") {
|
|
1615
|
+
return `fn_${value.name}(${value.length})`;
|
|
1393
1616
|
}
|
|
1394
|
-
if (
|
|
1395
|
-
|
|
1396
|
-
if (prePreparedInstance !== void 0) {
|
|
1397
|
-
this.logger?.debug(
|
|
1398
|
-
`[ServiceLocator] Using pre-prepared instance ${instanceName} from current request context ${this.currentRequestContext.requestId}`
|
|
1399
|
-
);
|
|
1400
|
-
deps.add(instanceName);
|
|
1401
|
-
return prePreparedInstance;
|
|
1402
|
-
}
|
|
1617
|
+
if (typeof value === "symbol") {
|
|
1618
|
+
return value.toString();
|
|
1403
1619
|
}
|
|
1404
|
-
return
|
|
1620
|
+
return JSON.stringify(value).slice(0, 40);
|
|
1405
1621
|
}
|
|
1406
1622
|
/**
|
|
1407
1623
|
* Creates a factory context for dependency injection during service instantiation.
|
|
1408
|
-
* @param
|
|
1624
|
+
* @param serviceLocator Reference to the service locator for dependency resolution
|
|
1409
1625
|
*/
|
|
1410
|
-
createFactoryContext(
|
|
1626
|
+
createFactoryContext(serviceLocator) {
|
|
1411
1627
|
const destroyListeners = /* @__PURE__ */ new Set();
|
|
1412
1628
|
const deps = /* @__PURE__ */ new Set();
|
|
1413
|
-
const self = this;
|
|
1414
1629
|
function addDestroyListener(listener) {
|
|
1415
1630
|
destroyListeners.add(listener);
|
|
1416
1631
|
}
|
|
@@ -1420,20 +1635,11 @@ var ServiceLocator = class {
|
|
|
1420
1635
|
return {
|
|
1421
1636
|
// @ts-expect-error This is correct type
|
|
1422
1637
|
async inject(token, args) {
|
|
1423
|
-
const
|
|
1424
|
-
const prePreparedInstance = self.tryGetPrePreparedInstance(
|
|
1425
|
-
instanceName,
|
|
1426
|
-
contextHolder,
|
|
1427
|
-
deps
|
|
1428
|
-
);
|
|
1429
|
-
if (prePreparedInstance !== void 0) {
|
|
1430
|
-
return prePreparedInstance;
|
|
1431
|
-
}
|
|
1432
|
-
const [error, instance] = await self.getInstance(
|
|
1638
|
+
const [error, instance] = await serviceLocator.getInstance(
|
|
1433
1639
|
token,
|
|
1434
1640
|
args,
|
|
1435
|
-
({ instanceName
|
|
1436
|
-
deps.add(
|
|
1641
|
+
({ instanceName }) => {
|
|
1642
|
+
deps.add(instanceName);
|
|
1437
1643
|
}
|
|
1438
1644
|
);
|
|
1439
1645
|
if (error) {
|
|
@@ -1443,31 +1649,204 @@ var ServiceLocator = class {
|
|
|
1443
1649
|
},
|
|
1444
1650
|
addDestroyListener,
|
|
1445
1651
|
getDestroyListeners,
|
|
1446
|
-
locator:
|
|
1652
|
+
locator: serviceLocator,
|
|
1447
1653
|
deps
|
|
1448
1654
|
};
|
|
1449
1655
|
}
|
|
1450
1656
|
/**
|
|
1451
|
-
*
|
|
1657
|
+
* Tries to get a pre-prepared instance from request contexts.
|
|
1452
1658
|
*/
|
|
1453
|
-
|
|
1454
|
-
if (
|
|
1455
|
-
|
|
1659
|
+
tryGetPrePreparedInstance(instanceName, contextHolder, deps, currentRequestContext) {
|
|
1660
|
+
if (contextHolder && contextHolder.priority > 0) {
|
|
1661
|
+
const prePreparedInstance = contextHolder.get(instanceName)?.instance;
|
|
1662
|
+
if (prePreparedInstance !== void 0) {
|
|
1663
|
+
this.logger?.debug(
|
|
1664
|
+
`[TokenProcessor] Using pre-prepared instance ${instanceName} from request context ${contextHolder.requestId}`
|
|
1665
|
+
);
|
|
1666
|
+
deps.add(instanceName);
|
|
1667
|
+
return prePreparedInstance;
|
|
1668
|
+
}
|
|
1456
1669
|
}
|
|
1457
|
-
|
|
1458
|
-
|
|
1670
|
+
if (currentRequestContext && currentRequestContext !== contextHolder) {
|
|
1671
|
+
const prePreparedInstance = currentRequestContext.get(instanceName)?.instance;
|
|
1672
|
+
if (prePreparedInstance !== void 0) {
|
|
1673
|
+
this.logger?.debug(
|
|
1674
|
+
`[TokenProcessor] Using pre-prepared instance ${instanceName} from current request context ${currentRequestContext.requestId}`
|
|
1675
|
+
);
|
|
1676
|
+
deps.add(instanceName);
|
|
1677
|
+
return prePreparedInstance;
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
return void 0;
|
|
1459
1681
|
}
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1682
|
+
};
|
|
1683
|
+
|
|
1684
|
+
// src/service-locator.mts
|
|
1685
|
+
var ServiceLocator = class {
|
|
1686
|
+
constructor(registry = globalRegistry, logger = null, injectors = defaultInjectors) {
|
|
1687
|
+
this.registry = registry;
|
|
1688
|
+
this.logger = logger;
|
|
1689
|
+
this.injectors = injectors;
|
|
1690
|
+
this.eventBus = new ServiceLocatorEventBus(logger);
|
|
1691
|
+
this.manager = new ServiceLocatorManager(logger);
|
|
1692
|
+
this.serviceInstantiator = new ServiceInstantiator(injectors);
|
|
1693
|
+
this.tokenProcessor = new TokenProcessor(logger);
|
|
1694
|
+
this.requestContextManager = new RequestContextManager(logger);
|
|
1695
|
+
this.serviceInvalidator = new ServiceInvalidator(
|
|
1696
|
+
this.manager,
|
|
1697
|
+
this.requestContextManager,
|
|
1698
|
+
this.eventBus,
|
|
1699
|
+
logger
|
|
1700
|
+
);
|
|
1701
|
+
this.instanceResolver = new InstanceResolver(
|
|
1702
|
+
this.registry,
|
|
1703
|
+
this.manager,
|
|
1704
|
+
this.serviceInstantiator,
|
|
1705
|
+
this.tokenProcessor,
|
|
1706
|
+
logger,
|
|
1707
|
+
this
|
|
1708
|
+
);
|
|
1709
|
+
}
|
|
1710
|
+
eventBus;
|
|
1711
|
+
manager;
|
|
1712
|
+
serviceInstantiator;
|
|
1713
|
+
tokenProcessor;
|
|
1714
|
+
requestContextManager;
|
|
1715
|
+
serviceInvalidator;
|
|
1716
|
+
instanceResolver;
|
|
1717
|
+
// ============================================================================
|
|
1718
|
+
// PUBLIC METHODS
|
|
1719
|
+
// ============================================================================
|
|
1720
|
+
getEventBus() {
|
|
1721
|
+
return this.eventBus;
|
|
1722
|
+
}
|
|
1723
|
+
getManager() {
|
|
1724
|
+
return this.manager;
|
|
1725
|
+
}
|
|
1726
|
+
getRequestContexts() {
|
|
1727
|
+
return this.requestContextManager.getRequestContexts();
|
|
1728
|
+
}
|
|
1729
|
+
getRequestContextManager() {
|
|
1730
|
+
return this.requestContextManager;
|
|
1731
|
+
}
|
|
1732
|
+
getServiceInvalidator() {
|
|
1733
|
+
return this.serviceInvalidator;
|
|
1734
|
+
}
|
|
1735
|
+
getInstanceIdentifier(token, args) {
|
|
1736
|
+
const [err, { actualToken, validatedArgs }] = this.tokenProcessor.validateAndResolveTokenArgs(token, args);
|
|
1737
|
+
if (err) {
|
|
1738
|
+
throw err;
|
|
1466
1739
|
}
|
|
1467
|
-
|
|
1468
|
-
|
|
1740
|
+
return this.tokenProcessor.generateInstanceName(actualToken, validatedArgs);
|
|
1741
|
+
}
|
|
1742
|
+
async getInstance(token, args, onPrepare) {
|
|
1743
|
+
const [err, data] = await this.instanceResolver.resolveInstance(
|
|
1744
|
+
token,
|
|
1745
|
+
args,
|
|
1746
|
+
this.requestContextManager.getCurrentRequestContext() || void 0
|
|
1747
|
+
);
|
|
1748
|
+
if (err) {
|
|
1749
|
+
return [err];
|
|
1469
1750
|
}
|
|
1470
|
-
|
|
1751
|
+
if (onPrepare) {
|
|
1752
|
+
const instanceName = this.getInstanceIdentifier(token, args);
|
|
1753
|
+
const [tokenErr, { actualToken, validatedArgs }] = this.tokenProcessor.validateAndResolveTokenArgs(token, args);
|
|
1754
|
+
if (!tokenErr) {
|
|
1755
|
+
onPrepare({ instanceName, actualToken, validatedArgs });
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
return [void 0, data];
|
|
1759
|
+
}
|
|
1760
|
+
async getOrThrowInstance(token, args) {
|
|
1761
|
+
const [error, instance] = await this.getInstance(token, args);
|
|
1762
|
+
if (error) {
|
|
1763
|
+
throw error;
|
|
1764
|
+
}
|
|
1765
|
+
return instance;
|
|
1766
|
+
}
|
|
1767
|
+
getSyncInstance(token, args) {
|
|
1768
|
+
return this.instanceResolver.getSyncInstance(
|
|
1769
|
+
token,
|
|
1770
|
+
args,
|
|
1771
|
+
this.requestContextManager.getCurrentRequestContext()
|
|
1772
|
+
);
|
|
1773
|
+
}
|
|
1774
|
+
invalidate(service, round = 1) {
|
|
1775
|
+
return this.serviceInvalidator.invalidate(service, round);
|
|
1776
|
+
}
|
|
1777
|
+
/**
|
|
1778
|
+
* Gracefully clears all services in the ServiceLocator using invalidation logic.
|
|
1779
|
+
* This method respects service dependencies and ensures proper cleanup order.
|
|
1780
|
+
* Services that depend on others will be invalidated first, then their dependencies.
|
|
1781
|
+
*
|
|
1782
|
+
* @param options Optional configuration for the clearing process
|
|
1783
|
+
* @returns Promise that resolves when all services have been cleared
|
|
1784
|
+
*/
|
|
1785
|
+
async clearAll(options = {}) {
|
|
1786
|
+
return this.serviceInvalidator.clearAll(options);
|
|
1787
|
+
}
|
|
1788
|
+
// ============================================================================
|
|
1789
|
+
// REQUEST CONTEXT MANAGEMENT
|
|
1790
|
+
// ============================================================================
|
|
1791
|
+
/**
|
|
1792
|
+
* Begins a new request context with the given parameters.
|
|
1793
|
+
* @param requestId Unique identifier for this request
|
|
1794
|
+
* @param metadata Optional metadata for the request
|
|
1795
|
+
* @param priority Priority for resolution (higher = more priority)
|
|
1796
|
+
* @returns The created request context holder
|
|
1797
|
+
*/
|
|
1798
|
+
beginRequest(requestId, metadata, priority = 100) {
|
|
1799
|
+
return this.requestContextManager.beginRequest(
|
|
1800
|
+
requestId,
|
|
1801
|
+
metadata,
|
|
1802
|
+
priority
|
|
1803
|
+
);
|
|
1804
|
+
}
|
|
1805
|
+
/**
|
|
1806
|
+
* Ends a request context and cleans up all associated instances.
|
|
1807
|
+
* @param requestId The request ID to end
|
|
1808
|
+
*/
|
|
1809
|
+
async endRequest(requestId) {
|
|
1810
|
+
return this.requestContextManager.endRequest(requestId);
|
|
1811
|
+
}
|
|
1812
|
+
/**
|
|
1813
|
+
* Gets the current request context.
|
|
1814
|
+
* @returns The current request context holder or null
|
|
1815
|
+
*/
|
|
1816
|
+
getCurrentRequestContext() {
|
|
1817
|
+
return this.requestContextManager.getCurrentRequestContext();
|
|
1818
|
+
}
|
|
1819
|
+
/**
|
|
1820
|
+
* Sets the current request context.
|
|
1821
|
+
* @param requestId The request ID to set as current
|
|
1822
|
+
*/
|
|
1823
|
+
setCurrentRequestContext(requestId) {
|
|
1824
|
+
return this.requestContextManager.setCurrentRequestContext(requestId);
|
|
1825
|
+
}
|
|
1826
|
+
/**
|
|
1827
|
+
* Waits for all services to settle (either created, destroyed, or error state).
|
|
1828
|
+
*/
|
|
1829
|
+
async ready() {
|
|
1830
|
+
return this.serviceInvalidator.ready();
|
|
1831
|
+
}
|
|
1832
|
+
/**
|
|
1833
|
+
* Helper method for TokenProcessor to access pre-prepared instances.
|
|
1834
|
+
* This is needed for the factory context creation.
|
|
1835
|
+
*/
|
|
1836
|
+
tryGetPrePreparedInstance(instanceName, contextHolder, deps) {
|
|
1837
|
+
return this.tokenProcessor.tryGetPrePreparedInstance(
|
|
1838
|
+
instanceName,
|
|
1839
|
+
contextHolder,
|
|
1840
|
+
deps,
|
|
1841
|
+
this.requestContextManager.getCurrentRequestContext()
|
|
1842
|
+
);
|
|
1843
|
+
}
|
|
1844
|
+
/**
|
|
1845
|
+
* Helper method for InstanceResolver to generate instance names.
|
|
1846
|
+
* This is needed for the factory context creation.
|
|
1847
|
+
*/
|
|
1848
|
+
generateInstanceName(token, args) {
|
|
1849
|
+
return this.tokenProcessor.generateInstanceName(token, args);
|
|
1471
1850
|
}
|
|
1472
1851
|
};
|
|
1473
1852
|
|
|
@@ -1506,15 +1885,54 @@ var _Container = class _Container {
|
|
|
1506
1885
|
* Invalidates a service and its dependencies
|
|
1507
1886
|
*/
|
|
1508
1887
|
async invalidate(service) {
|
|
1509
|
-
const
|
|
1510
|
-
if (holderMap.size === 0) {
|
|
1511
|
-
return;
|
|
1512
|
-
}
|
|
1513
|
-
const holder = holderMap.values().next().value;
|
|
1888
|
+
const holder = this.getHolderByInstance(service);
|
|
1514
1889
|
if (holder) {
|
|
1515
1890
|
await this.serviceLocator.invalidate(holder.name);
|
|
1891
|
+
} else {
|
|
1892
|
+
const requestHolder = this.getRequestHolderByInstance(service);
|
|
1893
|
+
if (requestHolder) {
|
|
1894
|
+
await this.serviceLocator.invalidate(requestHolder.name);
|
|
1895
|
+
}
|
|
1516
1896
|
}
|
|
1517
1897
|
}
|
|
1898
|
+
/**
|
|
1899
|
+
* Gets a service holder by instance (reverse lookup)
|
|
1900
|
+
*/
|
|
1901
|
+
getHolderByInstance(instance) {
|
|
1902
|
+
const holderMap = Array.from(
|
|
1903
|
+
this.serviceLocator.getManager().filter((holder) => holder.instance === instance).values()
|
|
1904
|
+
);
|
|
1905
|
+
return holderMap.length > 0 ? holderMap[0] : null;
|
|
1906
|
+
}
|
|
1907
|
+
getRequestHolderByInstance(instance) {
|
|
1908
|
+
const requestContexts = this.serviceLocator.getRequestContextManager().getRequestContexts();
|
|
1909
|
+
if (requestContexts) {
|
|
1910
|
+
for (const requestContext of requestContexts.values()) {
|
|
1911
|
+
for (const holder of requestContext.holders.values()) {
|
|
1912
|
+
if (holder.instance === instance) {
|
|
1913
|
+
return holder;
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
return null;
|
|
1919
|
+
}
|
|
1920
|
+
/**
|
|
1921
|
+
* Checks if a service is registered in the container
|
|
1922
|
+
*/
|
|
1923
|
+
isRegistered(token) {
|
|
1924
|
+
try {
|
|
1925
|
+
return this.serviceLocator.getInstanceIdentifier(token) !== null;
|
|
1926
|
+
} catch {
|
|
1927
|
+
return false;
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
/**
|
|
1931
|
+
* Disposes the container and cleans up all resources
|
|
1932
|
+
*/
|
|
1933
|
+
async dispose() {
|
|
1934
|
+
await this.serviceLocator.clearAll();
|
|
1935
|
+
}
|
|
1518
1936
|
/**
|
|
1519
1937
|
* Waits for all pending operations to complete
|
|
1520
1938
|
*/
|
|
@@ -1560,7 +1978,7 @@ var _Container = class _Container {
|
|
|
1560
1978
|
* This is useful for testing or resetting the container state.
|
|
1561
1979
|
*/
|
|
1562
1980
|
clear() {
|
|
1563
|
-
this.serviceLocator.
|
|
1981
|
+
return this.serviceLocator.clearAll();
|
|
1564
1982
|
}
|
|
1565
1983
|
};
|
|
1566
1984
|
_init = __decoratorStart(null);
|