@navios/core 0.1.2 → 0.1.4

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.
@@ -0,0 +1,46 @@
1
+ import { NaviosException } from '@navios/common'
2
+
3
+ import type { ClassType } from './injection-token.mjs'
4
+ import type { ServiceLocatorAbstractFactoryContext } from './service-locator-abstract-factory-context.mjs'
5
+
6
+ import { getServiceLocator, provideServiceLocator } from './injector.mjs'
7
+ import { makeProxyServiceLocator } from './proxy-service-locator.mjs'
8
+ import { setPromiseCollector } from './sync-injector.mjs'
9
+
10
+ export async function resolveService<T extends ClassType>(
11
+ ctx: ServiceLocatorAbstractFactoryContext,
12
+ target: T,
13
+ args: any[] = [],
14
+ ): Promise<InstanceType<T>> {
15
+ const proxyServiceLocator = makeProxyServiceLocator(getServiceLocator(), ctx)
16
+ let promises: Promise<any>[] = []
17
+ const promiseCollector = (promise: Promise<any>) => {
18
+ promises.push(promise)
19
+ }
20
+ const originalPromiseCollector = setPromiseCollector(promiseCollector)
21
+ const tryLoad = () => {
22
+ const original = provideServiceLocator(proxyServiceLocator)
23
+ let result = new target(...args)
24
+ provideServiceLocator(original)
25
+ return result
26
+ }
27
+ let instance = tryLoad()
28
+ setPromiseCollector(originalPromiseCollector)
29
+ if (promises.length > 0) {
30
+ await Promise.all(promises)
31
+ promises = []
32
+ instance = tryLoad()
33
+ }
34
+ if (promises.length > 0) {
35
+ console.error(`[ServiceLocator] ${target.name} has problem with it's definition.
36
+
37
+ One or more of the dependencies are registered as a InjectableScope.Instance and are used with syncInject.
38
+
39
+ Please use inject instead of syncInject to load those dependencies.`)
40
+ throw new NaviosException(
41
+ `[ServiceLocator] Service ${target.name} cannot be instantiated.`,
42
+ )
43
+ }
44
+
45
+ return instance
46
+ }
@@ -6,8 +6,17 @@ import type { ServiceLocatorAbstractFactoryContext } from './service-locator-abs
6
6
  import type { ServiceLocatorInstanceHolder } from './service-locator-instance-holder.mjs'
7
7
 
8
8
  import { InjectableScope } from './enums/index.mjs'
9
- import { ErrorsEnum, FactoryNotFound, UnknownError } from './errors/index.mjs'
10
- import { getInjectableToken } from './index.mjs'
9
+ import {
10
+ ErrorsEnum,
11
+ FactoryNotFound,
12
+ FactoryTokenNotResolved,
13
+ UnknownError,
14
+ } from './errors/index.mjs'
15
+ import {
16
+ BoundInjectionToken,
17
+ FactoryInjectionToken,
18
+ getInjectableToken,
19
+ } from './index.mjs'
11
20
  import { InjectionToken } from './injection-token.mjs'
12
21
  import { ServiceLocatorEventBus } from './service-locator-event-bus.mjs'
13
22
  import {
@@ -86,7 +95,7 @@ export class ServiceLocator {
86
95
  }
87
96
  }
88
97
 
89
- public getInstanceIdentifier<
98
+ private resolveTokenArgs<
90
99
  Instance,
91
100
  Schema extends AnyZodObject | ZodOptional<AnyZodObject> | undefined,
92
101
  >(
@@ -96,18 +105,57 @@ export class ServiceLocator {
96
105
  : Schema extends ZodOptional<AnyZodObject>
97
106
  ? z.input<Schema> | undefined
98
107
  : undefined,
99
- ): string {
100
- const validatedArgs = token.schema
101
- ? token.schema.safeParse(args)
102
- : undefined
108
+ ):
109
+ | [
110
+ undefined,
111
+ Schema extends AnyZodObject
112
+ ? z.input<Schema>
113
+ : Schema extends ZodOptional<AnyZodObject>
114
+ ? z.input<Schema> | undefined
115
+ : undefined,
116
+ ]
117
+ | [FactoryTokenNotResolved | UnknownError] {
118
+ let realArgs = args
119
+ if (token instanceof BoundInjectionToken) {
120
+ realArgs = token.value
121
+ } else if (token instanceof FactoryInjectionToken) {
122
+ if (token.resolved) {
123
+ realArgs = token.value
124
+ } else {
125
+ return [new FactoryTokenNotResolved(token.name)]
126
+ }
127
+ }
128
+ if (!token.schema) {
129
+ return [undefined, realArgs]
130
+ }
131
+ const validatedArgs = token.schema?.safeParse(realArgs)
103
132
  if (validatedArgs && !validatedArgs.success) {
104
133
  this.logger?.error(
105
134
  `[ServiceLocator]#getInstance(): Error validating args for ${token.name.toString()}`,
106
135
  validatedArgs.error,
107
136
  )
108
- throw new UnknownError(validatedArgs.error)
137
+ return [new UnknownError(validatedArgs.error)]
138
+ }
139
+ // @ts-expect-error We return correct type
140
+ return [undefined, validatedArgs?.data]
141
+ }
142
+
143
+ public getInstanceIdentifier<
144
+ Instance,
145
+ Schema extends AnyZodObject | ZodOptional<AnyZodObject> | undefined,
146
+ >(
147
+ token: InjectionToken<Instance, Schema>,
148
+ args: Schema extends AnyZodObject
149
+ ? z.input<Schema>
150
+ : Schema extends ZodOptional<AnyZodObject>
151
+ ? z.input<Schema> | undefined
152
+ : undefined,
153
+ ): string {
154
+ const [err, realArgs] = this.resolveTokenArgs(token, args)
155
+ if (err) {
156
+ throw err
109
157
  }
110
- return this.makeInstanceName(token, validatedArgs)
158
+ return this.makeInstanceName(token, realArgs)
111
159
  }
112
160
 
113
161
  public async getInstance<
@@ -121,17 +169,17 @@ export class ServiceLocator {
121
169
  ? z.input<Schema> | undefined
122
170
  : undefined,
123
171
  ): Promise<[undefined, Instance] | [UnknownError | FactoryNotFound]> {
124
- const validatedArgs = token.schema
125
- ? token.schema.safeParse(args)
126
- : undefined
127
- if (validatedArgs && !validatedArgs.success) {
128
- this.logger?.error(
129
- `[ServiceLocator]#getInstance(): Error validating args for ${token.name.toString()}`,
130
- validatedArgs.error,
131
- )
132
- return [new UnknownError(validatedArgs.error)]
172
+ const [err, realArgs] = this.resolveTokenArgs(token, args)
173
+ if (err instanceof UnknownError) {
174
+ throw err
175
+ } else if (
176
+ err instanceof FactoryTokenNotResolved &&
177
+ token instanceof FactoryInjectionToken
178
+ ) {
179
+ await token.resolve()
180
+ return this.getInstance(token, args)
133
181
  }
134
- const instanceName = this.makeInstanceName(token, validatedArgs)
182
+ const instanceName = this.makeInstanceName(token, realArgs)
135
183
  const [error, holder] = this.manager.get(instanceName)
136
184
  if (!error) {
137
185
  if (holder.status === ServiceLocatorInstanceHolderStatus.Creating) {
@@ -167,7 +215,8 @@ export class ServiceLocator {
167
215
  default:
168
216
  return [error]
169
217
  }
170
- return this.createInstance(instanceName, token, args)
218
+ // @ts-expect-error TS2322 It's validated
219
+ return this.createInstance(instanceName, token, realArgs)
171
220
  }
172
221
 
173
222
  public async getOrThrowInstance<
@@ -213,13 +262,22 @@ export class ServiceLocator {
213
262
  this.logger?.log(
214
263
  `[ServiceLocator]#createInstance() Creating instance for ${instanceName}`,
215
264
  )
265
+ let realToken =
266
+ token instanceof BoundInjectionToken ||
267
+ token instanceof FactoryInjectionToken
268
+ ? token.token
269
+ : token
216
270
  if (
217
- this.abstractFactories.has(token) ||
218
- this.instanceFactories.has(token)
271
+ this.abstractFactories.has(realToken) ||
272
+ this.instanceFactories.has(realToken)
219
273
  ) {
220
- return this.createInstanceFromAbstractFactory(instanceName, token, args)
274
+ return this.createInstanceFromAbstractFactory(
275
+ instanceName,
276
+ realToken,
277
+ args,
278
+ )
221
279
  } else {
222
- return [new FactoryNotFound(token.name.toString())]
280
+ return [new FactoryNotFound(realToken.name.toString())]
223
281
  }
224
282
  }
225
283
 
@@ -276,7 +334,7 @@ export class ServiceLocator {
276
334
  )
277
335
  })
278
336
  }
279
- if (holder.ttl === 0) {
337
+ if (holder.ttl === 0 || !shouldStore) {
280
338
  // One time instance
281
339
  await this.invalidate(instanceName)
282
340
  }
@@ -371,17 +429,11 @@ export class ServiceLocator {
371
429
  ? z.input<Schema> | undefined
372
430
  : undefined,
373
431
  ): Instance | null {
374
- const validatedArgs = token.schema
375
- ? token.schema.safeParse(args)
376
- : undefined
377
- if (validatedArgs && !validatedArgs.success) {
378
- this.logger?.error(
379
- `[ServiceLocator]#getInstance(): Error validating args for ${token.name.toString()}`,
380
- validatedArgs.error,
381
- )
382
- throw new UnknownError(validatedArgs.error)
432
+ const [err, realArgs] = this.resolveTokenArgs(token, args)
433
+ if (err) {
434
+ return null
383
435
  }
384
- const instanceName = this.makeInstanceName(token, validatedArgs)
436
+ const instanceName = this.makeInstanceName(token, realArgs)
385
437
  const [error, holder] = this.manager.get(instanceName)
386
438
  if (error) {
387
439
  return null
@@ -24,14 +24,6 @@ export function syncInject<
24
24
  Token extends InjectionToken<T>,
25
25
  S extends AnyZodObject | unknown = Token['schema'],
26
26
  >(token: Token, args?: S extends AnyZodObject ? z.input<S> : never): T {
27
- if (token.schema) {
28
- const parsed = token.schema.safeParse(args)
29
- if (!parsed.success) {
30
- throw new Error(
31
- `[ServiceLocator] Invalid arguments for ${token.name.toString()}: ${parsed.error}`,
32
- )
33
- }
34
- }
35
27
  let realToken: InjectionToken<T, S> = token
36
28
  if (!(token instanceof InjectionToken)) {
37
29
  realToken = getInjectableToken(token) as InjectionToken<T, S>
@@ -1,12 +1,13 @@
1
- import type { FastifyInstance } from 'fastify'
1
+ import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'
2
2
  import type { ZodTypeProvider } from 'fastify-type-provider-zod'
3
3
 
4
- import type { ModuleMetadata } from '../metadata/index.mjs'
4
+ import { NaviosException } from '@navios/common'
5
+
6
+ import type { EndpointMetadata, ModuleMetadata } from '../metadata/index.mjs'
5
7
  import type { ClassType } from '../service-locator/index.mjs'
6
8
 
7
- import { HttpException } from '../exceptions/index.mjs'
8
9
  import { Logger } from '../logger/index.mjs'
9
- import { extractControllerMetadata } from '../metadata/index.mjs'
10
+ import { EndpointType, extractControllerMetadata } from '../metadata/index.mjs'
10
11
  import {
11
12
  getServiceLocator,
12
13
  inject,
@@ -31,9 +32,9 @@ export class ControllerAdapterService {
31
32
  ): void {
32
33
  const controllerMetadata = extractControllerMetadata(controller)
33
34
  for (const endpoint of controllerMetadata.endpoints) {
34
- const { classMethod, url, httpMethod, config } = endpoint
35
+ const { classMethod, url, httpMethod } = endpoint
35
36
 
36
- if (!url || !config) {
37
+ if (!url) {
37
38
  throw new Error(
38
39
  `[Navios] Malformed Endpoint ${controller.name}:${classMethod}`,
39
40
  )
@@ -43,47 +44,24 @@ export class ControllerAdapterService {
43
44
  controllerMetadata,
44
45
  endpoint,
45
46
  )
46
- const guards = this.guardRunner.makeContext(executionContext)
47
- const { querySchema, requestSchema, responseSchema } = config
48
- const schema: Record<string, any> = {}
49
- if (querySchema) {
50
- schema.querystring = querySchema
51
- }
52
- if (requestSchema) {
53
- schema.body = requestSchema
54
- }
55
- if (responseSchema) {
56
- schema.response = {
57
- 200: responseSchema,
58
- }
59
- }
60
47
  instance.withTypeProvider<ZodTypeProvider>().route({
61
48
  method: httpMethod,
62
49
  url: url.replaceAll('$', ':'),
63
- schema,
64
- preHandler: async (request, reply) => {
65
- if (guards.size > 0) {
66
- getServiceLocator().registerInstance(Request, request)
67
- getServiceLocator().registerInstance(Reply, reply)
68
- getServiceLocator().registerInstance(
69
- ExecutionContextToken,
70
- executionContext,
71
- )
72
- executionContext.provideRequest(request)
73
- executionContext.provideReply(reply)
74
- const canActivate = await this.guardRunner.runGuards(
75
- guards,
76
- executionContext,
77
- )
78
- getServiceLocator().removeInstance(Request)
79
- getServiceLocator().removeInstance(Reply)
80
- getServiceLocator().removeInstance(ExecutionContextToken)
81
- if (!canActivate) {
82
- return reply
83
- }
84
- }
85
- },
86
- handler: async (request, reply) => {
50
+ schema: this.provideSchemaForConfig(endpoint),
51
+ preHandler: this.providePreHandler(executionContext),
52
+ handler: this.provideHandler(controller, executionContext, endpoint),
53
+ })
54
+
55
+ this.logger.debug(
56
+ `Registered ${httpMethod} ${url} for ${controller.name}:${classMethod}`,
57
+ )
58
+ }
59
+ }
60
+
61
+ providePreHandler(executionContext: ExecutionContext) {
62
+ const guards = this.guardRunner.makeContext(executionContext)
63
+ return guards.size > 0
64
+ ? async (request: FastifyRequest, reply: FastifyReply) => {
87
65
  getServiceLocator().registerInstance(Request, request)
88
66
  getServiceLocator().registerInstance(Reply, reply)
89
67
  getServiceLocator().registerInstance(
@@ -92,41 +70,108 @@ export class ControllerAdapterService {
92
70
  )
93
71
  executionContext.provideRequest(request)
94
72
  executionContext.provideReply(reply)
95
- const controllerInstance = await inject(controller)
73
+ let canActivate = true
96
74
  try {
97
- const { query, params, body } = request
98
- const argument: Record<string, any> = {}
99
- if (query && Object.keys(query).length > 0) {
100
- argument.params = query
101
- }
102
- if (params && Object.keys(params).length > 0) {
103
- argument.urlParams = params
104
- }
105
- if (body) {
106
- argument.data = body
107
- }
108
- const result = await controllerInstance[classMethod](argument)
109
- reply.status(200).send(result)
110
- } catch (error) {
111
- if (error instanceof HttpException) {
112
- reply.status(error.statusCode).send(error.response)
113
- } else {
114
- reply.status(500).send({
115
- message: 'Internal server error',
116
- error: (error as Error).message,
117
- })
118
- }
75
+ canActivate = await this.guardRunner.runGuards(
76
+ guards,
77
+ executionContext,
78
+ )
119
79
  } finally {
120
80
  getServiceLocator().removeInstance(Request)
121
81
  getServiceLocator().removeInstance(Reply)
122
82
  getServiceLocator().removeInstance(ExecutionContextToken)
123
83
  }
124
- },
125
- })
84
+ if (!canActivate) {
85
+ return reply
86
+ }
87
+ }
88
+ : undefined
89
+ }
126
90
 
127
- this.logger.debug(
128
- `Registered ${httpMethod} ${url} for ${controller.name}:${classMethod}`,
91
+ private provideSchemaForConfig(endpointMetadata: EndpointMetadata) {
92
+ if (!endpointMetadata.config) {
93
+ this.logger.warn(`No config found for endpoint ${endpointMetadata.url}`)
94
+ return {}
95
+ }
96
+ const { querySchema, requestSchema, responseSchema } =
97
+ endpointMetadata.config
98
+ const schema: Record<string, any> = {}
99
+ if (querySchema) {
100
+ schema.querystring = querySchema
101
+ }
102
+ if (requestSchema) {
103
+ schema.body = requestSchema
104
+ }
105
+ if (responseSchema) {
106
+ schema.response = {
107
+ 200: responseSchema,
108
+ }
109
+ }
110
+
111
+ return schema
112
+ }
113
+
114
+ private provideHandler(
115
+ controller: ClassType,
116
+ executionContext: ExecutionContext,
117
+ endpointMetadata: EndpointMetadata,
118
+ ): (request: FastifyRequest, reply: FastifyReply) => Promise<void> {
119
+ switch (endpointMetadata.type) {
120
+ case EndpointType.Unknown:
121
+ this.logger.error(
122
+ `Unknown endpoint type ${endpointMetadata.type} for ${controller.name}:${endpointMetadata.classMethod}`,
123
+ )
124
+ throw new NaviosException('Unknown endpoint type')
125
+ case EndpointType.Config:
126
+ return this.provideHandlerForConfig(
127
+ controller,
128
+ executionContext,
129
+ endpointMetadata,
130
+ )
131
+ case EndpointType.Handler:
132
+ this.logger.error('Not implemented yet')
133
+ throw new NaviosException('Not implemented yet')
134
+ }
135
+ }
136
+
137
+ private provideHandlerForConfig(
138
+ controller: ClassType,
139
+ executionContext: ExecutionContext,
140
+ endpointMetadata: EndpointMetadata,
141
+ ): (request: FastifyRequest, reply: FastifyReply) => Promise<void> {
142
+ return async (request, reply) => {
143
+ getServiceLocator().registerInstance(Request, request)
144
+ getServiceLocator().registerInstance(Reply, reply)
145
+ getServiceLocator().registerInstance(
146
+ ExecutionContextToken,
147
+ executionContext,
129
148
  )
149
+ executionContext.provideRequest(request)
150
+ executionContext.provideReply(reply)
151
+ const controllerInstance = await inject(controller)
152
+ try {
153
+ const { query, params, body } = request
154
+ const argument: Record<string, any> = {}
155
+ if (query && Object.keys(query).length > 0) {
156
+ argument.params = query
157
+ }
158
+ if (params && Object.keys(params).length > 0) {
159
+ argument.urlParams = params
160
+ }
161
+ if (body) {
162
+ argument.data = body
163
+ }
164
+ const result =
165
+ await controllerInstance[endpointMetadata.classMethod](argument)
166
+ reply
167
+ .status(endpointMetadata.successStatusCode)
168
+ .headers(endpointMetadata.headers)
169
+ .send(result)
170
+ } finally {
171
+ getServiceLocator().removeInstance(Request)
172
+ getServiceLocator().removeInstance(Reply)
173
+ getServiceLocator().removeInstance(ExecutionContextToken)
174
+ }
130
175
  }
131
176
  }
132
177
  }