@navios/di 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +301 -39
- package/docs/README.md +122 -49
- package/docs/api-reference.md +763 -0
- package/docs/container.md +274 -0
- package/docs/examples/basic-usage.mts +97 -0
- package/docs/examples/factory-pattern.mts +318 -0
- package/docs/examples/injection-tokens.mts +225 -0
- package/docs/examples/request-scope-example.mts +254 -0
- package/docs/examples/service-lifecycle.mts +359 -0
- package/docs/factory.md +584 -0
- package/docs/getting-started.md +308 -0
- package/docs/injectable.md +496 -0
- package/docs/injection-tokens.md +400 -0
- package/docs/lifecycle.md +539 -0
- package/docs/scopes.md +749 -0
- package/lib/_tsup-dts-rollup.d.mts +495 -150
- package/lib/_tsup-dts-rollup.d.ts +495 -150
- package/lib/index.d.mts +26 -12
- package/lib/index.d.ts +26 -12
- package/lib/index.js +993 -462
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +983 -453
- package/lib/index.mjs.map +1 -1
- package/package.json +2 -2
- package/project.json +10 -2
- package/src/__tests__/container.spec.mts +1301 -0
- package/src/__tests__/factory.spec.mts +137 -0
- package/src/__tests__/injectable.spec.mts +32 -88
- package/src/__tests__/injection-token.spec.mts +333 -17
- package/src/__tests__/request-scope.spec.mts +263 -0
- package/src/__type-tests__/factory.spec-d.mts +65 -0
- package/src/__type-tests__/inject.spec-d.mts +27 -28
- package/src/__type-tests__/injectable.spec-d.mts +42 -206
- package/src/container.mts +167 -0
- package/src/decorators/factory.decorator.mts +79 -0
- package/src/decorators/index.mts +1 -0
- package/src/decorators/injectable.decorator.mts +6 -56
- package/src/enums/injectable-scope.enum.mts +5 -1
- package/src/event-emitter.mts +18 -20
- package/src/factory-context.mts +2 -10
- package/src/index.mts +3 -2
- package/src/injection-token.mts +24 -9
- package/src/injector.mts +8 -20
- package/src/interfaces/factory.interface.mts +3 -3
- package/src/interfaces/index.mts +2 -0
- package/src/interfaces/on-service-destroy.interface.mts +3 -0
- package/src/interfaces/on-service-init.interface.mts +3 -0
- package/src/registry.mts +7 -16
- package/src/request-context-holder.mts +145 -0
- package/src/service-instantiator.mts +158 -0
- package/src/service-locator-event-bus.mts +0 -28
- package/src/service-locator-instance-holder.mts +27 -16
- package/src/service-locator-manager.mts +84 -0
- package/src/service-locator.mts +550 -395
- package/src/utils/defer.mts +73 -0
- package/src/utils/get-injectors.mts +93 -80
- package/src/utils/index.mts +2 -0
- package/src/utils/types.mts +52 -0
- package/docs/concepts/injectable.md +0 -182
- package/docs/concepts/injection-token.md +0 -145
- package/src/proxy-service-locator.mts +0 -83
- package/src/resolve-service.mts +0 -41
package/lib/index.js
CHANGED
|
@@ -1,19 +1,50 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
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__ */ ((
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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__ */ ((
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return
|
|
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,
|
|
121
|
-
this.factories.set(token.id, {
|
|
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/
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
"[
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
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
|
|
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,195 @@ var EventEmitter = class {
|
|
|
423
473
|
if (!this.listeners.has(event)) {
|
|
424
474
|
return;
|
|
425
475
|
}
|
|
426
|
-
return Promise.all(
|
|
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/request-context-holder.mts
|
|
623
|
+
var DefaultRequestContextHolder = class {
|
|
624
|
+
constructor(requestId, priority = 100, initialMetadata) {
|
|
625
|
+
this.requestId = requestId;
|
|
626
|
+
this.priority = priority;
|
|
627
|
+
if (initialMetadata) {
|
|
628
|
+
Object.entries(initialMetadata).forEach(([key, value]) => {
|
|
629
|
+
this.metadata.set(key, value);
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
instances = /* @__PURE__ */ new Map();
|
|
634
|
+
holders = /* @__PURE__ */ new Map();
|
|
635
|
+
metadata = /* @__PURE__ */ new Map();
|
|
636
|
+
createdAt = Date.now();
|
|
637
|
+
addInstance(instanceName, instance, holder) {
|
|
638
|
+
this.instances.set(instanceName, instance);
|
|
639
|
+
this.holders.set(instanceName, holder);
|
|
640
|
+
}
|
|
641
|
+
getInstance(instanceName) {
|
|
642
|
+
return this.instances.get(instanceName);
|
|
643
|
+
}
|
|
644
|
+
getHolder(instanceName) {
|
|
645
|
+
return this.holders.get(instanceName);
|
|
646
|
+
}
|
|
647
|
+
hasInstance(instanceName) {
|
|
648
|
+
return this.instances.has(instanceName);
|
|
649
|
+
}
|
|
650
|
+
clear() {
|
|
651
|
+
this.instances.clear();
|
|
652
|
+
this.holders.clear();
|
|
653
|
+
this.metadata.clear();
|
|
427
654
|
}
|
|
428
|
-
|
|
429
|
-
return this.
|
|
655
|
+
getMetadata(key) {
|
|
656
|
+
return this.metadata.get(key);
|
|
657
|
+
}
|
|
658
|
+
setMetadata(key, value) {
|
|
659
|
+
this.metadata.set(key, value);
|
|
430
660
|
}
|
|
431
661
|
};
|
|
662
|
+
function createRequestContextHolder(requestId, priority = 100, initialMetadata) {
|
|
663
|
+
return new DefaultRequestContextHolder(requestId, priority, initialMetadata);
|
|
664
|
+
}
|
|
432
665
|
|
|
433
666
|
// src/service-locator-event-bus.mts
|
|
434
667
|
var ServiceLocatorEventBus = class {
|
|
@@ -461,19 +694,6 @@ var ServiceLocatorEventBus = class {
|
|
|
461
694
|
return;
|
|
462
695
|
}
|
|
463
696
|
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
697
|
this.logger?.debug(`[ServiceLocatorEventBus]#emit(): ${key}:${event}`);
|
|
478
698
|
const res = await Promise.allSettled(
|
|
479
699
|
[...events.get(event) ?? []].map((listener) => listener(event))
|
|
@@ -490,32 +710,16 @@ var ServiceLocatorEventBus = class {
|
|
|
490
710
|
}
|
|
491
711
|
return results;
|
|
492
712
|
});
|
|
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
713
|
return res;
|
|
505
714
|
}
|
|
506
715
|
};
|
|
507
716
|
|
|
508
717
|
// 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
718
|
var ServiceLocatorInstanceHolderStatus = /* @__PURE__ */ ((ServiceLocatorInstanceHolderStatus2) => {
|
|
516
719
|
ServiceLocatorInstanceHolderStatus2["Created"] = "created";
|
|
517
720
|
ServiceLocatorInstanceHolderStatus2["Creating"] = "creating";
|
|
518
721
|
ServiceLocatorInstanceHolderStatus2["Destroying"] = "destroying";
|
|
722
|
+
ServiceLocatorInstanceHolderStatus2["Error"] = "error";
|
|
519
723
|
return ServiceLocatorInstanceHolderStatus2;
|
|
520
724
|
})(ServiceLocatorInstanceHolderStatus || {});
|
|
521
725
|
|
|
@@ -541,6 +745,11 @@ var ServiceLocatorManager = class {
|
|
|
541
745
|
`[ServiceLocatorManager]#getInstanceHolder() Instance ${holder.name} is destroying`
|
|
542
746
|
);
|
|
543
747
|
return [new InstanceDestroying(holder.name), holder];
|
|
748
|
+
} else if (holder.status === "error" /* Error */) {
|
|
749
|
+
this.logger?.log(
|
|
750
|
+
`[ServiceLocatorManager]#getInstanceHolder() Instance ${holder.name} is in error state`
|
|
751
|
+
);
|
|
752
|
+
return [holder.instance, holder];
|
|
544
753
|
}
|
|
545
754
|
return [void 0, holder];
|
|
546
755
|
} else {
|
|
@@ -575,116 +784,115 @@ var ServiceLocatorManager = class {
|
|
|
575
784
|
)
|
|
576
785
|
);
|
|
577
786
|
}
|
|
787
|
+
/**
|
|
788
|
+
* Creates a new holder with Creating status and a deferred creation promise.
|
|
789
|
+
* This is useful for creating placeholder holders that can be fulfilled later.
|
|
790
|
+
* @param name The name of the instance
|
|
791
|
+
* @param type The injectable type
|
|
792
|
+
* @param scope The injectable scope
|
|
793
|
+
* @param deps Optional set of dependencies
|
|
794
|
+
* @param ttl Optional time-to-live in milliseconds (defaults to Infinity)
|
|
795
|
+
* @returns A tuple containing the deferred promise and the holder
|
|
796
|
+
*/
|
|
797
|
+
createCreatingHolder(name, type, scope, deps = /* @__PURE__ */ new Set(), ttl = Infinity) {
|
|
798
|
+
const deferred = createDeferred();
|
|
799
|
+
const holder = {
|
|
800
|
+
status: "creating" /* Creating */,
|
|
801
|
+
name,
|
|
802
|
+
instance: null,
|
|
803
|
+
creationPromise: deferred.promise,
|
|
804
|
+
destroyPromise: null,
|
|
805
|
+
type,
|
|
806
|
+
scope,
|
|
807
|
+
deps,
|
|
808
|
+
destroyListeners: [],
|
|
809
|
+
createdAt: Date.now(),
|
|
810
|
+
ttl
|
|
811
|
+
};
|
|
812
|
+
return [deferred, holder];
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Creates a new holder with Created status and an actual instance.
|
|
816
|
+
* This is useful for creating holders that already have their instance ready.
|
|
817
|
+
* @param name The name of the instance
|
|
818
|
+
* @param instance The actual instance to store
|
|
819
|
+
* @param type The injectable type
|
|
820
|
+
* @param scope The injectable scope
|
|
821
|
+
* @param deps Optional set of dependencies
|
|
822
|
+
* @param ttl Optional time-to-live in milliseconds (defaults to Infinity)
|
|
823
|
+
* @returns The created holder
|
|
824
|
+
*/
|
|
825
|
+
storeCreatedHolder(name, instance, type, scope, deps = /* @__PURE__ */ new Set(), ttl = Infinity) {
|
|
826
|
+
const holder = {
|
|
827
|
+
status: "created" /* Created */,
|
|
828
|
+
name,
|
|
829
|
+
instance,
|
|
830
|
+
creationPromise: null,
|
|
831
|
+
destroyPromise: null,
|
|
832
|
+
type,
|
|
833
|
+
scope,
|
|
834
|
+
deps,
|
|
835
|
+
destroyListeners: [],
|
|
836
|
+
createdAt: Date.now(),
|
|
837
|
+
ttl
|
|
838
|
+
};
|
|
839
|
+
this.instancesHolders.set(name, holder);
|
|
840
|
+
return holder;
|
|
841
|
+
}
|
|
578
842
|
};
|
|
579
843
|
|
|
580
844
|
// src/service-locator.mts
|
|
581
845
|
var ServiceLocator = class {
|
|
582
|
-
constructor(registry = globalRegistry, logger = null) {
|
|
846
|
+
constructor(registry = globalRegistry, logger = null, injectors = defaultInjectors) {
|
|
583
847
|
this.registry = registry;
|
|
584
848
|
this.logger = logger;
|
|
849
|
+
this.injectors = injectors;
|
|
585
850
|
this.eventBus = new ServiceLocatorEventBus(logger);
|
|
586
851
|
this.manager = new ServiceLocatorManager(logger);
|
|
852
|
+
this.serviceInstantiator = new ServiceInstantiator(injectors);
|
|
587
853
|
}
|
|
588
854
|
eventBus;
|
|
589
855
|
manager;
|
|
856
|
+
serviceInstantiator;
|
|
857
|
+
requestContexts = /* @__PURE__ */ new Map();
|
|
858
|
+
currentRequestContext = null;
|
|
859
|
+
// ============================================================================
|
|
860
|
+
// PUBLIC METHODS
|
|
861
|
+
// ============================================================================
|
|
590
862
|
getEventBus() {
|
|
591
863
|
return this.eventBus;
|
|
592
864
|
}
|
|
593
|
-
|
|
594
|
-
|
|
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);
|
|
865
|
+
getManager() {
|
|
866
|
+
return this.manager;
|
|
609
867
|
}
|
|
610
|
-
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
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)];
|
|
868
|
+
getInstanceIdentifier(token, args) {
|
|
869
|
+
const [err, { actualToken, validatedArgs }] = this.validateAndResolveTokenArgs(token, args);
|
|
870
|
+
if (err) {
|
|
871
|
+
throw err;
|
|
635
872
|
}
|
|
636
|
-
return
|
|
873
|
+
return this.generateInstanceName(actualToken, validatedArgs);
|
|
637
874
|
}
|
|
638
|
-
|
|
639
|
-
const [err,
|
|
875
|
+
async getInstance(token, args, onPrepare) {
|
|
876
|
+
const [err, data] = await this.resolveTokenAndPrepareInstanceName(
|
|
640
877
|
token,
|
|
641
878
|
args
|
|
642
879
|
);
|
|
643
880
|
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
881
|
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
882
|
}
|
|
659
|
-
const instanceName
|
|
660
|
-
|
|
661
|
-
|
|
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];
|
|
883
|
+
const { instanceName, validatedArgs, actualToken, realToken } = data;
|
|
884
|
+
if (onPrepare) {
|
|
885
|
+
onPrepare({ instanceName, actualToken, validatedArgs });
|
|
668
886
|
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
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];
|
|
887
|
+
const [error, holder] = await this.retrieveOrCreateInstanceByInstanceName(
|
|
888
|
+
instanceName,
|
|
889
|
+
realToken,
|
|
890
|
+
validatedArgs
|
|
891
|
+
);
|
|
892
|
+
if (error) {
|
|
893
|
+
return [error];
|
|
686
894
|
}
|
|
687
|
-
return
|
|
895
|
+
return [void 0, holder.instance];
|
|
688
896
|
}
|
|
689
897
|
async getOrThrowInstance(token, args) {
|
|
690
898
|
const [error, instance] = await this.getInstance(token, args);
|
|
@@ -693,142 +901,18 @@ var ServiceLocator = class {
|
|
|
693
901
|
}
|
|
694
902
|
return instance;
|
|
695
903
|
}
|
|
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
904
|
getSyncInstance(token, args) {
|
|
827
|
-
const [err,
|
|
905
|
+
const [err, { actualToken, validatedArgs }] = this.validateAndResolveTokenArgs(token, args);
|
|
828
906
|
if (err) {
|
|
829
907
|
return null;
|
|
830
908
|
}
|
|
831
|
-
const instanceName = this.
|
|
909
|
+
const instanceName = this.generateInstanceName(actualToken, validatedArgs);
|
|
910
|
+
if (this.currentRequestContext) {
|
|
911
|
+
const requestHolder = this.currentRequestContext.getHolder(instanceName);
|
|
912
|
+
if (requestHolder) {
|
|
913
|
+
return requestHolder.instance;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
832
916
|
const [error, holder] = this.manager.get(instanceName);
|
|
833
917
|
if (error) {
|
|
834
918
|
return null;
|
|
@@ -840,7 +924,7 @@ var ServiceLocator = class {
|
|
|
840
924
|
`[ServiceLocator]#invalidate(): Starting Invalidating process of ${service}`
|
|
841
925
|
);
|
|
842
926
|
const toInvalidate = this.manager.filter(
|
|
843
|
-
(holder) => holder.name === service || holder.deps.
|
|
927
|
+
(holder) => holder.name === service || holder.deps.has(service)
|
|
844
928
|
);
|
|
845
929
|
const promises = [];
|
|
846
930
|
for (const [key, holder] of toInvalidate.entries()) {
|
|
@@ -876,7 +960,7 @@ var ServiceLocator = class {
|
|
|
876
960
|
holder.destroyListeners.map((listener) => listener())
|
|
877
961
|
).then(async () => {
|
|
878
962
|
this.manager.delete(key);
|
|
879
|
-
await this.
|
|
963
|
+
await this.emitInstanceEvent(key, "destroy");
|
|
880
964
|
});
|
|
881
965
|
promises.push(holder.destroyPromise);
|
|
882
966
|
}
|
|
@@ -895,7 +979,384 @@ var ServiceLocator = class {
|
|
|
895
979
|
})
|
|
896
980
|
).then(() => null);
|
|
897
981
|
}
|
|
898
|
-
|
|
982
|
+
// ============================================================================
|
|
983
|
+
// REQUEST CONTEXT MANAGEMENT
|
|
984
|
+
// ============================================================================
|
|
985
|
+
/**
|
|
986
|
+
* Begins a new request context with the given parameters.
|
|
987
|
+
* @param requestId Unique identifier for this request
|
|
988
|
+
* @param metadata Optional metadata for the request
|
|
989
|
+
* @param priority Priority for resolution (higher = more priority)
|
|
990
|
+
* @returns The created request context holder
|
|
991
|
+
*/
|
|
992
|
+
beginRequest(requestId, metadata, priority = 100) {
|
|
993
|
+
if (this.requestContexts.has(requestId)) {
|
|
994
|
+
throw new Error(
|
|
995
|
+
`[ServiceLocator] Request context ${requestId} already exists`
|
|
996
|
+
);
|
|
997
|
+
}
|
|
998
|
+
const contextHolder = new DefaultRequestContextHolder(
|
|
999
|
+
requestId,
|
|
1000
|
+
priority,
|
|
1001
|
+
metadata
|
|
1002
|
+
);
|
|
1003
|
+
this.requestContexts.set(requestId, contextHolder);
|
|
1004
|
+
this.currentRequestContext = contextHolder;
|
|
1005
|
+
this.logger?.log(`[ServiceLocator] Started request context: ${requestId}`);
|
|
1006
|
+
return contextHolder;
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Ends a request context and cleans up all associated instances.
|
|
1010
|
+
* @param requestId The request ID to end
|
|
1011
|
+
*/
|
|
1012
|
+
async endRequest(requestId) {
|
|
1013
|
+
const contextHolder = this.requestContexts.get(requestId);
|
|
1014
|
+
if (!contextHolder) {
|
|
1015
|
+
this.logger?.warn(
|
|
1016
|
+
`[ServiceLocator] Request context ${requestId} not found`
|
|
1017
|
+
);
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
this.logger?.log(`[ServiceLocator] Ending request context: ${requestId}`);
|
|
1021
|
+
const cleanupPromises = [];
|
|
1022
|
+
for (const [, holder] of contextHolder.holders) {
|
|
1023
|
+
if (holder.destroyListeners.length > 0) {
|
|
1024
|
+
cleanupPromises.push(
|
|
1025
|
+
Promise.all(holder.destroyListeners.map((listener) => listener()))
|
|
1026
|
+
);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
await Promise.all(cleanupPromises);
|
|
1030
|
+
contextHolder.clear();
|
|
1031
|
+
this.requestContexts.delete(requestId);
|
|
1032
|
+
if (this.currentRequestContext === contextHolder) {
|
|
1033
|
+
this.currentRequestContext = Array.from(this.requestContexts.values()).at(-1) ?? null;
|
|
1034
|
+
}
|
|
1035
|
+
this.logger?.log(`[ServiceLocator] Request context ${requestId} ended`);
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Gets the current request context.
|
|
1039
|
+
* @returns The current request context holder or null
|
|
1040
|
+
*/
|
|
1041
|
+
getCurrentRequestContext() {
|
|
1042
|
+
return this.currentRequestContext;
|
|
1043
|
+
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Sets the current request context.
|
|
1046
|
+
* @param requestId The request ID to set as current
|
|
1047
|
+
*/
|
|
1048
|
+
setCurrentRequestContext(requestId) {
|
|
1049
|
+
const contextHolder = this.requestContexts.get(requestId);
|
|
1050
|
+
if (!contextHolder) {
|
|
1051
|
+
throw new Error(`[ServiceLocator] Request context ${requestId} not found`);
|
|
1052
|
+
}
|
|
1053
|
+
this.currentRequestContext = contextHolder;
|
|
1054
|
+
}
|
|
1055
|
+
// ============================================================================
|
|
1056
|
+
// PRIVATE METHODS
|
|
1057
|
+
// ============================================================================
|
|
1058
|
+
/**
|
|
1059
|
+
* Validates and resolves token arguments, handling factory token resolution and validation.
|
|
1060
|
+
*/
|
|
1061
|
+
validateAndResolveTokenArgs(token, args) {
|
|
1062
|
+
let actualToken = token;
|
|
1063
|
+
if (typeof token === "function") {
|
|
1064
|
+
actualToken = getInjectableToken(token);
|
|
1065
|
+
}
|
|
1066
|
+
let realArgs = args;
|
|
1067
|
+
if (actualToken instanceof BoundInjectionToken) {
|
|
1068
|
+
realArgs = actualToken.value;
|
|
1069
|
+
} else if (actualToken instanceof FactoryInjectionToken) {
|
|
1070
|
+
if (actualToken.resolved) {
|
|
1071
|
+
realArgs = actualToken.value;
|
|
1072
|
+
} else {
|
|
1073
|
+
return [new FactoryTokenNotResolved(token.name), { actualToken }];
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
if (!actualToken.schema) {
|
|
1077
|
+
return [void 0, { actualToken, validatedArgs: realArgs }];
|
|
1078
|
+
}
|
|
1079
|
+
const validatedArgs = actualToken.schema?.safeParse(realArgs);
|
|
1080
|
+
if (validatedArgs && !validatedArgs.success) {
|
|
1081
|
+
this.logger?.error(
|
|
1082
|
+
`[ServiceLocator]#validateAndResolveTokenArgs(): Error validating args for ${actualToken.name.toString()}`,
|
|
1083
|
+
validatedArgs.error
|
|
1084
|
+
);
|
|
1085
|
+
return [new UnknownError(validatedArgs.error), { actualToken }];
|
|
1086
|
+
}
|
|
1087
|
+
return [void 0, { actualToken, validatedArgs: validatedArgs?.data }];
|
|
1088
|
+
}
|
|
1089
|
+
/**
|
|
1090
|
+
* Internal method to resolve token args and create instance name.
|
|
1091
|
+
* Handles factory token resolution and validation.
|
|
1092
|
+
*/
|
|
1093
|
+
async resolveTokenAndPrepareInstanceName(token, args) {
|
|
1094
|
+
const [err, { actualToken, validatedArgs }] = this.validateAndResolveTokenArgs(token, args);
|
|
1095
|
+
if (err instanceof UnknownError) {
|
|
1096
|
+
return [err];
|
|
1097
|
+
} else if (err instanceof FactoryTokenNotResolved && actualToken instanceof FactoryInjectionToken) {
|
|
1098
|
+
this.logger?.log(
|
|
1099
|
+
`[ServiceLocator]#resolveTokenAndPrepareInstanceName() Factory token not resolved, resolving it`
|
|
1100
|
+
);
|
|
1101
|
+
await actualToken.resolve(this.createFactoryContext());
|
|
1102
|
+
return this.resolveTokenAndPrepareInstanceName(token);
|
|
1103
|
+
}
|
|
1104
|
+
const instanceName = this.generateInstanceName(actualToken, validatedArgs);
|
|
1105
|
+
const realToken = actualToken instanceof BoundInjectionToken || actualToken instanceof FactoryInjectionToken ? actualToken.token : actualToken;
|
|
1106
|
+
return [void 0, { instanceName, validatedArgs, actualToken, realToken }];
|
|
1107
|
+
}
|
|
1108
|
+
/**
|
|
1109
|
+
* Gets an instance by its instance name, handling all the logic after instance name creation.
|
|
1110
|
+
*/
|
|
1111
|
+
async retrieveOrCreateInstanceByInstanceName(instanceName, realToken, realArgs) {
|
|
1112
|
+
if (this.registry.has(realToken)) {
|
|
1113
|
+
const record = this.registry.get(realToken);
|
|
1114
|
+
if (record.scope === "Request" /* Request */) {
|
|
1115
|
+
if (!this.currentRequestContext) {
|
|
1116
|
+
this.logger?.log(
|
|
1117
|
+
`[ServiceLocator]#retrieveOrCreateInstanceByInstanceName() No current request context available for request-scoped service ${instanceName}`
|
|
1118
|
+
);
|
|
1119
|
+
return [new UnknownError("InstanceNotFound" /* InstanceNotFound */)];
|
|
1120
|
+
}
|
|
1121
|
+
const requestHolder = this.currentRequestContext.getHolder(instanceName);
|
|
1122
|
+
if (requestHolder) {
|
|
1123
|
+
if (requestHolder.status === "creating" /* Creating */) {
|
|
1124
|
+
await requestHolder.creationPromise;
|
|
1125
|
+
return this.retrieveOrCreateInstanceByInstanceName(
|
|
1126
|
+
instanceName,
|
|
1127
|
+
realToken,
|
|
1128
|
+
realArgs
|
|
1129
|
+
);
|
|
1130
|
+
} else if (requestHolder.status === "destroying" /* Destroying */) {
|
|
1131
|
+
return [new UnknownError("InstanceDestroying" /* InstanceDestroying */)];
|
|
1132
|
+
}
|
|
1133
|
+
return [void 0, requestHolder];
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
const [error, holder] = this.manager.get(instanceName);
|
|
1138
|
+
if (!error) {
|
|
1139
|
+
if (holder.status === "creating" /* Creating */) {
|
|
1140
|
+
await holder.creationPromise;
|
|
1141
|
+
return this.retrieveOrCreateInstanceByInstanceName(
|
|
1142
|
+
instanceName,
|
|
1143
|
+
realToken,
|
|
1144
|
+
realArgs
|
|
1145
|
+
);
|
|
1146
|
+
} else if (holder.status === "destroying" /* Destroying */) {
|
|
1147
|
+
return [new UnknownError("InstanceDestroying" /* InstanceDestroying */)];
|
|
1148
|
+
}
|
|
1149
|
+
return [void 0, holder];
|
|
1150
|
+
}
|
|
1151
|
+
switch (error.code) {
|
|
1152
|
+
case "InstanceDestroying" /* InstanceDestroying */:
|
|
1153
|
+
this.logger?.log(
|
|
1154
|
+
`[ServiceLocator]#retrieveOrCreateInstanceByInstanceName() TTL expired for ${holder?.name}`
|
|
1155
|
+
);
|
|
1156
|
+
await holder?.destroyPromise;
|
|
1157
|
+
return this.retrieveOrCreateInstanceByInstanceName(
|
|
1158
|
+
instanceName,
|
|
1159
|
+
realToken,
|
|
1160
|
+
realArgs
|
|
1161
|
+
);
|
|
1162
|
+
case "InstanceExpired" /* InstanceExpired */:
|
|
1163
|
+
this.logger?.log(
|
|
1164
|
+
`[ServiceLocator]#retrieveOrCreateInstanceByInstanceName() TTL expired for ${holder?.name}`
|
|
1165
|
+
);
|
|
1166
|
+
await this.invalidate(instanceName);
|
|
1167
|
+
return this.retrieveOrCreateInstanceByInstanceName(
|
|
1168
|
+
instanceName,
|
|
1169
|
+
realToken,
|
|
1170
|
+
realArgs
|
|
1171
|
+
);
|
|
1172
|
+
case "InstanceNotFound" /* InstanceNotFound */:
|
|
1173
|
+
break;
|
|
1174
|
+
default:
|
|
1175
|
+
return [error];
|
|
1176
|
+
}
|
|
1177
|
+
const result = await this.createNewInstance(
|
|
1178
|
+
instanceName,
|
|
1179
|
+
realToken,
|
|
1180
|
+
realArgs
|
|
1181
|
+
);
|
|
1182
|
+
if (result[0]) {
|
|
1183
|
+
return [result[0]];
|
|
1184
|
+
}
|
|
1185
|
+
if (result[1].status === "creating" /* Creating */) {
|
|
1186
|
+
await result[1].creationPromise;
|
|
1187
|
+
}
|
|
1188
|
+
if (result[1].status === "error" /* Error */) {
|
|
1189
|
+
return [result[1].instance];
|
|
1190
|
+
}
|
|
1191
|
+
return [void 0, result[1]];
|
|
1192
|
+
}
|
|
1193
|
+
/**
|
|
1194
|
+
* Emits events to listeners for instance lifecycle events.
|
|
1195
|
+
*/
|
|
1196
|
+
emitInstanceEvent(name, event = "create") {
|
|
1197
|
+
this.logger?.log(
|
|
1198
|
+
`[ServiceLocator]#emitInstanceEvent() Notifying listeners for ${name} with event ${event}`
|
|
1199
|
+
);
|
|
1200
|
+
return this.eventBus.emit(name, event);
|
|
1201
|
+
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Creates a new instance for the given token and arguments.
|
|
1204
|
+
*/
|
|
1205
|
+
async createNewInstance(instanceName, realToken, args) {
|
|
1206
|
+
this.logger?.log(
|
|
1207
|
+
`[ServiceLocator]#createNewInstance() Creating instance for ${instanceName}`
|
|
1208
|
+
);
|
|
1209
|
+
if (this.registry.has(realToken)) {
|
|
1210
|
+
return this.instantiateServiceFromRegistry(
|
|
1211
|
+
instanceName,
|
|
1212
|
+
realToken,
|
|
1213
|
+
args
|
|
1214
|
+
);
|
|
1215
|
+
} else {
|
|
1216
|
+
return [new FactoryNotFound(realToken.name.toString())];
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
/**
|
|
1220
|
+
* Instantiates a service from the registry using the service instantiator.
|
|
1221
|
+
*/
|
|
1222
|
+
instantiateServiceFromRegistry(instanceName, token, args) {
|
|
1223
|
+
this.logger?.log(
|
|
1224
|
+
`[ServiceLocator]#instantiateServiceFromRegistry(): Creating instance for ${instanceName} from abstract factory`
|
|
1225
|
+
);
|
|
1226
|
+
const ctx = this.createFactoryContext(
|
|
1227
|
+
this.currentRequestContext || void 0
|
|
1228
|
+
);
|
|
1229
|
+
let record = this.registry.get(token);
|
|
1230
|
+
let { scope, type } = record;
|
|
1231
|
+
const [deferred, holder] = this.manager.createCreatingHolder(
|
|
1232
|
+
instanceName,
|
|
1233
|
+
type,
|
|
1234
|
+
scope,
|
|
1235
|
+
ctx.deps,
|
|
1236
|
+
Infinity
|
|
1237
|
+
);
|
|
1238
|
+
this.serviceInstantiator.instantiateService(ctx, record, args).then(async ([error, instance]) => {
|
|
1239
|
+
holder.destroyListeners = ctx.getDestroyListeners();
|
|
1240
|
+
holder.creationPromise = null;
|
|
1241
|
+
if (error) {
|
|
1242
|
+
this.logger?.error(
|
|
1243
|
+
`[ServiceLocator]#instantiateServiceFromRegistry(): Error creating instance for ${instanceName}`,
|
|
1244
|
+
error
|
|
1245
|
+
);
|
|
1246
|
+
holder.status = "error" /* Error */;
|
|
1247
|
+
holder.instance = error;
|
|
1248
|
+
if (scope === "Singleton" /* Singleton */) {
|
|
1249
|
+
setTimeout(() => this.invalidate(instanceName), 10);
|
|
1250
|
+
}
|
|
1251
|
+
deferred.reject(error);
|
|
1252
|
+
} else {
|
|
1253
|
+
holder.instance = instance;
|
|
1254
|
+
holder.status = "created" /* Created */;
|
|
1255
|
+
if (ctx.deps.size > 0) {
|
|
1256
|
+
ctx.deps.forEach((dependency) => {
|
|
1257
|
+
holder.destroyListeners.push(
|
|
1258
|
+
this.eventBus.on(
|
|
1259
|
+
dependency,
|
|
1260
|
+
"destroy",
|
|
1261
|
+
() => this.invalidate(instanceName)
|
|
1262
|
+
)
|
|
1263
|
+
);
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
await this.emitInstanceEvent(instanceName);
|
|
1267
|
+
deferred.resolve([void 0, instance]);
|
|
1268
|
+
}
|
|
1269
|
+
}).catch((error) => {
|
|
1270
|
+
this.logger?.error(
|
|
1271
|
+
`[ServiceLocator]#instantiateServiceFromRegistry(): Unexpected error creating instance for ${instanceName}`,
|
|
1272
|
+
error
|
|
1273
|
+
);
|
|
1274
|
+
holder.status = "error" /* Error */;
|
|
1275
|
+
holder.instance = error;
|
|
1276
|
+
holder.creationPromise = null;
|
|
1277
|
+
if (scope === "Singleton" /* Singleton */) {
|
|
1278
|
+
setTimeout(() => this.invalidate(instanceName), 10);
|
|
1279
|
+
}
|
|
1280
|
+
deferred.reject(error);
|
|
1281
|
+
});
|
|
1282
|
+
if (scope === "Singleton" /* Singleton */) {
|
|
1283
|
+
this.logger?.debug(
|
|
1284
|
+
`[ServiceLocator]#instantiateServiceFromRegistry(): Setting instance for ${instanceName}`
|
|
1285
|
+
);
|
|
1286
|
+
this.manager.set(instanceName, holder);
|
|
1287
|
+
} else if (scope === "Request" /* Request */ && this.currentRequestContext) {
|
|
1288
|
+
this.logger?.debug(
|
|
1289
|
+
`[ServiceLocator]#instantiateServiceFromRegistry(): Setting request-scoped instance for ${instanceName}`
|
|
1290
|
+
);
|
|
1291
|
+
this.currentRequestContext.addInstance(
|
|
1292
|
+
instanceName,
|
|
1293
|
+
holder.instance,
|
|
1294
|
+
holder
|
|
1295
|
+
);
|
|
1296
|
+
}
|
|
1297
|
+
return [void 0, holder];
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* Creates a factory context for dependency injection during service instantiation.
|
|
1301
|
+
* @param contextHolder Optional request context holder for priority-based resolution
|
|
1302
|
+
*/
|
|
1303
|
+
createFactoryContext(contextHolder) {
|
|
1304
|
+
const destroyListeners = /* @__PURE__ */ new Set();
|
|
1305
|
+
const deps = /* @__PURE__ */ new Set();
|
|
1306
|
+
const self = this;
|
|
1307
|
+
function addDestroyListener(listener) {
|
|
1308
|
+
destroyListeners.add(listener);
|
|
1309
|
+
}
|
|
1310
|
+
function getDestroyListeners() {
|
|
1311
|
+
return Array.from(destroyListeners);
|
|
1312
|
+
}
|
|
1313
|
+
return {
|
|
1314
|
+
// @ts-expect-error This is correct type
|
|
1315
|
+
async inject(token, args) {
|
|
1316
|
+
if (contextHolder && contextHolder.priority > 0) {
|
|
1317
|
+
const instanceName = self.generateInstanceName(token, args);
|
|
1318
|
+
const prePreparedInstance = contextHolder.getInstance(instanceName);
|
|
1319
|
+
if (prePreparedInstance !== void 0) {
|
|
1320
|
+
self.logger?.debug(
|
|
1321
|
+
`[ServiceLocator] Using pre-prepared instance ${instanceName} from request context ${contextHolder.requestId}`
|
|
1322
|
+
);
|
|
1323
|
+
deps.add(instanceName);
|
|
1324
|
+
return prePreparedInstance;
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
if (self.currentRequestContext && self.currentRequestContext !== contextHolder) {
|
|
1328
|
+
const instanceName = self.generateInstanceName(token, args);
|
|
1329
|
+
const prePreparedInstance = self.currentRequestContext.getInstance(instanceName);
|
|
1330
|
+
if (prePreparedInstance !== void 0) {
|
|
1331
|
+
self.logger?.debug(
|
|
1332
|
+
`[ServiceLocator] Using pre-prepared instance ${instanceName} from current request context ${self.currentRequestContext.requestId}`
|
|
1333
|
+
);
|
|
1334
|
+
deps.add(instanceName);
|
|
1335
|
+
return prePreparedInstance;
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
const [error, instance] = await self.getInstance(
|
|
1339
|
+
token,
|
|
1340
|
+
args,
|
|
1341
|
+
({ instanceName }) => {
|
|
1342
|
+
deps.add(instanceName);
|
|
1343
|
+
}
|
|
1344
|
+
);
|
|
1345
|
+
if (error) {
|
|
1346
|
+
throw error;
|
|
1347
|
+
}
|
|
1348
|
+
return instance;
|
|
1349
|
+
},
|
|
1350
|
+
addDestroyListener,
|
|
1351
|
+
getDestroyListeners,
|
|
1352
|
+
locator: self,
|
|
1353
|
+
deps
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
/**
|
|
1357
|
+
* Generates a unique instance name based on token and arguments.
|
|
1358
|
+
*/
|
|
1359
|
+
generateInstanceName(token, args) {
|
|
899
1360
|
const formattedArgs = args ? ":" + Object.entries(args ?? {}).sort(([keyA], [keyB]) => keyA.localeCompare(keyB)).map(([key, value]) => {
|
|
900
1361
|
if (typeof value === "function") {
|
|
901
1362
|
return `${key}=fn_${value.name}(${value.length})`;
|
|
@@ -909,27 +1370,99 @@ var ServiceLocator = class {
|
|
|
909
1370
|
}
|
|
910
1371
|
};
|
|
911
1372
|
|
|
912
|
-
// src/
|
|
913
|
-
var
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
1373
|
+
// src/container.mts
|
|
1374
|
+
var _Container_decorators, _init2;
|
|
1375
|
+
_Container_decorators = [Injectable()];
|
|
1376
|
+
var _Container = class _Container {
|
|
1377
|
+
serviceLocator;
|
|
1378
|
+
constructor(registry = globalRegistry, logger = null, injectors = defaultInjectors) {
|
|
1379
|
+
this.serviceLocator = new ServiceLocator(registry, logger, injectors);
|
|
1380
|
+
this.registerSelf();
|
|
1381
|
+
}
|
|
1382
|
+
registerSelf() {
|
|
1383
|
+
const token = getInjectableToken(_Container);
|
|
1384
|
+
const instanceName = this.serviceLocator.getInstanceIdentifier(token);
|
|
1385
|
+
this.serviceLocator.getManager().storeCreatedHolder(
|
|
1386
|
+
instanceName,
|
|
1387
|
+
this,
|
|
1388
|
+
"Class" /* Class */,
|
|
1389
|
+
"Singleton" /* Singleton */
|
|
918
1390
|
);
|
|
919
1391
|
}
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1392
|
+
async get(token, args) {
|
|
1393
|
+
return this.serviceLocator.getOrThrowInstance(token, args);
|
|
1394
|
+
}
|
|
1395
|
+
/**
|
|
1396
|
+
* Gets the underlying ServiceLocator instance for advanced usage
|
|
1397
|
+
*/
|
|
1398
|
+
getServiceLocator() {
|
|
1399
|
+
return this.serviceLocator;
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* Invalidates a service and its dependencies
|
|
1403
|
+
*/
|
|
1404
|
+
async invalidate(service) {
|
|
1405
|
+
const holderMap = this.serviceLocator.getManager().filter((holder2) => holder2.instance === service);
|
|
1406
|
+
if (holderMap.size === 0) {
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
const holder = holderMap.values().next().value;
|
|
1410
|
+
if (holder) {
|
|
1411
|
+
await this.serviceLocator.invalidate(holder.name);
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
/**
|
|
1415
|
+
* Waits for all pending operations to complete
|
|
1416
|
+
*/
|
|
1417
|
+
async ready() {
|
|
1418
|
+
await this.serviceLocator.ready();
|
|
1419
|
+
}
|
|
1420
|
+
// ============================================================================
|
|
1421
|
+
// REQUEST CONTEXT MANAGEMENT
|
|
1422
|
+
// ============================================================================
|
|
1423
|
+
/**
|
|
1424
|
+
* Begins a new request context with the given parameters.
|
|
1425
|
+
* @param requestId Unique identifier for this request
|
|
1426
|
+
* @param metadata Optional metadata for the request
|
|
1427
|
+
* @param priority Priority for resolution (higher = more priority)
|
|
1428
|
+
* @returns The created request context holder
|
|
1429
|
+
*/
|
|
1430
|
+
beginRequest(requestId, metadata, priority = 100) {
|
|
1431
|
+
return this.serviceLocator.beginRequest(requestId, metadata, priority);
|
|
1432
|
+
}
|
|
1433
|
+
/**
|
|
1434
|
+
* Ends a request context and cleans up all associated instances.
|
|
1435
|
+
* @param requestId The request ID to end
|
|
1436
|
+
*/
|
|
1437
|
+
async endRequest(requestId) {
|
|
1438
|
+
await this.serviceLocator.endRequest(requestId);
|
|
1439
|
+
}
|
|
1440
|
+
/**
|
|
1441
|
+
* Gets the current request context.
|
|
1442
|
+
* @returns The current request context holder or null
|
|
1443
|
+
*/
|
|
1444
|
+
getCurrentRequestContext() {
|
|
1445
|
+
return this.serviceLocator.getCurrentRequestContext();
|
|
1446
|
+
}
|
|
1447
|
+
/**
|
|
1448
|
+
* Sets the current request context.
|
|
1449
|
+
* @param requestId The request ID to set as current
|
|
1450
|
+
*/
|
|
1451
|
+
setCurrentRequestContext(requestId) {
|
|
1452
|
+
this.serviceLocator.setCurrentRequestContext(requestId);
|
|
1453
|
+
}
|
|
1454
|
+
};
|
|
1455
|
+
_init2 = __decoratorStart();
|
|
1456
|
+
_Container = __decorateElement(_init2, 0, "Container", _Container_decorators, _Container);
|
|
1457
|
+
__runInitializers(_init2, 1, _Container);
|
|
1458
|
+
var Container = _Container;
|
|
929
1459
|
|
|
930
1460
|
exports.BoundInjectionToken = BoundInjectionToken;
|
|
1461
|
+
exports.Container = Container;
|
|
1462
|
+
exports.DefaultRequestContextHolder = DefaultRequestContextHolder;
|
|
1463
|
+
exports.Deferred = Deferred;
|
|
931
1464
|
exports.ErrorsEnum = ErrorsEnum;
|
|
932
|
-
exports.
|
|
1465
|
+
exports.Factory = Factory;
|
|
933
1466
|
exports.FactoryInjectionToken = FactoryInjectionToken;
|
|
934
1467
|
exports.FactoryNotFound = FactoryNotFound;
|
|
935
1468
|
exports.FactoryTokenNotResolved = FactoryTokenNotResolved;
|
|
@@ -938,27 +1471,25 @@ exports.InjectableScope = InjectableScope;
|
|
|
938
1471
|
exports.InjectableTokenMeta = InjectableTokenMeta;
|
|
939
1472
|
exports.InjectableType = InjectableType;
|
|
940
1473
|
exports.InjectionToken = InjectionToken;
|
|
941
|
-
exports.InjectorsBase = InjectorsBase;
|
|
942
1474
|
exports.InstanceDestroying = InstanceDestroying;
|
|
943
1475
|
exports.InstanceExpired = InstanceExpired;
|
|
944
1476
|
exports.InstanceNotFound = InstanceNotFound;
|
|
945
|
-
exports.ProxyServiceLocator = ProxyServiceLocator;
|
|
946
1477
|
exports.Registry = Registry;
|
|
1478
|
+
exports.ServiceInstantiator = ServiceInstantiator;
|
|
947
1479
|
exports.ServiceLocator = ServiceLocator;
|
|
948
1480
|
exports.ServiceLocatorEventBus = ServiceLocatorEventBus;
|
|
949
|
-
exports.ServiceLocatorInstanceHolderKind = ServiceLocatorInstanceHolderKind;
|
|
950
1481
|
exports.ServiceLocatorInstanceHolderStatus = ServiceLocatorInstanceHolderStatus;
|
|
951
1482
|
exports.ServiceLocatorManager = ServiceLocatorManager;
|
|
952
1483
|
exports.UnknownError = UnknownError;
|
|
953
|
-
exports.
|
|
1484
|
+
exports.asyncInject = asyncInject;
|
|
1485
|
+
exports.createDeferred = createDeferred;
|
|
1486
|
+
exports.createRequestContextHolder = createRequestContextHolder;
|
|
1487
|
+
exports.defaultInjectors = defaultInjectors;
|
|
954
1488
|
exports.getInjectableToken = getInjectableToken;
|
|
955
1489
|
exports.getInjectors = getInjectors;
|
|
956
1490
|
exports.globalRegistry = globalRegistry;
|
|
957
1491
|
exports.inject = inject;
|
|
958
|
-
exports.
|
|
959
|
-
exports.provideServiceLocator = provideServiceLocator;
|
|
960
|
-
exports.resolveService = resolveService;
|
|
961
|
-
exports.syncInject = syncInject;
|
|
1492
|
+
exports.provideFactoryContext = provideFactoryContext;
|
|
962
1493
|
exports.wrapSyncInit = wrapSyncInit;
|
|
963
1494
|
//# sourceMappingURL=index.js.map
|
|
964
1495
|
//# sourceMappingURL=index.js.map
|