@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.
- package/LICENSE +7 -0
- package/README.md +2 -0
- package/dist/_tsup-dts-rollup.d.mts +536 -0
- package/dist/_tsup-dts-rollup.d.ts +536 -0
- package/dist/index.d.mts +54 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +1019 -0
- package/dist/index.mjs +960 -0
- package/package.json +46 -0
- package/src/__tests__/injectable.spec.mts +167 -0
- package/src/__tests__/injection-token.spec.mts +127 -0
- package/src/decorators/index.mts +1 -0
- package/src/decorators/injectable.decorator.mts +107 -0
- package/src/enums/index.mts +2 -0
- package/src/enums/injectable-scope.enum.mts +10 -0
- package/src/enums/injectable-type.enum.mts +4 -0
- package/src/errors/errors.enum.mts +8 -0
- package/src/errors/factory-not-found.mts +8 -0
- package/src/errors/factory-token-not-resolved.mts +10 -0
- package/src/errors/index.mts +7 -0
- package/src/errors/instance-destroying.mts +8 -0
- package/src/errors/instance-expired.mts +8 -0
- package/src/errors/instance-not-found.mts +8 -0
- package/src/errors/unknown-error.mts +15 -0
- package/src/event-emitter.mts +107 -0
- package/src/factory-context.mts +16 -0
- package/src/index.mts +16 -0
- package/src/injection-token.mts +130 -0
- package/src/injector.mts +19 -0
- package/src/interfaces/factory.interface.mts +11 -0
- package/src/interfaces/index.mts +1 -0
- package/src/proxy-service-locator.mts +83 -0
- package/src/registry.mts +65 -0
- package/src/resolve-service.mts +43 -0
- package/src/service-locator-event-bus.mts +96 -0
- package/src/service-locator-instance-holder.mts +63 -0
- package/src/service-locator-manager.mts +89 -0
- package/src/service-locator.mts +571 -0
- package/src/utils/get-injectable-token.mts +19 -0
- package/src/utils/get-injectors.mts +133 -0
- 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
|
+
}
|