@navios/di 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +299 -38
  2. package/docs/README.md +121 -48
  3. package/docs/api-reference.md +763 -0
  4. package/docs/container.md +274 -0
  5. package/docs/examples/basic-usage.mts +97 -0
  6. package/docs/examples/factory-pattern.mts +318 -0
  7. package/docs/examples/injection-tokens.mts +225 -0
  8. package/docs/examples/request-scope-example.mts +254 -0
  9. package/docs/examples/service-lifecycle.mts +359 -0
  10. package/docs/factory.md +584 -0
  11. package/docs/getting-started.md +308 -0
  12. package/docs/injectable.md +496 -0
  13. package/docs/injection-tokens.md +400 -0
  14. package/docs/lifecycle.md +539 -0
  15. package/docs/scopes.md +749 -0
  16. package/lib/_tsup-dts-rollup.d.mts +494 -145
  17. package/lib/_tsup-dts-rollup.d.ts +494 -145
  18. package/lib/index.d.mts +26 -12
  19. package/lib/index.d.ts +26 -12
  20. package/lib/index.js +1021 -470
  21. package/lib/index.js.map +1 -1
  22. package/lib/index.mjs +1011 -461
  23. package/lib/index.mjs.map +1 -1
  24. package/package.json +2 -2
  25. package/project.json +10 -2
  26. package/src/__tests__/container.spec.mts +1301 -0
  27. package/src/__tests__/factory.spec.mts +137 -0
  28. package/src/__tests__/injectable.spec.mts +32 -88
  29. package/src/__tests__/injection-token.spec.mts +333 -17
  30. package/src/__tests__/request-scope.spec.mts +427 -0
  31. package/src/__type-tests__/factory.spec-d.mts +65 -0
  32. package/src/__type-tests__/inject.spec-d.mts +27 -28
  33. package/src/__type-tests__/injectable.spec-d.mts +42 -206
  34. package/src/container.mts +167 -0
  35. package/src/decorators/factory.decorator.mts +79 -0
  36. package/src/decorators/index.mts +1 -0
  37. package/src/decorators/injectable.decorator.mts +6 -56
  38. package/src/enums/injectable-scope.enum.mts +5 -1
  39. package/src/event-emitter.mts +18 -20
  40. package/src/factory-context.mts +2 -10
  41. package/src/index.mts +3 -2
  42. package/src/injection-token.mts +19 -4
  43. package/src/injector.mts +8 -20
  44. package/src/interfaces/factory.interface.mts +3 -3
  45. package/src/interfaces/index.mts +2 -0
  46. package/src/interfaces/on-service-destroy.interface.mts +3 -0
  47. package/src/interfaces/on-service-init.interface.mts +3 -0
  48. package/src/registry.mts +7 -16
  49. package/src/request-context-holder.mts +174 -0
  50. package/src/service-instantiator.mts +158 -0
  51. package/src/service-locator-event-bus.mts +0 -28
  52. package/src/service-locator-instance-holder.mts +27 -16
  53. package/src/service-locator-manager.mts +84 -0
  54. package/src/service-locator.mts +548 -393
  55. package/src/utils/defer.mts +73 -0
  56. package/src/utils/get-injectors.mts +91 -78
  57. package/src/utils/index.mts +2 -0
  58. package/src/utils/types.mts +52 -0
  59. package/docs/concepts/injectable.md +0 -182
  60. package/docs/concepts/injection-token.md +0 -145
  61. package/src/proxy-service-locator.mts +0 -83
  62. package/src/resolve-service.mts +0 -41
package/lib/index.mjs CHANGED
@@ -1,17 +1,48 @@
1
- // src/decorators/injectable.decorator.mts
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
5
+ var __typeError = (msg) => {
6
+ throw TypeError(msg);
7
+ };
8
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
10
+ var __decoratorStart = (base) => [, , , __create(null)];
11
+ var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
12
+ var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
13
+ var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) });
14
+ var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
15
+ var __runInitializers = (array, flags, self, value) => {
16
+ for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) fns[i].call(self) ;
17
+ return value;
18
+ };
19
+ var __decorateElement = (array, flags, name, decorators, target, extra) => {
20
+ var it, done, ctx, k = flags & 7, p = false;
21
+ var j = 0;
22
+ var extraInitializers = array[j] || (array[j] = []);
23
+ var desc = k && ((target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(target , name));
24
+ __name(target, name);
25
+ for (var i = decorators.length - 1; i >= 0; i--) {
26
+ ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
27
+ it = (0, decorators[i])(target, ctx), done._ = 1;
28
+ __expectFn(it) && (target = it);
29
+ }
30
+ return __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
31
+ };
2
32
 
3
33
  // src/enums/injectable-scope.enum.mts
4
- var InjectableScope = /* @__PURE__ */ ((InjectableScope3) => {
5
- InjectableScope3["Singleton"] = "Singleton";
6
- InjectableScope3["Instance"] = "Instance";
7
- return InjectableScope3;
34
+ var InjectableScope = /* @__PURE__ */ ((InjectableScope4) => {
35
+ InjectableScope4["Singleton"] = "Singleton";
36
+ InjectableScope4["Transient"] = "Transient";
37
+ InjectableScope4["Request"] = "Request";
38
+ return InjectableScope4;
8
39
  })(InjectableScope || {});
9
40
 
10
41
  // src/enums/injectable-type.enum.mts
11
- var InjectableType = /* @__PURE__ */ ((InjectableType2) => {
12
- InjectableType2["Class"] = "Class";
13
- InjectableType2["Factory"] = "Factory";
14
- return InjectableType2;
42
+ var InjectableType = /* @__PURE__ */ ((InjectableType4) => {
43
+ InjectableType4["Class"] = "Class";
44
+ InjectableType4["Factory"] = "Factory";
45
+ return InjectableType4;
15
46
  })(InjectableType || {});
16
47
 
17
48
  // src/injection-token.mts
@@ -78,9 +109,9 @@ var FactoryInjectionToken = class {
78
109
  id;
79
110
  name;
80
111
  schema;
81
- async resolve() {
112
+ async resolve(ctx) {
82
113
  if (!this.value) {
83
- this.value = await this.factory();
114
+ this.value = await this.factory(ctx);
84
115
  this.resolved = true;
85
116
  }
86
117
  return this.value;
@@ -115,8 +146,8 @@ var Registry = class {
115
146
  }
116
147
  return factory;
117
148
  }
118
- set(token, factory, scope) {
119
- this.factories.set(token.id, { factory, scope, originalToken: token });
149
+ set(token, scope, target, type) {
150
+ this.factories.set(token.id, { scope, originalToken: token, target, type });
120
151
  }
121
152
  delete(token) {
122
153
  this.factories.delete(token.id);
@@ -124,166 +155,29 @@ var Registry = class {
124
155
  };
125
156
  var globalRegistry = new Registry();
126
157
 
127
- // src/proxy-service-locator.mts
128
- var ProxyServiceLocator = class {
129
- constructor(serviceLocator, ctx) {
130
- this.serviceLocator = serviceLocator;
131
- this.ctx = ctx;
132
- }
133
- getEventBus() {
134
- return this.serviceLocator.getEventBus();
135
- }
136
- // @ts-expect-error We don't need all the properties of the class
137
- getInstance(token, args) {
138
- return this.ctx.inject(token, args).then(
139
- (instance) => {
140
- return [void 0, instance];
141
- },
142
- (error) => {
143
- return [error];
144
- }
145
- );
146
- }
147
- getOrThrowInstance(token, args) {
148
- return this.ctx.inject(token, args);
149
- }
150
- getSyncInstance(token, args) {
151
- return this.serviceLocator.getSyncInstance(token, args);
152
- }
153
- invalidate(service, round) {
154
- return this.serviceLocator.invalidate(service, round);
155
- }
156
- ready() {
157
- return this.serviceLocator.ready();
158
- }
159
- makeInstanceName(token, args) {
160
- return this.serviceLocator.makeInstanceName(token, args);
161
- }
162
- };
163
- function makeProxyServiceLocator(serviceLocator, ctx) {
164
- return new ProxyServiceLocator(serviceLocator, ctx);
165
- }
166
-
167
158
  // src/symbols/injectable-token.mts
168
159
  var InjectableTokenMeta = Symbol.for("InjectableTokenMeta");
169
160
 
170
- // src/utils/get-injectors.mts
171
- var InjectorsBase = /* @__PURE__ */ new Map();
172
- function getInjectors({ baseLocator }) {
173
- if (InjectorsBase.has(baseLocator)) {
174
- return InjectorsBase.get(baseLocator);
175
- }
176
- let currentLocator = baseLocator;
177
- function getServiceLocator() {
178
- if (!currentLocator) {
161
+ // src/decorators/factory.decorator.mts
162
+ function Factory({
163
+ scope = "Singleton" /* Singleton */,
164
+ token,
165
+ registry = globalRegistry
166
+ } = {}) {
167
+ return (target, context) => {
168
+ if (context && context.kind !== "class" || target instanceof Function && !context) {
179
169
  throw new Error(
180
- "[Injector] Service locator is not initialized. Please provide the service locator before using the @Injectable decorator."
181
- );
182
- }
183
- return currentLocator;
184
- }
185
- function provideServiceLocator2(locator) {
186
- const original = currentLocator;
187
- currentLocator = locator;
188
- return original;
189
- }
190
- function inject2(token, args) {
191
- const realToken = token[InjectableTokenMeta] ?? token;
192
- return getServiceLocator().getOrThrowInstance(realToken, args);
193
- }
194
- let promiseCollector = null;
195
- function wrapSyncInit2(cb) {
196
- return () => {
197
- const promises = [];
198
- const originalPromiseCollector = promiseCollector;
199
- promiseCollector = (promise) => {
200
- promises.push(promise);
201
- };
202
- const result = cb();
203
- promiseCollector = originalPromiseCollector;
204
- return [result, promises];
205
- };
206
- }
207
- function syncInject2(token, args) {
208
- const realToken = token[InjectableTokenMeta] ?? token;
209
- const instance = getServiceLocator().getSyncInstance(realToken, args);
210
- if (!instance) {
211
- if (promiseCollector) {
212
- const promise = getServiceLocator().getInstance(realToken, args);
213
- promiseCollector(promise);
214
- } else {
215
- throw new Error(`[Injector] Cannot initiate ${realToken.toString()}`);
216
- }
217
- return new Proxy(
218
- {},
219
- {
220
- get() {
221
- throw new Error(
222
- `[Injector] Trying to access ${realToken.toString()} before it's initialized, please use inject() instead of syncInject() or do not use the value outside of class methods`
223
- );
224
- }
225
- }
170
+ "[ServiceLocator] @Factory decorator can only be used on classes."
226
171
  );
227
172
  }
228
- return instance;
229
- }
230
- const injectors = {
231
- inject: inject2,
232
- syncInject: syncInject2,
233
- wrapSyncInit: wrapSyncInit2,
234
- provideServiceLocator: provideServiceLocator2
173
+ let injectableToken = token ?? InjectionToken.create(target);
174
+ registry.set(injectableToken, scope, target, "Factory" /* Factory */);
175
+ target[InjectableTokenMeta] = injectableToken;
176
+ return target;
235
177
  };
236
- InjectorsBase.set(baseLocator, injectors);
237
- return injectors;
238
- }
239
-
240
- // src/utils/get-injectable-token.mts
241
- function getInjectableToken(target) {
242
- const token = target[InjectableTokenMeta];
243
- if (!token) {
244
- throw new Error(
245
- `[ServiceLocator] Class ${target.name} is not decorated with @Injectable.`
246
- );
247
- }
248
- return token;
249
- }
250
-
251
- // src/resolve-service.mts
252
- async function resolveService(ctx, target, args = []) {
253
- const { wrapSyncInit: wrapSyncInit2, provideServiceLocator: provideServiceLocator2 } = getInjectors({
254
- baseLocator: ctx.locator
255
- });
256
- const proxyServiceLocator = makeProxyServiceLocator(ctx.locator, ctx);
257
- const tryLoad = wrapSyncInit2(() => {
258
- const original = provideServiceLocator2(proxyServiceLocator);
259
- let result = new target(...args);
260
- provideServiceLocator2(original);
261
- return result;
262
- });
263
- let [instance, promises] = tryLoad();
264
- if (promises.length > 0) {
265
- await Promise.allSettled(promises);
266
- const newRes = tryLoad();
267
- instance = newRes[0];
268
- promises = newRes[1];
269
- }
270
- if (promises.length > 0) {
271
- console.error(`[ServiceLocator] ${target.name} has problem with it's definition.
272
-
273
- One or more of the dependencies are registered as a InjectableScope.Instance and are used with syncInject.
274
-
275
- Please use inject instead of syncInject to load those dependencies.`);
276
- throw new Error(
277
- `[ServiceLocator] Service ${target.name} cannot be instantiated.`
278
- );
279
- }
280
- return instance;
281
178
  }
282
-
283
- // src/decorators/injectable.decorator.mts
284
179
  function Injectable({
285
180
  scope = "Singleton" /* Singleton */,
286
- type = "Class" /* Class */,
287
181
  token,
288
182
  registry = globalRegistry
289
183
  } = {}) {
@@ -294,27 +188,7 @@ function Injectable({
294
188
  );
295
189
  }
296
190
  let injectableToken = token ?? InjectionToken.create(target);
297
- if (type === "Class" /* Class */) {
298
- registry.set(
299
- injectableToken,
300
- async (ctx, args) => resolveService(ctx, target, [args]),
301
- scope
302
- );
303
- } else if (type === "Factory" /* Factory */) {
304
- registry.set(
305
- injectableToken,
306
- async (ctx, args) => {
307
- const builder = await resolveService(ctx, target);
308
- if (typeof builder.create !== "function") {
309
- throw new Error(
310
- `[ServiceLocator] Factory ${target.name} does not implement the create method.`
311
- );
312
- }
313
- return builder.create(ctx, args);
314
- },
315
- scope
316
- );
317
- }
191
+ registry.set(injectableToken, scope, target, "Class" /* Class */);
318
192
  target[InjectableTokenMeta] = injectableToken;
319
193
  return target;
320
194
  };
@@ -389,7 +263,183 @@ var UnknownError = class extends Error {
389
263
  }
390
264
  };
391
265
 
266
+ // src/utils/get-injectors.mts
267
+ function getInjectors() {
268
+ let currentFactoryContext = null;
269
+ function provideFactoryContext2(context) {
270
+ const original = currentFactoryContext;
271
+ currentFactoryContext = context;
272
+ return original;
273
+ }
274
+ function getFactoryContext() {
275
+ if (!currentFactoryContext) {
276
+ throw new Error(
277
+ "[Injector] Trying to access injection context outside of a injectable context"
278
+ );
279
+ }
280
+ return currentFactoryContext;
281
+ }
282
+ let promiseCollector = null;
283
+ let injectState = null;
284
+ function asyncInject2(token, args) {
285
+ if (!injectState) {
286
+ throw new Error(
287
+ "[Injector] Trying to access inject outside of a injectable context"
288
+ );
289
+ }
290
+ if (injectState.isFrozen) {
291
+ const idx = injectState.currentIndex++;
292
+ const request = injectState.requests[idx];
293
+ if (request.token !== token) {
294
+ throw new Error(
295
+ `[Injector] Wrong token order. Expected ${request.token.toString()} but got ${token.toString()}`
296
+ );
297
+ }
298
+ return request.promise;
299
+ }
300
+ const promise = getFactoryContext().inject(token, args);
301
+ injectState.requests.push({
302
+ token,
303
+ promise
304
+ });
305
+ injectState.currentIndex++;
306
+ return promise;
307
+ }
308
+ function wrapSyncInit2(cb) {
309
+ return (previousState) => {
310
+ const promises = [];
311
+ const originalPromiseCollector = promiseCollector;
312
+ const originalInjectState = injectState;
313
+ injectState = previousState ? {
314
+ ...previousState,
315
+ currentIndex: 0
316
+ } : {
317
+ currentIndex: 0,
318
+ isFrozen: false,
319
+ requests: []
320
+ };
321
+ promiseCollector = (promise) => {
322
+ promises.push(promise);
323
+ };
324
+ const result = cb();
325
+ promiseCollector = originalPromiseCollector;
326
+ const newInjectState = {
327
+ ...injectState,
328
+ isFrozen: true
329
+ };
330
+ injectState = originalInjectState;
331
+ return [result, promises, newInjectState];
332
+ };
333
+ }
334
+ function inject2(token, args) {
335
+ const realToken = token[InjectableTokenMeta] ?? token;
336
+ const instance = getFactoryContext().locator.getSyncInstance(
337
+ realToken,
338
+ args
339
+ );
340
+ if (!instance) {
341
+ if (promiseCollector) {
342
+ const promise = getFactoryContext().inject(realToken, args);
343
+ promiseCollector(promise);
344
+ } else {
345
+ throw new Error(`[Injector] Cannot initiate ${realToken.toString()}`);
346
+ }
347
+ return new Proxy(
348
+ {},
349
+ {
350
+ get() {
351
+ throw new Error(
352
+ `[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`
353
+ );
354
+ }
355
+ }
356
+ );
357
+ }
358
+ return instance;
359
+ }
360
+ const injectors = {
361
+ asyncInject: asyncInject2,
362
+ inject: inject2,
363
+ wrapSyncInit: wrapSyncInit2,
364
+ provideFactoryContext: provideFactoryContext2
365
+ };
366
+ return injectors;
367
+ }
368
+
369
+ // src/utils/get-injectable-token.mts
370
+ function getInjectableToken(target) {
371
+ const token = target[InjectableTokenMeta];
372
+ if (!token) {
373
+ throw new Error(
374
+ `[ServiceLocator] Class ${target.name} is not decorated with @Injectable.`
375
+ );
376
+ }
377
+ return token;
378
+ }
379
+
380
+ // src/utils/defer.mts
381
+ var Deferred = class {
382
+ promise;
383
+ _resolve;
384
+ _reject;
385
+ _isResolved = false;
386
+ _isRejected = false;
387
+ constructor() {
388
+ this.promise = new Promise((resolve, reject) => {
389
+ this._resolve = resolve;
390
+ this._reject = reject;
391
+ });
392
+ }
393
+ /**
394
+ * Resolves the deferred promise with the given value.
395
+ * @param value The value to resolve with
396
+ * @throws Error if the promise has already been resolved or rejected
397
+ */
398
+ resolve(value) {
399
+ if (this._isResolved || this._isRejected) {
400
+ throw new Error("Deferred promise has already been resolved or rejected");
401
+ }
402
+ this._isResolved = true;
403
+ this._resolve(value);
404
+ }
405
+ /**
406
+ * Rejects the deferred promise with the given reason.
407
+ * @param reason The reason for rejection
408
+ * @throws Error if the promise has already been resolved or rejected
409
+ */
410
+ reject(reason) {
411
+ if (this._isResolved || this._isRejected) {
412
+ throw new Error("Deferred promise has already been resolved or rejected");
413
+ }
414
+ this._isRejected = true;
415
+ this._reject(reason);
416
+ }
417
+ /**
418
+ * Returns true if the promise has been resolved.
419
+ */
420
+ get isResolved() {
421
+ return this._isResolved;
422
+ }
423
+ /**
424
+ * Returns true if the promise has been rejected.
425
+ */
426
+ get isRejected() {
427
+ return this._isRejected;
428
+ }
429
+ /**
430
+ * Returns true if the promise has been settled (resolved or rejected).
431
+ */
432
+ get isSettled() {
433
+ return this._isResolved || this._isRejected;
434
+ }
435
+ };
436
+ function createDeferred() {
437
+ return new Deferred();
438
+ }
439
+
392
440
  // src/event-emitter.mts
441
+ var _EventEmitter_decorators, _init;
442
+ _EventEmitter_decorators = [Injectable({ scope: "Transient" /* Transient */ })];
393
443
  var EventEmitter = class {
394
444
  listeners = /* @__PURE__ */ new Map();
395
445
  on(event, listener) {
@@ -421,12 +471,224 @@ var EventEmitter = class {
421
471
  if (!this.listeners.has(event)) {
422
472
  return;
423
473
  }
424
- return Promise.all(Array.from(this.listeners.get(event)).map((listener) => listener(...args)));
474
+ return Promise.all(
475
+ Array.from(this.listeners.get(event)).map(
476
+ (listener) => listener(...args)
477
+ )
478
+ );
479
+ }
480
+ };
481
+ _init = __decoratorStart();
482
+ EventEmitter = __decorateElement(_init, 0, "EventEmitter", _EventEmitter_decorators, EventEmitter);
483
+ __runInitializers(_init, 1, EventEmitter);
484
+
485
+ // src/injector.mts
486
+ var defaultInjectors = getInjectors();
487
+ var asyncInject = defaultInjectors.asyncInject;
488
+ var inject = defaultInjectors.inject;
489
+ var wrapSyncInit = defaultInjectors.wrapSyncInit;
490
+ var provideFactoryContext = defaultInjectors.provideFactoryContext;
491
+
492
+ // src/service-instantiator.mts
493
+ var ServiceInstantiator = class {
494
+ constructor(injectors) {
495
+ this.injectors = injectors;
496
+ }
497
+ /**
498
+ * Instantiates a service based on its registry record.
499
+ * @param ctx The factory context for dependency injection
500
+ * @param record The factory record from the registry
501
+ * @param args Optional arguments for the service
502
+ * @returns Promise resolving to [undefined, instance] or [error]
503
+ */
504
+ async instantiateService(ctx, record, args = void 0) {
505
+ try {
506
+ switch (record.type) {
507
+ case "Class" /* Class */:
508
+ return this.instantiateClass(ctx, record, args);
509
+ case "Factory" /* Factory */:
510
+ return this.instantiateFactory(ctx, record, args);
511
+ default:
512
+ throw new Error(
513
+ `[ServiceInstantiator] Unknown service type: ${record.type}`
514
+ );
515
+ }
516
+ } catch (error) {
517
+ return [error instanceof Error ? error : new Error(String(error))];
518
+ }
519
+ }
520
+ /**
521
+ * Instantiates a class-based service (Injectable decorator).
522
+ * @param ctx The factory context for dependency injection
523
+ * @param record The factory record from the registry
524
+ * @param args Optional arguments for the service constructor
525
+ * @returns Promise resolving to [undefined, instance] or [error]
526
+ */
527
+ async instantiateClass(ctx, record, args) {
528
+ try {
529
+ const tryLoad = this.injectors.wrapSyncInit(() => {
530
+ const original = this.injectors.provideFactoryContext(ctx);
531
+ let result = new record.target(...args ? [args] : []);
532
+ this.injectors.provideFactoryContext(original);
533
+ return result;
534
+ });
535
+ let [instance, promises, injectState] = tryLoad();
536
+ if (promises.length > 0) {
537
+ const results = await Promise.allSettled(promises);
538
+ if (results.some((result) => result.status === "rejected")) {
539
+ throw new Error(
540
+ `[ServiceInstantiator] Service ${record.target.name} cannot be instantiated.`
541
+ );
542
+ }
543
+ const newRes = tryLoad(injectState);
544
+ instance = newRes[0];
545
+ promises = newRes[1];
546
+ }
547
+ if (promises.length > 0) {
548
+ console.error(`[ServiceInstantiator] ${record.target.name} has problem with it's definition.
549
+
550
+ One or more of the dependencies are registered as a InjectableScope.Instance and are used with inject.
551
+
552
+ Please use inject asyncInject of inject to load those dependencies.`);
553
+ throw new Error(
554
+ `[ServiceInstantiator] Service ${record.target.name} cannot be instantiated.`
555
+ );
556
+ }
557
+ if ("onServiceInit" in instance) {
558
+ await instance.onServiceInit();
559
+ }
560
+ if ("onServiceDestroy" in instance) {
561
+ ctx.addDestroyListener(async () => {
562
+ await instance.onServiceDestroy();
563
+ });
564
+ }
565
+ return [void 0, instance];
566
+ } catch (error) {
567
+ return [error instanceof Error ? error : new Error(String(error))];
568
+ }
569
+ }
570
+ /**
571
+ * Instantiates a factory-based service (Factory decorator).
572
+ * @param ctx The factory context for dependency injection
573
+ * @param record The factory record from the registry
574
+ * @param args Optional arguments for the factory
575
+ * @returns Promise resolving to [undefined, instance] or [error]
576
+ */
577
+ async instantiateFactory(ctx, record, args) {
578
+ try {
579
+ const tryLoad = this.injectors.wrapSyncInit(() => {
580
+ const original = this.injectors.provideFactoryContext(ctx);
581
+ let result = new record.target();
582
+ this.injectors.provideFactoryContext(original);
583
+ return result;
584
+ });
585
+ let [builder, promises, injectState] = tryLoad();
586
+ if (promises.length > 0) {
587
+ const results = await Promise.allSettled(promises);
588
+ if (results.some((result) => result.status === "rejected")) {
589
+ throw new Error(
590
+ `[ServiceInstantiator] Service ${record.target.name} cannot be instantiated.`
591
+ );
592
+ }
593
+ const newRes = tryLoad(injectState);
594
+ builder = newRes[0];
595
+ promises = newRes[1];
596
+ }
597
+ if (promises.length > 0) {
598
+ console.error(`[ServiceInstantiator] ${record.target.name} has problem with it's definition.
599
+
600
+ One or more of the dependencies are registered as a InjectableScope.Instance and are used with inject.
601
+
602
+ Please use asyncInject instead of inject to load those dependencies.`);
603
+ throw new Error(
604
+ `[ServiceInstantiator] Service ${record.target.name} cannot be instantiated.`
605
+ );
606
+ }
607
+ if (typeof builder.create !== "function") {
608
+ throw new Error(
609
+ `[ServiceInstantiator] Factory ${record.target.name} does not implement the create method.`
610
+ );
611
+ }
612
+ const instance = await builder.create(ctx, args);
613
+ return [void 0, instance];
614
+ } catch (error) {
615
+ return [error instanceof Error ? error : new Error(String(error))];
616
+ }
617
+ }
618
+ };
619
+
620
+ // src/service-locator-instance-holder.mts
621
+ var ServiceLocatorInstanceHolderStatus = /* @__PURE__ */ ((ServiceLocatorInstanceHolderStatus2) => {
622
+ ServiceLocatorInstanceHolderStatus2["Created"] = "created";
623
+ ServiceLocatorInstanceHolderStatus2["Creating"] = "creating";
624
+ ServiceLocatorInstanceHolderStatus2["Destroying"] = "destroying";
625
+ ServiceLocatorInstanceHolderStatus2["Error"] = "error";
626
+ return ServiceLocatorInstanceHolderStatus2;
627
+ })(ServiceLocatorInstanceHolderStatus || {});
628
+
629
+ // src/request-context-holder.mts
630
+ var DefaultRequestContextHolder = class {
631
+ constructor(requestId, priority = 100, initialMetadata) {
632
+ this.requestId = requestId;
633
+ this.priority = priority;
634
+ if (initialMetadata) {
635
+ Object.entries(initialMetadata).forEach(([key, value]) => {
636
+ this.metadata.set(key, value);
637
+ });
638
+ }
639
+ }
640
+ instances = /* @__PURE__ */ new Map();
641
+ holders = /* @__PURE__ */ new Map();
642
+ metadata = /* @__PURE__ */ new Map();
643
+ createdAt = Date.now();
644
+ addInstance(instanceName, instance, holder) {
645
+ if (instanceName instanceof InjectionToken) {
646
+ this.instances.set(instanceName.toString(), instance);
647
+ this.holders.set(instanceName.toString(), {
648
+ instance,
649
+ status: "created" /* Created */,
650
+ creationPromise: null,
651
+ destroyPromise: null,
652
+ destroyListeners: [],
653
+ deps: /* @__PURE__ */ new Set(),
654
+ name: instanceName.toString(),
655
+ type: "Class" /* Class */,
656
+ scope: "Singleton" /* Singleton */,
657
+ createdAt: Date.now(),
658
+ ttl: Infinity
659
+ });
660
+ } else {
661
+ if (!holder) {
662
+ throw new Error("Holder is required when adding an instance by name");
663
+ }
664
+ this.instances.set(instanceName, instance);
665
+ this.holders.set(instanceName, holder);
666
+ }
425
667
  }
426
- addChannel(ns, event, target) {
427
- return this.on(event, (...args) => target.emit(ns, event, ...args));
668
+ getInstance(instanceName) {
669
+ return this.instances.get(instanceName);
670
+ }
671
+ getHolder(instanceName) {
672
+ return this.holders.get(instanceName);
673
+ }
674
+ hasInstance(instanceName) {
675
+ return this.instances.has(instanceName);
676
+ }
677
+ clear() {
678
+ this.instances.clear();
679
+ this.holders.clear();
680
+ this.metadata.clear();
681
+ }
682
+ getMetadata(key) {
683
+ return this.metadata.get(key);
684
+ }
685
+ setMetadata(key, value) {
686
+ this.metadata.set(key, value);
428
687
  }
429
688
  };
689
+ function createRequestContextHolder(requestId, priority = 100, initialMetadata) {
690
+ return new DefaultRequestContextHolder(requestId, priority, initialMetadata);
691
+ }
430
692
 
431
693
  // src/service-locator-event-bus.mts
432
694
  var ServiceLocatorEventBus = class {
@@ -459,19 +721,6 @@ var ServiceLocatorEventBus = class {
459
721
  return;
460
722
  }
461
723
  const events = this.listeners.get(key);
462
- const preEvent = `pre:${event}`;
463
- const postEvent = `post:${event}`;
464
- this.logger?.debug(`[ServiceLocatorEventBus]#emit(): ${key}:${preEvent}`);
465
- await Promise.allSettled(
466
- [...events.get(preEvent) ?? []].map((listener) => listener(preEvent))
467
- ).then((results) => {
468
- results.filter((result) => result.status === "rejected").forEach((result) => {
469
- this.logger?.warn(
470
- `[ServiceLocatorEventBus]#emit(): ${key}:${preEvent} rejected with`,
471
- result.reason
472
- );
473
- });
474
- });
475
724
  this.logger?.debug(`[ServiceLocatorEventBus]#emit(): ${key}:${event}`);
476
725
  const res = await Promise.allSettled(
477
726
  [...events.get(event) ?? []].map((listener) => listener(event))
@@ -488,35 +737,10 @@ var ServiceLocatorEventBus = class {
488
737
  }
489
738
  return results;
490
739
  });
491
- this.logger?.debug(`[ServiceLocatorEventBus]#emit(): ${key}:${postEvent}`);
492
- await Promise.allSettled(
493
- [...events.get(postEvent) ?? []].map((listener) => listener(postEvent))
494
- ).then((results) => {
495
- results.filter((result) => result.status === "rejected").forEach((result) => {
496
- this.logger?.warn(
497
- `[ServiceLocatorEventBus]#emit(): ${key}:${postEvent} rejected with`,
498
- result.reason
499
- );
500
- });
501
- });
502
740
  return res;
503
741
  }
504
742
  };
505
743
 
506
- // src/service-locator-instance-holder.mts
507
- var ServiceLocatorInstanceHolderKind = /* @__PURE__ */ ((ServiceLocatorInstanceHolderKind2) => {
508
- ServiceLocatorInstanceHolderKind2["Instance"] = "instance";
509
- ServiceLocatorInstanceHolderKind2["Factory"] = "factory";
510
- ServiceLocatorInstanceHolderKind2["AbstractFactory"] = "abstractFactory";
511
- return ServiceLocatorInstanceHolderKind2;
512
- })(ServiceLocatorInstanceHolderKind || {});
513
- var ServiceLocatorInstanceHolderStatus = /* @__PURE__ */ ((ServiceLocatorInstanceHolderStatus2) => {
514
- ServiceLocatorInstanceHolderStatus2["Created"] = "created";
515
- ServiceLocatorInstanceHolderStatus2["Creating"] = "creating";
516
- ServiceLocatorInstanceHolderStatus2["Destroying"] = "destroying";
517
- return ServiceLocatorInstanceHolderStatus2;
518
- })(ServiceLocatorInstanceHolderStatus || {});
519
-
520
744
  // src/service-locator-manager.mts
521
745
  var ServiceLocatorManager = class {
522
746
  constructor(logger = null) {
@@ -539,6 +763,11 @@ var ServiceLocatorManager = class {
539
763
  `[ServiceLocatorManager]#getInstanceHolder() Instance ${holder.name} is destroying`
540
764
  );
541
765
  return [new InstanceDestroying(holder.name), holder];
766
+ } else if (holder.status === "error" /* Error */) {
767
+ this.logger?.log(
768
+ `[ServiceLocatorManager]#getInstanceHolder() Instance ${holder.name} is in error state`
769
+ );
770
+ return [holder.instance, holder];
542
771
  }
543
772
  return [void 0, holder];
544
773
  } else {
@@ -573,116 +802,115 @@ var ServiceLocatorManager = class {
573
802
  )
574
803
  );
575
804
  }
805
+ /**
806
+ * Creates a new holder with Creating status and a deferred creation promise.
807
+ * This is useful for creating placeholder holders that can be fulfilled later.
808
+ * @param name The name of the instance
809
+ * @param type The injectable type
810
+ * @param scope The injectable scope
811
+ * @param deps Optional set of dependencies
812
+ * @param ttl Optional time-to-live in milliseconds (defaults to Infinity)
813
+ * @returns A tuple containing the deferred promise and the holder
814
+ */
815
+ createCreatingHolder(name, type, scope, deps = /* @__PURE__ */ new Set(), ttl = Infinity) {
816
+ const deferred = createDeferred();
817
+ const holder = {
818
+ status: "creating" /* Creating */,
819
+ name,
820
+ instance: null,
821
+ creationPromise: deferred.promise,
822
+ destroyPromise: null,
823
+ type,
824
+ scope,
825
+ deps,
826
+ destroyListeners: [],
827
+ createdAt: Date.now(),
828
+ ttl
829
+ };
830
+ return [deferred, holder];
831
+ }
832
+ /**
833
+ * Creates a new holder with Created status and an actual instance.
834
+ * This is useful for creating holders that already have their instance ready.
835
+ * @param name The name of the instance
836
+ * @param instance The actual instance to store
837
+ * @param type The injectable type
838
+ * @param scope The injectable scope
839
+ * @param deps Optional set of dependencies
840
+ * @param ttl Optional time-to-live in milliseconds (defaults to Infinity)
841
+ * @returns The created holder
842
+ */
843
+ storeCreatedHolder(name, instance, type, scope, deps = /* @__PURE__ */ new Set(), ttl = Infinity) {
844
+ const holder = {
845
+ status: "created" /* Created */,
846
+ name,
847
+ instance,
848
+ creationPromise: null,
849
+ destroyPromise: null,
850
+ type,
851
+ scope,
852
+ deps,
853
+ destroyListeners: [],
854
+ createdAt: Date.now(),
855
+ ttl
856
+ };
857
+ this.instancesHolders.set(name, holder);
858
+ return holder;
859
+ }
576
860
  };
577
861
 
578
862
  // src/service-locator.mts
579
863
  var ServiceLocator = class {
580
- constructor(registry = globalRegistry, logger = null) {
864
+ constructor(registry = globalRegistry, logger = null, injectors = defaultInjectors) {
581
865
  this.registry = registry;
582
866
  this.logger = logger;
867
+ this.injectors = injectors;
583
868
  this.eventBus = new ServiceLocatorEventBus(logger);
584
869
  this.manager = new ServiceLocatorManager(logger);
870
+ this.serviceInstantiator = new ServiceInstantiator(injectors);
585
871
  }
586
872
  eventBus;
587
873
  manager;
874
+ serviceInstantiator;
875
+ requestContexts = /* @__PURE__ */ new Map();
876
+ currentRequestContext = null;
877
+ // ============================================================================
878
+ // PUBLIC METHODS
879
+ // ============================================================================
588
880
  getEventBus() {
589
881
  return this.eventBus;
590
882
  }
591
- storeInstance(instance, token, args) {
592
- const instanceName = this.getInstanceIdentifier(token, args);
593
- this.manager.set(instanceName, {
594
- name: instanceName,
595
- instance,
596
- status: "created" /* Created */,
597
- kind: "instance" /* Instance */,
598
- createdAt: Date.now(),
599
- ttl: Infinity,
600
- deps: [],
601
- destroyListeners: [],
602
- effects: [],
603
- destroyPromise: null,
604
- creationPromise: null
605
- });
606
- this.notifyListeners(instanceName);
607
- }
608
- removeInstance(token, args) {
609
- const instanceName = this.getInstanceIdentifier(token, args);
610
- return this.invalidate(instanceName);
883
+ getManager() {
884
+ return this.manager;
611
885
  }
612
- resolveTokenArgs(token, args) {
613
- let realArgs = args;
614
- if (token instanceof BoundInjectionToken) {
615
- realArgs = token.value;
616
- } else if (token instanceof FactoryInjectionToken) {
617
- if (token.resolved) {
618
- realArgs = token.value;
619
- } else {
620
- return [new FactoryTokenNotResolved(token.name)];
621
- }
622
- }
623
- if (!token.schema) {
624
- return [void 0, realArgs];
625
- }
626
- const validatedArgs = token.schema?.safeParse(realArgs);
627
- if (validatedArgs && !validatedArgs.success) {
628
- this.logger?.error(
629
- `[ServiceLocator]#resolveTokenArgs(): Error validating args for ${token.name.toString()}`,
630
- validatedArgs.error
631
- );
632
- return [new UnknownError(validatedArgs.error)];
886
+ getInstanceIdentifier(token, args) {
887
+ const [err, { actualToken, validatedArgs }] = this.validateAndResolveTokenArgs(token, args);
888
+ if (err) {
889
+ throw err;
633
890
  }
634
- return [void 0, validatedArgs?.data];
891
+ return this.generateInstanceName(actualToken, validatedArgs);
635
892
  }
636
- getInstanceIdentifier(token, args) {
637
- const [err, realArgs] = this.resolveTokenArgs(
893
+ async getInstance(token, args, onPrepare) {
894
+ const [err, data] = await this.resolveTokenAndPrepareInstanceName(
638
895
  token,
639
896
  args
640
897
  );
641
898
  if (err) {
642
- throw err;
643
- }
644
- return this.makeInstanceName(token, realArgs);
645
- }
646
- async getInstance(token, args) {
647
- const [err, realArgs] = this.resolveTokenArgs(token, args);
648
- if (err instanceof UnknownError) {
649
899
  return [err];
650
- } else if (err instanceof FactoryTokenNotResolved && token instanceof FactoryInjectionToken) {
651
- this.logger?.log(
652
- `[ServiceLocator]#getInstance() Factory token not resolved, resolving it`
653
- );
654
- await token.resolve();
655
- return this.getInstance(token);
656
900
  }
657
- const instanceName = this.makeInstanceName(token, realArgs);
658
- const [error, holder] = this.manager.get(instanceName);
659
- if (!error) {
660
- if (holder.status === "creating" /* Creating */) {
661
- return holder.creationPromise;
662
- } else if (holder.status === "destroying" /* Destroying */) {
663
- return [new UnknownError("InstanceDestroying" /* InstanceDestroying */)];
664
- }
665
- return [void 0, holder.instance];
901
+ const { instanceName, validatedArgs, actualToken, realToken } = data;
902
+ if (onPrepare) {
903
+ onPrepare({ instanceName, actualToken, validatedArgs });
666
904
  }
667
- switch (error.code) {
668
- case "InstanceDestroying" /* InstanceDestroying */:
669
- this.logger?.log(
670
- `[ServiceLocator]#getInstance() TTL expired for ${holder?.name}`
671
- );
672
- await holder?.destroyPromise;
673
- return this.getInstance(token, args);
674
- case "InstanceExpired" /* InstanceExpired */:
675
- this.logger?.log(
676
- `[ServiceLocator]#getInstance() TTL expired for ${holder?.name}`
677
- );
678
- await this.invalidate(instanceName);
679
- return this.getInstance(token, args);
680
- case "InstanceNotFound" /* InstanceNotFound */:
681
- break;
682
- default:
683
- return [error];
905
+ const [error, holder] = await this.retrieveOrCreateInstanceByInstanceName(
906
+ instanceName,
907
+ realToken,
908
+ validatedArgs
909
+ );
910
+ if (error) {
911
+ return [error];
684
912
  }
685
- return this.createInstance(instanceName, token, realArgs);
913
+ return [void 0, holder.instance];
686
914
  }
687
915
  async getOrThrowInstance(token, args) {
688
916
  const [error, instance] = await this.getInstance(token, args);
@@ -691,142 +919,18 @@ var ServiceLocator = class {
691
919
  }
692
920
  return instance;
693
921
  }
694
- notifyListeners(name, event = "create") {
695
- this.logger?.log(
696
- `[ServiceLocator]#notifyListeners() Notifying listeners for ${name} with event ${event}`
697
- );
698
- return this.eventBus.emit(name, event);
699
- }
700
- async createInstance(instanceName, token, args) {
701
- this.logger?.log(
702
- `[ServiceLocator]#createInstance() Creating instance for ${instanceName}`
703
- );
704
- let realToken = token instanceof BoundInjectionToken || token instanceof FactoryInjectionToken ? token.token : token;
705
- if (this.registry.has(realToken)) {
706
- return this.resolveInstance(instanceName, realToken, args);
707
- } else {
708
- return [new FactoryNotFound(realToken.name.toString())];
709
- }
710
- }
711
- resolveInstance(instanceName, token, args) {
712
- this.logger?.log(
713
- `[ServiceLocator]#resolveInstance(): Creating instance for ${instanceName} from abstract factory`
714
- );
715
- const ctx = this.createFactoryContext(instanceName);
716
- let { factory, scope } = this.registry.get(token);
717
- const holder = {
718
- name: instanceName,
719
- instance: null,
720
- status: "creating" /* Creating */,
721
- kind: "abstractFactory" /* AbstractFactory */,
722
- // @ts-expect-error TS2322 This is correct type
723
- creationPromise: factory(ctx, args).then(async (instance) => {
724
- holder.instance = instance;
725
- holder.status = "created" /* Created */;
726
- holder.deps = ctx.getDependencies();
727
- holder.destroyListeners = ctx.getDestroyListeners();
728
- holder.ttl = ctx.getTtl();
729
- if (holder.deps.length > 0) {
730
- this.logger?.log(
731
- `[ServiceLocator]#createInstanceFromAbstractFactory(): Adding subscriptions for ${instanceName} dependencies for their invalidations: ${holder.deps.join(
732
- ", "
733
- )}`
734
- );
735
- holder.deps.forEach((dependency) => {
736
- holder.destroyListeners.push(
737
- this.eventBus.on(
738
- dependency,
739
- "destroy",
740
- () => this.invalidate(instanceName)
741
- )
742
- );
743
- });
744
- }
745
- if (holder.ttl === 0 || scope === "Instance" /* Instance */) {
746
- await this.invalidate(instanceName);
747
- }
748
- await this.notifyListeners(instanceName);
749
- return [void 0, instance];
750
- }).catch((error) => {
751
- this.logger?.error(
752
- `[ServiceLocator]#createInstanceFromAbstractFactory(): Error creating instance for ${instanceName}`,
753
- error
754
- );
755
- return [new UnknownError(error)];
756
- }),
757
- effects: [],
758
- deps: [],
759
- destroyListeners: [],
760
- createdAt: Date.now(),
761
- ttl: Infinity
762
- };
763
- if (scope === "Singleton" /* Singleton */) {
764
- this.logger?.debug(
765
- `[ServiceLocator]#resolveInstance(): Setting instance for ${instanceName}`
766
- );
767
- this.manager.set(instanceName, holder);
768
- }
769
- return holder.creationPromise;
770
- }
771
- createFactoryContext(instanceName) {
772
- const dependencies = /* @__PURE__ */ new Set();
773
- const destroyListeners = /* @__PURE__ */ new Set();
774
- const self = this;
775
- function invalidate(name = instanceName) {
776
- return self.invalidate(name);
777
- }
778
- function addEffect(listener) {
779
- destroyListeners.add(listener);
780
- }
781
- let ttl = Infinity;
782
- function setTtl(value) {
783
- ttl = value;
784
- }
785
- function getTtl() {
786
- return ttl;
787
- }
788
- function on(key, event, listener) {
789
- destroyListeners.add(self.eventBus.on(key, event, listener));
790
- }
791
- return {
792
- // @ts-expect-error This is correct type
793
- async inject(token, args) {
794
- let injectionToken = token;
795
- if (typeof token === "function") {
796
- injectionToken = getInjectableToken(token);
797
- }
798
- if (injectionToken) {
799
- const [err, validatedArgs] = self.resolveTokenArgs(
800
- injectionToken,
801
- args
802
- );
803
- if (err) {
804
- throw err;
805
- }
806
- const instanceName2 = self.makeInstanceName(token, validatedArgs);
807
- dependencies.add(instanceName2);
808
- return self.getOrThrowInstance(injectionToken, args);
809
- }
810
- throw new Error(
811
- `[ServiceLocator]#inject(): Invalid token type: ${typeof token}. Expected a class or an InjectionToken.`
812
- );
813
- },
814
- invalidate,
815
- on,
816
- getDependencies: () => Array.from(dependencies),
817
- addEffect,
818
- getDestroyListeners: () => Array.from(destroyListeners),
819
- setTtl,
820
- getTtl,
821
- locator: self
822
- };
823
- }
824
922
  getSyncInstance(token, args) {
825
- const [err, realArgs] = this.resolveTokenArgs(token, args);
923
+ const [err, { actualToken, validatedArgs }] = this.validateAndResolveTokenArgs(token, args);
826
924
  if (err) {
827
925
  return null;
828
926
  }
829
- const instanceName = this.makeInstanceName(token, realArgs);
927
+ const instanceName = this.generateInstanceName(actualToken, validatedArgs);
928
+ if (this.currentRequestContext) {
929
+ const requestHolder = this.currentRequestContext.getHolder(instanceName);
930
+ if (requestHolder) {
931
+ return requestHolder.instance;
932
+ }
933
+ }
830
934
  const [error, holder] = this.manager.get(instanceName);
831
935
  if (error) {
832
936
  return null;
@@ -838,7 +942,7 @@ var ServiceLocator = class {
838
942
  `[ServiceLocator]#invalidate(): Starting Invalidating process of ${service}`
839
943
  );
840
944
  const toInvalidate = this.manager.filter(
841
- (holder) => holder.name === service || holder.deps.includes(service)
945
+ (holder) => holder.name === service || holder.deps.has(service)
842
946
  );
843
947
  const promises = [];
844
948
  for (const [key, holder] of toInvalidate.entries()) {
@@ -874,7 +978,7 @@ var ServiceLocator = class {
874
978
  holder.destroyListeners.map((listener) => listener())
875
979
  ).then(async () => {
876
980
  this.manager.delete(key);
877
- await this.notifyListeners(key, "destroy");
981
+ await this.emitInstanceEvent(key, "destroy");
878
982
  });
879
983
  promises.push(holder.destroyPromise);
880
984
  }
@@ -893,7 +997,384 @@ var ServiceLocator = class {
893
997
  })
894
998
  ).then(() => null);
895
999
  }
896
- makeInstanceName(token, args) {
1000
+ // ============================================================================
1001
+ // REQUEST CONTEXT MANAGEMENT
1002
+ // ============================================================================
1003
+ /**
1004
+ * Begins a new request context with the given parameters.
1005
+ * @param requestId Unique identifier for this request
1006
+ * @param metadata Optional metadata for the request
1007
+ * @param priority Priority for resolution (higher = more priority)
1008
+ * @returns The created request context holder
1009
+ */
1010
+ beginRequest(requestId, metadata, priority = 100) {
1011
+ if (this.requestContexts.has(requestId)) {
1012
+ throw new Error(
1013
+ `[ServiceLocator] Request context ${requestId} already exists`
1014
+ );
1015
+ }
1016
+ const contextHolder = new DefaultRequestContextHolder(
1017
+ requestId,
1018
+ priority,
1019
+ metadata
1020
+ );
1021
+ this.requestContexts.set(requestId, contextHolder);
1022
+ this.currentRequestContext = contextHolder;
1023
+ this.logger?.log(`[ServiceLocator] Started request context: ${requestId}`);
1024
+ return contextHolder;
1025
+ }
1026
+ /**
1027
+ * Ends a request context and cleans up all associated instances.
1028
+ * @param requestId The request ID to end
1029
+ */
1030
+ async endRequest(requestId) {
1031
+ const contextHolder = this.requestContexts.get(requestId);
1032
+ if (!contextHolder) {
1033
+ this.logger?.warn(
1034
+ `[ServiceLocator] Request context ${requestId} not found`
1035
+ );
1036
+ return;
1037
+ }
1038
+ this.logger?.log(`[ServiceLocator] Ending request context: ${requestId}`);
1039
+ const cleanupPromises = [];
1040
+ for (const [, holder] of contextHolder.holders) {
1041
+ if (holder.destroyListeners.length > 0) {
1042
+ cleanupPromises.push(
1043
+ Promise.all(holder.destroyListeners.map((listener) => listener()))
1044
+ );
1045
+ }
1046
+ }
1047
+ await Promise.all(cleanupPromises);
1048
+ contextHolder.clear();
1049
+ this.requestContexts.delete(requestId);
1050
+ if (this.currentRequestContext === contextHolder) {
1051
+ this.currentRequestContext = Array.from(this.requestContexts.values()).at(-1) ?? null;
1052
+ }
1053
+ this.logger?.log(`[ServiceLocator] Request context ${requestId} ended`);
1054
+ }
1055
+ /**
1056
+ * Gets the current request context.
1057
+ * @returns The current request context holder or null
1058
+ */
1059
+ getCurrentRequestContext() {
1060
+ return this.currentRequestContext;
1061
+ }
1062
+ /**
1063
+ * Sets the current request context.
1064
+ * @param requestId The request ID to set as current
1065
+ */
1066
+ setCurrentRequestContext(requestId) {
1067
+ const contextHolder = this.requestContexts.get(requestId);
1068
+ if (!contextHolder) {
1069
+ throw new Error(`[ServiceLocator] Request context ${requestId} not found`);
1070
+ }
1071
+ this.currentRequestContext = contextHolder;
1072
+ }
1073
+ // ============================================================================
1074
+ // PRIVATE METHODS
1075
+ // ============================================================================
1076
+ /**
1077
+ * Validates and resolves token arguments, handling factory token resolution and validation.
1078
+ */
1079
+ validateAndResolveTokenArgs(token, args) {
1080
+ let actualToken = token;
1081
+ if (typeof token === "function") {
1082
+ actualToken = getInjectableToken(token);
1083
+ }
1084
+ let realArgs = args;
1085
+ if (actualToken instanceof BoundInjectionToken) {
1086
+ realArgs = actualToken.value;
1087
+ } else if (actualToken instanceof FactoryInjectionToken) {
1088
+ if (actualToken.resolved) {
1089
+ realArgs = actualToken.value;
1090
+ } else {
1091
+ return [new FactoryTokenNotResolved(token.name), { actualToken }];
1092
+ }
1093
+ }
1094
+ if (!actualToken.schema) {
1095
+ return [void 0, { actualToken, validatedArgs: realArgs }];
1096
+ }
1097
+ const validatedArgs = actualToken.schema?.safeParse(realArgs);
1098
+ if (validatedArgs && !validatedArgs.success) {
1099
+ this.logger?.error(
1100
+ `[ServiceLocator]#validateAndResolveTokenArgs(): Error validating args for ${actualToken.name.toString()}`,
1101
+ validatedArgs.error
1102
+ );
1103
+ return [new UnknownError(validatedArgs.error), { actualToken }];
1104
+ }
1105
+ return [void 0, { actualToken, validatedArgs: validatedArgs?.data }];
1106
+ }
1107
+ /**
1108
+ * Internal method to resolve token args and create instance name.
1109
+ * Handles factory token resolution and validation.
1110
+ */
1111
+ async resolveTokenAndPrepareInstanceName(token, args) {
1112
+ const [err, { actualToken, validatedArgs }] = this.validateAndResolveTokenArgs(token, args);
1113
+ if (err instanceof UnknownError) {
1114
+ return [err];
1115
+ } else if (err instanceof FactoryTokenNotResolved && actualToken instanceof FactoryInjectionToken) {
1116
+ this.logger?.log(
1117
+ `[ServiceLocator]#resolveTokenAndPrepareInstanceName() Factory token not resolved, resolving it`
1118
+ );
1119
+ await actualToken.resolve(this.createFactoryContext());
1120
+ return this.resolveTokenAndPrepareInstanceName(token);
1121
+ }
1122
+ const instanceName = this.generateInstanceName(actualToken, validatedArgs);
1123
+ const realToken = actualToken instanceof BoundInjectionToken || actualToken instanceof FactoryInjectionToken ? actualToken.token : actualToken;
1124
+ return [void 0, { instanceName, validatedArgs, actualToken, realToken }];
1125
+ }
1126
+ /**
1127
+ * Gets an instance by its instance name, handling all the logic after instance name creation.
1128
+ */
1129
+ async retrieveOrCreateInstanceByInstanceName(instanceName, realToken, realArgs) {
1130
+ if (this.registry.has(realToken)) {
1131
+ const record = this.registry.get(realToken);
1132
+ if (record.scope === "Request" /* Request */) {
1133
+ if (!this.currentRequestContext) {
1134
+ this.logger?.log(
1135
+ `[ServiceLocator]#retrieveOrCreateInstanceByInstanceName() No current request context available for request-scoped service ${instanceName}`
1136
+ );
1137
+ return [new UnknownError("InstanceNotFound" /* InstanceNotFound */)];
1138
+ }
1139
+ const requestHolder = this.currentRequestContext.getHolder(instanceName);
1140
+ if (requestHolder) {
1141
+ if (requestHolder.status === "creating" /* Creating */) {
1142
+ await requestHolder.creationPromise;
1143
+ return this.retrieveOrCreateInstanceByInstanceName(
1144
+ instanceName,
1145
+ realToken,
1146
+ realArgs
1147
+ );
1148
+ } else if (requestHolder.status === "destroying" /* Destroying */) {
1149
+ return [new UnknownError("InstanceDestroying" /* InstanceDestroying */)];
1150
+ }
1151
+ return [void 0, requestHolder];
1152
+ }
1153
+ }
1154
+ }
1155
+ const [error, holder] = this.manager.get(instanceName);
1156
+ if (!error) {
1157
+ if (holder.status === "creating" /* Creating */) {
1158
+ await holder.creationPromise;
1159
+ return this.retrieveOrCreateInstanceByInstanceName(
1160
+ instanceName,
1161
+ realToken,
1162
+ realArgs
1163
+ );
1164
+ } else if (holder.status === "destroying" /* Destroying */) {
1165
+ return [new UnknownError("InstanceDestroying" /* InstanceDestroying */)];
1166
+ }
1167
+ return [void 0, holder];
1168
+ }
1169
+ switch (error.code) {
1170
+ case "InstanceDestroying" /* InstanceDestroying */:
1171
+ this.logger?.log(
1172
+ `[ServiceLocator]#retrieveOrCreateInstanceByInstanceName() TTL expired for ${holder?.name}`
1173
+ );
1174
+ await holder?.destroyPromise;
1175
+ return this.retrieveOrCreateInstanceByInstanceName(
1176
+ instanceName,
1177
+ realToken,
1178
+ realArgs
1179
+ );
1180
+ case "InstanceExpired" /* InstanceExpired */:
1181
+ this.logger?.log(
1182
+ `[ServiceLocator]#retrieveOrCreateInstanceByInstanceName() TTL expired for ${holder?.name}`
1183
+ );
1184
+ await this.invalidate(instanceName);
1185
+ return this.retrieveOrCreateInstanceByInstanceName(
1186
+ instanceName,
1187
+ realToken,
1188
+ realArgs
1189
+ );
1190
+ case "InstanceNotFound" /* InstanceNotFound */:
1191
+ break;
1192
+ default:
1193
+ return [error];
1194
+ }
1195
+ const result = await this.createNewInstance(
1196
+ instanceName,
1197
+ realToken,
1198
+ realArgs
1199
+ );
1200
+ if (result[0]) {
1201
+ return [result[0]];
1202
+ }
1203
+ if (result[1].status === "creating" /* Creating */) {
1204
+ await result[1].creationPromise;
1205
+ }
1206
+ if (result[1].status === "error" /* Error */) {
1207
+ return [result[1].instance];
1208
+ }
1209
+ return [void 0, result[1]];
1210
+ }
1211
+ /**
1212
+ * Emits events to listeners for instance lifecycle events.
1213
+ */
1214
+ emitInstanceEvent(name, event = "create") {
1215
+ this.logger?.log(
1216
+ `[ServiceLocator]#emitInstanceEvent() Notifying listeners for ${name} with event ${event}`
1217
+ );
1218
+ return this.eventBus.emit(name, event);
1219
+ }
1220
+ /**
1221
+ * Creates a new instance for the given token and arguments.
1222
+ */
1223
+ async createNewInstance(instanceName, realToken, args) {
1224
+ this.logger?.log(
1225
+ `[ServiceLocator]#createNewInstance() Creating instance for ${instanceName}`
1226
+ );
1227
+ if (this.registry.has(realToken)) {
1228
+ return this.instantiateServiceFromRegistry(
1229
+ instanceName,
1230
+ realToken,
1231
+ args
1232
+ );
1233
+ } else {
1234
+ return [new FactoryNotFound(realToken.name.toString())];
1235
+ }
1236
+ }
1237
+ /**
1238
+ * Instantiates a service from the registry using the service instantiator.
1239
+ */
1240
+ instantiateServiceFromRegistry(instanceName, token, args) {
1241
+ this.logger?.log(
1242
+ `[ServiceLocator]#instantiateServiceFromRegistry(): Creating instance for ${instanceName} from abstract factory`
1243
+ );
1244
+ const ctx = this.createFactoryContext(
1245
+ this.currentRequestContext || void 0
1246
+ );
1247
+ let record = this.registry.get(token);
1248
+ let { scope, type } = record;
1249
+ const [deferred, holder] = this.manager.createCreatingHolder(
1250
+ instanceName,
1251
+ type,
1252
+ scope,
1253
+ ctx.deps,
1254
+ Infinity
1255
+ );
1256
+ this.serviceInstantiator.instantiateService(ctx, record, args).then(async ([error, instance]) => {
1257
+ holder.destroyListeners = ctx.getDestroyListeners();
1258
+ holder.creationPromise = null;
1259
+ if (error) {
1260
+ this.logger?.error(
1261
+ `[ServiceLocator]#instantiateServiceFromRegistry(): Error creating instance for ${instanceName}`,
1262
+ error
1263
+ );
1264
+ holder.status = "error" /* Error */;
1265
+ holder.instance = error;
1266
+ if (scope === "Singleton" /* Singleton */) {
1267
+ setTimeout(() => this.invalidate(instanceName), 10);
1268
+ }
1269
+ deferred.reject(error);
1270
+ } else {
1271
+ holder.instance = instance;
1272
+ holder.status = "created" /* Created */;
1273
+ if (ctx.deps.size > 0) {
1274
+ ctx.deps.forEach((dependency) => {
1275
+ holder.destroyListeners.push(
1276
+ this.eventBus.on(
1277
+ dependency,
1278
+ "destroy",
1279
+ () => this.invalidate(instanceName)
1280
+ )
1281
+ );
1282
+ });
1283
+ }
1284
+ await this.emitInstanceEvent(instanceName);
1285
+ deferred.resolve([void 0, instance]);
1286
+ }
1287
+ }).catch((error) => {
1288
+ this.logger?.error(
1289
+ `[ServiceLocator]#instantiateServiceFromRegistry(): Unexpected error creating instance for ${instanceName}`,
1290
+ error
1291
+ );
1292
+ holder.status = "error" /* Error */;
1293
+ holder.instance = error;
1294
+ holder.creationPromise = null;
1295
+ if (scope === "Singleton" /* Singleton */) {
1296
+ setTimeout(() => this.invalidate(instanceName), 10);
1297
+ }
1298
+ deferred.reject(error);
1299
+ });
1300
+ if (scope === "Singleton" /* Singleton */) {
1301
+ this.logger?.debug(
1302
+ `[ServiceLocator]#instantiateServiceFromRegistry(): Setting instance for ${instanceName}`
1303
+ );
1304
+ this.manager.set(instanceName, holder);
1305
+ } else if (scope === "Request" /* Request */ && this.currentRequestContext) {
1306
+ this.logger?.debug(
1307
+ `[ServiceLocator]#instantiateServiceFromRegistry(): Setting request-scoped instance for ${instanceName}`
1308
+ );
1309
+ this.currentRequestContext.addInstance(
1310
+ instanceName,
1311
+ holder.instance,
1312
+ holder
1313
+ );
1314
+ }
1315
+ return [void 0, holder];
1316
+ }
1317
+ /**
1318
+ * Creates a factory context for dependency injection during service instantiation.
1319
+ * @param contextHolder Optional request context holder for priority-based resolution
1320
+ */
1321
+ createFactoryContext(contextHolder) {
1322
+ const destroyListeners = /* @__PURE__ */ new Set();
1323
+ const deps = /* @__PURE__ */ new Set();
1324
+ const self = this;
1325
+ function addDestroyListener(listener) {
1326
+ destroyListeners.add(listener);
1327
+ }
1328
+ function getDestroyListeners() {
1329
+ return Array.from(destroyListeners);
1330
+ }
1331
+ return {
1332
+ // @ts-expect-error This is correct type
1333
+ async inject(token, args) {
1334
+ if (contextHolder && contextHolder.priority > 0) {
1335
+ const instanceName = self.generateInstanceName(token, args);
1336
+ const prePreparedInstance = contextHolder.getInstance(instanceName);
1337
+ if (prePreparedInstance !== void 0) {
1338
+ self.logger?.debug(
1339
+ `[ServiceLocator] Using pre-prepared instance ${instanceName} from request context ${contextHolder.requestId}`
1340
+ );
1341
+ deps.add(instanceName);
1342
+ return prePreparedInstance;
1343
+ }
1344
+ }
1345
+ if (self.currentRequestContext && self.currentRequestContext !== contextHolder) {
1346
+ const instanceName = self.generateInstanceName(token, args);
1347
+ const prePreparedInstance = self.currentRequestContext.getInstance(instanceName);
1348
+ if (prePreparedInstance !== void 0) {
1349
+ self.logger?.debug(
1350
+ `[ServiceLocator] Using pre-prepared instance ${instanceName} from current request context ${self.currentRequestContext.requestId}`
1351
+ );
1352
+ deps.add(instanceName);
1353
+ return prePreparedInstance;
1354
+ }
1355
+ }
1356
+ const [error, instance] = await self.getInstance(
1357
+ token,
1358
+ args,
1359
+ ({ instanceName }) => {
1360
+ deps.add(instanceName);
1361
+ }
1362
+ );
1363
+ if (error) {
1364
+ throw error;
1365
+ }
1366
+ return instance;
1367
+ },
1368
+ addDestroyListener,
1369
+ getDestroyListeners,
1370
+ locator: self,
1371
+ deps
1372
+ };
1373
+ }
1374
+ /**
1375
+ * Generates a unique instance name based on token and arguments.
1376
+ */
1377
+ generateInstanceName(token, args) {
897
1378
  const formattedArgs = args ? ":" + Object.entries(args ?? {}).sort(([keyA], [keyB]) => keyA.localeCompare(keyB)).map(([key, value]) => {
898
1379
  if (typeof value === "function") {
899
1380
  return `${key}=fn_${value.name}(${value.length})`;
@@ -907,24 +1388,93 @@ var ServiceLocator = class {
907
1388
  }
908
1389
  };
909
1390
 
910
- // src/injector.mts
911
- var globalServiceLocator = new ServiceLocator(globalRegistry);
912
- function getGlobalServiceLocator() {
913
- if (!globalServiceLocator) {
914
- throw new Error(
915
- "[ServiceLocator] Service locator is not initialized. Please provide the service locator before using the @Injectable decorator."
1391
+ // src/container.mts
1392
+ var _Container_decorators, _init2;
1393
+ _Container_decorators = [Injectable()];
1394
+ var _Container = class _Container {
1395
+ serviceLocator;
1396
+ constructor(registry = globalRegistry, logger = null, injectors = defaultInjectors) {
1397
+ this.serviceLocator = new ServiceLocator(registry, logger, injectors);
1398
+ this.registerSelf();
1399
+ }
1400
+ registerSelf() {
1401
+ const token = getInjectableToken(_Container);
1402
+ const instanceName = this.serviceLocator.getInstanceIdentifier(token);
1403
+ this.serviceLocator.getManager().storeCreatedHolder(
1404
+ instanceName,
1405
+ this,
1406
+ "Class" /* Class */,
1407
+ "Singleton" /* Singleton */
916
1408
  );
917
1409
  }
918
- return globalServiceLocator;
919
- }
920
- var values = getInjectors({
921
- baseLocator: globalServiceLocator
922
- });
923
- var inject = values.inject;
924
- var syncInject = values.syncInject;
925
- var wrapSyncInit = values.wrapSyncInit;
926
- var provideServiceLocator = values.provideServiceLocator;
1410
+ async get(token, args) {
1411
+ return this.serviceLocator.getOrThrowInstance(token, args);
1412
+ }
1413
+ /**
1414
+ * Gets the underlying ServiceLocator instance for advanced usage
1415
+ */
1416
+ getServiceLocator() {
1417
+ return this.serviceLocator;
1418
+ }
1419
+ /**
1420
+ * Invalidates a service and its dependencies
1421
+ */
1422
+ async invalidate(service) {
1423
+ const holderMap = this.serviceLocator.getManager().filter((holder2) => holder2.instance === service);
1424
+ if (holderMap.size === 0) {
1425
+ return;
1426
+ }
1427
+ const holder = holderMap.values().next().value;
1428
+ if (holder) {
1429
+ await this.serviceLocator.invalidate(holder.name);
1430
+ }
1431
+ }
1432
+ /**
1433
+ * Waits for all pending operations to complete
1434
+ */
1435
+ async ready() {
1436
+ await this.serviceLocator.ready();
1437
+ }
1438
+ // ============================================================================
1439
+ // REQUEST CONTEXT MANAGEMENT
1440
+ // ============================================================================
1441
+ /**
1442
+ * Begins a new request context with the given parameters.
1443
+ * @param requestId Unique identifier for this request
1444
+ * @param metadata Optional metadata for the request
1445
+ * @param priority Priority for resolution (higher = more priority)
1446
+ * @returns The created request context holder
1447
+ */
1448
+ beginRequest(requestId, metadata, priority = 100) {
1449
+ return this.serviceLocator.beginRequest(requestId, metadata, priority);
1450
+ }
1451
+ /**
1452
+ * Ends a request context and cleans up all associated instances.
1453
+ * @param requestId The request ID to end
1454
+ */
1455
+ async endRequest(requestId) {
1456
+ await this.serviceLocator.endRequest(requestId);
1457
+ }
1458
+ /**
1459
+ * Gets the current request context.
1460
+ * @returns The current request context holder or null
1461
+ */
1462
+ getCurrentRequestContext() {
1463
+ return this.serviceLocator.getCurrentRequestContext();
1464
+ }
1465
+ /**
1466
+ * Sets the current request context.
1467
+ * @param requestId The request ID to set as current
1468
+ */
1469
+ setCurrentRequestContext(requestId) {
1470
+ this.serviceLocator.setCurrentRequestContext(requestId);
1471
+ }
1472
+ };
1473
+ _init2 = __decoratorStart();
1474
+ _Container = __decorateElement(_init2, 0, "Container", _Container_decorators, _Container);
1475
+ __runInitializers(_init2, 1, _Container);
1476
+ var Container = _Container;
927
1477
 
928
- export { BoundInjectionToken, ErrorsEnum, EventEmitter, FactoryInjectionToken, FactoryNotFound, FactoryTokenNotResolved, Injectable, InjectableScope, InjectableTokenMeta, InjectableType, InjectionToken, InjectorsBase, InstanceDestroying, InstanceExpired, InstanceNotFound, ProxyServiceLocator, Registry, ServiceLocator, ServiceLocatorEventBus, ServiceLocatorInstanceHolderKind, ServiceLocatorInstanceHolderStatus, ServiceLocatorManager, UnknownError, getGlobalServiceLocator, getInjectableToken, getInjectors, globalRegistry, inject, makeProxyServiceLocator, provideServiceLocator, resolveService, syncInject, wrapSyncInit };
1478
+ export { BoundInjectionToken, Container, DefaultRequestContextHolder, Deferred, ErrorsEnum, EventEmitter, Factory, FactoryInjectionToken, FactoryNotFound, FactoryTokenNotResolved, Injectable, InjectableScope, InjectableTokenMeta, InjectableType, InjectionToken, InstanceDestroying, InstanceExpired, InstanceNotFound, Registry, ServiceInstantiator, ServiceLocator, ServiceLocatorEventBus, ServiceLocatorInstanceHolderStatus, ServiceLocatorManager, UnknownError, asyncInject, createDeferred, createRequestContextHolder, defaultInjectors, getInjectableToken, getInjectors, globalRegistry, inject, provideFactoryContext, wrapSyncInit };
929
1479
  //# sourceMappingURL=index.mjs.map
930
1480
  //# sourceMappingURL=index.mjs.map