@navios/core 0.1.2 → 0.1.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@navios/core",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "author": {
5
5
  "name": "Oleksandr Hanzha",
6
6
  "email": "alex@granted.name"
@@ -9,7 +9,6 @@ import {
9
9
  Injectable,
10
10
  InjectableType,
11
11
  InjectionToken,
12
- syncInject,
13
12
  } from '../service-locator/index.mjs'
14
13
  import { ConfigServiceInstance } from './config.service.mjs'
15
14
 
@@ -1,7 +1,11 @@
1
- import type { BaseEndpointConfig, EndpointFunctionArgs, HttpMethod } from '@navios/common'
1
+ import type {
2
+ BaseEndpointConfig,
3
+ EndpointFunctionArgs,
4
+ HttpMethod,
5
+ } from '@navios/common'
2
6
  import type { AnyZodObject, z, ZodType } from 'zod'
3
7
 
4
- import { getEndpointMetadata } from '../metadata/index.mjs'
8
+ import { EndpointType, getEndpointMetadata } from '../metadata/index.mjs'
5
9
 
6
10
  export type EndpointParams<
7
11
  EndpointDeclaration extends {
@@ -72,6 +76,7 @@ export function Endpoint<
72
76
  }
73
77
  // @ts-expect-error We don't need to set correctly in the metadata
74
78
  endpointMetadata.config = config
79
+ endpointMetadata.type = EndpointType.Config
75
80
  endpointMetadata.classMethod = target.name
76
81
  endpointMetadata.httpMethod = config.method
77
82
  endpointMetadata.url = config.url
@@ -0,0 +1,18 @@
1
+ import type { HttpHeader } from 'fastify/types/utils.js'
2
+
3
+ import { getEndpointMetadata } from '../metadata/index.mjs'
4
+
5
+ export function Header(name: HttpHeader, value: string | number | string[]) {
6
+ return <T extends Function>(
7
+ target: T,
8
+ context: ClassMethodDecoratorContext,
9
+ ) => {
10
+ if (context.kind !== 'method') {
11
+ throw new Error('[Navios] Header decorator can only be used on methods.')
12
+ }
13
+ const metadata = getEndpointMetadata(target, context)
14
+ metadata.headers[name] = value
15
+
16
+ return target
17
+ }
18
+ }
@@ -0,0 +1,18 @@
1
+ import { getEndpointMetadata } from '../metadata/index.mjs'
2
+
3
+ export function HttpCode(code: number) {
4
+ return <T extends Function>(
5
+ target: T,
6
+ context: ClassMethodDecoratorContext,
7
+ ) => {
8
+ if (context.kind !== 'method') {
9
+ throw new Error(
10
+ '[Navios] HttpCode decorator can only be used on methods.',
11
+ )
12
+ }
13
+ const metadata = getEndpointMetadata(target, context)
14
+ metadata.successStatusCode = code
15
+
16
+ return target
17
+ }
18
+ }
@@ -1,4 +1,6 @@
1
1
  export * from './controller.decorator.mjs'
2
2
  export * from './endpoint.decorator.mjs'
3
+ export * from './header.decorator.mjs'
4
+ export * from './http-code.decorator.mjs'
3
5
  export * from './module.decorator.mjs'
4
6
  export * from './use-guards.decorator.mjs'
@@ -184,7 +184,6 @@ export class LoggerInstance implements LoggerService {
184
184
  }
185
185
 
186
186
  static overrideLogger(logger: LoggerService | LogLevel[] | boolean) {
187
- console.log(logger)
188
187
  if (Array.isArray(logger)) {
189
188
  LoggerInstance.logLevels = logger
190
189
  return this.staticInstanceRef?.setLogLevels?.(logger)
@@ -20,8 +20,9 @@ export class PinoWrapper {
20
20
  this.logger.warn(message, ...optionalParams)
21
21
  }
22
22
 
23
- info(message: any, ...optionalParams: any[]) {
24
- this.logger.log(message, ...optionalParams)
23
+ info() {
24
+ // We don't want to populate the logs with the original fastify logs
25
+ // this.logger.debug?.('INFO', message, ...optionalParams)
25
26
  }
26
27
 
27
28
  debug(message: any, ...optionalParams: any[]) {
@@ -32,7 +33,7 @@ export class PinoWrapper {
32
33
  this.logger.verbose?.(message, ...optionalParams)
33
34
  }
34
35
 
35
- silent(message: any, ...optionalParams: any[]) {
36
+ silent() {
36
37
  // noop
37
38
  }
38
39
 
@@ -56,8 +57,8 @@ export class PinoWrapper {
56
57
  }
57
58
  const levels = LoggerInstance['logLevels']
58
59
  if (levels) {
59
- return levels[0]
60
+ return levels.find((level) => level !== 'verbose')
60
61
  }
61
- return 'info'
62
+ return 'warn'
62
63
  }
63
64
  }
@@ -1,4 +1,5 @@
1
1
  import type { BaseEndpointConfig, HttpMethod } from '@navios/common'
2
+ import type { HttpHeader } from 'fastify/types/utils.js'
2
3
 
3
4
  import type { CanActivate } from '../interfaces/index.mjs'
4
5
  import type {
@@ -8,9 +9,18 @@ import type {
8
9
 
9
10
  export const EndpointMetadataKey = Symbol('EndpointMetadataKey')
10
11
 
12
+ export enum EndpointType {
13
+ Unknown = 'unknown',
14
+ Config = 'config',
15
+ Handler = 'handler',
16
+ }
17
+
11
18
  export interface EndpointMetadata {
12
19
  classMethod: string
13
20
  url: string
21
+ successStatusCode: number
22
+ type: EndpointType
23
+ headers: Partial<Record<HttpHeader, number | string | string[] | undefined>>
14
24
  httpMethod: HttpMethod
15
25
  config: BaseEndpointConfig | null
16
26
  guards: Set<
@@ -52,6 +62,9 @@ export function getEndpointMetadata(
52
62
  const newMetadata: EndpointMetadata = {
53
63
  classMethod: target.name,
54
64
  url: '',
65
+ successStatusCode: 200,
66
+ headers: {},
67
+ type: EndpointType.Unknown,
55
68
  httpMethod: 'GET',
56
69
  config: null,
57
70
  guards: new Set<
@@ -16,6 +16,7 @@ import type { NaviosModule } from './interfaces/index.mjs'
16
16
  import type { LoggerService, LogLevel } from './logger/index.mjs'
17
17
  import type { ClassTypeWithInstance } from './service-locator/index.mjs'
18
18
 
19
+ import { HttpException } from './exceptions/index.mjs'
19
20
  import { Logger, PinoWrapper } from './logger/index.mjs'
20
21
  import {
21
22
  getServiceLocator,
@@ -68,6 +69,7 @@ export class NaviosApplication {
68
69
  }
69
70
  await this.moduleLoader.loadModules(this.appModule)
70
71
  this.server = await this.getFastifyInstance(this.options)
72
+ this.configureFastifyInstance(this.server)
71
73
  getServiceLocator().registerInstance(Application, this.server)
72
74
  // Add schema validator and serializer
73
75
  this.server.setValidatorCompiler(validatorCompiler)
@@ -78,6 +80,7 @@ export class NaviosApplication {
78
80
  }
79
81
 
80
82
  await this.initModules()
83
+ await this.server.ready()
81
84
 
82
85
  this.logger.debug('Navios application initialized')
83
86
  }
@@ -110,6 +113,37 @@ export class NaviosApplication {
110
113
  }
111
114
  }
112
115
 
116
+ private configureFastifyInstance(fastifyInstance: FastifyInstance) {
117
+ fastifyInstance.setErrorHandler((error, request, reply) => {
118
+ if (error instanceof HttpException) {
119
+ return reply.status(error.statusCode).send(error.response)
120
+ } else {
121
+ const statusCode = error.statusCode || 500
122
+ const message = error.message || 'Internal Server Error'
123
+ const response = {
124
+ statusCode,
125
+ message,
126
+ error: error.name || 'InternalServerError',
127
+ }
128
+ this.logger.error(
129
+ `Error occurred: ${error.message} on ${request.url}`,
130
+ error,
131
+ )
132
+ return reply.status(statusCode).send(response)
133
+ }
134
+ })
135
+
136
+ fastifyInstance.setNotFoundHandler((req, reply) => {
137
+ const response = {
138
+ statusCode: 404,
139
+ message: 'Not Found',
140
+ error: 'NotFound',
141
+ }
142
+ this.logger.error(`Route not found: ${req.url}`)
143
+ return reply.status(404).send(response)
144
+ })
145
+ }
146
+
113
147
  private async initModules() {
114
148
  const modules = this.moduleLoader.getAllModules()
115
149
  const promises: PromiseLike<any>[] = []
@@ -161,6 +195,7 @@ export class NaviosApplication {
161
195
  if (!this.server) {
162
196
  throw new Error('Server is not initialized. Call init() first.')
163
197
  }
164
- await this.server.listen(options)
198
+ const res = await this.server.listen(options)
199
+ this.logger.debug(`Navios is listening on ${res}`)
165
200
  }
166
201
  }
@@ -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
  }