@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.
Files changed (72) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +145 -0
  3. package/dist/index.d.mts +425 -0
  4. package/dist/index.d.ts +425 -0
  5. package/dist/index.js +1744 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/index.mjs +1657 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/package.json +45 -0
  10. package/src/__tests__/controller.spec.mts +59 -0
  11. package/src/attribute.factory.mts +155 -0
  12. package/src/decorators/controller.decorator.mts +36 -0
  13. package/src/decorators/endpoint.decorator.mts +81 -0
  14. package/src/decorators/index.mts +4 -0
  15. package/src/decorators/module.decorator.mts +47 -0
  16. package/src/decorators/use-guards.decorator.mts +43 -0
  17. package/src/exceptions/bad-request.exception.mts +7 -0
  18. package/src/exceptions/conflict.exception.mts +7 -0
  19. package/src/exceptions/forbidden.exception.mts +7 -0
  20. package/src/exceptions/http.exception.mts +7 -0
  21. package/src/exceptions/index.mts +7 -0
  22. package/src/exceptions/internal-server-error.exception.mts +7 -0
  23. package/src/exceptions/not-found.exception.mts +10 -0
  24. package/src/exceptions/unauthorized.exception.mts +7 -0
  25. package/src/index.mts +10 -0
  26. package/src/interfaces/can-activate.mts +5 -0
  27. package/src/interfaces/index.mts +2 -0
  28. package/src/interfaces/navios-module.mts +3 -0
  29. package/src/metadata/controller.metadata.mts +71 -0
  30. package/src/metadata/endpoint.metadata.mts +69 -0
  31. package/src/metadata/index.mts +4 -0
  32. package/src/metadata/injectable.metadata.mts +11 -0
  33. package/src/metadata/module.metadata.mts +62 -0
  34. package/src/navios.application.mts +113 -0
  35. package/src/navios.factory.mts +17 -0
  36. package/src/service-locator/__tests__/injectable.spec.mts +170 -0
  37. package/src/service-locator/decorators/get-injectable-token.mts +23 -0
  38. package/src/service-locator/decorators/index.mts +2 -0
  39. package/src/service-locator/decorators/injectable.decorator.mts +103 -0
  40. package/src/service-locator/enums/index.mts +1 -0
  41. package/src/service-locator/enums/injectable-scope.enum.mts +10 -0
  42. package/src/service-locator/errors/errors.enum.mts +7 -0
  43. package/src/service-locator/errors/factory-not-found.mts +8 -0
  44. package/src/service-locator/errors/index.mts +6 -0
  45. package/src/service-locator/errors/instance-destroying.mts +8 -0
  46. package/src/service-locator/errors/instance-expired.mts +8 -0
  47. package/src/service-locator/errors/instance-not-found.mts +8 -0
  48. package/src/service-locator/errors/unknown-error.mts +15 -0
  49. package/src/service-locator/event-emitter.mts +107 -0
  50. package/src/service-locator/index.mts +14 -0
  51. package/src/service-locator/inject.mts +37 -0
  52. package/src/service-locator/injection-token.mts +36 -0
  53. package/src/service-locator/injector.mts +18 -0
  54. package/src/service-locator/interfaces/factory.interface.mts +3 -0
  55. package/src/service-locator/override.mts +22 -0
  56. package/src/service-locator/proxy-service-locator.mts +80 -0
  57. package/src/service-locator/service-locator-abstract-factory-context.mts +23 -0
  58. package/src/service-locator/service-locator-event-bus.mts +96 -0
  59. package/src/service-locator/service-locator-instance-holder.mts +63 -0
  60. package/src/service-locator/service-locator-manager.mts +89 -0
  61. package/src/service-locator/service-locator.mts +445 -0
  62. package/src/service-locator/sync-injector.mts +55 -0
  63. package/src/services/controller-adapter.service.mts +124 -0
  64. package/src/services/execution-context.mts +53 -0
  65. package/src/services/guard-runner.service.mts +93 -0
  66. package/src/services/index.mts +4 -0
  67. package/src/services/module-loader.service.mts +68 -0
  68. package/src/tokens/application.token.mts +9 -0
  69. package/src/tokens/execution-context.token.mts +8 -0
  70. package/src/tokens/index.mts +4 -0
  71. package/src/tokens/reply.token.mts +7 -0
  72. 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
+ }