@navios/core 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 +145 -0
- package/dist/index.d.mts +425 -0
- package/dist/index.d.ts +425 -0
- package/dist/index.js +1744 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1657 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +45 -0
- package/src/__tests__/controller.spec.mts +59 -0
- package/src/attribute.factory.mts +155 -0
- package/src/decorators/controller.decorator.mts +36 -0
- package/src/decorators/endpoint.decorator.mts +81 -0
- package/src/decorators/index.mts +4 -0
- package/src/decorators/module.decorator.mts +47 -0
- package/src/decorators/use-guards.decorator.mts +43 -0
- package/src/exceptions/bad-request.exception.mts +7 -0
- package/src/exceptions/conflict.exception.mts +7 -0
- package/src/exceptions/forbidden.exception.mts +7 -0
- package/src/exceptions/http.exception.mts +7 -0
- package/src/exceptions/index.mts +7 -0
- package/src/exceptions/internal-server-error.exception.mts +7 -0
- package/src/exceptions/not-found.exception.mts +10 -0
- package/src/exceptions/unauthorized.exception.mts +7 -0
- package/src/index.mts +10 -0
- package/src/interfaces/can-activate.mts +5 -0
- package/src/interfaces/index.mts +2 -0
- package/src/interfaces/navios-module.mts +3 -0
- package/src/metadata/controller.metadata.mts +71 -0
- package/src/metadata/endpoint.metadata.mts +69 -0
- package/src/metadata/index.mts +4 -0
- package/src/metadata/injectable.metadata.mts +11 -0
- package/src/metadata/module.metadata.mts +62 -0
- package/src/navios.application.mts +113 -0
- package/src/navios.factory.mts +17 -0
- package/src/service-locator/__tests__/injectable.spec.mts +170 -0
- package/src/service-locator/decorators/get-injectable-token.mts +23 -0
- package/src/service-locator/decorators/index.mts +2 -0
- package/src/service-locator/decorators/injectable.decorator.mts +103 -0
- package/src/service-locator/enums/index.mts +1 -0
- package/src/service-locator/enums/injectable-scope.enum.mts +10 -0
- package/src/service-locator/errors/errors.enum.mts +7 -0
- package/src/service-locator/errors/factory-not-found.mts +8 -0
- package/src/service-locator/errors/index.mts +6 -0
- package/src/service-locator/errors/instance-destroying.mts +8 -0
- package/src/service-locator/errors/instance-expired.mts +8 -0
- package/src/service-locator/errors/instance-not-found.mts +8 -0
- package/src/service-locator/errors/unknown-error.mts +15 -0
- package/src/service-locator/event-emitter.mts +107 -0
- package/src/service-locator/index.mts +14 -0
- package/src/service-locator/inject.mts +37 -0
- package/src/service-locator/injection-token.mts +36 -0
- package/src/service-locator/injector.mts +18 -0
- package/src/service-locator/interfaces/factory.interface.mts +3 -0
- package/src/service-locator/override.mts +22 -0
- package/src/service-locator/proxy-service-locator.mts +80 -0
- package/src/service-locator/service-locator-abstract-factory-context.mts +23 -0
- package/src/service-locator/service-locator-event-bus.mts +96 -0
- package/src/service-locator/service-locator-instance-holder.mts +63 -0
- package/src/service-locator/service-locator-manager.mts +89 -0
- package/src/service-locator/service-locator.mts +445 -0
- package/src/service-locator/sync-injector.mts +55 -0
- package/src/services/controller-adapter.service.mts +124 -0
- package/src/services/execution-context.mts +53 -0
- package/src/services/guard-runner.service.mts +93 -0
- package/src/services/index.mts +4 -0
- package/src/services/module-loader.service.mts +68 -0
- package/src/tokens/application.token.mts +9 -0
- package/src/tokens/execution-context.token.mts +8 -0
- package/src/tokens/index.mts +4 -0
- package/src/tokens/reply.token.mts +7 -0
- package/src/tokens/request.token.mts +9 -0
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-empty-object-type */
|
|
3
|
+
import type { AnyZodObject, z } from 'zod'
|
|
4
|
+
|
|
5
|
+
import type { ServiceLocatorAbstractFactoryContext } from './service-locator-abstract-factory-context.mjs'
|
|
6
|
+
import type { ServiceLocatorInstanceHolder } from './service-locator-instance-holder.mjs'
|
|
7
|
+
|
|
8
|
+
import { InjectableScope } from './enums/index.mjs'
|
|
9
|
+
import { ErrorsEnum, FactoryNotFound, UnknownError } from './errors/index.mjs'
|
|
10
|
+
import { getInjectableToken } from './index.mjs'
|
|
11
|
+
import { InjectionToken } from './injection-token.mjs'
|
|
12
|
+
import { ServiceLocatorEventBus } from './service-locator-event-bus.mjs'
|
|
13
|
+
import {
|
|
14
|
+
ServiceLocatorInstanceHolderKind,
|
|
15
|
+
ServiceLocatorInstanceHolderStatus,
|
|
16
|
+
} from './service-locator-instance-holder.mjs'
|
|
17
|
+
import { ServiceLocatorManager } from './service-locator-manager.mjs'
|
|
18
|
+
|
|
19
|
+
export class ServiceLocator {
|
|
20
|
+
private abstractFactories: Map<
|
|
21
|
+
InjectionToken<any, any>,
|
|
22
|
+
(ctx: ServiceLocatorAbstractFactoryContext, ...args: any) => Promise<any>
|
|
23
|
+
> = new Map()
|
|
24
|
+
private instanceFactories: Map<
|
|
25
|
+
InjectionToken<any, any>,
|
|
26
|
+
(ctx: ServiceLocatorAbstractFactoryContext, ...args: any) => Promise<any>
|
|
27
|
+
> = new Map()
|
|
28
|
+
|
|
29
|
+
private readonly eventBus: ServiceLocatorEventBus
|
|
30
|
+
private readonly manager: ServiceLocatorManager
|
|
31
|
+
|
|
32
|
+
constructor(private readonly logger: Console | null = null) {
|
|
33
|
+
this.eventBus = new ServiceLocatorEventBus(logger)
|
|
34
|
+
this.manager = new ServiceLocatorManager(logger)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getEventBus() {
|
|
38
|
+
return this.eventBus
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public registerInstance<Instance>(
|
|
42
|
+
token: InjectionToken<Instance, undefined>,
|
|
43
|
+
instance: Instance,
|
|
44
|
+
): void {
|
|
45
|
+
const instanceName = this.getInstanceIdentifier(token, undefined)
|
|
46
|
+
this.manager.set(instanceName, {
|
|
47
|
+
name: instanceName,
|
|
48
|
+
instance,
|
|
49
|
+
status: ServiceLocatorInstanceHolderStatus.Created,
|
|
50
|
+
kind: ServiceLocatorInstanceHolderKind.Instance,
|
|
51
|
+
createdAt: Date.now(),
|
|
52
|
+
ttl: Infinity,
|
|
53
|
+
deps: [],
|
|
54
|
+
destroyListeners: [],
|
|
55
|
+
effects: [],
|
|
56
|
+
destroyPromise: null,
|
|
57
|
+
creationPromise: null,
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public removeInstance<Instance>(token: InjectionToken<Instance, undefined>) {
|
|
62
|
+
const instanceName = this.getInstanceIdentifier(token, undefined)
|
|
63
|
+
return this.invalidate(instanceName)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public registerAbstractFactory<
|
|
67
|
+
Instance,
|
|
68
|
+
Schema extends AnyZodObject | undefined,
|
|
69
|
+
>(
|
|
70
|
+
token: InjectionToken<Instance, Schema>,
|
|
71
|
+
factory: (
|
|
72
|
+
ctx: ServiceLocatorAbstractFactoryContext,
|
|
73
|
+
values: Schema extends AnyZodObject ? z.output<Schema> : undefined,
|
|
74
|
+
) => Promise<Instance>,
|
|
75
|
+
type: InjectableScope = InjectableScope.Singleton,
|
|
76
|
+
) {
|
|
77
|
+
this.logger?.log(
|
|
78
|
+
`[ServiceLocator]#registerAbstractFactory(): Registering abstract factory for ${name}`,
|
|
79
|
+
)
|
|
80
|
+
if (type === InjectableScope.Instance) {
|
|
81
|
+
this.instanceFactories.set(token, factory)
|
|
82
|
+
this.abstractFactories.delete(token)
|
|
83
|
+
} else {
|
|
84
|
+
this.abstractFactories.set(token, factory)
|
|
85
|
+
this.instanceFactories.delete(token)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
public getInstanceIdentifier<
|
|
90
|
+
Instance,
|
|
91
|
+
Schema extends AnyZodObject | undefined,
|
|
92
|
+
>(
|
|
93
|
+
token: InjectionToken<Instance, Schema>,
|
|
94
|
+
args: Schema extends AnyZodObject ? z.input<Schema> : undefined,
|
|
95
|
+
): string {
|
|
96
|
+
const validatedArgs = token.schema
|
|
97
|
+
? token.schema.safeParse(args)
|
|
98
|
+
: undefined
|
|
99
|
+
if (validatedArgs && !validatedArgs.success) {
|
|
100
|
+
this.logger?.error(
|
|
101
|
+
`[ServiceLocator]#getInstance(): Error validating args for ${token.name.toString()}`,
|
|
102
|
+
validatedArgs.error,
|
|
103
|
+
)
|
|
104
|
+
throw new UnknownError(validatedArgs.error)
|
|
105
|
+
}
|
|
106
|
+
return this.makeInstanceName(token, validatedArgs)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
public async getInstance<Instance, Schema extends AnyZodObject | undefined>(
|
|
110
|
+
token: InjectionToken<Instance, Schema>,
|
|
111
|
+
args: Schema extends AnyZodObject ? z.input<Schema> : undefined,
|
|
112
|
+
): Promise<[undefined, Instance] | [UnknownError | FactoryNotFound]> {
|
|
113
|
+
const validatedArgs = token.schema
|
|
114
|
+
? token.schema.safeParse(args)
|
|
115
|
+
: undefined
|
|
116
|
+
if (validatedArgs && !validatedArgs.success) {
|
|
117
|
+
this.logger?.error(
|
|
118
|
+
`[ServiceLocator]#getInstance(): Error validating args for ${token.name.toString()}`,
|
|
119
|
+
validatedArgs.error,
|
|
120
|
+
)
|
|
121
|
+
return [new UnknownError(validatedArgs.error)]
|
|
122
|
+
}
|
|
123
|
+
const instanceName = this.makeInstanceName(token, validatedArgs)
|
|
124
|
+
const [error, holder] = this.manager.get(instanceName)
|
|
125
|
+
if (!error) {
|
|
126
|
+
if (holder.status === ServiceLocatorInstanceHolderStatus.Creating) {
|
|
127
|
+
// @ts-expect-error TS2322 We should redefine the instance name type
|
|
128
|
+
return holder.creationPromise
|
|
129
|
+
} else if (
|
|
130
|
+
holder.status === ServiceLocatorInstanceHolderStatus.Destroying
|
|
131
|
+
) {
|
|
132
|
+
// Should never happen
|
|
133
|
+
return [new UnknownError(ErrorsEnum.InstanceDestroying)]
|
|
134
|
+
}
|
|
135
|
+
// @ts-expect-error We should redefine the instance name type
|
|
136
|
+
return [undefined, holder.instance]
|
|
137
|
+
}
|
|
138
|
+
switch (error.code) {
|
|
139
|
+
case ErrorsEnum.InstanceDestroying:
|
|
140
|
+
this.logger?.log(
|
|
141
|
+
`[ServiceLocator]#getInstance() TTL expired for ${holder?.name}`,
|
|
142
|
+
)
|
|
143
|
+
await holder?.destroyPromise
|
|
144
|
+
//Maybe we already have a new instance
|
|
145
|
+
return this.getInstance<Instance, Schema>(token, args)
|
|
146
|
+
|
|
147
|
+
case ErrorsEnum.InstanceExpired:
|
|
148
|
+
this.logger?.log(
|
|
149
|
+
`[ServiceLocator]#getInstance() TTL expired for ${holder?.name}`,
|
|
150
|
+
)
|
|
151
|
+
await this.invalidate(instanceName)
|
|
152
|
+
//Maybe we already have a new instance
|
|
153
|
+
return this.getInstance<Instance, Schema>(token, args)
|
|
154
|
+
case ErrorsEnum.InstanceNotFound:
|
|
155
|
+
break
|
|
156
|
+
default:
|
|
157
|
+
return [error]
|
|
158
|
+
}
|
|
159
|
+
return this.createInstance(instanceName, token, args)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
public async getOrThrowInstance<
|
|
163
|
+
Instance,
|
|
164
|
+
Schema extends AnyZodObject | undefined,
|
|
165
|
+
>(
|
|
166
|
+
token: InjectionToken<Instance, Schema>,
|
|
167
|
+
args: Schema extends AnyZodObject ? z.input<Schema> : undefined,
|
|
168
|
+
): Promise<Instance> {
|
|
169
|
+
const [error, instance] = await this.getInstance(token, args)
|
|
170
|
+
if (error) {
|
|
171
|
+
throw error
|
|
172
|
+
}
|
|
173
|
+
return instance
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private notifyListeners(
|
|
177
|
+
name: string,
|
|
178
|
+
event: 'create' | 'destroy' = 'create',
|
|
179
|
+
) {
|
|
180
|
+
this.logger?.log(
|
|
181
|
+
`[ServiceLocator]#notifyListeners() Notifying listeners for ${name} with event ${event}`,
|
|
182
|
+
)
|
|
183
|
+
return this.eventBus.emit(name, event)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private async createInstance<
|
|
187
|
+
Instance,
|
|
188
|
+
Schema extends AnyZodObject | undefined,
|
|
189
|
+
>(
|
|
190
|
+
instanceName: string,
|
|
191
|
+
token: InjectionToken<Instance, Schema>,
|
|
192
|
+
args: Schema extends AnyZodObject ? z.input<Schema> : undefined,
|
|
193
|
+
): Promise<[undefined, Instance] | [FactoryNotFound | UnknownError]> {
|
|
194
|
+
this.logger?.log(
|
|
195
|
+
`[ServiceLocator]#createInstance() Creating instance for ${instanceName}`,
|
|
196
|
+
)
|
|
197
|
+
if (
|
|
198
|
+
this.abstractFactories.has(token) ||
|
|
199
|
+
this.instanceFactories.has(token)
|
|
200
|
+
) {
|
|
201
|
+
return this.createInstanceFromAbstractFactory(instanceName, token, args)
|
|
202
|
+
} else {
|
|
203
|
+
return [new FactoryNotFound(token.name.toString())]
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private async createInstanceFromAbstractFactory<
|
|
208
|
+
Instance,
|
|
209
|
+
Schema extends AnyZodObject | undefined,
|
|
210
|
+
Args extends Schema extends AnyZodObject ? z.input<Schema> : undefined,
|
|
211
|
+
>(
|
|
212
|
+
instanceName: string,
|
|
213
|
+
token: InjectionToken<Instance, Schema>,
|
|
214
|
+
args: Args,
|
|
215
|
+
): Promise<[undefined, Instance] | [FactoryNotFound]> {
|
|
216
|
+
this.logger?.log(
|
|
217
|
+
`[ServiceLocator]#createInstanceFromAbstractFactory(): Creating instance for ${instanceName} from abstract factory`,
|
|
218
|
+
)
|
|
219
|
+
const ctx = this.createContextForAbstractFactory(instanceName)
|
|
220
|
+
let shouldStore = true
|
|
221
|
+
let abstractFactory = this.abstractFactories.get(token)
|
|
222
|
+
if (!abstractFactory) {
|
|
223
|
+
abstractFactory = this.instanceFactories.get(token)
|
|
224
|
+
shouldStore = false
|
|
225
|
+
if (!abstractFactory) {
|
|
226
|
+
return [new FactoryNotFound(token.name.toString())]
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const holder: ServiceLocatorInstanceHolder<Instance> = {
|
|
230
|
+
name: instanceName,
|
|
231
|
+
instance: null,
|
|
232
|
+
status: ServiceLocatorInstanceHolderStatus.Creating,
|
|
233
|
+
kind: ServiceLocatorInstanceHolderKind.AbstractFactory,
|
|
234
|
+
// @ts-expect-error TS2322 This is correct type
|
|
235
|
+
creationPromise: abstractFactory(ctx, args)
|
|
236
|
+
.then(async (instance: Instance) => {
|
|
237
|
+
holder.instance = instance
|
|
238
|
+
holder.status = ServiceLocatorInstanceHolderStatus.Created
|
|
239
|
+
holder.deps = ctx.getDependencies()
|
|
240
|
+
holder.destroyListeners = ctx.getDestroyListeners()
|
|
241
|
+
holder.ttl = ctx.getTtl()
|
|
242
|
+
if (holder.deps.length > 0) {
|
|
243
|
+
this.logger?.log(
|
|
244
|
+
`[ServiceLocator]#createInstanceFromAbstractFactory(): Adding subscriptions for ${instanceName} dependencies for their invalidations: ${holder.deps.join(
|
|
245
|
+
', ',
|
|
246
|
+
)}`,
|
|
247
|
+
)
|
|
248
|
+
holder.deps.forEach((dependency) => {
|
|
249
|
+
holder.destroyListeners.push(
|
|
250
|
+
this.eventBus.on(dependency, 'destroy', () =>
|
|
251
|
+
this.invalidate(instanceName),
|
|
252
|
+
),
|
|
253
|
+
)
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
if (holder.ttl === 0) {
|
|
257
|
+
// One time instance
|
|
258
|
+
await this.invalidate(instanceName)
|
|
259
|
+
}
|
|
260
|
+
await this.notifyListeners(instanceName)
|
|
261
|
+
return [undefined, instance as Instance]
|
|
262
|
+
})
|
|
263
|
+
.catch((error) => {
|
|
264
|
+
this.logger?.error(
|
|
265
|
+
`[ServiceLocator]#createInstanceFromAbstractFactory(): Error creating instance for ${instanceName}`,
|
|
266
|
+
error,
|
|
267
|
+
)
|
|
268
|
+
return [new UnknownError(error)]
|
|
269
|
+
}),
|
|
270
|
+
effects: [],
|
|
271
|
+
deps: [],
|
|
272
|
+
destroyListeners: [],
|
|
273
|
+
createdAt: Date.now(),
|
|
274
|
+
ttl: Infinity,
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (shouldStore) {
|
|
278
|
+
this.manager.set(instanceName, holder)
|
|
279
|
+
}
|
|
280
|
+
// @ts-expect-error TS2322 This is correct type
|
|
281
|
+
return holder.creationPromise
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
private createContextForAbstractFactory(
|
|
285
|
+
instanceName: string,
|
|
286
|
+
): ServiceLocatorAbstractFactoryContext {
|
|
287
|
+
const dependencies = new Set<string>()
|
|
288
|
+
const destroyListeners = new Set<() => void>()
|
|
289
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
290
|
+
const self = this
|
|
291
|
+
|
|
292
|
+
function invalidate(name = instanceName) {
|
|
293
|
+
return self.invalidate(name)
|
|
294
|
+
}
|
|
295
|
+
function addEffect(listener: () => void) {
|
|
296
|
+
destroyListeners.add(listener)
|
|
297
|
+
}
|
|
298
|
+
let ttl = Infinity
|
|
299
|
+
function setTtl(value: number) {
|
|
300
|
+
ttl = value
|
|
301
|
+
}
|
|
302
|
+
function getTtl() {
|
|
303
|
+
return ttl
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function on(key: string, event: string, listener: (event: string) => void) {
|
|
307
|
+
destroyListeners.add(self.eventBus.on(key, event, listener))
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
// @ts-expect-error This is correct type
|
|
312
|
+
async inject(token, args) {
|
|
313
|
+
let injectionToken = token
|
|
314
|
+
if (typeof token === 'function') {
|
|
315
|
+
injectionToken = getInjectableToken(token)
|
|
316
|
+
}
|
|
317
|
+
if (injectionToken instanceof InjectionToken) {
|
|
318
|
+
const validatedArgs = token.schema
|
|
319
|
+
? token.schema.safeParse(args)
|
|
320
|
+
: undefined
|
|
321
|
+
const instanceName = self.makeInstanceName(token, validatedArgs)
|
|
322
|
+
dependencies.add(instanceName)
|
|
323
|
+
// @ts-expect-error TS2322 This is correct type
|
|
324
|
+
return self.getOrThrowInstance(injectionToken, args)
|
|
325
|
+
}
|
|
326
|
+
throw new Error(
|
|
327
|
+
`[ServiceLocator]#inject(): Invalid token type: ${typeof token}. Expected a class or an InjectionToken.`,
|
|
328
|
+
)
|
|
329
|
+
},
|
|
330
|
+
invalidate,
|
|
331
|
+
eventBus: self.eventBus,
|
|
332
|
+
on: on as ServiceLocatorEventBus['on'],
|
|
333
|
+
getDependencies: () => Array.from(dependencies),
|
|
334
|
+
addEffect,
|
|
335
|
+
getDestroyListeners: () => Array.from(destroyListeners),
|
|
336
|
+
setTtl,
|
|
337
|
+
getTtl,
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
public getSyncInstance<Instance, Schema extends AnyZodObject | undefined>(
|
|
342
|
+
token: InjectionToken<Instance, Schema>,
|
|
343
|
+
args: Schema extends AnyZodObject ? z.input<Schema> : undefined,
|
|
344
|
+
): Instance | null {
|
|
345
|
+
const validatedArgs = token.schema
|
|
346
|
+
? token.schema.safeParse(args)
|
|
347
|
+
: undefined
|
|
348
|
+
if (validatedArgs && !validatedArgs.success) {
|
|
349
|
+
this.logger?.error(
|
|
350
|
+
`[ServiceLocator]#getInstance(): Error validating args for ${token.name.toString()}`,
|
|
351
|
+
validatedArgs.error,
|
|
352
|
+
)
|
|
353
|
+
throw new UnknownError(validatedArgs.error)
|
|
354
|
+
}
|
|
355
|
+
const instanceName = this.makeInstanceName(token, validatedArgs)
|
|
356
|
+
const [error, holder] = this.manager.get(instanceName)
|
|
357
|
+
if (error) {
|
|
358
|
+
return null
|
|
359
|
+
}
|
|
360
|
+
return holder.instance as Instance
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
invalidate(service: string, round = 1): Promise<any> {
|
|
364
|
+
this.logger?.log(
|
|
365
|
+
`[ServiceLocator]#invalidate(): Starting Invalidating process of ${service}`,
|
|
366
|
+
)
|
|
367
|
+
const toInvalidate = this.manager.filter(
|
|
368
|
+
(holder) => holder.name === service || holder.deps.includes(service),
|
|
369
|
+
)
|
|
370
|
+
const promises = []
|
|
371
|
+
for (const [key, holder] of toInvalidate.entries()) {
|
|
372
|
+
if (holder.status === ServiceLocatorInstanceHolderStatus.Destroying) {
|
|
373
|
+
this.logger?.trace(
|
|
374
|
+
`[ServiceLocator]#invalidate(): ${key} is already being destroyed`,
|
|
375
|
+
)
|
|
376
|
+
promises.push(holder.destroyPromise)
|
|
377
|
+
continue
|
|
378
|
+
}
|
|
379
|
+
if (holder.status === ServiceLocatorInstanceHolderStatus.Creating) {
|
|
380
|
+
this.logger?.trace(
|
|
381
|
+
`[ServiceLocator]#invalidate(): ${key} is being created, waiting for creation to finish`,
|
|
382
|
+
)
|
|
383
|
+
promises.push(
|
|
384
|
+
holder.creationPromise?.then(() => {
|
|
385
|
+
if (round > 3) {
|
|
386
|
+
this.logger?.error(
|
|
387
|
+
`[ServiceLocator]#invalidate(): ${key} creation is triggering a new invalidation round, but it is still not created`,
|
|
388
|
+
)
|
|
389
|
+
return
|
|
390
|
+
}
|
|
391
|
+
return this.invalidate(key, round + 1)
|
|
392
|
+
}),
|
|
393
|
+
)
|
|
394
|
+
continue
|
|
395
|
+
}
|
|
396
|
+
// @ts-expect-error TS2322 we are changing the status
|
|
397
|
+
holder.status = ServiceLocatorInstanceHolderStatus.Destroying
|
|
398
|
+
this.logger?.log(
|
|
399
|
+
`[ServiceLocator]#invalidate(): Invalidating ${key} and notifying listeners`,
|
|
400
|
+
)
|
|
401
|
+
// @ts-expect-error TS2322 we are changing the status
|
|
402
|
+
holder.destroyPromise = Promise.all(
|
|
403
|
+
holder.destroyListeners.map((listener) => listener()),
|
|
404
|
+
).then(async () => {
|
|
405
|
+
this.manager.delete(key)
|
|
406
|
+
await this.notifyListeners(key, 'destroy')
|
|
407
|
+
})
|
|
408
|
+
promises.push(holder.destroyPromise)
|
|
409
|
+
}
|
|
410
|
+
return Promise.all(promises)
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
async ready() {
|
|
414
|
+
return Promise.all(
|
|
415
|
+
Array.from(this.manager.filter(() => true)).map(([, holder]) => {
|
|
416
|
+
if (holder.status === ServiceLocatorInstanceHolderStatus.Creating) {
|
|
417
|
+
return holder.creationPromise?.then(() => null)
|
|
418
|
+
}
|
|
419
|
+
if (holder.status === ServiceLocatorInstanceHolderStatus.Destroying) {
|
|
420
|
+
return holder.destroyPromise.then(() => null)
|
|
421
|
+
}
|
|
422
|
+
return Promise.resolve(null)
|
|
423
|
+
}),
|
|
424
|
+
).then(() => null)
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
makeInstanceName(token: InjectionToken<any, any>, args: any) {
|
|
428
|
+
let stringifiedArgs = args
|
|
429
|
+
? ':' +
|
|
430
|
+
JSON.stringify(args)
|
|
431
|
+
.replaceAll(/"/g, '')
|
|
432
|
+
.replaceAll(/:/g, '=')
|
|
433
|
+
.replaceAll(/,/g, '|')
|
|
434
|
+
: ''
|
|
435
|
+
const { name } = token
|
|
436
|
+
if (typeof name === 'function') {
|
|
437
|
+
const className = name.name
|
|
438
|
+
return `${className}(${token.id})${stringifiedArgs}`
|
|
439
|
+
} else if (typeof name === 'symbol') {
|
|
440
|
+
return `${name.toString()}(${token.id})${stringifiedArgs}`
|
|
441
|
+
} else {
|
|
442
|
+
return `${name}(${token.id})${stringifiedArgs}`
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { AnyZodObject, z } from 'zod'
|
|
2
|
+
|
|
3
|
+
import type { ClassType } from './injection-token.mjs'
|
|
4
|
+
|
|
5
|
+
import { getInjectableToken } from './decorators/index.mjs'
|
|
6
|
+
import { InjectionToken } from './injection-token.mjs'
|
|
7
|
+
import { getServiceLocator } from './injector.mjs'
|
|
8
|
+
|
|
9
|
+
let promiseCollector: null | ((promise: Promise<any>) => void) = null
|
|
10
|
+
|
|
11
|
+
export function syncInject<T extends ClassType>(token: T): InstanceType<T>
|
|
12
|
+
export function syncInject<T, S extends AnyZodObject>(
|
|
13
|
+
token: InjectionToken<T, S>,
|
|
14
|
+
args: z.input<S>,
|
|
15
|
+
): T
|
|
16
|
+
export function syncInject<T>(token: InjectionToken<T, undefined>): T
|
|
17
|
+
export function syncInject<
|
|
18
|
+
T,
|
|
19
|
+
Token extends InjectionToken<T>,
|
|
20
|
+
S extends AnyZodObject | unknown = Token['schema'],
|
|
21
|
+
>(token: Token, args?: S extends AnyZodObject ? z.input<S> : never): T {
|
|
22
|
+
if (token.schema) {
|
|
23
|
+
const parsed = token.schema.safeParse(args)
|
|
24
|
+
if (!parsed.success) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
`[ServiceLocator] Invalid arguments for ${token.name.toString()}: ${parsed.error}`,
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
let realToken: InjectionToken<T, S> = token
|
|
31
|
+
if (!(token instanceof InjectionToken)) {
|
|
32
|
+
realToken = getInjectableToken(token) as InjectionToken<T, S>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const instance = getServiceLocator().getSyncInstance(realToken, args)
|
|
36
|
+
if (!instance) {
|
|
37
|
+
if (promiseCollector) {
|
|
38
|
+
const promise = getServiceLocator().getInstance(realToken, args)
|
|
39
|
+
promiseCollector(promise)
|
|
40
|
+
} else {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`[ServiceLocator] No instance found for ${realToken.name.toString()}`,
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return instance as unknown as T
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function setPromiseCollector(
|
|
50
|
+
collector: null | ((promise: Promise<any>) => void),
|
|
51
|
+
): typeof promiseCollector {
|
|
52
|
+
const original = promiseCollector
|
|
53
|
+
promiseCollector = collector
|
|
54
|
+
return original
|
|
55
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { FastifyInstance } from 'fastify'
|
|
2
|
+
import type { ZodTypeProvider } from 'fastify-type-provider-zod'
|
|
3
|
+
|
|
4
|
+
import type { ModuleMetadata } from '../metadata/index.mjs'
|
|
5
|
+
import type { ClassType } from '../service-locator/index.mjs'
|
|
6
|
+
|
|
7
|
+
import { HttpException } from '../exceptions/index.mjs'
|
|
8
|
+
import { extractControllerMetadata } from '../metadata/index.mjs'
|
|
9
|
+
import {
|
|
10
|
+
getServiceLocator,
|
|
11
|
+
inject,
|
|
12
|
+
Injectable,
|
|
13
|
+
syncInject,
|
|
14
|
+
} from '../service-locator/index.mjs'
|
|
15
|
+
import { ExecutionContextToken, Reply, Request } from '../tokens/index.mjs'
|
|
16
|
+
import { ExecutionContext } from './execution-context.mjs'
|
|
17
|
+
import { GuardRunnerService } from './guard-runner.service.mjs'
|
|
18
|
+
|
|
19
|
+
@Injectable()
|
|
20
|
+
export class ControllerAdapterService {
|
|
21
|
+
guardRunner = syncInject(GuardRunnerService)
|
|
22
|
+
|
|
23
|
+
setupController(
|
|
24
|
+
controller: ClassType,
|
|
25
|
+
instance: FastifyInstance,
|
|
26
|
+
moduleMetadata: ModuleMetadata,
|
|
27
|
+
): void {
|
|
28
|
+
const controllerMetadata = extractControllerMetadata(controller)
|
|
29
|
+
for (const endpoint of controllerMetadata.endpoints) {
|
|
30
|
+
const { classMethod, url, httpMethod, config } = endpoint
|
|
31
|
+
|
|
32
|
+
if (!url || !config) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`[Navios] Malformed Endpoint ${controller.name}:${classMethod}`,
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
const executionContext = new ExecutionContext(
|
|
38
|
+
moduleMetadata,
|
|
39
|
+
controllerMetadata,
|
|
40
|
+
endpoint,
|
|
41
|
+
)
|
|
42
|
+
const guards = this.guardRunner.makeContext(executionContext)
|
|
43
|
+
const { querySchema, requestSchema, responseSchema } = config
|
|
44
|
+
const schema: Record<string, any> = {}
|
|
45
|
+
if (querySchema) {
|
|
46
|
+
schema.querystring = querySchema
|
|
47
|
+
}
|
|
48
|
+
if (requestSchema) {
|
|
49
|
+
schema.body = requestSchema
|
|
50
|
+
}
|
|
51
|
+
if (responseSchema) {
|
|
52
|
+
schema.response = {
|
|
53
|
+
200: responseSchema,
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
instance.withTypeProvider<ZodTypeProvider>().route({
|
|
57
|
+
method: httpMethod,
|
|
58
|
+
url: url.replaceAll('$', ':'),
|
|
59
|
+
schema,
|
|
60
|
+
preHandler: async (request, reply) => {
|
|
61
|
+
if (guards.size > 0) {
|
|
62
|
+
getServiceLocator().registerInstance(Request, request)
|
|
63
|
+
getServiceLocator().registerInstance(Reply, reply)
|
|
64
|
+
getServiceLocator().registerInstance(
|
|
65
|
+
ExecutionContextToken,
|
|
66
|
+
executionContext,
|
|
67
|
+
)
|
|
68
|
+
executionContext.provideRequest(request)
|
|
69
|
+
executionContext.provideReply(reply)
|
|
70
|
+
const canActivate = await this.guardRunner.runGuards(
|
|
71
|
+
guards,
|
|
72
|
+
executionContext,
|
|
73
|
+
)
|
|
74
|
+
getServiceLocator().removeInstance(Request)
|
|
75
|
+
getServiceLocator().removeInstance(Reply)
|
|
76
|
+
getServiceLocator().removeInstance(ExecutionContextToken)
|
|
77
|
+
if (!canActivate) {
|
|
78
|
+
return reply
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
handler: async (request, reply) => {
|
|
83
|
+
getServiceLocator().registerInstance(Request, request)
|
|
84
|
+
getServiceLocator().registerInstance(Reply, reply)
|
|
85
|
+
getServiceLocator().registerInstance(
|
|
86
|
+
ExecutionContextToken,
|
|
87
|
+
executionContext,
|
|
88
|
+
)
|
|
89
|
+
executionContext.provideRequest(request)
|
|
90
|
+
executionContext.provideReply(reply)
|
|
91
|
+
const controllerInstance = await inject(controller)
|
|
92
|
+
try {
|
|
93
|
+
const { query, params, body } = request
|
|
94
|
+
const argument: Record<string, any> = {}
|
|
95
|
+
if (query && Object.keys(query).length > 0) {
|
|
96
|
+
argument.params = query
|
|
97
|
+
}
|
|
98
|
+
if (params && Object.keys(params).length > 0) {
|
|
99
|
+
argument.urlParams = params
|
|
100
|
+
}
|
|
101
|
+
if (body) {
|
|
102
|
+
argument.data = body
|
|
103
|
+
}
|
|
104
|
+
const result = await controllerInstance[classMethod](argument)
|
|
105
|
+
reply.status(200).send(result)
|
|
106
|
+
} catch (error) {
|
|
107
|
+
if (error instanceof HttpException) {
|
|
108
|
+
reply.status(error.statusCode).send(error.response)
|
|
109
|
+
} else {
|
|
110
|
+
reply.status(500).send({
|
|
111
|
+
message: 'Internal server error',
|
|
112
|
+
error: (error as Error).message,
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
} finally {
|
|
116
|
+
getServiceLocator().removeInstance(Request)
|
|
117
|
+
getServiceLocator().removeInstance(Reply)
|
|
118
|
+
getServiceLocator().removeInstance(ExecutionContextToken)
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { FastifyReply, FastifyRequest } from 'fastify'
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
ControllerMetadata,
|
|
5
|
+
EndpointMetadata,
|
|
6
|
+
ModuleMetadata,
|
|
7
|
+
} from '../metadata/index.mjs'
|
|
8
|
+
|
|
9
|
+
export class ExecutionContext {
|
|
10
|
+
private request: FastifyRequest | undefined
|
|
11
|
+
private reply: FastifyReply | undefined
|
|
12
|
+
constructor(
|
|
13
|
+
private readonly module: ModuleMetadata,
|
|
14
|
+
private readonly controller: ControllerMetadata,
|
|
15
|
+
private readonly handler: EndpointMetadata,
|
|
16
|
+
) {}
|
|
17
|
+
getModule(): ModuleMetadata {
|
|
18
|
+
return this.module
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getController(): ControllerMetadata {
|
|
22
|
+
return this.controller
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
getHandler(): EndpointMetadata {
|
|
26
|
+
return this.handler
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
getRequest(): FastifyRequest {
|
|
30
|
+
if (!this.request) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
'[Navios] Request is not set. Make sure to set it before using it.',
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
return this.request
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
getReply(): FastifyReply {
|
|
39
|
+
if (!this.reply) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
'[Navios] Reply is not set. Make sure to set it before using it.',
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
return this.reply
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
provideRequest(request: FastifyRequest): void {
|
|
48
|
+
this.request = request
|
|
49
|
+
}
|
|
50
|
+
provideReply(reply: FastifyReply): void {
|
|
51
|
+
this.reply = reply
|
|
52
|
+
}
|
|
53
|
+
}
|