@navios/di 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +447 -212
- package/lib/_tsup-dts-rollup.d.ts +447 -212
- package/lib/chunk-44F3LXW5.mjs +2043 -0
- package/lib/chunk-44F3LXW5.mjs.map +1 -0
- package/lib/index.d.mts +6 -4
- package/lib/index.d.ts +6 -4
- package/lib/index.js +1199 -773
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +4 -1599
- package/lib/index.mjs.map +1 -1
- package/lib/testing/index.d.mts +2 -0
- package/lib/testing/index.d.ts +2 -0
- package/lib/testing/index.js +2060 -0
- package/lib/testing/index.js.map +1 -0
- package/lib/testing/index.mjs +73 -0
- package/lib/testing/index.mjs.map +1 -0
- package/package.json +11 -1
- 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-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 +70 -10
- 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-instance-holder.mts +0 -4
- package/src/service-locator-manager.mts +10 -40
- package/src/service-locator.mts +86 -782
- package/src/testing/README.md +80 -0
- package/src/testing/__tests__/test-container.spec.mts +173 -0
- package/src/testing/index.mts +1 -0
- package/src/testing/test-container.mts +120 -0
- 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/tsup.config.mts +1 -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/index.js
CHANGED
|
@@ -181,6 +181,7 @@ function Factory({
|
|
|
181
181
|
function Injectable({
|
|
182
182
|
scope = "Singleton" /* Singleton */,
|
|
183
183
|
token,
|
|
184
|
+
schema,
|
|
184
185
|
registry = globalRegistry
|
|
185
186
|
} = {}) {
|
|
186
187
|
return (target, context) => {
|
|
@@ -189,79 +190,73 @@ function Injectable({
|
|
|
189
190
|
"[ServiceLocator] @Injectable decorator can only be used on classes."
|
|
190
191
|
);
|
|
191
192
|
}
|
|
192
|
-
|
|
193
|
+
if (schema && token) {
|
|
194
|
+
throw new Error(
|
|
195
|
+
"[ServiceLocator] @Injectable decorator cannot have both a token and a schema"
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
let injectableToken = token ?? InjectionToken.create(target, schema);
|
|
193
199
|
registry.set(injectableToken, scope, target, "Class" /* Class */);
|
|
194
200
|
target[InjectableTokenMeta] = injectableToken;
|
|
195
201
|
return target;
|
|
196
202
|
};
|
|
197
203
|
}
|
|
198
204
|
|
|
199
|
-
// src/errors/
|
|
200
|
-
var
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
this.
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
205
|
+
// src/errors/di-error.mts
|
|
206
|
+
var DIErrorCode = /* @__PURE__ */ ((DIErrorCode2) => {
|
|
207
|
+
DIErrorCode2["FactoryNotFound"] = "FactoryNotFound";
|
|
208
|
+
DIErrorCode2["FactoryTokenNotResolved"] = "FactoryTokenNotResolved";
|
|
209
|
+
DIErrorCode2["InstanceNotFound"] = "InstanceNotFound";
|
|
210
|
+
DIErrorCode2["InstanceDestroying"] = "InstanceDestroying";
|
|
211
|
+
DIErrorCode2["UnknownError"] = "UnknownError";
|
|
212
|
+
return DIErrorCode2;
|
|
213
|
+
})(DIErrorCode || {});
|
|
214
|
+
var DIError = class _DIError extends Error {
|
|
215
|
+
code;
|
|
216
|
+
context;
|
|
217
|
+
constructor(code, message, context) {
|
|
218
|
+
super(message);
|
|
219
|
+
this.name = "DIError";
|
|
220
|
+
this.code = code;
|
|
221
|
+
this.context = context;
|
|
222
|
+
}
|
|
223
|
+
// Static factory methods for common error types
|
|
224
|
+
static factoryNotFound(name) {
|
|
225
|
+
return new _DIError(
|
|
226
|
+
"FactoryNotFound" /* FactoryNotFound */,
|
|
227
|
+
`Factory ${name} not found`,
|
|
228
|
+
{ name }
|
|
229
|
+
);
|
|
224
230
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
this.name = name;
|
|
231
|
+
static factoryTokenNotResolved(token) {
|
|
232
|
+
return new _DIError(
|
|
233
|
+
"FactoryTokenNotResolved" /* FactoryTokenNotResolved */,
|
|
234
|
+
`Factory token not resolved: ${token?.toString() ?? "unknown"}`,
|
|
235
|
+
{ token }
|
|
236
|
+
);
|
|
232
237
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
super(`Instance ${name} expired`);
|
|
240
|
-
this.name = name;
|
|
238
|
+
static instanceNotFound(name) {
|
|
239
|
+
return new _DIError(
|
|
240
|
+
"InstanceNotFound" /* InstanceNotFound */,
|
|
241
|
+
`Instance ${name} not found`,
|
|
242
|
+
{ name }
|
|
243
|
+
);
|
|
241
244
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
super(`Instance ${name} not found`);
|
|
249
|
-
this.name = name;
|
|
245
|
+
static instanceDestroying(name) {
|
|
246
|
+
return new _DIError(
|
|
247
|
+
"InstanceDestroying" /* InstanceDestroying */,
|
|
248
|
+
`Instance ${name} destroying`,
|
|
249
|
+
{ name }
|
|
250
|
+
);
|
|
250
251
|
}
|
|
251
|
-
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
// src/errors/unknown-error.mts
|
|
255
|
-
var UnknownError = class extends Error {
|
|
256
|
-
code = "UnknownError" /* UnknownError */;
|
|
257
|
-
parent;
|
|
258
|
-
constructor(message) {
|
|
252
|
+
static unknown(message, context) {
|
|
259
253
|
if (message instanceof Error) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
254
|
+
return new _DIError("UnknownError" /* UnknownError */, message.message, {
|
|
255
|
+
...context,
|
|
256
|
+
parent: message
|
|
257
|
+
});
|
|
263
258
|
}
|
|
264
|
-
|
|
259
|
+
return new _DIError("UnknownError" /* UnknownError */, message, context);
|
|
265
260
|
}
|
|
266
261
|
};
|
|
267
262
|
|
|
@@ -283,29 +278,58 @@ function getInjectors() {
|
|
|
283
278
|
}
|
|
284
279
|
let promiseCollector = null;
|
|
285
280
|
let injectState = null;
|
|
286
|
-
function
|
|
281
|
+
function getRequest(token, args) {
|
|
287
282
|
if (!injectState) {
|
|
288
283
|
throw new Error(
|
|
289
|
-
"[Injector] Trying to
|
|
284
|
+
"[Injector] Trying to make a request outside of a injectable context"
|
|
290
285
|
);
|
|
291
286
|
}
|
|
292
287
|
if (injectState.isFrozen) {
|
|
293
288
|
const idx = injectState.currentIndex++;
|
|
294
|
-
const
|
|
295
|
-
if (
|
|
289
|
+
const request2 = injectState.requests[idx];
|
|
290
|
+
if (request2.token !== token) {
|
|
296
291
|
throw new Error(
|
|
297
|
-
`[Injector] Wrong token order. Expected ${
|
|
292
|
+
`[Injector] Wrong token order. Expected ${request2.token.toString()} but got ${token.toString()}`
|
|
298
293
|
);
|
|
299
294
|
}
|
|
300
|
-
return
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
295
|
+
return request2;
|
|
296
|
+
}
|
|
297
|
+
let result = null;
|
|
298
|
+
let error = null;
|
|
299
|
+
const promise = getFactoryContext().inject(token, args).then((r) => {
|
|
300
|
+
result = r;
|
|
301
|
+
return r;
|
|
302
|
+
}).catch((e) => {
|
|
303
|
+
error = e;
|
|
306
304
|
});
|
|
305
|
+
const request = {
|
|
306
|
+
token,
|
|
307
|
+
promise,
|
|
308
|
+
get result() {
|
|
309
|
+
return result;
|
|
310
|
+
},
|
|
311
|
+
get error() {
|
|
312
|
+
return error;
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
injectState.requests.push(request);
|
|
307
316
|
injectState.currentIndex++;
|
|
308
|
-
return
|
|
317
|
+
return request;
|
|
318
|
+
}
|
|
319
|
+
function asyncInject2(token, args) {
|
|
320
|
+
if (!injectState) {
|
|
321
|
+
throw new Error(
|
|
322
|
+
"[Injector] Trying to access inject outside of a injectable context"
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
const realToken = token[InjectableTokenMeta] ?? token;
|
|
326
|
+
const request = getRequest(realToken, args);
|
|
327
|
+
return request.promise.then((result) => {
|
|
328
|
+
if (request.error) {
|
|
329
|
+
throw request.error;
|
|
330
|
+
}
|
|
331
|
+
return result;
|
|
332
|
+
});
|
|
309
333
|
}
|
|
310
334
|
function wrapSyncInit2(cb) {
|
|
311
335
|
return (previousState) => {
|
|
@@ -335,23 +359,31 @@ function getInjectors() {
|
|
|
335
359
|
}
|
|
336
360
|
function inject2(token, args) {
|
|
337
361
|
const realToken = token[InjectableTokenMeta] ?? token;
|
|
362
|
+
if (!injectState) {
|
|
363
|
+
throw new Error(
|
|
364
|
+
"[Injector] Trying to access inject outside of a injectable context"
|
|
365
|
+
);
|
|
366
|
+
}
|
|
338
367
|
const instance = getFactoryContext().locator.getSyncInstance(
|
|
339
368
|
realToken,
|
|
340
369
|
args
|
|
341
370
|
);
|
|
342
371
|
if (!instance) {
|
|
372
|
+
const request = getRequest(realToken, args);
|
|
373
|
+
if (request.error) {
|
|
374
|
+
throw request.error;
|
|
375
|
+
} else if (request.result) {
|
|
376
|
+
return request.result;
|
|
377
|
+
}
|
|
343
378
|
if (promiseCollector) {
|
|
344
|
-
|
|
345
|
-
promiseCollector(promise);
|
|
346
|
-
} else {
|
|
347
|
-
throw new Error(`[Injector] Cannot initiate ${realToken.toString()}`);
|
|
379
|
+
promiseCollector(request.promise);
|
|
348
380
|
}
|
|
349
381
|
return new Proxy(
|
|
350
382
|
{},
|
|
351
383
|
{
|
|
352
384
|
get() {
|
|
353
385
|
throw new Error(
|
|
354
|
-
`[Injector] Trying to access ${realToken.toString()} before it's initialized, please
|
|
386
|
+
`[Injector] Trying to access ${realToken.toString()} before it's initialized, please move the code to a onServiceInit method`
|
|
355
387
|
);
|
|
356
388
|
}
|
|
357
389
|
}
|
|
@@ -359,9 +391,17 @@ function getInjectors() {
|
|
|
359
391
|
}
|
|
360
392
|
return instance;
|
|
361
393
|
}
|
|
394
|
+
function optional2(token, args) {
|
|
395
|
+
try {
|
|
396
|
+
return inject2(token, args);
|
|
397
|
+
} catch {
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
362
401
|
const injectors = {
|
|
363
402
|
asyncInject: asyncInject2,
|
|
364
403
|
inject: inject2,
|
|
404
|
+
optional: optional2,
|
|
365
405
|
wrapSyncInit: wrapSyncInit2,
|
|
366
406
|
provideFactoryContext: provideFactoryContext2
|
|
367
407
|
};
|
|
@@ -379,73 +419,13 @@ function getInjectableToken(target) {
|
|
|
379
419
|
return token;
|
|
380
420
|
}
|
|
381
421
|
|
|
382
|
-
// src/utils/defer.mts
|
|
383
|
-
var Deferred = class {
|
|
384
|
-
promise;
|
|
385
|
-
_resolve;
|
|
386
|
-
_reject;
|
|
387
|
-
_isResolved = false;
|
|
388
|
-
_isRejected = false;
|
|
389
|
-
constructor() {
|
|
390
|
-
this.promise = new Promise((resolve, reject) => {
|
|
391
|
-
this._resolve = resolve;
|
|
392
|
-
this._reject = reject;
|
|
393
|
-
});
|
|
394
|
-
}
|
|
395
|
-
/**
|
|
396
|
-
* Resolves the deferred promise with the given value.
|
|
397
|
-
* @param value The value to resolve with
|
|
398
|
-
* @throws Error if the promise has already been resolved or rejected
|
|
399
|
-
*/
|
|
400
|
-
resolve(value) {
|
|
401
|
-
if (this._isResolved || this._isRejected) {
|
|
402
|
-
throw new Error("Deferred promise has already been resolved or rejected");
|
|
403
|
-
}
|
|
404
|
-
this._isResolved = true;
|
|
405
|
-
this._resolve(value);
|
|
406
|
-
}
|
|
407
|
-
/**
|
|
408
|
-
* Rejects the deferred promise with the given reason.
|
|
409
|
-
* @param reason The reason for rejection
|
|
410
|
-
* @throws Error if the promise has already been resolved or rejected
|
|
411
|
-
*/
|
|
412
|
-
reject(reason) {
|
|
413
|
-
if (this._isResolved || this._isRejected) {
|
|
414
|
-
throw new Error("Deferred promise has already been resolved or rejected");
|
|
415
|
-
}
|
|
416
|
-
this._isRejected = true;
|
|
417
|
-
this._reject(reason);
|
|
418
|
-
}
|
|
419
|
-
/**
|
|
420
|
-
* Returns true if the promise has been resolved.
|
|
421
|
-
*/
|
|
422
|
-
get isResolved() {
|
|
423
|
-
return this._isResolved;
|
|
424
|
-
}
|
|
425
|
-
/**
|
|
426
|
-
* Returns true if the promise has been rejected.
|
|
427
|
-
*/
|
|
428
|
-
get isRejected() {
|
|
429
|
-
return this._isRejected;
|
|
430
|
-
}
|
|
431
|
-
/**
|
|
432
|
-
* Returns true if the promise has been settled (resolved or rejected).
|
|
433
|
-
*/
|
|
434
|
-
get isSettled() {
|
|
435
|
-
return this._isResolved || this._isRejected;
|
|
436
|
-
}
|
|
437
|
-
};
|
|
438
|
-
function createDeferred() {
|
|
439
|
-
return new Deferred();
|
|
440
|
-
}
|
|
441
|
-
|
|
442
422
|
// src/service-locator-instance-holder.mts
|
|
443
|
-
var ServiceLocatorInstanceHolderStatus = /* @__PURE__ */ ((
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
return
|
|
423
|
+
var ServiceLocatorInstanceHolderStatus = /* @__PURE__ */ ((ServiceLocatorInstanceHolderStatus2) => {
|
|
424
|
+
ServiceLocatorInstanceHolderStatus2["Created"] = "created";
|
|
425
|
+
ServiceLocatorInstanceHolderStatus2["Creating"] = "creating";
|
|
426
|
+
ServiceLocatorInstanceHolderStatus2["Destroying"] = "destroying";
|
|
427
|
+
ServiceLocatorInstanceHolderStatus2["Error"] = "error";
|
|
428
|
+
return ServiceLocatorInstanceHolderStatus2;
|
|
449
429
|
})(ServiceLocatorInstanceHolderStatus || {});
|
|
450
430
|
|
|
451
431
|
// src/base-instance-holder-manager.mts
|
|
@@ -498,11 +478,10 @@ var BaseInstanceHolderManager = class {
|
|
|
498
478
|
* @param type The injectable type
|
|
499
479
|
* @param scope The injectable scope
|
|
500
480
|
* @param deps Optional set of dependencies
|
|
501
|
-
* @param ttl Optional time-to-live in milliseconds (defaults to Infinity)
|
|
502
481
|
* @returns A tuple containing the deferred promise and the holder
|
|
503
482
|
*/
|
|
504
|
-
createCreatingHolder(name, type, scope, deps = /* @__PURE__ */ new Set()
|
|
505
|
-
const deferred =
|
|
483
|
+
createCreatingHolder(name, type, scope, deps = /* @__PURE__ */ new Set()) {
|
|
484
|
+
const deferred = Promise.withResolvers();
|
|
506
485
|
const holder = {
|
|
507
486
|
status: "creating" /* Creating */,
|
|
508
487
|
name,
|
|
@@ -513,8 +492,7 @@ var BaseInstanceHolderManager = class {
|
|
|
513
492
|
scope,
|
|
514
493
|
deps,
|
|
515
494
|
destroyListeners: [],
|
|
516
|
-
createdAt: Date.now()
|
|
517
|
-
ttl
|
|
495
|
+
createdAt: Date.now()
|
|
518
496
|
};
|
|
519
497
|
return [deferred, holder];
|
|
520
498
|
}
|
|
@@ -526,10 +504,9 @@ var BaseInstanceHolderManager = class {
|
|
|
526
504
|
* @param type The injectable type
|
|
527
505
|
* @param scope The injectable scope
|
|
528
506
|
* @param deps Optional set of dependencies
|
|
529
|
-
* @param ttl Optional time-to-live in milliseconds (defaults to Infinity)
|
|
530
507
|
* @returns The created holder
|
|
531
508
|
*/
|
|
532
|
-
createCreatedHolder(name, instance, type, scope, deps = /* @__PURE__ */ new Set()
|
|
509
|
+
createCreatedHolder(name, instance, type, scope, deps = /* @__PURE__ */ new Set()) {
|
|
533
510
|
const holder = {
|
|
534
511
|
status: "created" /* Created */,
|
|
535
512
|
name,
|
|
@@ -540,8 +517,7 @@ var BaseInstanceHolderManager = class {
|
|
|
540
517
|
scope,
|
|
541
518
|
deps,
|
|
542
519
|
destroyListeners: [],
|
|
543
|
-
createdAt: Date.now()
|
|
544
|
-
ttl
|
|
520
|
+
createdAt: Date.now()
|
|
545
521
|
};
|
|
546
522
|
return holder;
|
|
547
523
|
}
|
|
@@ -614,6 +590,7 @@ __runInitializers(_init, 1, exports.EventEmitter);
|
|
|
614
590
|
var defaultInjectors = getInjectors();
|
|
615
591
|
var asyncInject = defaultInjectors.asyncInject;
|
|
616
592
|
var inject = defaultInjectors.inject;
|
|
593
|
+
var optional = defaultInjectors.optional;
|
|
617
594
|
var wrapSyncInit = defaultInjectors.wrapSyncInit;
|
|
618
595
|
var provideFactoryContext = defaultInjectors.provideFactoryContext;
|
|
619
596
|
|
|
@@ -745,362 +722,425 @@ var ServiceInstantiator = class {
|
|
|
745
722
|
}
|
|
746
723
|
};
|
|
747
724
|
|
|
748
|
-
// src/
|
|
749
|
-
var
|
|
750
|
-
constructor(
|
|
751
|
-
|
|
752
|
-
this.
|
|
753
|
-
this.
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
725
|
+
// src/instance-resolver.mts
|
|
726
|
+
var InstanceResolver = class {
|
|
727
|
+
constructor(registry, manager, serviceInstantiator, tokenProcessor, logger = null, serviceLocator) {
|
|
728
|
+
this.registry = registry;
|
|
729
|
+
this.manager = manager;
|
|
730
|
+
this.serviceInstantiator = serviceInstantiator;
|
|
731
|
+
this.tokenProcessor = tokenProcessor;
|
|
732
|
+
this.logger = logger;
|
|
733
|
+
this.serviceLocator = serviceLocator;
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Resolves an instance for the given token and arguments.
|
|
737
|
+
*/
|
|
738
|
+
async resolveInstance(token, args, requestContext) {
|
|
739
|
+
const [err, data] = await this.resolveTokenAndPrepareInstanceName(
|
|
740
|
+
token,
|
|
741
|
+
args
|
|
742
|
+
);
|
|
743
|
+
if (err) {
|
|
744
|
+
return [err];
|
|
745
|
+
}
|
|
746
|
+
const {
|
|
747
|
+
instanceName,
|
|
748
|
+
validatedArgs,
|
|
749
|
+
realToken
|
|
750
|
+
} = data;
|
|
751
|
+
const [error, holder] = await this.retrieveOrCreateInstanceByInstanceName(
|
|
752
|
+
instanceName,
|
|
753
|
+
realToken,
|
|
754
|
+
validatedArgs,
|
|
755
|
+
requestContext
|
|
756
|
+
);
|
|
757
|
+
if (error) {
|
|
758
|
+
return [error];
|
|
758
759
|
}
|
|
760
|
+
return [void 0, holder.instance];
|
|
759
761
|
}
|
|
760
|
-
metadata = /* @__PURE__ */ new Map();
|
|
761
|
-
createdAt = Date.now();
|
|
762
762
|
/**
|
|
763
|
-
*
|
|
763
|
+
* Gets a synchronous instance (for sync operations).
|
|
764
764
|
*/
|
|
765
|
-
|
|
766
|
-
|
|
765
|
+
getSyncInstance(token, args, currentRequestContext) {
|
|
766
|
+
const [err, { actualToken, validatedArgs }] = this.tokenProcessor.validateAndResolveTokenArgs(token, args);
|
|
767
|
+
if (err) {
|
|
768
|
+
return null;
|
|
769
|
+
}
|
|
770
|
+
const instanceName = this.tokenProcessor.generateInstanceName(
|
|
771
|
+
actualToken,
|
|
772
|
+
validatedArgs
|
|
773
|
+
);
|
|
774
|
+
if (currentRequestContext) {
|
|
775
|
+
const requestHolder = currentRequestContext.get(instanceName);
|
|
776
|
+
if (requestHolder) {
|
|
777
|
+
return requestHolder.instance;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
const [error, holder] = this.manager.get(instanceName);
|
|
781
|
+
if (error) {
|
|
782
|
+
return null;
|
|
783
|
+
}
|
|
784
|
+
return holder.instance;
|
|
767
785
|
}
|
|
768
786
|
/**
|
|
769
|
-
*
|
|
787
|
+
* Internal method to resolve token args and create instance name.
|
|
788
|
+
* Handles factory token resolution and validation.
|
|
770
789
|
*/
|
|
771
|
-
|
|
772
|
-
|
|
790
|
+
async resolveTokenAndPrepareInstanceName(token, args) {
|
|
791
|
+
const [err, { actualToken, validatedArgs }] = this.tokenProcessor.validateAndResolveTokenArgs(token, args);
|
|
792
|
+
if (err instanceof DIError && err.code === "UnknownError" /* UnknownError */) {
|
|
793
|
+
return [err];
|
|
794
|
+
} else if (err instanceof DIError && err.code === "FactoryTokenNotResolved" /* FactoryTokenNotResolved */ && actualToken instanceof FactoryInjectionToken) {
|
|
795
|
+
this.logger?.log(
|
|
796
|
+
`[InstanceResolver]#resolveTokenAndPrepareInstanceName() Factory token not resolved, resolving it`
|
|
797
|
+
);
|
|
798
|
+
await actualToken.resolve(this.createFactoryContext());
|
|
799
|
+
return this.resolveTokenAndPrepareInstanceName(token);
|
|
800
|
+
}
|
|
801
|
+
const instanceName = this.tokenProcessor.generateInstanceName(
|
|
802
|
+
actualToken,
|
|
803
|
+
validatedArgs
|
|
804
|
+
);
|
|
805
|
+
const realToken = actualToken instanceof BoundInjectionToken || actualToken instanceof FactoryInjectionToken ? actualToken.token : actualToken;
|
|
806
|
+
return [void 0, { instanceName, validatedArgs, actualToken, realToken }];
|
|
773
807
|
}
|
|
774
808
|
/**
|
|
775
|
-
*
|
|
809
|
+
* Gets an instance by its instance name, handling all the logic after instance name creation.
|
|
776
810
|
*/
|
|
777
|
-
|
|
778
|
-
this.
|
|
811
|
+
async retrieveOrCreateInstanceByInstanceName(instanceName, realToken, realArgs, requestContext) {
|
|
812
|
+
const existingHolder = await this.tryGetExistingInstance(
|
|
813
|
+
instanceName,
|
|
814
|
+
realToken,
|
|
815
|
+
requestContext
|
|
816
|
+
);
|
|
817
|
+
if (existingHolder) {
|
|
818
|
+
return existingHolder;
|
|
819
|
+
}
|
|
820
|
+
const result = await this.createNewInstance(
|
|
821
|
+
instanceName,
|
|
822
|
+
realToken,
|
|
823
|
+
realArgs,
|
|
824
|
+
requestContext
|
|
825
|
+
);
|
|
826
|
+
if (result[0]) {
|
|
827
|
+
return [result[0]];
|
|
828
|
+
}
|
|
829
|
+
const [, holder] = result;
|
|
830
|
+
return this.waitForInstanceReady(holder);
|
|
779
831
|
}
|
|
780
832
|
/**
|
|
781
|
-
*
|
|
833
|
+
* Attempts to retrieve an existing instance, handling request-scoped and singleton instances.
|
|
834
|
+
* Returns null if no instance exists and a new one should be created.
|
|
782
835
|
*/
|
|
783
|
-
|
|
784
|
-
|
|
836
|
+
async tryGetExistingInstance(instanceName, realToken, requestContext) {
|
|
837
|
+
const requestResult = await this.tryGetRequestScopedInstance(
|
|
838
|
+
instanceName,
|
|
839
|
+
realToken,
|
|
840
|
+
requestContext
|
|
841
|
+
);
|
|
842
|
+
if (requestResult) {
|
|
843
|
+
return requestResult;
|
|
844
|
+
}
|
|
845
|
+
return this.tryGetSingletonInstance(instanceName);
|
|
785
846
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
847
|
+
/**
|
|
848
|
+
* Attempts to get a request-scoped instance if applicable.
|
|
849
|
+
*/
|
|
850
|
+
async tryGetRequestScopedInstance(instanceName, realToken, requestContext) {
|
|
851
|
+
if (!this.registry.has(realToken)) {
|
|
852
|
+
return null;
|
|
853
|
+
}
|
|
854
|
+
const record = this.registry.get(realToken);
|
|
855
|
+
if (record.scope !== "Request" /* Request */) {
|
|
856
|
+
return null;
|
|
857
|
+
}
|
|
858
|
+
if (!requestContext) {
|
|
859
|
+
this.logger?.log(
|
|
860
|
+
`[InstanceResolver] No current request context available for request-scoped service ${instanceName}`
|
|
796
861
|
);
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
this._holders.set(instanceName, holder);
|
|
862
|
+
return [
|
|
863
|
+
DIError.unknown(
|
|
864
|
+
`No current request context available for request-scoped service ${instanceName}`
|
|
865
|
+
)
|
|
866
|
+
];
|
|
803
867
|
}
|
|
868
|
+
const requestHolder = requestContext.get(instanceName);
|
|
869
|
+
if (!requestHolder) {
|
|
870
|
+
return null;
|
|
871
|
+
}
|
|
872
|
+
return this.waitForInstanceReady(requestHolder);
|
|
804
873
|
}
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
setMetadata(key, value) {
|
|
813
|
-
this.metadata.set(key, value);
|
|
814
|
-
}
|
|
815
|
-
};
|
|
816
|
-
function createRequestContextHolder(requestId, priority = 100, initialMetadata) {
|
|
817
|
-
return new DefaultRequestContextHolder(requestId, priority, initialMetadata);
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
// src/service-locator-event-bus.mts
|
|
821
|
-
var ServiceLocatorEventBus = class {
|
|
822
|
-
constructor(logger = null) {
|
|
823
|
-
this.logger = logger;
|
|
824
|
-
}
|
|
825
|
-
listeners = /* @__PURE__ */ new Map();
|
|
826
|
-
on(ns, event, listener) {
|
|
827
|
-
this.logger?.debug(`[ServiceLocatorEventBus]#on(): ns:${ns} event:${event}`);
|
|
828
|
-
if (!this.listeners.has(ns)) {
|
|
829
|
-
this.listeners.set(ns, /* @__PURE__ */ new Map());
|
|
874
|
+
/**
|
|
875
|
+
* Attempts to get a singleton instance from the manager.
|
|
876
|
+
*/
|
|
877
|
+
async tryGetSingletonInstance(instanceName) {
|
|
878
|
+
const [error, holder] = this.manager.get(instanceName);
|
|
879
|
+
if (!error) {
|
|
880
|
+
return this.waitForInstanceReady(holder);
|
|
830
881
|
}
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
882
|
+
switch (error.code) {
|
|
883
|
+
case "InstanceDestroying" /* InstanceDestroying */:
|
|
884
|
+
this.logger?.log(
|
|
885
|
+
`[InstanceResolver] Instance ${instanceName} is being destroyed, waiting...`
|
|
886
|
+
);
|
|
887
|
+
await holder?.destroyPromise;
|
|
888
|
+
return this.tryGetSingletonInstance(instanceName);
|
|
889
|
+
case "InstanceNotFound" /* InstanceNotFound */:
|
|
890
|
+
return null;
|
|
891
|
+
// Instance doesn't exist, should create new one
|
|
892
|
+
default:
|
|
893
|
+
return [error];
|
|
834
894
|
}
|
|
835
|
-
nsEvents.get(event).add(listener);
|
|
836
|
-
return () => {
|
|
837
|
-
nsEvents.get(event).delete(listener);
|
|
838
|
-
if (nsEvents.get(event)?.size === 0) {
|
|
839
|
-
nsEvents.delete(event);
|
|
840
|
-
}
|
|
841
|
-
if (nsEvents.size === 0) {
|
|
842
|
-
this.listeners.delete(ns);
|
|
843
|
-
}
|
|
844
|
-
};
|
|
845
|
-
}
|
|
846
|
-
async emit(key, event) {
|
|
847
|
-
if (!this.listeners.has(key)) {
|
|
848
|
-
return;
|
|
849
|
-
}
|
|
850
|
-
const events = this.listeners.get(key);
|
|
851
|
-
this.logger?.debug(`[ServiceLocatorEventBus]#emit(): ${key}:${event}`);
|
|
852
|
-
const res = await Promise.allSettled(
|
|
853
|
-
[...events.get(event) ?? []].map((listener) => listener(event))
|
|
854
|
-
).then((results) => {
|
|
855
|
-
const res2 = results.filter((result) => result.status === "rejected").map((result) => {
|
|
856
|
-
this.logger?.warn(
|
|
857
|
-
`[ServiceLocatorEventBus]#emit(): ${key}:${event} rejected with`,
|
|
858
|
-
result.reason
|
|
859
|
-
);
|
|
860
|
-
return result;
|
|
861
|
-
});
|
|
862
|
-
if (res2.length > 0) {
|
|
863
|
-
return Promise.reject(res2);
|
|
864
|
-
}
|
|
865
|
-
return results;
|
|
866
|
-
});
|
|
867
|
-
return res;
|
|
868
895
|
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
return [new InstanceExpired(holder.name), holder];
|
|
886
|
-
}
|
|
887
|
-
} else if (holder.status === "destroying" /* Destroying */) {
|
|
888
|
-
this.logger?.log(
|
|
889
|
-
`[ServiceLocatorManager]#getInstanceHolder() Instance ${holder.name} is destroying`
|
|
890
|
-
);
|
|
891
|
-
return [new InstanceDestroying(holder.name), holder];
|
|
892
|
-
} else if (holder.status === "error" /* Error */) {
|
|
893
|
-
this.logger?.log(
|
|
894
|
-
`[ServiceLocatorManager]#getInstanceHolder() Instance ${holder.name} is in error state`
|
|
895
|
-
);
|
|
896
|
-
return [holder.instance, holder];
|
|
897
|
-
}
|
|
898
|
-
return [void 0, holder];
|
|
899
|
-
} else {
|
|
900
|
-
this.logger?.log(
|
|
901
|
-
`[ServiceLocatorManager]#getInstanceHolder() Instance ${name} not found`
|
|
902
|
-
);
|
|
903
|
-
return [new InstanceNotFound(name)];
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
set(name, holder) {
|
|
907
|
-
this._holders.set(name, holder);
|
|
908
|
-
}
|
|
909
|
-
has(name) {
|
|
910
|
-
const [error, holder] = this.get(name);
|
|
911
|
-
if (!error) {
|
|
912
|
-
return [void 0, true];
|
|
913
|
-
}
|
|
914
|
-
if (["InstanceExpired" /* InstanceExpired */, "InstanceDestroying" /* InstanceDestroying */].includes(
|
|
915
|
-
error.code
|
|
916
|
-
)) {
|
|
917
|
-
return [error];
|
|
896
|
+
/**
|
|
897
|
+
* Waits for an instance holder to be ready and returns the appropriate result.
|
|
898
|
+
*/
|
|
899
|
+
async waitForInstanceReady(holder) {
|
|
900
|
+
switch (holder.status) {
|
|
901
|
+
case "creating" /* Creating */:
|
|
902
|
+
await holder.creationPromise;
|
|
903
|
+
return this.waitForInstanceReady(holder);
|
|
904
|
+
case "destroying" /* Destroying */:
|
|
905
|
+
return [DIError.instanceDestroying(holder.name)];
|
|
906
|
+
case "error" /* Error */:
|
|
907
|
+
return [holder.instance];
|
|
908
|
+
case "created" /* Created */:
|
|
909
|
+
return [void 0, holder];
|
|
910
|
+
default:
|
|
911
|
+
return [DIError.instanceNotFound("unknown")];
|
|
918
912
|
}
|
|
919
|
-
return [void 0, !!holder];
|
|
920
913
|
}
|
|
921
|
-
// delete and filter methods are inherited from BaseInstanceHolderManager
|
|
922
|
-
// createCreatingHolder method is inherited from BaseInstanceHolderManager
|
|
923
914
|
/**
|
|
924
|
-
* Creates a new
|
|
925
|
-
* This is useful for creating holders that already have their instance ready.
|
|
926
|
-
* @param name The name of the instance
|
|
927
|
-
* @param instance The actual instance to store
|
|
928
|
-
* @param type The injectable type
|
|
929
|
-
* @param scope The injectable scope
|
|
930
|
-
* @param deps Optional set of dependencies
|
|
931
|
-
* @param ttl Optional time-to-live in milliseconds (defaults to Infinity)
|
|
932
|
-
* @returns The created holder
|
|
915
|
+
* Creates a new instance for the given token and arguments.
|
|
933
916
|
*/
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
instance,
|
|
938
|
-
type,
|
|
939
|
-
scope,
|
|
940
|
-
deps,
|
|
941
|
-
ttl
|
|
917
|
+
async createNewInstance(instanceName, realToken, args, requestContext) {
|
|
918
|
+
this.logger?.log(
|
|
919
|
+
`[InstanceResolver]#createNewInstance() Creating instance for ${instanceName}`
|
|
942
920
|
);
|
|
943
|
-
this.
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
this.logger = logger;
|
|
953
|
-
this.injectors = injectors;
|
|
954
|
-
this.eventBus = new ServiceLocatorEventBus(logger);
|
|
955
|
-
this.manager = new ServiceLocatorManager(logger);
|
|
956
|
-
this.serviceInstantiator = new ServiceInstantiator(injectors);
|
|
957
|
-
}
|
|
958
|
-
eventBus;
|
|
959
|
-
manager;
|
|
960
|
-
serviceInstantiator;
|
|
961
|
-
requestContexts = /* @__PURE__ */ new Map();
|
|
962
|
-
currentRequestContext = null;
|
|
963
|
-
// ============================================================================
|
|
964
|
-
// PUBLIC METHODS
|
|
965
|
-
// ============================================================================
|
|
966
|
-
getEventBus() {
|
|
967
|
-
return this.eventBus;
|
|
968
|
-
}
|
|
969
|
-
getManager() {
|
|
970
|
-
return this.manager;
|
|
971
|
-
}
|
|
972
|
-
getInstanceIdentifier(token, args) {
|
|
973
|
-
const [err, { actualToken, validatedArgs }] = this.validateAndResolveTokenArgs(token, args);
|
|
974
|
-
if (err) {
|
|
975
|
-
throw err;
|
|
921
|
+
if (this.registry.has(realToken)) {
|
|
922
|
+
return this.instantiateServiceFromRegistry(
|
|
923
|
+
instanceName,
|
|
924
|
+
realToken,
|
|
925
|
+
args,
|
|
926
|
+
requestContext
|
|
927
|
+
);
|
|
928
|
+
} else {
|
|
929
|
+
return [DIError.factoryNotFound(realToken.name.toString())];
|
|
976
930
|
}
|
|
977
|
-
return this.generateInstanceName(actualToken, validatedArgs);
|
|
978
931
|
}
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
932
|
+
/**
|
|
933
|
+
* Instantiates a service from the registry using the service instantiator.
|
|
934
|
+
*/
|
|
935
|
+
instantiateServiceFromRegistry(instanceName, token, args, requestContext) {
|
|
936
|
+
this.logger?.log(
|
|
937
|
+
`[InstanceResolver]#instantiateServiceFromRegistry(): Creating instance for ${instanceName} from abstract factory`
|
|
983
938
|
);
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
}
|
|
987
|
-
const
|
|
988
|
-
onPrepare?.({ instanceName, actualToken, validatedArgs });
|
|
989
|
-
const [error, holder] = await this.retrieveOrCreateInstanceByInstanceName(
|
|
939
|
+
const ctx = this.createFactoryContext();
|
|
940
|
+
let record = this.registry.get(token);
|
|
941
|
+
let { scope, type } = record;
|
|
942
|
+
const [deferred, holder] = this.manager.createCreatingHolder(
|
|
990
943
|
instanceName,
|
|
991
|
-
|
|
992
|
-
|
|
944
|
+
type,
|
|
945
|
+
scope,
|
|
946
|
+
ctx.deps
|
|
993
947
|
);
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
948
|
+
this.serviceInstantiator.instantiateService(ctx, record, args).then(async ([error, instance]) => {
|
|
949
|
+
await this.handleInstantiationResult(
|
|
950
|
+
instanceName,
|
|
951
|
+
holder,
|
|
952
|
+
ctx,
|
|
953
|
+
deferred,
|
|
954
|
+
scope,
|
|
955
|
+
error,
|
|
956
|
+
instance,
|
|
957
|
+
requestContext
|
|
958
|
+
);
|
|
959
|
+
}).catch(async (error) => {
|
|
960
|
+
await this.handleInstantiationError(
|
|
961
|
+
instanceName,
|
|
962
|
+
holder,
|
|
963
|
+
deferred,
|
|
964
|
+
scope,
|
|
965
|
+
error
|
|
966
|
+
);
|
|
967
|
+
});
|
|
968
|
+
this.storeInstanceByScope(scope, instanceName, holder, requestContext);
|
|
969
|
+
return [void 0, holder];
|
|
998
970
|
}
|
|
999
|
-
|
|
1000
|
-
|
|
971
|
+
/**
|
|
972
|
+
* Handles the result of service instantiation.
|
|
973
|
+
*/
|
|
974
|
+
async handleInstantiationResult(instanceName, holder, ctx, deferred, scope, error, instance, _requestContext) {
|
|
975
|
+
holder.destroyListeners = ctx.getDestroyListeners();
|
|
976
|
+
holder.creationPromise = null;
|
|
1001
977
|
if (error) {
|
|
1002
|
-
|
|
978
|
+
await this.handleInstantiationError(
|
|
979
|
+
instanceName,
|
|
980
|
+
holder,
|
|
981
|
+
deferred,
|
|
982
|
+
scope,
|
|
983
|
+
error
|
|
984
|
+
);
|
|
985
|
+
} else {
|
|
986
|
+
await this.handleInstantiationSuccess(
|
|
987
|
+
instanceName,
|
|
988
|
+
holder,
|
|
989
|
+
ctx,
|
|
990
|
+
deferred,
|
|
991
|
+
instance
|
|
992
|
+
);
|
|
1003
993
|
}
|
|
1004
|
-
return instance;
|
|
1005
994
|
}
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
if (
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
995
|
+
/**
|
|
996
|
+
* Handles successful service instantiation.
|
|
997
|
+
*/
|
|
998
|
+
async handleInstantiationSuccess(instanceName, holder, ctx, deferred, instance) {
|
|
999
|
+
holder.instance = instance;
|
|
1000
|
+
holder.status = "created" /* Created */;
|
|
1001
|
+
if (ctx.deps.size > 0) {
|
|
1002
|
+
ctx.deps.forEach((dependency) => {
|
|
1003
|
+
holder.destroyListeners.push(
|
|
1004
|
+
this.serviceLocator.getEventBus().on(dependency, "destroy", () => {
|
|
1005
|
+
this.logger?.log(
|
|
1006
|
+
`[InstanceResolver] Dependency ${dependency} destroyed, invalidating ${instanceName}`
|
|
1007
|
+
);
|
|
1008
|
+
this.serviceLocator.getServiceInvalidator().invalidate(instanceName);
|
|
1009
|
+
})
|
|
1010
|
+
);
|
|
1011
|
+
});
|
|
1021
1012
|
}
|
|
1022
|
-
return holder.instance;
|
|
1023
|
-
}
|
|
1024
|
-
invalidate(service, round = 1) {
|
|
1025
1013
|
this.logger?.log(
|
|
1026
|
-
`[
|
|
1014
|
+
`[InstanceResolver] Instance ${instanceName} created successfully`
|
|
1027
1015
|
);
|
|
1028
|
-
|
|
1029
|
-
|
|
1016
|
+
deferred.resolve([void 0, instance]);
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* Handles service instantiation errors.
|
|
1020
|
+
*/
|
|
1021
|
+
async handleInstantiationError(instanceName, holder, deferred, scope, error) {
|
|
1022
|
+
this.logger?.error(
|
|
1023
|
+
`[InstanceResolver] Error creating instance for ${instanceName}`,
|
|
1024
|
+
error
|
|
1030
1025
|
);
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1026
|
+
holder.status = "error" /* Error */;
|
|
1027
|
+
holder.instance = error;
|
|
1028
|
+
holder.creationPromise = null;
|
|
1029
|
+
if (scope === "Singleton" /* Singleton */) {
|
|
1030
|
+
this.logger?.log(
|
|
1031
|
+
`[InstanceResolver] Singleton ${instanceName} failed, will be invalidated`
|
|
1032
|
+
);
|
|
1033
|
+
this.serviceLocator.getServiceInvalidator().invalidate(instanceName);
|
|
1034
1034
|
}
|
|
1035
|
-
|
|
1035
|
+
deferred.reject(error);
|
|
1036
1036
|
}
|
|
1037
1037
|
/**
|
|
1038
|
-
*
|
|
1038
|
+
* Stores an instance holder based on its scope.
|
|
1039
1039
|
*/
|
|
1040
|
-
|
|
1041
|
-
switch (
|
|
1042
|
-
case "
|
|
1043
|
-
this.logger?.
|
|
1044
|
-
|
|
1045
|
-
break;
|
|
1046
|
-
case "creating" /* Creating */:
|
|
1047
|
-
this.logger?.trace(
|
|
1048
|
-
`[ServiceLocator] ${key} is being created, waiting...`
|
|
1040
|
+
storeInstanceByScope(scope, instanceName, holder, requestContext) {
|
|
1041
|
+
switch (scope) {
|
|
1042
|
+
case "Singleton" /* Singleton */:
|
|
1043
|
+
this.logger?.debug(
|
|
1044
|
+
`[InstanceResolver] Setting singleton instance for ${instanceName}`
|
|
1049
1045
|
);
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1046
|
+
this.manager.set(instanceName, holder);
|
|
1047
|
+
break;
|
|
1048
|
+
case "Request" /* Request */:
|
|
1049
|
+
if (requestContext) {
|
|
1050
|
+
this.logger?.debug(
|
|
1051
|
+
`[InstanceResolver] Setting request-scoped instance for ${instanceName}`
|
|
1054
1052
|
);
|
|
1055
|
-
|
|
1053
|
+
requestContext.addInstance(instanceName, holder.instance, holder);
|
|
1056
1054
|
}
|
|
1057
|
-
await this.invalidate(key, round + 1);
|
|
1058
|
-
break;
|
|
1059
|
-
default:
|
|
1060
|
-
await this.destroyHolder(key, holder);
|
|
1061
1055
|
break;
|
|
1062
1056
|
}
|
|
1063
1057
|
}
|
|
1064
1058
|
/**
|
|
1065
|
-
*
|
|
1059
|
+
* Creates a factory context for dependency injection during service instantiation.
|
|
1066
1060
|
*/
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
this.logger?.log(
|
|
1070
|
-
`[ServiceLocator] Invalidating ${key} and notifying listeners`
|
|
1071
|
-
);
|
|
1072
|
-
holder.destroyPromise = Promise.all(
|
|
1073
|
-
holder.destroyListeners.map((listener) => listener())
|
|
1074
|
-
).then(async () => {
|
|
1075
|
-
this.manager.delete(key);
|
|
1076
|
-
await this.emitInstanceEvent(key, "destroy");
|
|
1077
|
-
});
|
|
1078
|
-
await holder.destroyPromise;
|
|
1061
|
+
createFactoryContext() {
|
|
1062
|
+
return this.tokenProcessor.createFactoryContext(this.serviceLocator);
|
|
1079
1063
|
}
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1064
|
+
};
|
|
1065
|
+
|
|
1066
|
+
// src/request-context-holder.mts
|
|
1067
|
+
var DefaultRequestContextHolder = class extends BaseInstanceHolderManager {
|
|
1068
|
+
constructor(requestId, priority = 100, initialMetadata) {
|
|
1069
|
+
super(null);
|
|
1070
|
+
this.requestId = requestId;
|
|
1071
|
+
this.priority = priority;
|
|
1072
|
+
if (initialMetadata) {
|
|
1073
|
+
Object.entries(initialMetadata).forEach(([key, value]) => {
|
|
1074
|
+
this.metadata.set(key, value);
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1087
1077
|
}
|
|
1078
|
+
metadata = /* @__PURE__ */ new Map();
|
|
1079
|
+
createdAt = Date.now();
|
|
1088
1080
|
/**
|
|
1089
|
-
*
|
|
1081
|
+
* Public getter for holders to maintain interface compatibility.
|
|
1090
1082
|
*/
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1083
|
+
get holders() {
|
|
1084
|
+
return this._holders;
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Gets a holder by name. For RequestContextHolder, this is a simple lookup.
|
|
1088
|
+
*/
|
|
1089
|
+
get(name) {
|
|
1090
|
+
return this._holders.get(name);
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* Sets a holder by name.
|
|
1094
|
+
*/
|
|
1095
|
+
set(name, holder) {
|
|
1096
|
+
this._holders.set(name, holder);
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Checks if a holder exists by name.
|
|
1100
|
+
*/
|
|
1101
|
+
has(name) {
|
|
1102
|
+
return this._holders.has(name);
|
|
1103
|
+
}
|
|
1104
|
+
addInstance(instanceName, instance, holder) {
|
|
1105
|
+
if (instanceName instanceof InjectionToken) {
|
|
1106
|
+
const name = instanceName.toString();
|
|
1107
|
+
const createdHolder = this.createCreatedHolder(
|
|
1108
|
+
name,
|
|
1109
|
+
instance,
|
|
1110
|
+
"Class" /* Class */,
|
|
1111
|
+
"Singleton" /* Singleton */,
|
|
1112
|
+
/* @__PURE__ */ new Set()
|
|
1113
|
+
);
|
|
1114
|
+
this._holders.set(name, createdHolder);
|
|
1115
|
+
} else {
|
|
1116
|
+
if (!holder) {
|
|
1117
|
+
throw new Error("Holder is required when adding an instance by name");
|
|
1118
|
+
}
|
|
1119
|
+
this._holders.set(instanceName, holder);
|
|
1099
1120
|
}
|
|
1100
1121
|
}
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1122
|
+
clear() {
|
|
1123
|
+
super.clear();
|
|
1124
|
+
this.metadata.clear();
|
|
1125
|
+
}
|
|
1126
|
+
getMetadata(key) {
|
|
1127
|
+
return this.metadata.get(key);
|
|
1128
|
+
}
|
|
1129
|
+
setMetadata(key, value) {
|
|
1130
|
+
this.metadata.set(key, value);
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
1133
|
+
function createRequestContextHolder(requestId, priority = 100, initialMetadata) {
|
|
1134
|
+
return new DefaultRequestContextHolder(requestId, priority, initialMetadata);
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// src/request-context-manager.mts
|
|
1138
|
+
var RequestContextManager = class {
|
|
1139
|
+
constructor(logger = null) {
|
|
1140
|
+
this.logger = logger;
|
|
1141
|
+
}
|
|
1142
|
+
requestContexts = /* @__PURE__ */ new Map();
|
|
1143
|
+
currentRequestContext = null;
|
|
1104
1144
|
/**
|
|
1105
1145
|
* Begins a new request context with the given parameters.
|
|
1106
1146
|
* @param requestId Unique identifier for this request
|
|
@@ -1111,7 +1151,7 @@ var ServiceLocator = class {
|
|
|
1111
1151
|
beginRequest(requestId, metadata, priority = 100) {
|
|
1112
1152
|
if (this.requestContexts.has(requestId)) {
|
|
1113
1153
|
throw new Error(
|
|
1114
|
-
`[
|
|
1154
|
+
`[RequestContextManager] Request context ${requestId} already exists`
|
|
1115
1155
|
);
|
|
1116
1156
|
}
|
|
1117
1157
|
const contextHolder = new DefaultRequestContextHolder(
|
|
@@ -1121,7 +1161,9 @@ var ServiceLocator = class {
|
|
|
1121
1161
|
);
|
|
1122
1162
|
this.requestContexts.set(requestId, contextHolder);
|
|
1123
1163
|
this.currentRequestContext = contextHolder;
|
|
1124
|
-
this.logger?.log(
|
|
1164
|
+
this.logger?.log(
|
|
1165
|
+
`[RequestContextManager] Started request context: ${requestId}`
|
|
1166
|
+
);
|
|
1125
1167
|
return contextHolder;
|
|
1126
1168
|
}
|
|
1127
1169
|
/**
|
|
@@ -1132,11 +1174,13 @@ var ServiceLocator = class {
|
|
|
1132
1174
|
const contextHolder = this.requestContexts.get(requestId);
|
|
1133
1175
|
if (!contextHolder) {
|
|
1134
1176
|
this.logger?.warn(
|
|
1135
|
-
`[
|
|
1177
|
+
`[RequestContextManager] Request context ${requestId} not found`
|
|
1136
1178
|
);
|
|
1137
1179
|
return;
|
|
1138
1180
|
}
|
|
1139
|
-
this.logger?.log(
|
|
1181
|
+
this.logger?.log(
|
|
1182
|
+
`[RequestContextManager] Ending request context: ${requestId}`
|
|
1183
|
+
);
|
|
1140
1184
|
const cleanupPromises = [];
|
|
1141
1185
|
for (const [, holder] of contextHolder.holders) {
|
|
1142
1186
|
if (holder.destroyListeners.length > 0) {
|
|
@@ -1151,7 +1195,9 @@ var ServiceLocator = class {
|
|
|
1151
1195
|
if (this.currentRequestContext === contextHolder) {
|
|
1152
1196
|
this.currentRequestContext = Array.from(this.requestContexts.values()).at(-1) ?? null;
|
|
1153
1197
|
}
|
|
1154
|
-
this.logger?.log(
|
|
1198
|
+
this.logger?.log(
|
|
1199
|
+
`[RequestContextManager] Request context ${requestId} ended`
|
|
1200
|
+
);
|
|
1155
1201
|
}
|
|
1156
1202
|
/**
|
|
1157
1203
|
* Gets the current request context.
|
|
@@ -1167,354 +1213,521 @@ var ServiceLocator = class {
|
|
|
1167
1213
|
setCurrentRequestContext(requestId) {
|
|
1168
1214
|
const contextHolder = this.requestContexts.get(requestId);
|
|
1169
1215
|
if (!contextHolder) {
|
|
1170
|
-
throw new Error(
|
|
1216
|
+
throw new Error(
|
|
1217
|
+
`[RequestContextManager] Request context ${requestId} not found`
|
|
1218
|
+
);
|
|
1171
1219
|
}
|
|
1172
1220
|
this.currentRequestContext = contextHolder;
|
|
1173
1221
|
}
|
|
1174
|
-
// ============================================================================
|
|
1175
|
-
// PRIVATE METHODS
|
|
1176
|
-
// ============================================================================
|
|
1177
1222
|
/**
|
|
1178
|
-
*
|
|
1223
|
+
* Gets all request contexts.
|
|
1224
|
+
* @returns Map of request contexts
|
|
1179
1225
|
*/
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1226
|
+
getRequestContexts() {
|
|
1227
|
+
return this.requestContexts;
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Clears all request contexts.
|
|
1231
|
+
*/
|
|
1232
|
+
async clearAllRequestContexts() {
|
|
1233
|
+
const requestIds = Array.from(this.requestContexts.keys());
|
|
1234
|
+
if (requestIds.length === 0) {
|
|
1235
|
+
this.logger?.log("[RequestContextManager] No request contexts to clear");
|
|
1236
|
+
return;
|
|
1184
1237
|
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1238
|
+
this.logger?.log(
|
|
1239
|
+
`[RequestContextManager] Clearing ${requestIds.length} request contexts: ${requestIds.join(", ")}`
|
|
1240
|
+
);
|
|
1241
|
+
for (const requestId of requestIds) {
|
|
1242
|
+
try {
|
|
1243
|
+
await this.endRequest(requestId);
|
|
1244
|
+
} catch (error) {
|
|
1245
|
+
this.logger?.error(
|
|
1246
|
+
`[RequestContextManager] Error clearing request context ${requestId}:`,
|
|
1247
|
+
error
|
|
1248
|
+
);
|
|
1193
1249
|
}
|
|
1194
1250
|
}
|
|
1195
|
-
|
|
1196
|
-
|
|
1251
|
+
}
|
|
1252
|
+
};
|
|
1253
|
+
|
|
1254
|
+
// src/service-invalidator.mts
|
|
1255
|
+
var ServiceInvalidator = class {
|
|
1256
|
+
constructor(manager, requestContextManager, eventBus, logger = null) {
|
|
1257
|
+
this.manager = manager;
|
|
1258
|
+
this.requestContextManager = requestContextManager;
|
|
1259
|
+
this.eventBus = eventBus;
|
|
1260
|
+
this.logger = logger;
|
|
1261
|
+
}
|
|
1262
|
+
/**
|
|
1263
|
+
* Invalidates a service and all its dependencies.
|
|
1264
|
+
*/
|
|
1265
|
+
invalidate(service, round = 1) {
|
|
1266
|
+
this.logger?.log(
|
|
1267
|
+
`[ServiceInvalidator] Starting invalidation process for ${service}`
|
|
1268
|
+
);
|
|
1269
|
+
const [, toInvalidate] = this.manager.get(service);
|
|
1270
|
+
const promises = [];
|
|
1271
|
+
if (toInvalidate) {
|
|
1272
|
+
promises.push(this.invalidateHolder(service, toInvalidate, round));
|
|
1197
1273
|
}
|
|
1198
|
-
const
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1274
|
+
const requestContexts = this.requestContextManager.getRequestContexts();
|
|
1275
|
+
for (const [requestId, requestContext] of requestContexts.entries()) {
|
|
1276
|
+
const holder = requestContext.get(service);
|
|
1277
|
+
if (holder) {
|
|
1278
|
+
this.logger?.log(
|
|
1279
|
+
`[ServiceInvalidator] Invalidating request-scoped instance ${service} in request ${requestId}`
|
|
1280
|
+
);
|
|
1281
|
+
promises.push(
|
|
1282
|
+
this.invalidateRequestHolder(requestId, service, holder, round)
|
|
1283
|
+
);
|
|
1284
|
+
}
|
|
1205
1285
|
}
|
|
1206
|
-
return
|
|
1286
|
+
return Promise.all(promises);
|
|
1207
1287
|
}
|
|
1208
1288
|
/**
|
|
1209
|
-
*
|
|
1210
|
-
*
|
|
1289
|
+
* Gracefully clears all services in the ServiceLocator using invalidation logic.
|
|
1290
|
+
* This method respects service dependencies and ensures proper cleanup order.
|
|
1291
|
+
* Services that depend on others will be invalidated first, then their dependencies.
|
|
1211
1292
|
*/
|
|
1212
|
-
async
|
|
1213
|
-
const
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1293
|
+
async clearAll(options = {}) {
|
|
1294
|
+
const {
|
|
1295
|
+
clearRequestContexts = true,
|
|
1296
|
+
maxRounds = 10,
|
|
1297
|
+
waitForSettlement = true
|
|
1298
|
+
} = options;
|
|
1299
|
+
this.logger?.log(
|
|
1300
|
+
"[ServiceInvalidator] Starting graceful clearing of all services"
|
|
1301
|
+
);
|
|
1302
|
+
if (waitForSettlement) {
|
|
1217
1303
|
this.logger?.log(
|
|
1218
|
-
|
|
1304
|
+
"[ServiceInvalidator] Waiting for all services to settle..."
|
|
1219
1305
|
);
|
|
1220
|
-
await
|
|
1221
|
-
return this.resolveTokenAndPrepareInstanceName(token);
|
|
1306
|
+
await this.ready();
|
|
1222
1307
|
}
|
|
1223
|
-
const
|
|
1224
|
-
|
|
1225
|
-
|
|
1308
|
+
const allServiceNames = this.getAllServiceNames();
|
|
1309
|
+
if (allServiceNames.length === 0) {
|
|
1310
|
+
this.logger?.log("[ServiceInvalidator] No singleton services to clear");
|
|
1311
|
+
} else {
|
|
1312
|
+
this.logger?.log(
|
|
1313
|
+
`[ServiceInvalidator] Found ${allServiceNames.length} services to clear: ${allServiceNames.join(", ")}`
|
|
1314
|
+
);
|
|
1315
|
+
await this.clearServicesWithDependencyAwareness(
|
|
1316
|
+
allServiceNames,
|
|
1317
|
+
maxRounds
|
|
1318
|
+
);
|
|
1319
|
+
}
|
|
1320
|
+
if (clearRequestContexts) {
|
|
1321
|
+
await this.requestContextManager.clearAllRequestContexts();
|
|
1322
|
+
}
|
|
1323
|
+
this.logger?.log("[ServiceInvalidator] Graceful clearing completed");
|
|
1226
1324
|
}
|
|
1227
1325
|
/**
|
|
1228
|
-
*
|
|
1326
|
+
* Waits for all services to settle (either created, destroyed, or error state).
|
|
1229
1327
|
*/
|
|
1230
|
-
async
|
|
1231
|
-
const
|
|
1232
|
-
|
|
1233
|
-
realToken
|
|
1328
|
+
async ready() {
|
|
1329
|
+
const holders = Array.from(this.manager.filter(() => true)).map(
|
|
1330
|
+
([, holder]) => holder
|
|
1234
1331
|
);
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
}
|
|
1238
|
-
const result = await this.createNewInstance(
|
|
1239
|
-
instanceName,
|
|
1240
|
-
realToken,
|
|
1241
|
-
realArgs
|
|
1332
|
+
await Promise.all(
|
|
1333
|
+
holders.map((holder) => this.waitForHolderToSettle(holder))
|
|
1242
1334
|
);
|
|
1243
|
-
|
|
1244
|
-
|
|
1335
|
+
}
|
|
1336
|
+
/**
|
|
1337
|
+
* Invalidates a single holder based on its current status.
|
|
1338
|
+
*/
|
|
1339
|
+
async invalidateHolder(key, holder, round) {
|
|
1340
|
+
await this.invalidateHolderByStatus(holder, round, {
|
|
1341
|
+
context: key,
|
|
1342
|
+
isRequestScoped: false,
|
|
1343
|
+
onCreationError: () => this.logger?.error(
|
|
1344
|
+
`[ServiceInvalidator] ${key} creation triggered too many invalidation rounds`
|
|
1345
|
+
),
|
|
1346
|
+
onRecursiveInvalidate: () => this.invalidate(key, round + 1),
|
|
1347
|
+
onDestroy: () => this.destroyHolder(key, holder)
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Invalidates a request-scoped holder based on its current status.
|
|
1352
|
+
*/
|
|
1353
|
+
async invalidateRequestHolder(requestId, instanceName, holder, round) {
|
|
1354
|
+
await this.invalidateHolderByStatus(holder, round, {
|
|
1355
|
+
context: `Request-scoped ${instanceName} in ${requestId}`,
|
|
1356
|
+
isRequestScoped: true,
|
|
1357
|
+
onCreationError: () => this.logger?.error(
|
|
1358
|
+
`[ServiceInvalidator] Request-scoped ${instanceName} in ${requestId} creation triggered too many invalidation rounds`
|
|
1359
|
+
),
|
|
1360
|
+
onRecursiveInvalidate: () => this.invalidateRequestHolder(
|
|
1361
|
+
requestId,
|
|
1362
|
+
instanceName,
|
|
1363
|
+
holder,
|
|
1364
|
+
round + 1
|
|
1365
|
+
),
|
|
1366
|
+
onDestroy: () => this.destroyRequestHolder(requestId, instanceName, holder)
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
/**
|
|
1370
|
+
* Common invalidation logic for holders based on their status.
|
|
1371
|
+
*/
|
|
1372
|
+
async invalidateHolderByStatus(holder, round, options) {
|
|
1373
|
+
switch (holder.status) {
|
|
1374
|
+
case "destroying" /* Destroying */:
|
|
1375
|
+
await holder.destroyPromise;
|
|
1376
|
+
break;
|
|
1377
|
+
case "creating" /* Creating */:
|
|
1378
|
+
await holder.creationPromise;
|
|
1379
|
+
if (round > 3) {
|
|
1380
|
+
options.onCreationError();
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
await options.onRecursiveInvalidate();
|
|
1384
|
+
break;
|
|
1385
|
+
default:
|
|
1386
|
+
await options.onDestroy();
|
|
1387
|
+
break;
|
|
1245
1388
|
}
|
|
1246
|
-
const [, holder] = result;
|
|
1247
|
-
return this.waitForInstanceReady(holder);
|
|
1248
1389
|
}
|
|
1249
1390
|
/**
|
|
1250
|
-
*
|
|
1251
|
-
|
|
1391
|
+
* Destroys a holder and cleans up its resources.
|
|
1392
|
+
*/
|
|
1393
|
+
async destroyHolder(key, holder) {
|
|
1394
|
+
await this.destroyHolderWithCleanup(holder, {
|
|
1395
|
+
context: key,
|
|
1396
|
+
logMessage: `[ServiceInvalidator] Invalidating ${key} and notifying listeners`,
|
|
1397
|
+
cleanup: () => this.manager.delete(key),
|
|
1398
|
+
eventName: key
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* Destroys a request-scoped holder and cleans up its resources.
|
|
1403
|
+
*/
|
|
1404
|
+
async destroyRequestHolder(requestId, instanceName, holder) {
|
|
1405
|
+
await this.destroyHolderWithCleanup(holder, {
|
|
1406
|
+
context: `Request-scoped ${instanceName} in ${requestId}`,
|
|
1407
|
+
logMessage: `[ServiceInvalidator] Invalidating request-scoped ${instanceName} in ${requestId} and notifying listeners`,
|
|
1408
|
+
cleanup: () => {
|
|
1409
|
+
const requestContext = this.requestContextManager.getRequestContexts().get(requestId);
|
|
1410
|
+
if (requestContext) {
|
|
1411
|
+
requestContext.delete(instanceName);
|
|
1412
|
+
}
|
|
1413
|
+
},
|
|
1414
|
+
eventName: instanceName
|
|
1415
|
+
});
|
|
1416
|
+
}
|
|
1417
|
+
/**
|
|
1418
|
+
* Common destroy logic for holders with customizable cleanup.
|
|
1419
|
+
*/
|
|
1420
|
+
async destroyHolderWithCleanup(holder, options) {
|
|
1421
|
+
holder.status = "destroying" /* Destroying */;
|
|
1422
|
+
this.logger?.log(options.logMessage);
|
|
1423
|
+
holder.destroyPromise = Promise.all(
|
|
1424
|
+
holder.destroyListeners.map((listener) => listener())
|
|
1425
|
+
).then(async () => {
|
|
1426
|
+
holder.destroyListeners = [];
|
|
1427
|
+
holder.deps.clear();
|
|
1428
|
+
options.cleanup();
|
|
1429
|
+
await this.emitInstanceEvent(options.eventName, "destroy");
|
|
1430
|
+
});
|
|
1431
|
+
await holder.destroyPromise;
|
|
1432
|
+
}
|
|
1433
|
+
/**
|
|
1434
|
+
* Waits for a holder to settle (either created, destroyed, or error state).
|
|
1252
1435
|
*/
|
|
1253
|
-
async
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1436
|
+
async waitForHolderToSettle(holder) {
|
|
1437
|
+
switch (holder.status) {
|
|
1438
|
+
case "creating" /* Creating */:
|
|
1439
|
+
await holder.creationPromise;
|
|
1440
|
+
break;
|
|
1441
|
+
case "destroying" /* Destroying */:
|
|
1442
|
+
await holder.destroyPromise;
|
|
1443
|
+
break;
|
|
1260
1444
|
}
|
|
1261
|
-
return this.tryGetSingletonInstance(instanceName);
|
|
1262
1445
|
}
|
|
1263
1446
|
/**
|
|
1264
|
-
*
|
|
1447
|
+
* Clears services with dependency awareness, ensuring proper cleanup order.
|
|
1448
|
+
* Services with no dependencies are cleared first, then services that depend on them.
|
|
1265
1449
|
*/
|
|
1266
|
-
async
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
const record = this.registry.get(realToken);
|
|
1271
|
-
if (record.scope !== "Request" /* Request */) {
|
|
1272
|
-
return null;
|
|
1273
|
-
}
|
|
1274
|
-
if (!this.currentRequestContext) {
|
|
1450
|
+
async clearServicesWithDependencyAwareness(serviceNames, maxRounds) {
|
|
1451
|
+
const clearedServices = /* @__PURE__ */ new Set();
|
|
1452
|
+
let round = 1;
|
|
1453
|
+
while (clearedServices.size < serviceNames.length && round <= maxRounds) {
|
|
1275
1454
|
this.logger?.log(
|
|
1276
|
-
`[
|
|
1455
|
+
`[ServiceInvalidator] Clearing round ${round}/${maxRounds}, ${clearedServices.size}/${serviceNames.length} services cleared`
|
|
1456
|
+
);
|
|
1457
|
+
const servicesToClearThisRound = this.findServicesReadyForClearing(
|
|
1458
|
+
serviceNames,
|
|
1459
|
+
clearedServices
|
|
1460
|
+
);
|
|
1461
|
+
if (servicesToClearThisRound.length === 0) {
|
|
1462
|
+
const remainingServices = serviceNames.filter(
|
|
1463
|
+
(name) => !clearedServices.has(name)
|
|
1464
|
+
);
|
|
1465
|
+
if (remainingServices.length > 0) {
|
|
1466
|
+
this.logger?.warn(
|
|
1467
|
+
`[ServiceInvalidator] No services ready for clearing, forcing cleanup of remaining: ${remainingServices.join(", ")}`
|
|
1468
|
+
);
|
|
1469
|
+
await this.forceClearServices(remainingServices);
|
|
1470
|
+
remainingServices.forEach((name) => clearedServices.add(name));
|
|
1471
|
+
}
|
|
1472
|
+
break;
|
|
1473
|
+
}
|
|
1474
|
+
const clearPromises = servicesToClearThisRound.map(
|
|
1475
|
+
async (serviceName) => {
|
|
1476
|
+
try {
|
|
1477
|
+
await this.invalidate(serviceName, round);
|
|
1478
|
+
clearedServices.add(serviceName);
|
|
1479
|
+
this.logger?.log(
|
|
1480
|
+
`[ServiceInvalidator] Successfully cleared service: ${serviceName}`
|
|
1481
|
+
);
|
|
1482
|
+
} catch (error) {
|
|
1483
|
+
this.logger?.error(
|
|
1484
|
+
`[ServiceInvalidator] Error clearing service ${serviceName}:`,
|
|
1485
|
+
error
|
|
1486
|
+
);
|
|
1487
|
+
clearedServices.add(serviceName);
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1277
1490
|
);
|
|
1278
|
-
|
|
1491
|
+
await Promise.all(clearPromises);
|
|
1492
|
+
round++;
|
|
1279
1493
|
}
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1494
|
+
if (clearedServices.size < serviceNames.length) {
|
|
1495
|
+
this.logger?.warn(
|
|
1496
|
+
`[ServiceInvalidator] Clearing completed after ${maxRounds} rounds, but ${serviceNames.length - clearedServices.size} services may not have been properly cleared`
|
|
1497
|
+
);
|
|
1283
1498
|
}
|
|
1284
|
-
return this.waitForInstanceReady(requestHolder);
|
|
1285
1499
|
}
|
|
1286
1500
|
/**
|
|
1287
|
-
*
|
|
1501
|
+
* Finds services that are ready to be cleared in the current round.
|
|
1502
|
+
* A service is ready if all its dependencies have already been cleared.
|
|
1288
1503
|
*/
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1504
|
+
findServicesReadyForClearing(allServiceNames, clearedServices) {
|
|
1505
|
+
return allServiceNames.filter((serviceName) => {
|
|
1506
|
+
if (clearedServices.has(serviceName)) {
|
|
1507
|
+
return false;
|
|
1508
|
+
}
|
|
1509
|
+
const [error, holder] = this.manager.get(serviceName);
|
|
1510
|
+
if (error) {
|
|
1511
|
+
return true;
|
|
1512
|
+
}
|
|
1513
|
+
const hasUnclearedDependencies = Array.from(holder.deps).some(
|
|
1514
|
+
(dep) => !clearedServices.has(dep)
|
|
1515
|
+
);
|
|
1516
|
+
return !hasUnclearedDependencies;
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
/**
|
|
1520
|
+
* Force clears services that couldn't be cleared through normal dependency resolution.
|
|
1521
|
+
* This handles edge cases like circular dependencies.
|
|
1522
|
+
*/
|
|
1523
|
+
async forceClearServices(serviceNames) {
|
|
1524
|
+
const promises = serviceNames.map(async (serviceName) => {
|
|
1525
|
+
try {
|
|
1526
|
+
const [error, holder] = this.manager.get(serviceName);
|
|
1527
|
+
if (!error && holder) {
|
|
1528
|
+
await this.destroyHolder(serviceName, holder);
|
|
1529
|
+
}
|
|
1530
|
+
} catch (error) {
|
|
1531
|
+
this.logger?.error(
|
|
1532
|
+
`[ServiceInvalidator] Error force clearing service ${serviceName}:`,
|
|
1533
|
+
error
|
|
1304
1534
|
);
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
return null;
|
|
1309
|
-
// Instance doesn't exist, should create new one
|
|
1310
|
-
default:
|
|
1311
|
-
return [error];
|
|
1312
|
-
}
|
|
1535
|
+
}
|
|
1536
|
+
});
|
|
1537
|
+
await Promise.all(promises);
|
|
1313
1538
|
}
|
|
1314
1539
|
/**
|
|
1315
|
-
*
|
|
1540
|
+
* Gets all service names currently managed by the ServiceLocator.
|
|
1316
1541
|
*/
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
case "creating" /* Creating */:
|
|
1320
|
-
await holder.creationPromise;
|
|
1321
|
-
return this.waitForInstanceReady(holder);
|
|
1322
|
-
case "destroying" /* Destroying */:
|
|
1323
|
-
return [new UnknownError("InstanceDestroying" /* InstanceDestroying */)];
|
|
1324
|
-
case "error" /* Error */:
|
|
1325
|
-
return [holder.instance];
|
|
1326
|
-
case "created" /* Created */:
|
|
1327
|
-
return [void 0, holder];
|
|
1328
|
-
default:
|
|
1329
|
-
return [new UnknownError("InstanceNotFound" /* InstanceNotFound */)];
|
|
1330
|
-
}
|
|
1542
|
+
getAllServiceNames() {
|
|
1543
|
+
return this.manager.getAllNames();
|
|
1331
1544
|
}
|
|
1332
1545
|
/**
|
|
1333
1546
|
* Emits events to listeners for instance lifecycle events.
|
|
1334
1547
|
*/
|
|
1335
1548
|
emitInstanceEvent(name, event = "create") {
|
|
1336
1549
|
this.logger?.log(
|
|
1337
|
-
`[
|
|
1550
|
+
`[ServiceInvalidator]#emitInstanceEvent() Notifying listeners for ${name} with event ${event}`
|
|
1338
1551
|
);
|
|
1339
1552
|
return this.eventBus.emit(name, event);
|
|
1340
1553
|
}
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1554
|
+
};
|
|
1555
|
+
|
|
1556
|
+
// src/service-locator-event-bus.mts
|
|
1557
|
+
var ServiceLocatorEventBus = class {
|
|
1558
|
+
constructor(logger = null) {
|
|
1559
|
+
this.logger = logger;
|
|
1560
|
+
}
|
|
1561
|
+
listeners = /* @__PURE__ */ new Map();
|
|
1562
|
+
on(ns, event, listener) {
|
|
1563
|
+
this.logger?.debug(`[ServiceLocatorEventBus]#on(): ns:${ns} event:${event}`);
|
|
1564
|
+
if (!this.listeners.has(ns)) {
|
|
1565
|
+
this.listeners.set(ns, /* @__PURE__ */ new Map());
|
|
1566
|
+
}
|
|
1567
|
+
const nsEvents = this.listeners.get(ns);
|
|
1568
|
+
if (!nsEvents.has(event)) {
|
|
1569
|
+
nsEvents.set(event, /* @__PURE__ */ new Set());
|
|
1356
1570
|
}
|
|
1571
|
+
nsEvents.get(event).add(listener);
|
|
1572
|
+
return () => {
|
|
1573
|
+
nsEvents.get(event).delete(listener);
|
|
1574
|
+
if (nsEvents.get(event)?.size === 0) {
|
|
1575
|
+
nsEvents.delete(event);
|
|
1576
|
+
}
|
|
1577
|
+
if (nsEvents.size === 0) {
|
|
1578
|
+
this.listeners.delete(ns);
|
|
1579
|
+
}
|
|
1580
|
+
};
|
|
1357
1581
|
}
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
this.
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
await this.handleInstantiationResult(
|
|
1379
|
-
instanceName,
|
|
1380
|
-
holder,
|
|
1381
|
-
ctx,
|
|
1382
|
-
deferred,
|
|
1383
|
-
scope,
|
|
1384
|
-
error,
|
|
1385
|
-
instance
|
|
1386
|
-
);
|
|
1387
|
-
}).catch(async (error) => {
|
|
1388
|
-
await this.handleInstantiationError(
|
|
1389
|
-
instanceName,
|
|
1390
|
-
holder,
|
|
1391
|
-
deferred,
|
|
1392
|
-
scope,
|
|
1393
|
-
error
|
|
1394
|
-
);
|
|
1582
|
+
async emit(key, event) {
|
|
1583
|
+
if (!this.listeners.has(key)) {
|
|
1584
|
+
return;
|
|
1585
|
+
}
|
|
1586
|
+
const events = this.listeners.get(key);
|
|
1587
|
+
this.logger?.debug(`[ServiceLocatorEventBus]#emit(): ${key}:${event}`);
|
|
1588
|
+
const res = await Promise.allSettled(
|
|
1589
|
+
[...events.get(event) ?? []].map((listener) => listener(event))
|
|
1590
|
+
).then((results) => {
|
|
1591
|
+
const res2 = results.filter((result) => result.status === "rejected").map((result) => {
|
|
1592
|
+
this.logger?.warn(
|
|
1593
|
+
`[ServiceLocatorEventBus]#emit(): ${key}:${event} rejected with`,
|
|
1594
|
+
result.reason
|
|
1595
|
+
);
|
|
1596
|
+
return result;
|
|
1597
|
+
});
|
|
1598
|
+
if (res2.length > 0) {
|
|
1599
|
+
return Promise.reject(res2);
|
|
1600
|
+
}
|
|
1601
|
+
return results;
|
|
1395
1602
|
});
|
|
1396
|
-
|
|
1397
|
-
return [void 0, holder];
|
|
1603
|
+
return res;
|
|
1398
1604
|
}
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1605
|
+
};
|
|
1606
|
+
|
|
1607
|
+
// src/service-locator-manager.mts
|
|
1608
|
+
var ServiceLocatorManager = class extends BaseInstanceHolderManager {
|
|
1609
|
+
constructor(logger = null) {
|
|
1610
|
+
super(logger);
|
|
1611
|
+
}
|
|
1612
|
+
get(name) {
|
|
1613
|
+
const holder = this._holders.get(name);
|
|
1614
|
+
if (holder) {
|
|
1615
|
+
if (holder.status === "destroying" /* Destroying */) {
|
|
1616
|
+
this.logger?.log(
|
|
1617
|
+
`[ServiceLocatorManager]#getInstanceHolder() Instance ${holder.name} is destroying`
|
|
1618
|
+
);
|
|
1619
|
+
return [DIError.instanceDestroying(holder.name), holder];
|
|
1620
|
+
} else if (holder.status === "error" /* Error */) {
|
|
1621
|
+
this.logger?.log(
|
|
1622
|
+
`[ServiceLocatorManager]#getInstanceHolder() Instance ${holder.name} is in error state`
|
|
1623
|
+
);
|
|
1624
|
+
return [holder.instance, holder];
|
|
1625
|
+
}
|
|
1626
|
+
return [void 0, holder];
|
|
1413
1627
|
} else {
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
holder,
|
|
1417
|
-
ctx,
|
|
1418
|
-
deferred,
|
|
1419
|
-
instance
|
|
1628
|
+
this.logger?.log(
|
|
1629
|
+
`[ServiceLocatorManager]#getInstanceHolder() Instance ${name} not found`
|
|
1420
1630
|
);
|
|
1631
|
+
return [DIError.instanceNotFound(name)];
|
|
1421
1632
|
}
|
|
1422
1633
|
}
|
|
1634
|
+
set(name, holder) {
|
|
1635
|
+
this._holders.set(name, holder);
|
|
1636
|
+
}
|
|
1637
|
+
has(name) {
|
|
1638
|
+
const [error, holder] = this.get(name);
|
|
1639
|
+
if (!error) {
|
|
1640
|
+
return [void 0, true];
|
|
1641
|
+
}
|
|
1642
|
+
if (error.code === "InstanceDestroying" /* InstanceDestroying */) {
|
|
1643
|
+
return [error];
|
|
1644
|
+
}
|
|
1645
|
+
return [void 0, !!holder];
|
|
1646
|
+
}
|
|
1647
|
+
// delete and filter methods are inherited from BaseInstanceHolderManager
|
|
1648
|
+
// createCreatingHolder method is inherited from BaseInstanceHolderManager
|
|
1649
|
+
/**
|
|
1650
|
+
* Creates a new holder with Created status and an actual instance.
|
|
1651
|
+
* This is useful for creating holders that already have their instance ready.
|
|
1652
|
+
* @param name The name of the instance
|
|
1653
|
+
* @param instance The actual instance to store
|
|
1654
|
+
* @param type The injectable type
|
|
1655
|
+
* @param scope The injectable scope
|
|
1656
|
+
* @param deps Optional set of dependencies
|
|
1657
|
+
* @returns The created holder
|
|
1658
|
+
*/
|
|
1659
|
+
storeCreatedHolder(name, instance, type, scope, deps = /* @__PURE__ */ new Set()) {
|
|
1660
|
+
const holder = this.createCreatedHolder(name, instance, type, scope, deps);
|
|
1661
|
+
this._holders.set(name, holder);
|
|
1662
|
+
return holder;
|
|
1663
|
+
}
|
|
1664
|
+
};
|
|
1665
|
+
|
|
1666
|
+
// src/token-processor.mts
|
|
1667
|
+
var TokenProcessor = class {
|
|
1668
|
+
constructor(logger = null) {
|
|
1669
|
+
this.logger = logger;
|
|
1670
|
+
}
|
|
1423
1671
|
/**
|
|
1424
|
-
*
|
|
1672
|
+
* Validates and resolves token arguments, handling factory token resolution and validation.
|
|
1425
1673
|
*/
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
ctx.deps.forEach((dependency) => {
|
|
1431
|
-
holder.destroyListeners.push(
|
|
1432
|
-
this.eventBus.on(
|
|
1433
|
-
dependency,
|
|
1434
|
-
"destroy",
|
|
1435
|
-
() => this.invalidate(instanceName)
|
|
1436
|
-
)
|
|
1437
|
-
);
|
|
1438
|
-
});
|
|
1674
|
+
validateAndResolveTokenArgs(token, args) {
|
|
1675
|
+
let actualToken = token;
|
|
1676
|
+
if (typeof token === "function") {
|
|
1677
|
+
actualToken = getInjectableToken(token);
|
|
1439
1678
|
}
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
error
|
|
1450
|
-
);
|
|
1451
|
-
holder.status = "error" /* Error */;
|
|
1452
|
-
holder.instance = error;
|
|
1453
|
-
holder.creationPromise = null;
|
|
1454
|
-
if (scope === "Singleton" /* Singleton */) {
|
|
1455
|
-
setTimeout(() => this.invalidate(instanceName), 10);
|
|
1679
|
+
let realArgs = args;
|
|
1680
|
+
if (actualToken instanceof BoundInjectionToken) {
|
|
1681
|
+
realArgs = actualToken.value;
|
|
1682
|
+
} else if (actualToken instanceof FactoryInjectionToken) {
|
|
1683
|
+
if (actualToken.resolved) {
|
|
1684
|
+
realArgs = actualToken.value;
|
|
1685
|
+
} else {
|
|
1686
|
+
return [DIError.factoryTokenNotResolved(token.name), { actualToken }];
|
|
1687
|
+
}
|
|
1456
1688
|
}
|
|
1457
|
-
|
|
1689
|
+
if (!actualToken.schema) {
|
|
1690
|
+
return [void 0, { actualToken, validatedArgs: realArgs }];
|
|
1691
|
+
}
|
|
1692
|
+
const validatedArgs = actualToken.schema?.safeParse(realArgs);
|
|
1693
|
+
if (validatedArgs && !validatedArgs.success) {
|
|
1694
|
+
this.logger?.error(
|
|
1695
|
+
`[TokenProcessor]#validateAndResolveTokenArgs(): Error validating args for ${actualToken.name.toString()}`,
|
|
1696
|
+
validatedArgs.error
|
|
1697
|
+
);
|
|
1698
|
+
return [DIError.unknown(validatedArgs.error), { actualToken }];
|
|
1699
|
+
}
|
|
1700
|
+
return [void 0, { actualToken, validatedArgs: validatedArgs?.data }];
|
|
1458
1701
|
}
|
|
1459
1702
|
/**
|
|
1460
|
-
*
|
|
1703
|
+
* Generates a unique instance name based on token and arguments.
|
|
1461
1704
|
*/
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
this.logger?.debug(
|
|
1466
|
-
`[ServiceLocator] Setting singleton instance for ${instanceName}`
|
|
1467
|
-
);
|
|
1468
|
-
this.manager.set(instanceName, holder);
|
|
1469
|
-
break;
|
|
1470
|
-
case "Request" /* Request */:
|
|
1471
|
-
if (this.currentRequestContext) {
|
|
1472
|
-
this.logger?.debug(
|
|
1473
|
-
`[ServiceLocator] Setting request-scoped instance for ${instanceName}`
|
|
1474
|
-
);
|
|
1475
|
-
this.currentRequestContext.addInstance(
|
|
1476
|
-
instanceName,
|
|
1477
|
-
holder.instance,
|
|
1478
|
-
holder
|
|
1479
|
-
);
|
|
1480
|
-
}
|
|
1481
|
-
break;
|
|
1705
|
+
generateInstanceName(token, args) {
|
|
1706
|
+
if (!args) {
|
|
1707
|
+
return token.toString();
|
|
1482
1708
|
}
|
|
1709
|
+
const formattedArgs = Object.entries(args).sort(([keyA], [keyB]) => keyA.localeCompare(keyB)).map(([key, value]) => `${key}=${this.formatArgValue(value)}`).join(",");
|
|
1710
|
+
return `${token.toString()}:${formattedArgs.replaceAll(/"/g, "").replaceAll(/:/g, "=")}`;
|
|
1483
1711
|
}
|
|
1484
1712
|
/**
|
|
1485
|
-
*
|
|
1713
|
+
* Formats a single argument value for instance name generation.
|
|
1486
1714
|
*/
|
|
1487
|
-
|
|
1488
|
-
if (
|
|
1489
|
-
|
|
1490
|
-
if (prePreparedInstance !== void 0) {
|
|
1491
|
-
this.logger?.debug(
|
|
1492
|
-
`[ServiceLocator] Using pre-prepared instance ${instanceName} from request context ${contextHolder.requestId}`
|
|
1493
|
-
);
|
|
1494
|
-
deps.add(instanceName);
|
|
1495
|
-
return prePreparedInstance;
|
|
1496
|
-
}
|
|
1715
|
+
formatArgValue(value) {
|
|
1716
|
+
if (typeof value === "function") {
|
|
1717
|
+
return `fn_${value.name}(${value.length})`;
|
|
1497
1718
|
}
|
|
1498
|
-
if (
|
|
1499
|
-
|
|
1500
|
-
if (prePreparedInstance !== void 0) {
|
|
1501
|
-
this.logger?.debug(
|
|
1502
|
-
`[ServiceLocator] Using pre-prepared instance ${instanceName} from current request context ${this.currentRequestContext.requestId}`
|
|
1503
|
-
);
|
|
1504
|
-
deps.add(instanceName);
|
|
1505
|
-
return prePreparedInstance;
|
|
1506
|
-
}
|
|
1719
|
+
if (typeof value === "symbol") {
|
|
1720
|
+
return value.toString();
|
|
1507
1721
|
}
|
|
1508
|
-
return
|
|
1722
|
+
return JSON.stringify(value).slice(0, 40);
|
|
1509
1723
|
}
|
|
1510
1724
|
/**
|
|
1511
1725
|
* Creates a factory context for dependency injection during service instantiation.
|
|
1512
|
-
* @param
|
|
1726
|
+
* @param serviceLocator Reference to the service locator for dependency resolution
|
|
1513
1727
|
*/
|
|
1514
|
-
createFactoryContext(
|
|
1728
|
+
createFactoryContext(serviceLocator) {
|
|
1515
1729
|
const destroyListeners = /* @__PURE__ */ new Set();
|
|
1516
1730
|
const deps = /* @__PURE__ */ new Set();
|
|
1517
|
-
const self = this;
|
|
1518
1731
|
function addDestroyListener(listener) {
|
|
1519
1732
|
destroyListeners.add(listener);
|
|
1520
1733
|
}
|
|
@@ -1524,20 +1737,11 @@ var ServiceLocator = class {
|
|
|
1524
1737
|
return {
|
|
1525
1738
|
// @ts-expect-error This is correct type
|
|
1526
1739
|
async inject(token, args) {
|
|
1527
|
-
const
|
|
1528
|
-
const prePreparedInstance = self.tryGetPrePreparedInstance(
|
|
1529
|
-
instanceName,
|
|
1530
|
-
contextHolder,
|
|
1531
|
-
deps
|
|
1532
|
-
);
|
|
1533
|
-
if (prePreparedInstance !== void 0) {
|
|
1534
|
-
return prePreparedInstance;
|
|
1535
|
-
}
|
|
1536
|
-
const [error, instance] = await self.getInstance(
|
|
1740
|
+
const [error, instance] = await serviceLocator.getInstance(
|
|
1537
1741
|
token,
|
|
1538
1742
|
args,
|
|
1539
|
-
({ instanceName
|
|
1540
|
-
deps.add(
|
|
1743
|
+
({ instanceName }) => {
|
|
1744
|
+
deps.add(instanceName);
|
|
1541
1745
|
}
|
|
1542
1746
|
);
|
|
1543
1747
|
if (error) {
|
|
@@ -1547,31 +1751,204 @@ var ServiceLocator = class {
|
|
|
1547
1751
|
},
|
|
1548
1752
|
addDestroyListener,
|
|
1549
1753
|
getDestroyListeners,
|
|
1550
|
-
locator:
|
|
1754
|
+
locator: serviceLocator,
|
|
1551
1755
|
deps
|
|
1552
1756
|
};
|
|
1553
1757
|
}
|
|
1554
1758
|
/**
|
|
1555
|
-
*
|
|
1759
|
+
* Tries to get a pre-prepared instance from request contexts.
|
|
1556
1760
|
*/
|
|
1557
|
-
|
|
1558
|
-
if (
|
|
1559
|
-
|
|
1761
|
+
tryGetPrePreparedInstance(instanceName, contextHolder, deps, currentRequestContext) {
|
|
1762
|
+
if (contextHolder && contextHolder.priority > 0) {
|
|
1763
|
+
const prePreparedInstance = contextHolder.get(instanceName)?.instance;
|
|
1764
|
+
if (prePreparedInstance !== void 0) {
|
|
1765
|
+
this.logger?.debug(
|
|
1766
|
+
`[TokenProcessor] Using pre-prepared instance ${instanceName} from request context ${contextHolder.requestId}`
|
|
1767
|
+
);
|
|
1768
|
+
deps.add(instanceName);
|
|
1769
|
+
return prePreparedInstance;
|
|
1770
|
+
}
|
|
1560
1771
|
}
|
|
1561
|
-
|
|
1562
|
-
|
|
1772
|
+
if (currentRequestContext && currentRequestContext !== contextHolder) {
|
|
1773
|
+
const prePreparedInstance = currentRequestContext.get(instanceName)?.instance;
|
|
1774
|
+
if (prePreparedInstance !== void 0) {
|
|
1775
|
+
this.logger?.debug(
|
|
1776
|
+
`[TokenProcessor] Using pre-prepared instance ${instanceName} from current request context ${currentRequestContext.requestId}`
|
|
1777
|
+
);
|
|
1778
|
+
deps.add(instanceName);
|
|
1779
|
+
return prePreparedInstance;
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
return void 0;
|
|
1563
1783
|
}
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1784
|
+
};
|
|
1785
|
+
|
|
1786
|
+
// src/service-locator.mts
|
|
1787
|
+
var ServiceLocator = class {
|
|
1788
|
+
constructor(registry = globalRegistry, logger = null, injectors = defaultInjectors) {
|
|
1789
|
+
this.registry = registry;
|
|
1790
|
+
this.logger = logger;
|
|
1791
|
+
this.injectors = injectors;
|
|
1792
|
+
this.eventBus = new ServiceLocatorEventBus(logger);
|
|
1793
|
+
this.manager = new ServiceLocatorManager(logger);
|
|
1794
|
+
this.serviceInstantiator = new ServiceInstantiator(injectors);
|
|
1795
|
+
this.tokenProcessor = new TokenProcessor(logger);
|
|
1796
|
+
this.requestContextManager = new RequestContextManager(logger);
|
|
1797
|
+
this.serviceInvalidator = new ServiceInvalidator(
|
|
1798
|
+
this.manager,
|
|
1799
|
+
this.requestContextManager,
|
|
1800
|
+
this.eventBus,
|
|
1801
|
+
logger
|
|
1802
|
+
);
|
|
1803
|
+
this.instanceResolver = new InstanceResolver(
|
|
1804
|
+
this.registry,
|
|
1805
|
+
this.manager,
|
|
1806
|
+
this.serviceInstantiator,
|
|
1807
|
+
this.tokenProcessor,
|
|
1808
|
+
logger,
|
|
1809
|
+
this
|
|
1810
|
+
);
|
|
1811
|
+
}
|
|
1812
|
+
eventBus;
|
|
1813
|
+
manager;
|
|
1814
|
+
serviceInstantiator;
|
|
1815
|
+
tokenProcessor;
|
|
1816
|
+
requestContextManager;
|
|
1817
|
+
serviceInvalidator;
|
|
1818
|
+
instanceResolver;
|
|
1819
|
+
// ============================================================================
|
|
1820
|
+
// PUBLIC METHODS
|
|
1821
|
+
// ============================================================================
|
|
1822
|
+
getEventBus() {
|
|
1823
|
+
return this.eventBus;
|
|
1824
|
+
}
|
|
1825
|
+
getManager() {
|
|
1826
|
+
return this.manager;
|
|
1827
|
+
}
|
|
1828
|
+
getRequestContexts() {
|
|
1829
|
+
return this.requestContextManager.getRequestContexts();
|
|
1830
|
+
}
|
|
1831
|
+
getRequestContextManager() {
|
|
1832
|
+
return this.requestContextManager;
|
|
1833
|
+
}
|
|
1834
|
+
getServiceInvalidator() {
|
|
1835
|
+
return this.serviceInvalidator;
|
|
1836
|
+
}
|
|
1837
|
+
getInstanceIdentifier(token, args) {
|
|
1838
|
+
const [err, { actualToken, validatedArgs }] = this.tokenProcessor.validateAndResolveTokenArgs(token, args);
|
|
1839
|
+
if (err) {
|
|
1840
|
+
throw err;
|
|
1570
1841
|
}
|
|
1571
|
-
|
|
1572
|
-
|
|
1842
|
+
return this.tokenProcessor.generateInstanceName(actualToken, validatedArgs);
|
|
1843
|
+
}
|
|
1844
|
+
async getInstance(token, args, onPrepare) {
|
|
1845
|
+
const [err, data] = await this.instanceResolver.resolveInstance(
|
|
1846
|
+
token,
|
|
1847
|
+
args,
|
|
1848
|
+
this.requestContextManager.getCurrentRequestContext() || void 0
|
|
1849
|
+
);
|
|
1850
|
+
if (err) {
|
|
1851
|
+
return [err];
|
|
1573
1852
|
}
|
|
1574
|
-
|
|
1853
|
+
if (onPrepare) {
|
|
1854
|
+
const instanceName = this.getInstanceIdentifier(token, args);
|
|
1855
|
+
const [tokenErr, { actualToken, validatedArgs }] = this.tokenProcessor.validateAndResolveTokenArgs(token, args);
|
|
1856
|
+
if (!tokenErr) {
|
|
1857
|
+
onPrepare({ instanceName, actualToken, validatedArgs });
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
return [void 0, data];
|
|
1861
|
+
}
|
|
1862
|
+
async getOrThrowInstance(token, args) {
|
|
1863
|
+
const [error, instance] = await this.getInstance(token, args);
|
|
1864
|
+
if (error) {
|
|
1865
|
+
throw error;
|
|
1866
|
+
}
|
|
1867
|
+
return instance;
|
|
1868
|
+
}
|
|
1869
|
+
getSyncInstance(token, args) {
|
|
1870
|
+
return this.instanceResolver.getSyncInstance(
|
|
1871
|
+
token,
|
|
1872
|
+
args,
|
|
1873
|
+
this.requestContextManager.getCurrentRequestContext()
|
|
1874
|
+
);
|
|
1875
|
+
}
|
|
1876
|
+
invalidate(service, round = 1) {
|
|
1877
|
+
return this.serviceInvalidator.invalidate(service, round);
|
|
1878
|
+
}
|
|
1879
|
+
/**
|
|
1880
|
+
* Gracefully clears all services in the ServiceLocator using invalidation logic.
|
|
1881
|
+
* This method respects service dependencies and ensures proper cleanup order.
|
|
1882
|
+
* Services that depend on others will be invalidated first, then their dependencies.
|
|
1883
|
+
*
|
|
1884
|
+
* @param options Optional configuration for the clearing process
|
|
1885
|
+
* @returns Promise that resolves when all services have been cleared
|
|
1886
|
+
*/
|
|
1887
|
+
async clearAll(options = {}) {
|
|
1888
|
+
return this.serviceInvalidator.clearAll(options);
|
|
1889
|
+
}
|
|
1890
|
+
// ============================================================================
|
|
1891
|
+
// REQUEST CONTEXT MANAGEMENT
|
|
1892
|
+
// ============================================================================
|
|
1893
|
+
/**
|
|
1894
|
+
* Begins a new request context with the given parameters.
|
|
1895
|
+
* @param requestId Unique identifier for this request
|
|
1896
|
+
* @param metadata Optional metadata for the request
|
|
1897
|
+
* @param priority Priority for resolution (higher = more priority)
|
|
1898
|
+
* @returns The created request context holder
|
|
1899
|
+
*/
|
|
1900
|
+
beginRequest(requestId, metadata, priority = 100) {
|
|
1901
|
+
return this.requestContextManager.beginRequest(
|
|
1902
|
+
requestId,
|
|
1903
|
+
metadata,
|
|
1904
|
+
priority
|
|
1905
|
+
);
|
|
1906
|
+
}
|
|
1907
|
+
/**
|
|
1908
|
+
* Ends a request context and cleans up all associated instances.
|
|
1909
|
+
* @param requestId The request ID to end
|
|
1910
|
+
*/
|
|
1911
|
+
async endRequest(requestId) {
|
|
1912
|
+
return this.requestContextManager.endRequest(requestId);
|
|
1913
|
+
}
|
|
1914
|
+
/**
|
|
1915
|
+
* Gets the current request context.
|
|
1916
|
+
* @returns The current request context holder or null
|
|
1917
|
+
*/
|
|
1918
|
+
getCurrentRequestContext() {
|
|
1919
|
+
return this.requestContextManager.getCurrentRequestContext();
|
|
1920
|
+
}
|
|
1921
|
+
/**
|
|
1922
|
+
* Sets the current request context.
|
|
1923
|
+
* @param requestId The request ID to set as current
|
|
1924
|
+
*/
|
|
1925
|
+
setCurrentRequestContext(requestId) {
|
|
1926
|
+
return this.requestContextManager.setCurrentRequestContext(requestId);
|
|
1927
|
+
}
|
|
1928
|
+
/**
|
|
1929
|
+
* Waits for all services to settle (either created, destroyed, or error state).
|
|
1930
|
+
*/
|
|
1931
|
+
async ready() {
|
|
1932
|
+
return this.serviceInvalidator.ready();
|
|
1933
|
+
}
|
|
1934
|
+
/**
|
|
1935
|
+
* Helper method for TokenProcessor to access pre-prepared instances.
|
|
1936
|
+
* This is needed for the factory context creation.
|
|
1937
|
+
*/
|
|
1938
|
+
tryGetPrePreparedInstance(instanceName, contextHolder, deps) {
|
|
1939
|
+
return this.tokenProcessor.tryGetPrePreparedInstance(
|
|
1940
|
+
instanceName,
|
|
1941
|
+
contextHolder,
|
|
1942
|
+
deps,
|
|
1943
|
+
this.requestContextManager.getCurrentRequestContext()
|
|
1944
|
+
);
|
|
1945
|
+
}
|
|
1946
|
+
/**
|
|
1947
|
+
* Helper method for InstanceResolver to generate instance names.
|
|
1948
|
+
* This is needed for the factory context creation.
|
|
1949
|
+
*/
|
|
1950
|
+
generateInstanceName(token, args) {
|
|
1951
|
+
return this.tokenProcessor.generateInstanceName(token, args);
|
|
1575
1952
|
}
|
|
1576
1953
|
};
|
|
1577
1954
|
|
|
@@ -1579,11 +1956,14 @@ var ServiceLocator = class {
|
|
|
1579
1956
|
var _Container_decorators, _init2;
|
|
1580
1957
|
_Container_decorators = [Injectable()];
|
|
1581
1958
|
var _Container = class _Container {
|
|
1582
|
-
serviceLocator;
|
|
1583
1959
|
constructor(registry = globalRegistry, logger = null, injectors = defaultInjectors) {
|
|
1960
|
+
this.registry = registry;
|
|
1961
|
+
this.logger = logger;
|
|
1962
|
+
this.injectors = injectors;
|
|
1584
1963
|
this.serviceLocator = new ServiceLocator(registry, logger, injectors);
|
|
1585
1964
|
this.registerSelf();
|
|
1586
1965
|
}
|
|
1966
|
+
serviceLocator;
|
|
1587
1967
|
registerSelf() {
|
|
1588
1968
|
const token = getInjectableToken(_Container);
|
|
1589
1969
|
const instanceName = this.serviceLocator.getInstanceIdentifier(token);
|
|
@@ -1607,15 +1987,54 @@ var _Container = class _Container {
|
|
|
1607
1987
|
* Invalidates a service and its dependencies
|
|
1608
1988
|
*/
|
|
1609
1989
|
async invalidate(service) {
|
|
1610
|
-
const
|
|
1611
|
-
if (holderMap.size === 0) {
|
|
1612
|
-
return;
|
|
1613
|
-
}
|
|
1614
|
-
const holder = holderMap.values().next().value;
|
|
1990
|
+
const holder = this.getHolderByInstance(service);
|
|
1615
1991
|
if (holder) {
|
|
1616
1992
|
await this.serviceLocator.invalidate(holder.name);
|
|
1993
|
+
} else {
|
|
1994
|
+
const requestHolder = this.getRequestHolderByInstance(service);
|
|
1995
|
+
if (requestHolder) {
|
|
1996
|
+
await this.serviceLocator.invalidate(requestHolder.name);
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
/**
|
|
2001
|
+
* Gets a service holder by instance (reverse lookup)
|
|
2002
|
+
*/
|
|
2003
|
+
getHolderByInstance(instance) {
|
|
2004
|
+
const holderMap = Array.from(
|
|
2005
|
+
this.serviceLocator.getManager().filter((holder) => holder.instance === instance).values()
|
|
2006
|
+
);
|
|
2007
|
+
return holderMap.length > 0 ? holderMap[0] : null;
|
|
2008
|
+
}
|
|
2009
|
+
getRequestHolderByInstance(instance) {
|
|
2010
|
+
const requestContexts = this.serviceLocator.getRequestContextManager().getRequestContexts();
|
|
2011
|
+
if (requestContexts) {
|
|
2012
|
+
for (const requestContext of requestContexts.values()) {
|
|
2013
|
+
for (const holder of requestContext.holders.values()) {
|
|
2014
|
+
if (holder.instance === instance) {
|
|
2015
|
+
return holder;
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
return null;
|
|
2021
|
+
}
|
|
2022
|
+
/**
|
|
2023
|
+
* Checks if a service is registered in the container
|
|
2024
|
+
*/
|
|
2025
|
+
isRegistered(token) {
|
|
2026
|
+
try {
|
|
2027
|
+
return this.serviceLocator.getInstanceIdentifier(token) !== null;
|
|
2028
|
+
} catch {
|
|
2029
|
+
return false;
|
|
1617
2030
|
}
|
|
1618
2031
|
}
|
|
2032
|
+
/**
|
|
2033
|
+
* Disposes the container and cleans up all resources
|
|
2034
|
+
*/
|
|
2035
|
+
async dispose() {
|
|
2036
|
+
await this.serviceLocator.clearAll();
|
|
2037
|
+
}
|
|
1619
2038
|
/**
|
|
1620
2039
|
* Waits for all pending operations to complete
|
|
1621
2040
|
*/
|
|
@@ -1656,6 +2075,13 @@ var _Container = class _Container {
|
|
|
1656
2075
|
setCurrentRequestContext(requestId) {
|
|
1657
2076
|
this.serviceLocator.setCurrentRequestContext(requestId);
|
|
1658
2077
|
}
|
|
2078
|
+
/**
|
|
2079
|
+
* Clears all instances and bindings from the container.
|
|
2080
|
+
* This is useful for testing or resetting the container state.
|
|
2081
|
+
*/
|
|
2082
|
+
clear() {
|
|
2083
|
+
return this.serviceLocator.clearAll();
|
|
2084
|
+
}
|
|
1659
2085
|
};
|
|
1660
2086
|
_init2 = __decoratorStart();
|
|
1661
2087
|
_Container = __decorateElement(_init2, 0, "Container", _Container_decorators, _Container);
|
|
@@ -1665,36 +2091,36 @@ var Container = _Container;
|
|
|
1665
2091
|
exports.BaseInstanceHolderManager = BaseInstanceHolderManager;
|
|
1666
2092
|
exports.BoundInjectionToken = BoundInjectionToken;
|
|
1667
2093
|
exports.Container = Container;
|
|
2094
|
+
exports.DIError = DIError;
|
|
2095
|
+
exports.DIErrorCode = DIErrorCode;
|
|
1668
2096
|
exports.DefaultRequestContextHolder = DefaultRequestContextHolder;
|
|
1669
|
-
exports.
|
|
1670
|
-
exports.ErrorsEnum = ErrorsEnum;
|
|
2097
|
+
exports.ErrorsEnum = DIErrorCode;
|
|
1671
2098
|
exports.Factory = Factory;
|
|
1672
2099
|
exports.FactoryInjectionToken = FactoryInjectionToken;
|
|
1673
|
-
exports.FactoryNotFound =
|
|
1674
|
-
exports.FactoryTokenNotResolved =
|
|
2100
|
+
exports.FactoryNotFound = DIError;
|
|
2101
|
+
exports.FactoryTokenNotResolved = DIError;
|
|
1675
2102
|
exports.Injectable = Injectable;
|
|
1676
2103
|
exports.InjectableScope = InjectableScope;
|
|
1677
2104
|
exports.InjectableTokenMeta = InjectableTokenMeta;
|
|
1678
2105
|
exports.InjectableType = InjectableType;
|
|
1679
2106
|
exports.InjectionToken = InjectionToken;
|
|
1680
|
-
exports.InstanceDestroying =
|
|
1681
|
-
exports.
|
|
1682
|
-
exports.InstanceNotFound = InstanceNotFound;
|
|
2107
|
+
exports.InstanceDestroying = DIError;
|
|
2108
|
+
exports.InstanceNotFound = DIError;
|
|
1683
2109
|
exports.Registry = Registry;
|
|
1684
2110
|
exports.ServiceInstantiator = ServiceInstantiator;
|
|
1685
2111
|
exports.ServiceLocator = ServiceLocator;
|
|
1686
2112
|
exports.ServiceLocatorEventBus = ServiceLocatorEventBus;
|
|
1687
2113
|
exports.ServiceLocatorInstanceHolderStatus = ServiceLocatorInstanceHolderStatus;
|
|
1688
2114
|
exports.ServiceLocatorManager = ServiceLocatorManager;
|
|
1689
|
-
exports.UnknownError =
|
|
2115
|
+
exports.UnknownError = DIError;
|
|
1690
2116
|
exports.asyncInject = asyncInject;
|
|
1691
|
-
exports.createDeferred = createDeferred;
|
|
1692
2117
|
exports.createRequestContextHolder = createRequestContextHolder;
|
|
1693
2118
|
exports.defaultInjectors = defaultInjectors;
|
|
1694
2119
|
exports.getInjectableToken = getInjectableToken;
|
|
1695
2120
|
exports.getInjectors = getInjectors;
|
|
1696
2121
|
exports.globalRegistry = globalRegistry;
|
|
1697
2122
|
exports.inject = inject;
|
|
2123
|
+
exports.optional = optional;
|
|
1698
2124
|
exports.provideFactoryContext = provideFactoryContext;
|
|
1699
2125
|
exports.wrapSyncInit = wrapSyncInit;
|
|
1700
2126
|
//# sourceMappingURL=index.js.map
|