@navios/di 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +2 -0
  3. package/dist/_tsup-dts-rollup.d.mts +536 -0
  4. package/dist/_tsup-dts-rollup.d.ts +536 -0
  5. package/dist/index.d.mts +54 -0
  6. package/dist/index.d.ts +54 -0
  7. package/dist/index.js +1019 -0
  8. package/dist/index.mjs +960 -0
  9. package/package.json +46 -0
  10. package/src/__tests__/injectable.spec.mts +167 -0
  11. package/src/__tests__/injection-token.spec.mts +127 -0
  12. package/src/decorators/index.mts +1 -0
  13. package/src/decorators/injectable.decorator.mts +107 -0
  14. package/src/enums/index.mts +2 -0
  15. package/src/enums/injectable-scope.enum.mts +10 -0
  16. package/src/enums/injectable-type.enum.mts +4 -0
  17. package/src/errors/errors.enum.mts +8 -0
  18. package/src/errors/factory-not-found.mts +8 -0
  19. package/src/errors/factory-token-not-resolved.mts +10 -0
  20. package/src/errors/index.mts +7 -0
  21. package/src/errors/instance-destroying.mts +8 -0
  22. package/src/errors/instance-expired.mts +8 -0
  23. package/src/errors/instance-not-found.mts +8 -0
  24. package/src/errors/unknown-error.mts +15 -0
  25. package/src/event-emitter.mts +107 -0
  26. package/src/factory-context.mts +16 -0
  27. package/src/index.mts +16 -0
  28. package/src/injection-token.mts +130 -0
  29. package/src/injector.mts +19 -0
  30. package/src/interfaces/factory.interface.mts +11 -0
  31. package/src/interfaces/index.mts +1 -0
  32. package/src/proxy-service-locator.mts +83 -0
  33. package/src/registry.mts +65 -0
  34. package/src/resolve-service.mts +43 -0
  35. package/src/service-locator-event-bus.mts +96 -0
  36. package/src/service-locator-instance-holder.mts +63 -0
  37. package/src/service-locator-manager.mts +89 -0
  38. package/src/service-locator.mts +571 -0
  39. package/src/utils/get-injectable-token.mts +19 -0
  40. package/src/utils/get-injectors.mts +133 -0
  41. package/src/utils/index.mts +2 -0
@@ -0,0 +1,571 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /* eslint-disable @typescript-eslint/no-empty-object-type */
3
+ import type { AnyZodObject, z, ZodOptional } from 'zod'
4
+
5
+ import type { FactoryContext } from './factory-context.mjs'
6
+ import type { Registry } from './registry.mjs'
7
+ import type { ServiceLocatorInstanceHolder } from './service-locator-instance-holder.mjs'
8
+
9
+ import { InjectableScope } from './enums/index.mjs'
10
+ import {
11
+ ErrorsEnum,
12
+ FactoryNotFound,
13
+ FactoryTokenNotResolved,
14
+ UnknownError,
15
+ } from './errors/index.mjs'
16
+ import {
17
+ BoundInjectionToken,
18
+ FactoryInjectionToken,
19
+ InjectionToken,
20
+ } from './injection-token.mjs'
21
+ import { globalRegistry } from './registry.mjs'
22
+ import { ServiceLocatorEventBus } from './service-locator-event-bus.mjs'
23
+ import {
24
+ ServiceLocatorInstanceHolderKind,
25
+ ServiceLocatorInstanceHolderStatus,
26
+ } from './service-locator-instance-holder.mjs'
27
+ import { ServiceLocatorManager } from './service-locator-manager.mjs'
28
+ import { getInjectableToken } from './utils/index.mjs'
29
+
30
+ export class ServiceLocator {
31
+ private readonly eventBus: ServiceLocatorEventBus
32
+ private readonly manager: ServiceLocatorManager
33
+
34
+ constructor(
35
+ private readonly registry: Registry = globalRegistry,
36
+ private readonly logger: Console | null = null,
37
+ ) {
38
+ this.eventBus = new ServiceLocatorEventBus(logger)
39
+ this.manager = new ServiceLocatorManager(logger)
40
+ }
41
+
42
+ getEventBus() {
43
+ return this.eventBus
44
+ }
45
+
46
+ public storeInstance<Instance>(
47
+ instance: Instance,
48
+ token: BoundInjectionToken<Instance, any>,
49
+ ): void
50
+ public storeInstance<Instance>(
51
+ instance: Instance,
52
+ token: FactoryInjectionToken<Instance, any>,
53
+ ): void
54
+ public storeInstance<Instance>(
55
+ instance: Instance,
56
+ token: InjectionToken<Instance, undefined>,
57
+ ): void
58
+ public storeInstance<
59
+ Instance,
60
+ Schema extends AnyZodObject | ZodOptional<AnyZodObject>,
61
+ >(
62
+ instance: Instance,
63
+ token: InjectionToken<Instance, Schema>,
64
+ args: z.input<Schema>,
65
+ ): void
66
+ public storeInstance(
67
+ instance: any,
68
+ token:
69
+ | InjectionToken<any, any>
70
+ | BoundInjectionToken<any, any>
71
+ | FactoryInjectionToken<any, any>,
72
+ args?: any,
73
+ ): void {
74
+ // @ts-expect-error We should redefine the instance name type
75
+ const instanceName = this.getInstanceIdentifier(token, args)
76
+ this.manager.set(instanceName, {
77
+ name: instanceName,
78
+ instance,
79
+ status: ServiceLocatorInstanceHolderStatus.Created,
80
+ kind: ServiceLocatorInstanceHolderKind.Instance,
81
+ createdAt: Date.now(),
82
+ ttl: Infinity,
83
+ deps: [],
84
+ destroyListeners: [],
85
+ effects: [],
86
+ destroyPromise: null,
87
+ creationPromise: null,
88
+ })
89
+ this.notifyListeners(instanceName)
90
+ }
91
+
92
+ public removeInstance<Instance>(
93
+ instance: Instance,
94
+ token: BoundInjectionToken<Instance, any>,
95
+ ): void
96
+ public removeInstance<Instance>(
97
+ instance: Instance,
98
+ token: FactoryInjectionToken<Instance, any>,
99
+ ): void
100
+ public removeInstance<Instance>(
101
+ instance: Instance,
102
+ token: InjectionToken<Instance, undefined>,
103
+ ): void
104
+ public removeInstance<
105
+ Instance,
106
+ Schema extends AnyZodObject | ZodOptional<AnyZodObject>,
107
+ >(
108
+ instance: Instance,
109
+ token: InjectionToken<Instance, Schema>,
110
+ args: z.input<Schema>,
111
+ ): void
112
+ public removeInstance(
113
+ token:
114
+ | InjectionToken<any, any>
115
+ | BoundInjectionToken<any, any>
116
+ | FactoryInjectionToken<any, any>,
117
+ args?: any,
118
+ ) {
119
+ // @ts-expect-error We should redefine the instance name type
120
+ const instanceName = this.getInstanceIdentifier(token, args)
121
+ return this.invalidate(instanceName)
122
+ }
123
+ private resolveTokenArgs<Instance, Schema extends AnyZodObject>(
124
+ token: InjectionToken<Instance, Schema>,
125
+ args: z.input<Schema>,
126
+ ): [undefined, z.output<Schema>] | [UnknownError]
127
+ private resolveTokenArgs<Instance, Schema extends ZodOptional<AnyZodObject>>(
128
+ token: InjectionToken<Instance, Schema>,
129
+ args?: z.input<Schema>,
130
+ ): [undefined, z.output<Schema>] | [UnknownError]
131
+ private resolveTokenArgs<Instance, Schema extends AnyZodObject>(
132
+ token: BoundInjectionToken<Instance, Schema>,
133
+ ): [undefined, z.output<Schema>] | [UnknownError]
134
+ private resolveTokenArgs<Instance, Schema extends AnyZodObject>(
135
+ token: FactoryInjectionToken<Instance, Schema>,
136
+ ): [undefined, z.output<Schema>] | [FactoryTokenNotResolved | UnknownError]
137
+ private resolveTokenArgs(
138
+ token:
139
+ | InjectionToken<any, any>
140
+ | BoundInjectionToken<any, any>
141
+ | FactoryInjectionToken<any, any>,
142
+ args?: any,
143
+ ) {
144
+ let realArgs = args
145
+ if (token instanceof BoundInjectionToken) {
146
+ realArgs = token.value
147
+ } else if (token instanceof FactoryInjectionToken) {
148
+ if (token.resolved) {
149
+ realArgs = token.value
150
+ } else {
151
+ return [new FactoryTokenNotResolved(token.name)]
152
+ }
153
+ }
154
+ if (!token.schema) {
155
+ return [undefined, realArgs]
156
+ }
157
+ const validatedArgs = token.schema?.safeParse(realArgs)
158
+ if (validatedArgs && !validatedArgs.success) {
159
+ this.logger?.error(
160
+ `[ServiceLocator]#getInstance(): Error validating args for ${token.name.toString()}`,
161
+ validatedArgs.error,
162
+ )
163
+ return [new UnknownError(validatedArgs.error)]
164
+ }
165
+ return [undefined, validatedArgs?.data]
166
+ }
167
+
168
+ public getInstanceIdentifier<Instance, Schema extends AnyZodObject>(
169
+ token: InjectionToken<Instance, Schema>,
170
+ args: z.input<Schema>,
171
+ ): string
172
+ public getInstanceIdentifier<
173
+ Instance,
174
+ Schema extends ZodOptional<AnyZodObject>,
175
+ >(token: InjectionToken<Instance, Schema>, args?: z.input<Schema>): string
176
+ public getInstanceIdentifier<Instance>(
177
+ token: InjectionToken<Instance, undefined>,
178
+ ): string
179
+ public getInstanceIdentifier<Instance>(
180
+ token: BoundInjectionToken<Instance, any>,
181
+ ): string
182
+ public getInstanceIdentifier<Instance>(
183
+ token: FactoryInjectionToken<Instance, any>,
184
+ ): string
185
+ public getInstanceIdentifier(
186
+ token:
187
+ | InjectionToken<any, any>
188
+ | BoundInjectionToken<any, any>
189
+ | FactoryInjectionToken<any, any>,
190
+ args?: any,
191
+ ): string {
192
+ const [err, realArgs] = this.resolveTokenArgs(
193
+ token as InjectionToken<any>,
194
+ args,
195
+ )
196
+ if (err) {
197
+ throw err
198
+ }
199
+ return this.makeInstanceName(token as InjectionToken<any>, realArgs)
200
+ }
201
+
202
+ public getInstance<Instance, Schema extends AnyZodObject>(
203
+ token: InjectionToken<Instance, Schema>,
204
+ args: z.input<Schema>,
205
+ ): Promise<[undefined, Instance] | [UnknownError | FactoryNotFound]>
206
+ public getInstance<Instance, Schema extends ZodOptional<AnyZodObject>>(
207
+ token: InjectionToken<Instance, Schema>,
208
+ args?: z.input<Schema>,
209
+ ): Promise<[undefined, Instance] | [UnknownError | FactoryNotFound]>
210
+ public getInstance<Instance>(
211
+ token: InjectionToken<Instance, undefined>,
212
+ ): Promise<[undefined, Instance] | [UnknownError | FactoryNotFound]>
213
+ public getInstance<Instance>(
214
+ token: BoundInjectionToken<Instance, any>,
215
+ ): Promise<[undefined, Instance] | [UnknownError | FactoryNotFound]>
216
+ public getInstance<Instance>(
217
+ token: FactoryInjectionToken<Instance, any>,
218
+ ): Promise<[undefined, Instance] | [UnknownError | FactoryNotFound]>
219
+
220
+ public async getInstance(
221
+ token:
222
+ | InjectionToken<any, any>
223
+ | BoundInjectionToken<any, any>
224
+ | FactoryInjectionToken<any, any>,
225
+ args?: any,
226
+ ) {
227
+ const [err, realArgs] = this.resolveTokenArgs(token as any, args)
228
+ if (err instanceof UnknownError) {
229
+ return [err]
230
+ } else if (
231
+ (err as any) instanceof FactoryTokenNotResolved &&
232
+ token instanceof FactoryInjectionToken
233
+ ) {
234
+ await token.resolve()
235
+ // @ts-expect-error TS2322 We should redefine the instance name type
236
+ return this.getInstance(token, args)
237
+ }
238
+ const instanceName = this.makeInstanceName(token, realArgs)
239
+ const [error, holder] = this.manager.get(instanceName)
240
+ if (!error) {
241
+ if (holder.status === ServiceLocatorInstanceHolderStatus.Creating) {
242
+ return holder.creationPromise
243
+ } else if (
244
+ holder.status === ServiceLocatorInstanceHolderStatus.Destroying
245
+ ) {
246
+ // Should never happen
247
+ return [new UnknownError(ErrorsEnum.InstanceDestroying)]
248
+ }
249
+ return [undefined, holder.instance]
250
+ }
251
+ switch (error.code) {
252
+ case ErrorsEnum.InstanceDestroying:
253
+ this.logger?.log(
254
+ `[ServiceLocator]#getInstance() TTL expired for ${holder?.name}`,
255
+ )
256
+ await holder?.destroyPromise
257
+ //Maybe we already have a new instance
258
+ // @ts-expect-error We should redefine the instance name type
259
+ return this.getInstance(token, args)
260
+
261
+ case ErrorsEnum.InstanceExpired:
262
+ this.logger?.log(
263
+ `[ServiceLocator]#getInstance() TTL expired for ${holder?.name}`,
264
+ )
265
+ await this.invalidate(instanceName)
266
+ //Maybe we already have a new instance
267
+ // @ts-expect-error We should redefine the instance name type
268
+ return this.getInstance(token, args)
269
+ case ErrorsEnum.InstanceNotFound:
270
+ break
271
+ default:
272
+ return [error]
273
+ }
274
+ // @ts-expect-error TS2322 It's validated
275
+ return this.createInstance(instanceName, token, realArgs)
276
+ }
277
+
278
+ public async getOrThrowInstance<
279
+ Instance,
280
+ Schema extends AnyZodObject | ZodOptional<AnyZodObject> | undefined,
281
+ >(
282
+ token: InjectionToken<Instance, Schema>,
283
+ args: Schema extends AnyZodObject
284
+ ? z.input<Schema>
285
+ : Schema extends ZodOptional<AnyZodObject>
286
+ ? z.input<Schema> | undefined
287
+ : undefined,
288
+ ): Promise<Instance> {
289
+ const [error, instance] = await this.getInstance(token, args)
290
+ if (error) {
291
+ throw error
292
+ }
293
+ return instance
294
+ }
295
+
296
+ private notifyListeners(
297
+ name: string,
298
+ event: 'create' | 'destroy' = 'create',
299
+ ) {
300
+ this.logger?.log(
301
+ `[ServiceLocator]#notifyListeners() Notifying listeners for ${name} with event ${event}`,
302
+ )
303
+ return this.eventBus.emit(name, event)
304
+ }
305
+
306
+ private async createInstance<
307
+ Instance,
308
+ Schema extends AnyZodObject | ZodOptional<AnyZodObject> | undefined,
309
+ >(
310
+ instanceName: string,
311
+ token: InjectionToken<Instance, Schema>,
312
+ args: Schema extends AnyZodObject
313
+ ? z.input<Schema>
314
+ : Schema extends ZodOptional<AnyZodObject>
315
+ ? z.input<Schema> | undefined
316
+ : undefined,
317
+ ): Promise<[undefined, Instance] | [FactoryNotFound | UnknownError]> {
318
+ this.logger?.log(
319
+ `[ServiceLocator]#createInstance() Creating instance for ${instanceName}`,
320
+ )
321
+ let realToken =
322
+ token instanceof BoundInjectionToken ||
323
+ token instanceof FactoryInjectionToken
324
+ ? token.token
325
+ : token
326
+ if (this.registry.has(realToken)) {
327
+ return this.resolveInstance(instanceName, realToken, args)
328
+ } else {
329
+ return [new FactoryNotFound(realToken.name.toString())]
330
+ }
331
+ }
332
+
333
+ private async resolveInstance<
334
+ Instance,
335
+ Schema extends AnyZodObject | ZodOptional<AnyZodObject> | undefined,
336
+ Args extends Schema extends AnyZodObject
337
+ ? z.input<Schema>
338
+ : Schema extends ZodOptional<AnyZodObject>
339
+ ? z.input<Schema> | undefined
340
+ : undefined,
341
+ >(
342
+ instanceName: string,
343
+ token: InjectionToken<Instance, Schema>,
344
+ args: Args,
345
+ ): Promise<[undefined, Instance] | [FactoryNotFound]> {
346
+ this.logger?.log(
347
+ `[ServiceLocator]#resolveInstance(): Creating instance for ${instanceName} from abstract factory`,
348
+ )
349
+ const ctx = this.createFactoryContext(instanceName)
350
+ let { factory, scope } = this.registry.get<Instance, Schema>(token)
351
+ const holder: ServiceLocatorInstanceHolder<Instance> = {
352
+ name: instanceName,
353
+ instance: null,
354
+ status: ServiceLocatorInstanceHolderStatus.Creating,
355
+ kind: ServiceLocatorInstanceHolderKind.AbstractFactory,
356
+ // @ts-expect-error TS2322 This is correct type
357
+ creationPromise: factory(ctx, args)
358
+ .then(async (instance: Instance) => {
359
+ holder.instance = instance
360
+ holder.status = ServiceLocatorInstanceHolderStatus.Created
361
+ holder.deps = ctx.getDependencies()
362
+ holder.destroyListeners = ctx.getDestroyListeners()
363
+ holder.ttl = ctx.getTtl()
364
+ if (holder.deps.length > 0) {
365
+ this.logger?.log(
366
+ `[ServiceLocator]#createInstanceFromAbstractFactory(): Adding subscriptions for ${instanceName} dependencies for their invalidations: ${holder.deps.join(
367
+ ', ',
368
+ )}`,
369
+ )
370
+ holder.deps.forEach((dependency) => {
371
+ holder.destroyListeners.push(
372
+ this.eventBus.on(dependency, 'destroy', () =>
373
+ this.invalidate(instanceName),
374
+ ),
375
+ )
376
+ })
377
+ }
378
+ if (holder.ttl === 0 || scope === InjectableScope.Instance) {
379
+ // One time instance
380
+ await this.invalidate(instanceName)
381
+ }
382
+ await this.notifyListeners(instanceName)
383
+ return [undefined, instance as Instance]
384
+ })
385
+ .catch((error) => {
386
+ this.logger?.error(
387
+ `[ServiceLocator]#createInstanceFromAbstractFactory(): Error creating instance for ${instanceName}`,
388
+ error,
389
+ )
390
+ return [new UnknownError(error)]
391
+ }),
392
+ effects: [],
393
+ deps: [],
394
+ destroyListeners: [],
395
+ createdAt: Date.now(),
396
+ ttl: Infinity,
397
+ }
398
+
399
+ if (scope === InjectableScope.Singleton) {
400
+ this.manager.set(instanceName, holder)
401
+ }
402
+ // @ts-expect-error TS2322 This is correct type
403
+ return holder.creationPromise
404
+ }
405
+
406
+ private createFactoryContext(instanceName: string): FactoryContext {
407
+ const dependencies = new Set<string>()
408
+ const destroyListeners = new Set<() => void>()
409
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
410
+ const self = this
411
+
412
+ function invalidate(name = instanceName) {
413
+ return self.invalidate(name)
414
+ }
415
+ function addEffect(listener: () => void) {
416
+ destroyListeners.add(listener)
417
+ }
418
+ let ttl = Infinity
419
+ function setTtl(value: number) {
420
+ ttl = value
421
+ }
422
+ function getTtl() {
423
+ return ttl
424
+ }
425
+
426
+ function on(key: string, event: string, listener: (event: string) => void) {
427
+ destroyListeners.add(self.eventBus.on(key, event, listener))
428
+ }
429
+
430
+ return {
431
+ // @ts-expect-error This is correct type
432
+ async inject(token, args) {
433
+ let injectionToken = token
434
+ if (typeof token === 'function') {
435
+ injectionToken = getInjectableToken(token)
436
+ }
437
+ if (injectionToken instanceof InjectionToken) {
438
+ const validatedArgs = token.schema
439
+ ? token.schema.safeParse(args)
440
+ : undefined
441
+ const instanceName = self.makeInstanceName(token, validatedArgs)
442
+ dependencies.add(instanceName)
443
+ return self.getOrThrowInstance(injectionToken, args)
444
+ }
445
+ throw new Error(
446
+ `[ServiceLocator]#inject(): Invalid token type: ${typeof token}. Expected a class or an InjectionToken.`,
447
+ )
448
+ },
449
+ invalidate,
450
+ on: on as ServiceLocatorEventBus['on'],
451
+ getDependencies: () => Array.from(dependencies),
452
+ addEffect,
453
+ getDestroyListeners: () => Array.from(destroyListeners),
454
+ setTtl,
455
+ getTtl,
456
+ locator: self,
457
+ }
458
+ }
459
+
460
+ public getSyncInstance<
461
+ Instance,
462
+ Schema extends AnyZodObject | ZodOptional<AnyZodObject> | undefined,
463
+ >(
464
+ token: InjectionToken<Instance, Schema>,
465
+ args: Schema extends AnyZodObject
466
+ ? z.input<Schema>
467
+ : Schema extends ZodOptional<AnyZodObject>
468
+ ? z.input<Schema> | undefined
469
+ : undefined,
470
+ ): Instance | null {
471
+ const [err, realArgs] = this.resolveTokenArgs(token, args)
472
+ if (err) {
473
+ return null
474
+ }
475
+ const instanceName = this.makeInstanceName(token, realArgs)
476
+ const [error, holder] = this.manager.get(instanceName)
477
+ if (error) {
478
+ return null
479
+ }
480
+ return holder.instance as Instance
481
+ }
482
+
483
+ invalidate(service: string, round = 1): Promise<any> {
484
+ this.logger?.log(
485
+ `[ServiceLocator]#invalidate(): Starting Invalidating process of ${service}`,
486
+ )
487
+ const toInvalidate = this.manager.filter(
488
+ (holder) => holder.name === service || holder.deps.includes(service),
489
+ )
490
+ const promises = []
491
+ for (const [key, holder] of toInvalidate.entries()) {
492
+ if (holder.status === ServiceLocatorInstanceHolderStatus.Destroying) {
493
+ this.logger?.trace(
494
+ `[ServiceLocator]#invalidate(): ${key} is already being destroyed`,
495
+ )
496
+ promises.push(holder.destroyPromise)
497
+ continue
498
+ }
499
+ if (holder.status === ServiceLocatorInstanceHolderStatus.Creating) {
500
+ this.logger?.trace(
501
+ `[ServiceLocator]#invalidate(): ${key} is being created, waiting for creation to finish`,
502
+ )
503
+ promises.push(
504
+ holder.creationPromise?.then(() => {
505
+ if (round > 3) {
506
+ this.logger?.error(
507
+ `[ServiceLocator]#invalidate(): ${key} creation is triggering a new invalidation round, but it is still not created`,
508
+ )
509
+ return
510
+ }
511
+ return this.invalidate(key, round + 1)
512
+ }),
513
+ )
514
+ continue
515
+ }
516
+ // @ts-expect-error TS2322 we are changing the status
517
+ holder.status = ServiceLocatorInstanceHolderStatus.Destroying
518
+ this.logger?.log(
519
+ `[ServiceLocator]#invalidate(): Invalidating ${key} and notifying listeners`,
520
+ )
521
+ // @ts-expect-error TS2322 we are changing the status
522
+ holder.destroyPromise = Promise.all(
523
+ holder.destroyListeners.map((listener) => listener()),
524
+ ).then(async () => {
525
+ this.manager.delete(key)
526
+ await this.notifyListeners(key, 'destroy')
527
+ })
528
+ promises.push(holder.destroyPromise)
529
+ }
530
+ return Promise.all(promises)
531
+ }
532
+
533
+ async ready() {
534
+ return Promise.all(
535
+ Array.from(this.manager.filter(() => true)).map(([, holder]) => {
536
+ if (holder.status === ServiceLocatorInstanceHolderStatus.Creating) {
537
+ return holder.creationPromise?.then(() => null)
538
+ }
539
+ if (holder.status === ServiceLocatorInstanceHolderStatus.Destroying) {
540
+ return holder.destroyPromise.then(() => null)
541
+ }
542
+ return Promise.resolve(null)
543
+ }),
544
+ ).then(() => null)
545
+ }
546
+
547
+ makeInstanceName(
548
+ token:
549
+ | InjectionToken<any, any>
550
+ | BoundInjectionToken<any, any>
551
+ | FactoryInjectionToken<any, any>,
552
+ args: any,
553
+ ) {
554
+ const formattedArgs = args
555
+ ? ':' +
556
+ JSON.stringify(args, (_, value) => {
557
+ if (typeof value === 'function') {
558
+ return `function:${value.name}(${value.length})`
559
+ }
560
+ if (typeof value === 'symbol') {
561
+ return value.toString()
562
+ }
563
+ return value
564
+ })
565
+ .replaceAll(/"/g, '')
566
+ .replaceAll(/:/g, '=')
567
+ .replaceAll(/,/g, '|')
568
+ : ''
569
+ return `${token.toString()}${formattedArgs}`
570
+ }
571
+ }
@@ -0,0 +1,19 @@
1
+ import type { ClassType, InjectionToken } from '../injection-token.mjs'
2
+
3
+ import { InjectableTokenMeta } from '../decorators/index.mjs'
4
+
5
+ export function getInjectableToken<R>(
6
+ target: ClassType,
7
+ ): R extends { create(...args: any[]): infer V }
8
+ ? InjectionToken<V>
9
+ : InjectionToken<R> {
10
+ // @ts-expect-error We inject the token into the class itself
11
+ const token = target[InjectableTokenMeta] as InjectionToken<any, any>
12
+ if (!token) {
13
+ throw new Error(
14
+ `[ServiceLocator] Class ${target.name} is not decorated with @Injectable.`,
15
+ )
16
+ }
17
+ // @ts-expect-error We detect factory or class
18
+ return token
19
+ }