@navios/di 0.1.8 → 0.1.10
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/docs/README.md +81 -0
- package/lib/index.d.mts +433 -0
- package/lib/index.d.mts.map +1 -0
- package/lib/index.d.ts +433 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +791 -0
- package/lib/index.mjs +736 -0
- package/lib/index.mjs.map +1 -0
- package/package.json +9 -18
- package/project.json +50 -0
- package/src/__type-tests__/inject.spec-d.mts +99 -0
- package/src/decorators/injectable.decorator.mts +1 -3
- package/src/injection-token.mts +13 -4
- package/src/resolve-service.mts +3 -5
- package/src/service-locator.mts +1 -1
- package/src/utils/get-injectors.mts +57 -11
- package/tsconfig.json +8 -0
- package/tsdown.config.mts +10 -0
- package/vitest.config.mts +9 -0
- package/dist/_tsup-dts-rollup.d.mts +0 -532
- package/dist/_tsup-dts-rollup.d.ts +0 -532
- package/dist/index.d.mts +0 -61
- package/dist/index.d.ts +0 -61
- package/dist/index.js +0 -1026
- package/dist/index.mjs +0 -967
package/lib/index.mjs
ADDED
|
@@ -0,0 +1,736 @@
|
|
|
1
|
+
import { ZodOptional, ZodRecord, z } from "zod";
|
|
2
|
+
|
|
3
|
+
//#region src/enums/injectable-scope.enum.mts
|
|
4
|
+
let InjectableScope = /* @__PURE__ */ function(InjectableScope$1) {
|
|
5
|
+
/**
|
|
6
|
+
* Singleton scope: The instance is created once and shared across the application.
|
|
7
|
+
*/
|
|
8
|
+
InjectableScope$1["Singleton"] = "Singleton";
|
|
9
|
+
/**
|
|
10
|
+
* Instance scope: A new instance is created for each injection.
|
|
11
|
+
*/
|
|
12
|
+
InjectableScope$1["Instance"] = "Instance";
|
|
13
|
+
return InjectableScope$1;
|
|
14
|
+
}({});
|
|
15
|
+
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/enums/injectable-type.enum.mts
|
|
18
|
+
let InjectableType = /* @__PURE__ */ function(InjectableType$1) {
|
|
19
|
+
InjectableType$1["Class"] = "Class";
|
|
20
|
+
InjectableType$1["Factory"] = "Factory";
|
|
21
|
+
return InjectableType$1;
|
|
22
|
+
}({});
|
|
23
|
+
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/injection-token.mts
|
|
26
|
+
var InjectionToken = class InjectionToken {
|
|
27
|
+
id = globalThis.crypto.randomUUID();
|
|
28
|
+
formattedName = null;
|
|
29
|
+
constructor(name, schema) {
|
|
30
|
+
this.name = name;
|
|
31
|
+
this.schema = schema;
|
|
32
|
+
}
|
|
33
|
+
static create(name, schema) {
|
|
34
|
+
return new InjectionToken(name, schema);
|
|
35
|
+
}
|
|
36
|
+
static bound(token, value) {
|
|
37
|
+
return new BoundInjectionToken(token, value);
|
|
38
|
+
}
|
|
39
|
+
static factory(token, factory) {
|
|
40
|
+
return new FactoryInjectionToken(token, factory);
|
|
41
|
+
}
|
|
42
|
+
static refineType(token) {
|
|
43
|
+
return token;
|
|
44
|
+
}
|
|
45
|
+
toString() {
|
|
46
|
+
if (this.formattedName) return this.formattedName;
|
|
47
|
+
const { name } = this;
|
|
48
|
+
if (typeof name === "function") {
|
|
49
|
+
const className = name.name;
|
|
50
|
+
this.formattedName = `${className}(${this.id})`;
|
|
51
|
+
} else if (typeof name === "symbol") this.formattedName = `${name.toString()}(${this.id})`;
|
|
52
|
+
else this.formattedName = `${name}(${this.id})`;
|
|
53
|
+
return this.formattedName;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
var BoundInjectionToken = class {
|
|
57
|
+
id;
|
|
58
|
+
name;
|
|
59
|
+
schema;
|
|
60
|
+
constructor(token, value) {
|
|
61
|
+
this.token = token;
|
|
62
|
+
this.value = value;
|
|
63
|
+
this.name = token.name;
|
|
64
|
+
this.id = token.id;
|
|
65
|
+
this.schema = token.schema;
|
|
66
|
+
}
|
|
67
|
+
toString() {
|
|
68
|
+
return this.token.toString();
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
var FactoryInjectionToken = class {
|
|
72
|
+
value;
|
|
73
|
+
resolved = false;
|
|
74
|
+
id;
|
|
75
|
+
name;
|
|
76
|
+
schema;
|
|
77
|
+
constructor(token, factory) {
|
|
78
|
+
this.token = token;
|
|
79
|
+
this.factory = factory;
|
|
80
|
+
this.name = token.name;
|
|
81
|
+
this.id = token.id;
|
|
82
|
+
this.schema = token.schema;
|
|
83
|
+
}
|
|
84
|
+
async resolve() {
|
|
85
|
+
if (!this.value) {
|
|
86
|
+
this.value = await this.factory();
|
|
87
|
+
this.resolved = true;
|
|
88
|
+
}
|
|
89
|
+
return this.value;
|
|
90
|
+
}
|
|
91
|
+
toString() {
|
|
92
|
+
return this.token.toString();
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region src/registry.mts
|
|
98
|
+
var Registry = class {
|
|
99
|
+
factories = new Map();
|
|
100
|
+
constructor(parent) {
|
|
101
|
+
this.parent = parent;
|
|
102
|
+
}
|
|
103
|
+
has(token) {
|
|
104
|
+
if (this.factories.has(token.id)) return true;
|
|
105
|
+
if (this.parent) return this.parent.has(token);
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
get(token) {
|
|
109
|
+
const factory = this.factories.get(token.id);
|
|
110
|
+
if (!factory) {
|
|
111
|
+
if (this.parent) return this.parent.get(token);
|
|
112
|
+
throw new Error(`[Registry] No factory found for ${token.toString()}`);
|
|
113
|
+
}
|
|
114
|
+
return factory;
|
|
115
|
+
}
|
|
116
|
+
set(token, factory, scope) {
|
|
117
|
+
this.factories.set(token.id, {
|
|
118
|
+
factory,
|
|
119
|
+
scope,
|
|
120
|
+
originalToken: token
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
delete(token) {
|
|
124
|
+
this.factories.delete(token.id);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
const globalRegistry = new Registry();
|
|
128
|
+
|
|
129
|
+
//#endregion
|
|
130
|
+
//#region src/proxy-service-locator.mts
|
|
131
|
+
var ProxyServiceLocator = class {
|
|
132
|
+
constructor(serviceLocator, ctx) {
|
|
133
|
+
this.serviceLocator = serviceLocator;
|
|
134
|
+
this.ctx = ctx;
|
|
135
|
+
}
|
|
136
|
+
getEventBus() {
|
|
137
|
+
return this.serviceLocator.getEventBus();
|
|
138
|
+
}
|
|
139
|
+
getInstance(token, args) {
|
|
140
|
+
return this.ctx.inject(token, args).then((instance) => {
|
|
141
|
+
return [void 0, instance];
|
|
142
|
+
}, (error) => {
|
|
143
|
+
return [error];
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
getOrThrowInstance(token, args) {
|
|
147
|
+
return this.ctx.inject(token, args);
|
|
148
|
+
}
|
|
149
|
+
getSyncInstance(token, args) {
|
|
150
|
+
return this.serviceLocator.getSyncInstance(token, args);
|
|
151
|
+
}
|
|
152
|
+
invalidate(service, round) {
|
|
153
|
+
return this.serviceLocator.invalidate(service, round);
|
|
154
|
+
}
|
|
155
|
+
ready() {
|
|
156
|
+
return this.serviceLocator.ready();
|
|
157
|
+
}
|
|
158
|
+
makeInstanceName(token, args) {
|
|
159
|
+
return this.serviceLocator.makeInstanceName(token, args);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
function makeProxyServiceLocator(serviceLocator, ctx) {
|
|
163
|
+
return new ProxyServiceLocator(serviceLocator, ctx);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
//#endregion
|
|
167
|
+
//#region src/symbols/injectable-token.mts
|
|
168
|
+
const InjectableTokenMeta = Symbol.for("InjectableTokenMeta");
|
|
169
|
+
|
|
170
|
+
//#endregion
|
|
171
|
+
//#region src/utils/get-injectors.mts
|
|
172
|
+
const InjectorsBase = new Map();
|
|
173
|
+
function getInjectors({ baseLocator }) {
|
|
174
|
+
if (InjectorsBase.has(baseLocator)) return InjectorsBase.get(baseLocator);
|
|
175
|
+
let currentLocator = baseLocator;
|
|
176
|
+
function getServiceLocator() {
|
|
177
|
+
if (!currentLocator) throw new Error("[Injector] Service locator is not initialized. Please provide the service locator before using the @Injectable decorator.");
|
|
178
|
+
return currentLocator;
|
|
179
|
+
}
|
|
180
|
+
function provideServiceLocator$1(locator) {
|
|
181
|
+
const original = currentLocator;
|
|
182
|
+
currentLocator = locator;
|
|
183
|
+
return original;
|
|
184
|
+
}
|
|
185
|
+
function inject$1(token, args) {
|
|
186
|
+
const realToken = token[InjectableTokenMeta] ?? token;
|
|
187
|
+
return getServiceLocator().getOrThrowInstance(realToken, args);
|
|
188
|
+
}
|
|
189
|
+
let promiseCollector = null;
|
|
190
|
+
function wrapSyncInit$1(cb) {
|
|
191
|
+
return () => {
|
|
192
|
+
const promises = [];
|
|
193
|
+
const originalPromiseCollector = promiseCollector;
|
|
194
|
+
promiseCollector = (promise) => {
|
|
195
|
+
promises.push(promise);
|
|
196
|
+
};
|
|
197
|
+
const result = cb();
|
|
198
|
+
promiseCollector = originalPromiseCollector;
|
|
199
|
+
return [result, promises];
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function syncInject$1(token, args) {
|
|
203
|
+
const realToken = token[InjectableTokenMeta] ?? token;
|
|
204
|
+
const instance = getServiceLocator().getSyncInstance(realToken, args);
|
|
205
|
+
if (!instance) if (promiseCollector) {
|
|
206
|
+
const promise = getServiceLocator().getInstance(realToken, args);
|
|
207
|
+
promiseCollector(promise);
|
|
208
|
+
} else throw new Error(`[Injector] Cannot initiate ${realToken.toString()}`);
|
|
209
|
+
return instance;
|
|
210
|
+
}
|
|
211
|
+
const injectors = {
|
|
212
|
+
inject: inject$1,
|
|
213
|
+
syncInject: syncInject$1,
|
|
214
|
+
wrapSyncInit: wrapSyncInit$1,
|
|
215
|
+
provideServiceLocator: provideServiceLocator$1
|
|
216
|
+
};
|
|
217
|
+
InjectorsBase.set(baseLocator, injectors);
|
|
218
|
+
return injectors;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
//#endregion
|
|
222
|
+
//#region src/utils/get-injectable-token.mts
|
|
223
|
+
function getInjectableToken(target) {
|
|
224
|
+
const token = target[InjectableTokenMeta];
|
|
225
|
+
if (!token) throw new Error(`[ServiceLocator] Class ${target.name} is not decorated with @Injectable.`);
|
|
226
|
+
return token;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
//#endregion
|
|
230
|
+
//#region src/resolve-service.mts
|
|
231
|
+
async function resolveService(ctx, target, args = []) {
|
|
232
|
+
const proxyServiceLocator = makeProxyServiceLocator(ctx.locator, ctx);
|
|
233
|
+
const { wrapSyncInit: wrapSyncInit$1, provideServiceLocator: provideServiceLocator$1 } = getInjectors({ baseLocator: ctx.locator });
|
|
234
|
+
const tryLoad = wrapSyncInit$1(() => {
|
|
235
|
+
const original = provideServiceLocator$1(proxyServiceLocator);
|
|
236
|
+
let result = new target(...args);
|
|
237
|
+
provideServiceLocator$1(original);
|
|
238
|
+
return result;
|
|
239
|
+
});
|
|
240
|
+
let [instance, promises] = tryLoad();
|
|
241
|
+
if (promises.length > 0) {
|
|
242
|
+
await Promise.all(promises);
|
|
243
|
+
const newRes = tryLoad();
|
|
244
|
+
instance = newRes[0];
|
|
245
|
+
promises = newRes[1];
|
|
246
|
+
}
|
|
247
|
+
if (promises.length > 0) {
|
|
248
|
+
console.error(`[ServiceLocator] ${target.name} has problem with it's definition.
|
|
249
|
+
|
|
250
|
+
One or more of the dependencies are registered as a InjectableScope.Instance and are used with syncInject.
|
|
251
|
+
|
|
252
|
+
Please use inject instead of syncInject to load those dependencies.`);
|
|
253
|
+
throw new Error(`[ServiceLocator] Service ${target.name} cannot be instantiated.`);
|
|
254
|
+
}
|
|
255
|
+
return instance;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
//#endregion
|
|
259
|
+
//#region src/decorators/injectable.decorator.mts
|
|
260
|
+
function Injectable({ scope = InjectableScope.Singleton, type = InjectableType.Class, token, registry = globalRegistry } = {}) {
|
|
261
|
+
return (target, context) => {
|
|
262
|
+
if (context.kind !== "class") throw new Error("[ServiceLocator] @Injectable decorator can only be used on classes.");
|
|
263
|
+
let injectableToken = token ?? InjectionToken.create(target);
|
|
264
|
+
if (type === InjectableType.Class) registry.set(injectableToken, async (ctx, args) => resolveService(ctx, target, [args]), scope);
|
|
265
|
+
else if (type === InjectableType.Factory) registry.set(injectableToken, async (ctx, args) => {
|
|
266
|
+
const builder = await resolveService(ctx, target);
|
|
267
|
+
if (typeof builder.create !== "function") throw new Error(`[ServiceLocator] Factory ${target.name} does not implement the create method.`);
|
|
268
|
+
return builder.create(ctx, args);
|
|
269
|
+
}, scope);
|
|
270
|
+
target[InjectableTokenMeta] = injectableToken;
|
|
271
|
+
return target;
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
//#endregion
|
|
276
|
+
//#region src/errors/errors.enum.mts
|
|
277
|
+
let ErrorsEnum = /* @__PURE__ */ function(ErrorsEnum$1) {
|
|
278
|
+
ErrorsEnum$1["InstanceExpired"] = "InstanceExpired";
|
|
279
|
+
ErrorsEnum$1["InstanceNotFound"] = "InstanceNotFound";
|
|
280
|
+
ErrorsEnum$1["InstanceDestroying"] = "InstanceDestroying";
|
|
281
|
+
ErrorsEnum$1["UnknownError"] = "UnknownError";
|
|
282
|
+
ErrorsEnum$1["FactoryNotFound"] = "FactoryNotFound";
|
|
283
|
+
ErrorsEnum$1["FactoryTokenNotResolved"] = "FactoryTokenNotResolved";
|
|
284
|
+
return ErrorsEnum$1;
|
|
285
|
+
}({});
|
|
286
|
+
|
|
287
|
+
//#endregion
|
|
288
|
+
//#region src/errors/factory-not-found.mts
|
|
289
|
+
var FactoryNotFound = class extends Error {
|
|
290
|
+
code = ErrorsEnum.FactoryNotFound;
|
|
291
|
+
constructor(name) {
|
|
292
|
+
super(`Factory ${name} not found`);
|
|
293
|
+
this.name = name;
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
//#endregion
|
|
298
|
+
//#region src/errors/factory-token-not-resolved.mts
|
|
299
|
+
var FactoryTokenNotResolved = class extends Error {
|
|
300
|
+
code = ErrorsEnum.FactoryTokenNotResolved;
|
|
301
|
+
constructor(name) {
|
|
302
|
+
super(`Factory token not resolved: ${name.toString()}`);
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
//#endregion
|
|
307
|
+
//#region src/errors/instance-destroying.mts
|
|
308
|
+
var InstanceDestroying = class extends Error {
|
|
309
|
+
code = ErrorsEnum.InstanceDestroying;
|
|
310
|
+
constructor(name) {
|
|
311
|
+
super(`Instance ${name} destroying`);
|
|
312
|
+
this.name = name;
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
//#endregion
|
|
317
|
+
//#region src/errors/instance-expired.mts
|
|
318
|
+
var InstanceExpired = class extends Error {
|
|
319
|
+
code = ErrorsEnum.InstanceExpired;
|
|
320
|
+
constructor(name) {
|
|
321
|
+
super(`Instance ${name} expired`);
|
|
322
|
+
this.name = name;
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
//#endregion
|
|
327
|
+
//#region src/errors/instance-not-found.mts
|
|
328
|
+
var InstanceNotFound = class extends Error {
|
|
329
|
+
code = ErrorsEnum.InstanceNotFound;
|
|
330
|
+
constructor(name) {
|
|
331
|
+
super(`Instance ${name} not found`);
|
|
332
|
+
this.name = name;
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
//#endregion
|
|
337
|
+
//#region src/errors/unknown-error.mts
|
|
338
|
+
var UnknownError = class extends Error {
|
|
339
|
+
code = ErrorsEnum.UnknownError;
|
|
340
|
+
parent;
|
|
341
|
+
constructor(message) {
|
|
342
|
+
if (message instanceof Error) {
|
|
343
|
+
super(message.message);
|
|
344
|
+
this.parent = message;
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
super(message);
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
//#endregion
|
|
352
|
+
//#region src/event-emitter.mts
|
|
353
|
+
var EventEmitter = class {
|
|
354
|
+
listeners = new Map();
|
|
355
|
+
on(event, listener) {
|
|
356
|
+
if (!this.listeners.has(event)) this.listeners.set(event, new Set());
|
|
357
|
+
this.listeners.get(event).add(listener);
|
|
358
|
+
return () => {
|
|
359
|
+
this.off(event, listener);
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
off(event, listener) {
|
|
363
|
+
if (!this.listeners.has(event)) return;
|
|
364
|
+
this.listeners.get(event).delete(listener);
|
|
365
|
+
if (this.listeners.get(event).size === 0) this.listeners.delete(event);
|
|
366
|
+
}
|
|
367
|
+
once(event, listener) {
|
|
368
|
+
const off = this.on(event, (...args) => {
|
|
369
|
+
off();
|
|
370
|
+
listener(...args);
|
|
371
|
+
});
|
|
372
|
+
return off;
|
|
373
|
+
}
|
|
374
|
+
async emit(event, ...args) {
|
|
375
|
+
if (!this.listeners.has(event)) return;
|
|
376
|
+
return Promise.all(Array.from(this.listeners.get(event)).map((listener) => listener(...args)));
|
|
377
|
+
}
|
|
378
|
+
addChannel(ns, event, target) {
|
|
379
|
+
return this.on(event, (...args) => target.emit(ns, event, ...args));
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
//#endregion
|
|
384
|
+
//#region src/service-locator-event-bus.mts
|
|
385
|
+
var ServiceLocatorEventBus = class {
|
|
386
|
+
listeners = new Map();
|
|
387
|
+
constructor(logger = null) {
|
|
388
|
+
this.logger = logger;
|
|
389
|
+
}
|
|
390
|
+
on(ns, event, listener) {
|
|
391
|
+
this.logger?.debug(`[ServiceLocatorEventBus]#on(): ns:${ns} event:${event}`);
|
|
392
|
+
if (!this.listeners.has(ns)) this.listeners.set(ns, new Map());
|
|
393
|
+
const nsEvents = this.listeners.get(ns);
|
|
394
|
+
if (!nsEvents.has(event)) nsEvents.set(event, new Set());
|
|
395
|
+
nsEvents.get(event).add(listener);
|
|
396
|
+
return () => {
|
|
397
|
+
nsEvents.get(event).delete(listener);
|
|
398
|
+
if (nsEvents.get(event)?.size === 0) nsEvents.delete(event);
|
|
399
|
+
if (nsEvents.size === 0) this.listeners.delete(ns);
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
async emit(key, event) {
|
|
403
|
+
if (!this.listeners.has(key)) return;
|
|
404
|
+
const events = this.listeners.get(key);
|
|
405
|
+
const preEvent = `pre:${event}`;
|
|
406
|
+
const postEvent = `post:${event}`;
|
|
407
|
+
this.logger?.debug(`[ServiceLocatorEventBus]#emit(): ${key}:${preEvent}`);
|
|
408
|
+
await Promise.allSettled([...events.get(preEvent) ?? []].map((listener) => listener(preEvent))).then((results) => {
|
|
409
|
+
results.filter((result) => result.status === "rejected").forEach((result) => {
|
|
410
|
+
this.logger?.warn(`[ServiceLocatorEventBus]#emit(): ${key}:${preEvent} rejected with`, result.reason);
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
this.logger?.debug(`[ServiceLocatorEventBus]#emit(): ${key}:${event}`);
|
|
414
|
+
const res = await Promise.allSettled([...events.get(event) ?? []].map((listener) => listener(event))).then((results) => {
|
|
415
|
+
const res$1 = results.filter((result) => result.status === "rejected").map((result) => {
|
|
416
|
+
this.logger?.warn(`[ServiceLocatorEventBus]#emit(): ${key}:${event} rejected with`, result.reason);
|
|
417
|
+
return result;
|
|
418
|
+
});
|
|
419
|
+
if (res$1.length > 0) return Promise.reject(res$1);
|
|
420
|
+
return results;
|
|
421
|
+
});
|
|
422
|
+
this.logger?.debug(`[ServiceLocatorEventBus]#emit(): ${key}:${postEvent}`);
|
|
423
|
+
await Promise.allSettled([...events.get(postEvent) ?? []].map((listener) => listener(postEvent))).then((results) => {
|
|
424
|
+
results.filter((result) => result.status === "rejected").forEach((result) => {
|
|
425
|
+
this.logger?.warn(`[ServiceLocatorEventBus]#emit(): ${key}:${postEvent} rejected with`, result.reason);
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
return res;
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
//#endregion
|
|
433
|
+
//#region src/service-locator-instance-holder.mts
|
|
434
|
+
let ServiceLocatorInstanceHolderKind = /* @__PURE__ */ function(ServiceLocatorInstanceHolderKind$1) {
|
|
435
|
+
ServiceLocatorInstanceHolderKind$1["Instance"] = "instance";
|
|
436
|
+
ServiceLocatorInstanceHolderKind$1["Factory"] = "factory";
|
|
437
|
+
ServiceLocatorInstanceHolderKind$1["AbstractFactory"] = "abstractFactory";
|
|
438
|
+
return ServiceLocatorInstanceHolderKind$1;
|
|
439
|
+
}({});
|
|
440
|
+
let ServiceLocatorInstanceHolderStatus = /* @__PURE__ */ function(ServiceLocatorInstanceHolderStatus$1) {
|
|
441
|
+
ServiceLocatorInstanceHolderStatus$1["Created"] = "created";
|
|
442
|
+
ServiceLocatorInstanceHolderStatus$1["Creating"] = "creating";
|
|
443
|
+
ServiceLocatorInstanceHolderStatus$1["Destroying"] = "destroying";
|
|
444
|
+
return ServiceLocatorInstanceHolderStatus$1;
|
|
445
|
+
}({});
|
|
446
|
+
|
|
447
|
+
//#endregion
|
|
448
|
+
//#region src/service-locator-manager.mts
|
|
449
|
+
var ServiceLocatorManager = class {
|
|
450
|
+
instancesHolders = new Map();
|
|
451
|
+
constructor(logger = null) {
|
|
452
|
+
this.logger = logger;
|
|
453
|
+
}
|
|
454
|
+
get(name) {
|
|
455
|
+
const holder = this.instancesHolders.get(name);
|
|
456
|
+
if (holder) {
|
|
457
|
+
if (holder.ttl !== Infinity) {
|
|
458
|
+
const now = Date.now();
|
|
459
|
+
if (now - holder.createdAt > holder.ttl) {
|
|
460
|
+
this.logger?.log(`[ServiceLocatorManager]#getInstanceHolder() TTL expired for ${holder.name}`);
|
|
461
|
+
return [new InstanceExpired(holder.name), holder];
|
|
462
|
+
}
|
|
463
|
+
} else if (holder.status === ServiceLocatorInstanceHolderStatus.Destroying) {
|
|
464
|
+
this.logger?.log(`[ServiceLocatorManager]#getInstanceHolder() Instance ${holder.name} is destroying`);
|
|
465
|
+
return [new InstanceDestroying(holder.name), holder];
|
|
466
|
+
}
|
|
467
|
+
return [void 0, holder];
|
|
468
|
+
} else {
|
|
469
|
+
this.logger?.log(`[ServiceLocatorManager]#getInstanceHolder() Instance ${name} not found`);
|
|
470
|
+
return [new InstanceNotFound(name)];
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
set(name, holder) {
|
|
474
|
+
this.instancesHolders.set(name, holder);
|
|
475
|
+
}
|
|
476
|
+
has(name) {
|
|
477
|
+
const [error, holder] = this.get(name);
|
|
478
|
+
if (!error) return [void 0, true];
|
|
479
|
+
if ([ErrorsEnum.InstanceExpired, ErrorsEnum.InstanceDestroying].includes(error.code)) return [error];
|
|
480
|
+
return [void 0, !!holder];
|
|
481
|
+
}
|
|
482
|
+
delete(name) {
|
|
483
|
+
return this.instancesHolders.delete(name);
|
|
484
|
+
}
|
|
485
|
+
filter(predicate) {
|
|
486
|
+
return new Map([...this.instancesHolders].filter(([key, value]) => predicate(value, key)));
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
//#endregion
|
|
491
|
+
//#region src/service-locator.mts
|
|
492
|
+
var ServiceLocator = class {
|
|
493
|
+
eventBus;
|
|
494
|
+
manager;
|
|
495
|
+
constructor(registry = globalRegistry, logger = null) {
|
|
496
|
+
this.registry = registry;
|
|
497
|
+
this.logger = logger;
|
|
498
|
+
this.eventBus = new ServiceLocatorEventBus(logger);
|
|
499
|
+
this.manager = new ServiceLocatorManager(logger);
|
|
500
|
+
}
|
|
501
|
+
getEventBus() {
|
|
502
|
+
return this.eventBus;
|
|
503
|
+
}
|
|
504
|
+
storeInstance(instance, token, args) {
|
|
505
|
+
const instanceName = this.getInstanceIdentifier(token, args);
|
|
506
|
+
this.manager.set(instanceName, {
|
|
507
|
+
name: instanceName,
|
|
508
|
+
instance,
|
|
509
|
+
status: ServiceLocatorInstanceHolderStatus.Created,
|
|
510
|
+
kind: ServiceLocatorInstanceHolderKind.Instance,
|
|
511
|
+
createdAt: Date.now(),
|
|
512
|
+
ttl: Infinity,
|
|
513
|
+
deps: [],
|
|
514
|
+
destroyListeners: [],
|
|
515
|
+
effects: [],
|
|
516
|
+
destroyPromise: null,
|
|
517
|
+
creationPromise: null
|
|
518
|
+
});
|
|
519
|
+
this.notifyListeners(instanceName);
|
|
520
|
+
}
|
|
521
|
+
removeInstance(token, args) {
|
|
522
|
+
const instanceName = this.getInstanceIdentifier(token, args);
|
|
523
|
+
return this.invalidate(instanceName);
|
|
524
|
+
}
|
|
525
|
+
resolveTokenArgs(token, args) {
|
|
526
|
+
let realArgs = args;
|
|
527
|
+
if (token instanceof BoundInjectionToken) realArgs = token.value;
|
|
528
|
+
else if (token instanceof FactoryInjectionToken) if (token.resolved) realArgs = token.value;
|
|
529
|
+
else return [new FactoryTokenNotResolved(token.name)];
|
|
530
|
+
if (!token.schema) return [void 0, realArgs];
|
|
531
|
+
const validatedArgs = token.schema?.safeParse(realArgs);
|
|
532
|
+
if (validatedArgs && !validatedArgs.success) {
|
|
533
|
+
this.logger?.error(`[ServiceLocator]#getInstance(): Error validating args for ${token.name.toString()}`, validatedArgs.error);
|
|
534
|
+
return [new UnknownError(validatedArgs.error)];
|
|
535
|
+
}
|
|
536
|
+
return [void 0, validatedArgs?.data];
|
|
537
|
+
}
|
|
538
|
+
getInstanceIdentifier(token, args) {
|
|
539
|
+
const [err, realArgs] = this.resolveTokenArgs(token, args);
|
|
540
|
+
if (err) throw err;
|
|
541
|
+
return this.makeInstanceName(token, realArgs);
|
|
542
|
+
}
|
|
543
|
+
async getInstance(token, args) {
|
|
544
|
+
const [err, realArgs] = this.resolveTokenArgs(token, args);
|
|
545
|
+
if (err instanceof UnknownError) return [err];
|
|
546
|
+
else if (err instanceof FactoryTokenNotResolved && token instanceof FactoryInjectionToken) {
|
|
547
|
+
await token.resolve();
|
|
548
|
+
return this.getInstance(token, args);
|
|
549
|
+
}
|
|
550
|
+
const instanceName = this.makeInstanceName(token, realArgs);
|
|
551
|
+
const [error, holder] = this.manager.get(instanceName);
|
|
552
|
+
if (!error) {
|
|
553
|
+
if (holder.status === ServiceLocatorInstanceHolderStatus.Creating) return holder.creationPromise;
|
|
554
|
+
else if (holder.status === ServiceLocatorInstanceHolderStatus.Destroying) return [new UnknownError(ErrorsEnum.InstanceDestroying)];
|
|
555
|
+
return [void 0, holder.instance];
|
|
556
|
+
}
|
|
557
|
+
switch (error.code) {
|
|
558
|
+
case ErrorsEnum.InstanceDestroying:
|
|
559
|
+
this.logger?.log(`[ServiceLocator]#getInstance() TTL expired for ${holder?.name}`);
|
|
560
|
+
await holder?.destroyPromise;
|
|
561
|
+
return this.getInstance(token, args);
|
|
562
|
+
case ErrorsEnum.InstanceExpired:
|
|
563
|
+
this.logger?.log(`[ServiceLocator]#getInstance() TTL expired for ${holder?.name}`);
|
|
564
|
+
await this.invalidate(instanceName);
|
|
565
|
+
return this.getInstance(token, args);
|
|
566
|
+
case ErrorsEnum.InstanceNotFound: break;
|
|
567
|
+
default: return [error];
|
|
568
|
+
}
|
|
569
|
+
return this.createInstance(instanceName, token, realArgs);
|
|
570
|
+
}
|
|
571
|
+
async getOrThrowInstance(token, args) {
|
|
572
|
+
const [error, instance] = await this.getInstance(token, args);
|
|
573
|
+
if (error) throw error;
|
|
574
|
+
return instance;
|
|
575
|
+
}
|
|
576
|
+
notifyListeners(name, event = "create") {
|
|
577
|
+
this.logger?.log(`[ServiceLocator]#notifyListeners() Notifying listeners for ${name} with event ${event}`);
|
|
578
|
+
return this.eventBus.emit(name, event);
|
|
579
|
+
}
|
|
580
|
+
async createInstance(instanceName, token, args) {
|
|
581
|
+
this.logger?.log(`[ServiceLocator]#createInstance() Creating instance for ${instanceName}`);
|
|
582
|
+
let realToken = token instanceof BoundInjectionToken || token instanceof FactoryInjectionToken ? token.token : token;
|
|
583
|
+
if (this.registry.has(realToken)) return this.resolveInstance(instanceName, realToken, args);
|
|
584
|
+
else return [new FactoryNotFound(realToken.name.toString())];
|
|
585
|
+
}
|
|
586
|
+
async resolveInstance(instanceName, token, args) {
|
|
587
|
+
this.logger?.log(`[ServiceLocator]#resolveInstance(): Creating instance for ${instanceName} from abstract factory`);
|
|
588
|
+
const ctx = this.createFactoryContext(instanceName);
|
|
589
|
+
let { factory, scope } = this.registry.get(token);
|
|
590
|
+
const holder = {
|
|
591
|
+
name: instanceName,
|
|
592
|
+
instance: null,
|
|
593
|
+
status: ServiceLocatorInstanceHolderStatus.Creating,
|
|
594
|
+
kind: ServiceLocatorInstanceHolderKind.AbstractFactory,
|
|
595
|
+
creationPromise: factory(ctx, args).then(async (instance) => {
|
|
596
|
+
holder.instance = instance;
|
|
597
|
+
holder.status = ServiceLocatorInstanceHolderStatus.Created;
|
|
598
|
+
holder.deps = ctx.getDependencies();
|
|
599
|
+
holder.destroyListeners = ctx.getDestroyListeners();
|
|
600
|
+
holder.ttl = ctx.getTtl();
|
|
601
|
+
if (holder.deps.length > 0) {
|
|
602
|
+
this.logger?.log(`[ServiceLocator]#createInstanceFromAbstractFactory(): Adding subscriptions for ${instanceName} dependencies for their invalidations: ${holder.deps.join(", ")}`);
|
|
603
|
+
holder.deps.forEach((dependency) => {
|
|
604
|
+
holder.destroyListeners.push(this.eventBus.on(dependency, "destroy", () => this.invalidate(instanceName)));
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
if (holder.ttl === 0 || scope === InjectableScope.Instance) await this.invalidate(instanceName);
|
|
608
|
+
await this.notifyListeners(instanceName);
|
|
609
|
+
return [void 0, instance];
|
|
610
|
+
}).catch((error) => {
|
|
611
|
+
this.logger?.error(`[ServiceLocator]#createInstanceFromAbstractFactory(): Error creating instance for ${instanceName}`, error);
|
|
612
|
+
return [new UnknownError(error)];
|
|
613
|
+
}),
|
|
614
|
+
effects: [],
|
|
615
|
+
deps: [],
|
|
616
|
+
destroyListeners: [],
|
|
617
|
+
createdAt: Date.now(),
|
|
618
|
+
ttl: Infinity
|
|
619
|
+
};
|
|
620
|
+
if (scope === InjectableScope.Singleton) this.manager.set(instanceName, holder);
|
|
621
|
+
return holder.creationPromise;
|
|
622
|
+
}
|
|
623
|
+
createFactoryContext(instanceName) {
|
|
624
|
+
const dependencies = new Set();
|
|
625
|
+
const destroyListeners = new Set();
|
|
626
|
+
const self = this;
|
|
627
|
+
function invalidate(name = instanceName) {
|
|
628
|
+
return self.invalidate(name);
|
|
629
|
+
}
|
|
630
|
+
function addEffect(listener) {
|
|
631
|
+
destroyListeners.add(listener);
|
|
632
|
+
}
|
|
633
|
+
let ttl = Infinity;
|
|
634
|
+
function setTtl(value) {
|
|
635
|
+
ttl = value;
|
|
636
|
+
}
|
|
637
|
+
function getTtl() {
|
|
638
|
+
return ttl;
|
|
639
|
+
}
|
|
640
|
+
function on(key, event, listener) {
|
|
641
|
+
destroyListeners.add(self.eventBus.on(key, event, listener));
|
|
642
|
+
}
|
|
643
|
+
return {
|
|
644
|
+
async inject(token, args) {
|
|
645
|
+
let injectionToken = token;
|
|
646
|
+
if (typeof token === "function") injectionToken = getInjectableToken(token);
|
|
647
|
+
if (injectionToken instanceof InjectionToken) {
|
|
648
|
+
const validatedArgs = token.schema ? token.schema.safeParse(args) : void 0;
|
|
649
|
+
const instanceName$1 = self.makeInstanceName(token, validatedArgs);
|
|
650
|
+
dependencies.add(instanceName$1);
|
|
651
|
+
return self.getOrThrowInstance(injectionToken, args);
|
|
652
|
+
}
|
|
653
|
+
throw new Error(`[ServiceLocator]#inject(): Invalid token type: ${typeof token}. Expected a class or an InjectionToken.`);
|
|
654
|
+
},
|
|
655
|
+
invalidate,
|
|
656
|
+
on,
|
|
657
|
+
getDependencies: () => Array.from(dependencies),
|
|
658
|
+
addEffect,
|
|
659
|
+
getDestroyListeners: () => Array.from(destroyListeners),
|
|
660
|
+
setTtl,
|
|
661
|
+
getTtl,
|
|
662
|
+
locator: self
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
getSyncInstance(token, args) {
|
|
666
|
+
const [err, realArgs] = this.resolveTokenArgs(token, args);
|
|
667
|
+
if (err) return null;
|
|
668
|
+
const instanceName = this.makeInstanceName(token, realArgs);
|
|
669
|
+
const [error, holder] = this.manager.get(instanceName);
|
|
670
|
+
if (error) return null;
|
|
671
|
+
return holder.instance;
|
|
672
|
+
}
|
|
673
|
+
invalidate(service, round = 1) {
|
|
674
|
+
this.logger?.log(`[ServiceLocator]#invalidate(): Starting Invalidating process of ${service}`);
|
|
675
|
+
const toInvalidate = this.manager.filter((holder) => holder.name === service || holder.deps.includes(service));
|
|
676
|
+
const promises = [];
|
|
677
|
+
for (const [key, holder] of toInvalidate.entries()) {
|
|
678
|
+
if (holder.status === ServiceLocatorInstanceHolderStatus.Destroying) {
|
|
679
|
+
this.logger?.trace(`[ServiceLocator]#invalidate(): ${key} is already being destroyed`);
|
|
680
|
+
promises.push(holder.destroyPromise);
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
if (holder.status === ServiceLocatorInstanceHolderStatus.Creating) {
|
|
684
|
+
this.logger?.trace(`[ServiceLocator]#invalidate(): ${key} is being created, waiting for creation to finish`);
|
|
685
|
+
promises.push(holder.creationPromise?.then(() => {
|
|
686
|
+
if (round > 3) {
|
|
687
|
+
this.logger?.error(`[ServiceLocator]#invalidate(): ${key} creation is triggering a new invalidation round, but it is still not created`);
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
return this.invalidate(key, round + 1);
|
|
691
|
+
}));
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
holder.status = ServiceLocatorInstanceHolderStatus.Destroying;
|
|
695
|
+
this.logger?.log(`[ServiceLocator]#invalidate(): Invalidating ${key} and notifying listeners`);
|
|
696
|
+
holder.destroyPromise = Promise.all(holder.destroyListeners.map((listener) => listener())).then(async () => {
|
|
697
|
+
this.manager.delete(key);
|
|
698
|
+
await this.notifyListeners(key, "destroy");
|
|
699
|
+
});
|
|
700
|
+
promises.push(holder.destroyPromise);
|
|
701
|
+
}
|
|
702
|
+
return Promise.all(promises);
|
|
703
|
+
}
|
|
704
|
+
async ready() {
|
|
705
|
+
return Promise.all(Array.from(this.manager.filter(() => true)).map(([, holder]) => {
|
|
706
|
+
if (holder.status === ServiceLocatorInstanceHolderStatus.Creating) return holder.creationPromise?.then(() => null);
|
|
707
|
+
if (holder.status === ServiceLocatorInstanceHolderStatus.Destroying) return holder.destroyPromise.then(() => null);
|
|
708
|
+
return Promise.resolve(null);
|
|
709
|
+
})).then(() => null);
|
|
710
|
+
}
|
|
711
|
+
makeInstanceName(token, args) {
|
|
712
|
+
const formattedArgs = args ? ":" + JSON.stringify(args, (_, value) => {
|
|
713
|
+
if (typeof value === "function") return `function:${value.name}(${value.length})`;
|
|
714
|
+
if (typeof value === "symbol") return value.toString();
|
|
715
|
+
return value;
|
|
716
|
+
}).replaceAll(/"/g, "").replaceAll(/:/g, "=").replaceAll(/,/g, "|") : "";
|
|
717
|
+
return `${token.toString()}${formattedArgs}`;
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
//#endregion
|
|
722
|
+
//#region src/injector.mts
|
|
723
|
+
const globalServiceLocator = new ServiceLocator();
|
|
724
|
+
function getGlobalServiceLocator() {
|
|
725
|
+
if (!globalServiceLocator) throw new Error("[ServiceLocator] Service locator is not initialized. Please provide the service locator before using the @Injectable decorator.");
|
|
726
|
+
return globalServiceLocator;
|
|
727
|
+
}
|
|
728
|
+
const values = getInjectors({ baseLocator: globalServiceLocator });
|
|
729
|
+
const inject = values.inject;
|
|
730
|
+
const syncInject = values.syncInject;
|
|
731
|
+
const wrapSyncInit = values.wrapSyncInit;
|
|
732
|
+
const provideServiceLocator = values.provideServiceLocator;
|
|
733
|
+
|
|
734
|
+
//#endregion
|
|
735
|
+
export { BoundInjectionToken, ErrorsEnum, EventEmitter, FactoryInjectionToken, FactoryNotFound, FactoryTokenNotResolved, Injectable, InjectableScope, InjectableTokenMeta, InjectableType, InjectionToken, InjectorsBase, InstanceDestroying, InstanceExpired, InstanceNotFound, ProxyServiceLocator, Registry, ServiceLocator, ServiceLocatorEventBus, ServiceLocatorInstanceHolderKind, ServiceLocatorInstanceHolderStatus, ServiceLocatorManager, UnknownError, getGlobalServiceLocator, getInjectableToken, getInjectors, globalRegistry, inject, makeProxyServiceLocator, provideServiceLocator, resolveService, syncInject, wrapSyncInit };
|
|
736
|
+
//# sourceMappingURL=index.mjs.map
|