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