@navios/di 0.4.2 → 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.
Files changed (120) hide show
  1. package/README.md +211 -1
  2. package/coverage/clover.xml +1912 -1277
  3. package/coverage/coverage-final.json +37 -28
  4. package/coverage/docs/examples/basic-usage.mts.html +1 -1
  5. package/coverage/docs/examples/factory-pattern.mts.html +1 -1
  6. package/coverage/docs/examples/index.html +1 -1
  7. package/coverage/docs/examples/injection-tokens.mts.html +1 -1
  8. package/coverage/docs/examples/request-scope-example.mts.html +1 -1
  9. package/coverage/docs/examples/service-lifecycle.mts.html +1 -1
  10. package/coverage/index.html +71 -41
  11. package/coverage/lib/_tsup-dts-rollup.d.mts.html +682 -43
  12. package/coverage/lib/index.d.mts.html +7 -4
  13. package/coverage/lib/index.html +5 -5
  14. package/coverage/lib/testing/index.d.mts.html +91 -0
  15. package/coverage/lib/testing/index.html +116 -0
  16. package/coverage/src/base-instance-holder-manager.mts.html +589 -0
  17. package/coverage/src/container.mts.html +257 -74
  18. package/coverage/src/decorators/factory.decorator.mts.html +1 -1
  19. package/coverage/src/decorators/index.html +1 -1
  20. package/coverage/src/decorators/index.mts.html +1 -1
  21. package/coverage/src/decorators/injectable.decorator.mts.html +20 -20
  22. package/coverage/src/enums/index.html +1 -1
  23. package/coverage/src/enums/index.mts.html +1 -1
  24. package/coverage/src/enums/injectable-scope.enum.mts.html +1 -1
  25. package/coverage/src/enums/injectable-type.enum.mts.html +1 -1
  26. package/coverage/src/errors/di-error.mts.html +292 -0
  27. package/coverage/src/errors/errors.enum.mts.html +30 -21
  28. package/coverage/src/errors/factory-not-found.mts.html +31 -22
  29. package/coverage/src/errors/factory-token-not-resolved.mts.html +29 -26
  30. package/coverage/src/errors/index.html +56 -41
  31. package/coverage/src/errors/index.mts.html +15 -9
  32. package/coverage/src/errors/instance-destroying.mts.html +31 -22
  33. package/coverage/src/errors/instance-expired.mts.html +31 -22
  34. package/coverage/src/errors/instance-not-found.mts.html +31 -22
  35. package/coverage/src/errors/unknown-error.mts.html +31 -43
  36. package/coverage/src/event-emitter.mts.html +14 -14
  37. package/coverage/src/factory-context.mts.html +1 -1
  38. package/coverage/src/index.html +121 -46
  39. package/coverage/src/index.mts.html +7 -4
  40. package/coverage/src/injection-token.mts.html +28 -28
  41. package/coverage/src/injector.mts.html +1 -1
  42. package/coverage/src/instance-resolver.mts.html +1762 -0
  43. package/coverage/src/interfaces/factory.interface.mts.html +1 -1
  44. package/coverage/src/interfaces/index.html +1 -1
  45. package/coverage/src/interfaces/index.mts.html +1 -1
  46. package/coverage/src/interfaces/on-service-destroy.interface.mts.html +1 -1
  47. package/coverage/src/interfaces/on-service-init.interface.mts.html +1 -1
  48. package/coverage/src/registry.mts.html +28 -28
  49. package/coverage/src/request-context-holder.mts.html +183 -102
  50. package/coverage/src/request-context-manager.mts.html +532 -0
  51. package/coverage/src/service-instantiator.mts.html +49 -49
  52. package/coverage/src/service-invalidator.mts.html +1372 -0
  53. package/coverage/src/service-locator-event-bus.mts.html +48 -48
  54. package/coverage/src/service-locator-instance-holder.mts.html +2 -14
  55. package/coverage/src/service-locator-manager.mts.html +71 -335
  56. package/coverage/src/service-locator.mts.html +240 -2328
  57. package/coverage/src/symbols/index.html +1 -1
  58. package/coverage/src/symbols/index.mts.html +1 -1
  59. package/coverage/src/symbols/injectable-token.mts.html +1 -1
  60. package/coverage/src/testing/index.html +131 -0
  61. package/coverage/src/testing/index.mts.html +88 -0
  62. package/coverage/src/testing/test-container.mts.html +445 -0
  63. package/coverage/src/token-processor.mts.html +607 -0
  64. package/coverage/src/utils/defer.mts.html +28 -214
  65. package/coverage/src/utils/get-injectable-token.mts.html +7 -7
  66. package/coverage/src/utils/get-injectors.mts.html +99 -99
  67. package/coverage/src/utils/index.html +15 -15
  68. package/coverage/src/utils/index.mts.html +4 -7
  69. package/coverage/src/utils/types.mts.html +1 -1
  70. package/docs/injectable.md +51 -11
  71. package/docs/scopes.md +63 -29
  72. package/lib/_tsup-dts-rollup.d.mts +376 -213
  73. package/lib/_tsup-dts-rollup.d.ts +376 -213
  74. package/lib/{chunk-3NLYPYBY.mjs → chunk-44F3LXW5.mjs} +1021 -605
  75. package/lib/chunk-44F3LXW5.mjs.map +1 -0
  76. package/lib/index.d.mts +6 -4
  77. package/lib/index.d.ts +6 -4
  78. package/lib/index.js +1192 -776
  79. package/lib/index.js.map +1 -1
  80. package/lib/index.mjs +2 -2
  81. package/lib/testing/index.js +1258 -840
  82. package/lib/testing/index.js.map +1 -1
  83. package/lib/testing/index.mjs +1 -1
  84. package/package.json +1 -1
  85. package/src/__tests__/container.spec.mts +47 -13
  86. package/src/__tests__/errors.spec.mts +53 -27
  87. package/src/__tests__/injectable.spec.mts +73 -0
  88. package/src/__tests__/request-scope.spec.mts +0 -2
  89. package/src/__tests__/service-locator-manager.spec.mts +12 -82
  90. package/src/__tests__/service-locator.spec.mts +1009 -1
  91. package/src/__type-tests__/inject.spec-d.mts +30 -7
  92. package/src/__type-tests__/injectable.spec-d.mts +76 -37
  93. package/src/base-instance-holder-manager.mts +2 -9
  94. package/src/container.mts +61 -9
  95. package/src/decorators/injectable.decorator.mts +29 -5
  96. package/src/errors/di-error.mts +69 -0
  97. package/src/errors/index.mts +9 -7
  98. package/src/injection-token.mts +1 -0
  99. package/src/injector.mts +2 -0
  100. package/src/instance-resolver.mts +559 -0
  101. package/src/request-context-holder.mts +0 -2
  102. package/src/request-context-manager.mts +149 -0
  103. package/src/service-invalidator.mts +429 -0
  104. package/src/service-locator-instance-holder.mts +0 -4
  105. package/src/service-locator-manager.mts +10 -40
  106. package/src/service-locator.mts +86 -782
  107. package/src/token-processor.mts +174 -0
  108. package/src/utils/get-injectors.mts +161 -24
  109. package/src/utils/index.mts +0 -1
  110. package/src/utils/types.mts +12 -8
  111. package/lib/chunk-3NLYPYBY.mjs.map +0 -1
  112. package/src/__tests__/defer.spec.mts +0 -166
  113. package/src/errors/errors.enum.mts +0 -8
  114. package/src/errors/factory-not-found.mts +0 -8
  115. package/src/errors/factory-token-not-resolved.mts +0 -10
  116. package/src/errors/instance-destroying.mts +0 -8
  117. package/src/errors/instance-expired.mts +0 -8
  118. package/src/errors/instance-not-found.mts +0 -8
  119. package/src/errors/unknown-error.mts +0 -15
  120. package/src/utils/defer.mts +0 -73
@@ -149,6 +149,7 @@ var InjectableTokenMeta = Symbol.for("InjectableTokenMeta");
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
- let injectableToken = token ?? InjectionToken.create(target);
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 asyncInject2(token, args) {
191
+ function getRequest(token, args) {
186
192
  if (!injectState) {
187
193
  throw new Error(
188
- "[Injector] Trying to access inject outside of a injectable context"
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 request = injectState.requests[idx];
194
- if (request.token !== token) {
199
+ const request2 = injectState.requests[idx];
200
+ if (request2.token !== token) {
195
201
  throw new Error(
196
- `[Injector] Wrong token order. Expected ${request.token.toString()} but got ${token.toString()}`
202
+ `[Injector] Wrong token order. Expected ${request2.token.toString()} but got ${token.toString()}`
197
203
  );
198
204
  }
199
- return request.promise;
200
- }
201
- const promise = getFactoryContext().inject(token, args);
202
- injectState.requests.push({
203
- token,
204
- promise
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 promise;
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
- const promise = getFactoryContext().inject(realToken, args);
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 use asyncInject() instead of inject() or do not use the value outside of class methods`
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/factory-not-found.mts
345
- var FactoryNotFound = class extends Error {
346
- constructor(name) {
347
- super(`Factory ${name} not found`);
348
- this.name = name;
349
- }
350
- code = "FactoryNotFound" /* FactoryNotFound */;
351
- };
352
-
353
- // src/errors/factory-token-not-resolved.mts
354
- var FactoryTokenNotResolved = class extends Error {
355
- code = "FactoryTokenNotResolved" /* FactoryTokenNotResolved */;
356
- constructor(name) {
357
- super(`Factory token not resolved: ${name.toString()}`);
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
- // src/errors/instance-destroying.mts
362
- var InstanceDestroying = class extends Error {
363
- constructor(name) {
364
- super(`Instance ${name} destroying`);
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
- code = "InstanceDestroying" /* InstanceDestroying */;
368
- };
369
-
370
- // src/errors/instance-expired.mts
371
- var InstanceExpired = class extends Error {
372
- constructor(name) {
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
- code = "InstanceExpired" /* InstanceExpired */;
377
- };
378
-
379
- // src/errors/instance-not-found.mts
380
- var InstanceNotFound = class extends Error {
381
- constructor(name) {
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
- code = "InstanceNotFound" /* InstanceNotFound */;
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
- super(message.message);
395
- this.parent = message;
396
- return;
376
+ return new _DIError("UnknownError" /* UnknownError */, message.message, {
377
+ ...context,
378
+ parent: message
379
+ });
397
380
  }
398
- super(message);
381
+ return new _DIError("UnknownError" /* UnknownError */, message, context);
399
382
  }
400
383
  };
401
384
 
402
- // src/base-instance-holder-manager.mts
403
- var BaseInstanceHolderManager = class {
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._holders = /* @__PURE__ */ new Map();
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
- * Deletes a holder by name.
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
- delete(name) {
421
- return this._holders.delete(name);
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
- * Filters holders based on a predicate function.
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
- filter(predicate) {
429
- return new Map(
430
- [...this._holders].filter(([key, value]) => predicate(value, key))
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
- * Clears all holders from this manager.
447
+ * Internal method to resolve token args and create instance name.
448
+ * Handles factory token resolution and validation.
435
449
  */
436
- clear() {
437
- this._holders.clear();
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 number of holders currently managed.
469
+ * Gets an instance by its instance name, handling all the logic after instance name creation.
441
470
  */
442
- size() {
443
- return this._holders.size;
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
- * Creates a new holder with Creating status and a deferred creation promise.
447
- * This is useful for creating placeholder holders that can be fulfilled later.
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
- createCreatingHolder(name, type, scope, deps = /* @__PURE__ */ new Set(), ttl = Infinity) {
456
- const deferred = createDeferred();
457
- const holder = {
458
- status: "creating" /* Creating */,
459
- name,
460
- instance: null,
461
- creationPromise: deferred.promise,
462
- destroyPromise: null,
463
- type,
464
- scope,
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
- * Creates a new holder with Created status and an actual instance.
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
- createCreatedHolder(name, instance, type, scope, deps = /* @__PURE__ */ new Set(), ttl = Infinity) {
484
- const holder = {
485
- status: "created" /* Created */,
486
- name,
487
- instance,
488
- creationPromise: null,
489
- destroyPromise: null,
490
- type,
491
- scope,
492
- deps,
493
- destroyListeners: [],
494
- createdAt: Date.now(),
495
- ttl
496
- };
497
- return holder;
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
- * Gets all holder names currently managed.
535
+ * Attempts to get a singleton instance from the manager.
501
536
  */
502
- getAllNames() {
503
- return Array.from(this._holders.keys());
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
- * Gets all holders currently managed.
557
+ * Waits for an instance holder to be ready and returns the appropriate result.
507
558
  */
508
- getAllHolders() {
509
- return Array.from(this._holders.values());
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/service-instantiator.mts
589
- var ServiceInstantiator = class {
590
- constructor(injectors) {
591
- this.injectors = injectors;
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
- * Instantiates a service based on its registry record.
595
- * @param ctx The factory context for dependency injection
596
- * @param record The factory record from the registry
597
- * @param args Optional arguments for the service
598
- * @returns Promise resolving to [undefined, instance] or [error]
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
- async instantiateService(ctx, record, args = void 0) {
601
- try {
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) {
@@ -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.ttl !== Infinity) {
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 [new InstanceDestroying(holder.name), holder];
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 [new InstanceNotFound(name)];
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 (["InstanceExpired" /* InstanceExpired */, "InstanceDestroying" /* InstanceDestroying */].includes(
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
- * @param ttl Optional time-to-live in milliseconds (defaults to Infinity)
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
- async handleInstantiationSuccess(instanceName, holder, ctx, deferred, instance) {
1323
- holder.instance = instance;
1324
- holder.status = "created" /* Created */;
1325
- if (ctx.deps.size > 0) {
1326
- ctx.deps.forEach((dependency) => {
1327
- holder.destroyListeners.push(
1328
- this.eventBus.on(
1329
- dependency,
1330
- "destroy",
1331
- () => this.invalidate(instanceName)
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
- * Handles service instantiation errors.
1570
+ * Validates and resolves token arguments, handling factory token resolution and validation.
1341
1571
  */
1342
- async handleInstantiationError(instanceName, holder, deferred, scope, error) {
1343
- this.logger?.error(
1344
- `[ServiceLocator] Error creating instance for ${instanceName}`,
1345
- error
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
- deferred.reject(error);
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
- * Stores an instance holder based on its scope.
1601
+ * Generates a unique instance name based on token and arguments.
1357
1602
  */
1358
- storeInstanceByScope(scope, instanceName, holder) {
1359
- switch (scope) {
1360
- case "Singleton" /* Singleton */:
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
- * Tries to get a pre-prepared instance from request contexts.
1611
+ * Formats a single argument value for instance name generation.
1382
1612
  */
1383
- tryGetPrePreparedInstance(instanceName, contextHolder, deps) {
1384
- if (contextHolder && contextHolder.priority > 0) {
1385
- const prePreparedInstance = contextHolder.get(instanceName)?.instance;
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 (this.currentRequestContext && this.currentRequestContext !== contextHolder) {
1395
- const prePreparedInstance = this.currentRequestContext.get(instanceName)?.instance;
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 void 0;
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 contextHolder Optional request context holder for priority-based resolution
1624
+ * @param serviceLocator Reference to the service locator for dependency resolution
1409
1625
  */
1410
- createFactoryContext(contextHolder) {
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 instanceName = self.generateInstanceName(token, args);
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: instanceName2 }) => {
1436
- deps.add(instanceName2);
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: self,
1652
+ locator: serviceLocator,
1447
1653
  deps
1448
1654
  };
1449
1655
  }
1450
1656
  /**
1451
- * Generates a unique instance name based on token and arguments.
1657
+ * Tries to get a pre-prepared instance from request contexts.
1452
1658
  */
1453
- generateInstanceName(token, args) {
1454
- if (!args) {
1455
- return token.toString();
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
- const formattedArgs = Object.entries(args).sort(([keyA], [keyB]) => keyA.localeCompare(keyB)).map(([key, value]) => `${key}=${this.formatArgValue(value)}`).join(",");
1458
- return `${token.toString()}:${formattedArgs.replaceAll(/"/g, "").replaceAll(/:/g, "=")}`;
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
- * Formats a single argument value for instance name generation.
1462
- */
1463
- formatArgValue(value) {
1464
- if (typeof value === "function") {
1465
- return `fn_${value.name}(${value.length})`;
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
- if (typeof value === "symbol") {
1468
- return value.toString();
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
- return JSON.stringify(value).slice(0, 40);
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 holderMap = this.serviceLocator.getManager().filter((holder2) => holder2.instance === service);
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.getManager().clear();
1981
+ return this.serviceLocator.clearAll();
1564
1982
  }
1565
1983
  };
1566
1984
  _init = __decoratorStart(null);