@navios/core 0.1.8 → 0.1.10

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.8",
3
+ "version": "0.1.10",
4
4
  "author": {
5
5
  "name": "Oleksandr Hanzha",
6
6
  "email": "alex@granted.name"
@@ -15,7 +15,7 @@
15
15
  "main": "./dist/index.js",
16
16
  "module": "./dist/index.mjs",
17
17
  "peerDependencies": {
18
- "@navios/common": "^0.1.1",
18
+ "@navios/common": "^0.1.3",
19
19
  "zod": "^3.23.8"
20
20
  },
21
21
  "exports": {
@@ -32,7 +32,8 @@
32
32
  "./package.json": "./package.json"
33
33
  },
34
34
  "devDependencies": {
35
- "@navios/common": "^0.1.1",
35
+ "@fastify/multipart": "^9.0.3",
36
+ "@navios/common": "^0.1.3",
36
37
  "tsx": "^4.19.4",
37
38
  "typescript": "^5.8.3",
38
39
  "zod": "^3.24.4"
@@ -90,7 +90,7 @@ export function Endpoint<
90
90
  }
91
91
  // @ts-expect-error We don't need to set correctly in the metadata
92
92
  endpointMetadata.config = config
93
- endpointMetadata.type = EndpointType.Config
93
+ endpointMetadata.type = EndpointType.Endpoint
94
94
  endpointMetadata.classMethod = target.name
95
95
  endpointMetadata.httpMethod = config.method
96
96
  endpointMetadata.url = config.url
@@ -1,6 +1,6 @@
1
1
  import type { HttpHeader } from 'fastify/types/utils.js'
2
2
 
3
- import { getEndpointMetadata } from '../metadata/index.mjs'
3
+ import { EndpointType, getEndpointMetadata } from '../metadata/index.mjs'
4
4
 
5
5
  export function Header(name: HttpHeader, value: string | number | string[]) {
6
6
  return <T extends Function>(
@@ -11,6 +11,12 @@ export function Header(name: HttpHeader, value: string | number | string[]) {
11
11
  throw new Error('[Navios] Header decorator can only be used on methods.')
12
12
  }
13
13
  const metadata = getEndpointMetadata(target, context)
14
+ if (metadata.type === EndpointType.Stream) {
15
+ throw new Error(
16
+ '[Navios] HttpCode decorator cannot be used on stream endpoints.',
17
+ )
18
+ }
19
+
14
20
  metadata.headers[name] = value
15
21
 
16
22
  return target
@@ -1,4 +1,4 @@
1
- import { getEndpointMetadata } from '../metadata/index.mjs'
1
+ import { EndpointType, getEndpointMetadata } from '../metadata/index.mjs'
2
2
 
3
3
  export function HttpCode(code: number) {
4
4
  return <T extends Function>(
@@ -11,6 +11,11 @@ export function HttpCode(code: number) {
11
11
  )
12
12
  }
13
13
  const metadata = getEndpointMetadata(target, context)
14
+ if (metadata.type === EndpointType.Stream) {
15
+ throw new Error(
16
+ '[Navios] HttpCode decorator cannot be used on stream endpoints.',
17
+ )
18
+ }
14
19
  metadata.successStatusCode = code
15
20
 
16
21
  return target
@@ -3,4 +3,6 @@ export * from './endpoint.decorator.mjs'
3
3
  export * from './header.decorator.mjs'
4
4
  export * from './http-code.decorator.mjs'
5
5
  export * from './module.decorator.mjs'
6
+ export * from './multipart.decorator.mjs'
7
+ export * from './stream.decorator.mjs'
6
8
  export * from './use-guards.decorator.mjs'
@@ -0,0 +1,100 @@
1
+ import type {
2
+ BaseEndpointConfig,
3
+ EndpointFunctionArgs,
4
+ HttpMethod,
5
+ } from '@navios/common'
6
+ import type { AnyZodObject, z, ZodType } from 'zod'
7
+
8
+ import { ZodDiscriminatedUnion } from 'zod'
9
+
10
+ import { EndpointType, getEndpointMetadata } from '../metadata/index.mjs'
11
+
12
+ export type MultipartParams<
13
+ EndpointDeclaration extends {
14
+ config: BaseEndpointConfig<any, any, any, any, any>
15
+ },
16
+ Url extends string = EndpointDeclaration['config']['url'],
17
+ QuerySchema = EndpointDeclaration['config']['querySchema'],
18
+ > = QuerySchema extends AnyZodObject
19
+ ? EndpointDeclaration['config']['requestSchema'] extends ZodType
20
+ ? EndpointFunctionArgs<
21
+ Url,
22
+ QuerySchema,
23
+ EndpointDeclaration['config']['requestSchema']
24
+ >
25
+ : EndpointFunctionArgs<Url, QuerySchema, undefined>
26
+ : EndpointDeclaration['config']['requestSchema'] extends ZodType
27
+ ? EndpointFunctionArgs<
28
+ Url,
29
+ undefined,
30
+ EndpointDeclaration['config']['requestSchema']
31
+ >
32
+ : EndpointFunctionArgs<Url, undefined, undefined>
33
+
34
+ export type MultipartResult<
35
+ EndpointDeclaration extends {
36
+ config: BaseEndpointConfig<any, any, any, any, any>
37
+ },
38
+ > =
39
+ EndpointDeclaration['config']['responseSchema'] extends ZodDiscriminatedUnion<
40
+ any,
41
+ infer Options
42
+ >
43
+ ? Promise<z.input<Options[number]>>
44
+ : Promise<z.input<EndpointDeclaration['config']['responseSchema']>>
45
+
46
+ export function Multipart<
47
+ Method extends HttpMethod = HttpMethod,
48
+ Url extends string = string,
49
+ QuerySchema = undefined,
50
+ ResponseSchema extends ZodType = ZodType,
51
+ RequestSchema = ZodType,
52
+ >(endpoint: {
53
+ config: BaseEndpointConfig<
54
+ Method,
55
+ Url,
56
+ QuerySchema,
57
+ ResponseSchema,
58
+ RequestSchema
59
+ >
60
+ }) {
61
+ return (
62
+ target: (
63
+ params: QuerySchema extends AnyZodObject
64
+ ? RequestSchema extends ZodType
65
+ ? EndpointFunctionArgs<Url, QuerySchema, RequestSchema>
66
+ : EndpointFunctionArgs<Url, QuerySchema, undefined>
67
+ : RequestSchema extends ZodType
68
+ ? EndpointFunctionArgs<Url, undefined, RequestSchema>
69
+ : EndpointFunctionArgs<Url, undefined, undefined>,
70
+ ) => Promise<z.input<ResponseSchema>>,
71
+ context: ClassMethodDecoratorContext,
72
+ ) => {
73
+ if (typeof target !== 'function') {
74
+ throw new Error(
75
+ '[Navios] Endpoint decorator can only be used on functions.',
76
+ )
77
+ }
78
+ if (context.kind !== 'method') {
79
+ throw new Error(
80
+ '[Navios] Endpoint decorator can only be used on methods.',
81
+ )
82
+ }
83
+ const config = endpoint.config
84
+ if (context.metadata) {
85
+ let endpointMetadata = getEndpointMetadata(target, context)
86
+ if (endpointMetadata.config && endpointMetadata.config.url) {
87
+ throw new Error(
88
+ `[Navios] Endpoint ${config.method} ${config.url} already exists. Please use a different method or url.`,
89
+ )
90
+ }
91
+ // @ts-expect-error We don't need to set correctly in the metadata
92
+ endpointMetadata.config = config
93
+ endpointMetadata.type = EndpointType.Multipart
94
+ endpointMetadata.classMethod = target.name
95
+ endpointMetadata.httpMethod = config.method
96
+ endpointMetadata.url = config.url
97
+ }
98
+ return target
99
+ }
100
+ }
@@ -0,0 +1,81 @@
1
+ import type {
2
+ BaseStreamConfig,
3
+ EndpointFunctionArgs,
4
+ HttpMethod,
5
+ } from '@navios/common'
6
+ import type { FastifyReply } from 'fastify'
7
+ import type { AnyZodObject, ZodType } from 'zod'
8
+
9
+ import { EndpointType, getEndpointMetadata } from '../metadata/index.mjs'
10
+
11
+ export type StreamParams<
12
+ EndpointDeclaration extends {
13
+ config: BaseStreamConfig<any, any, any, any>
14
+ },
15
+ Url extends string = EndpointDeclaration['config']['url'],
16
+ QuerySchema = EndpointDeclaration['config']['querySchema'],
17
+ > = QuerySchema extends AnyZodObject
18
+ ? EndpointDeclaration['config']['requestSchema'] extends ZodType
19
+ ? EndpointFunctionArgs<
20
+ Url,
21
+ QuerySchema,
22
+ EndpointDeclaration['config']['requestSchema']
23
+ >
24
+ : EndpointFunctionArgs<Url, QuerySchema, undefined>
25
+ : EndpointDeclaration['config']['requestSchema'] extends ZodType
26
+ ? EndpointFunctionArgs<
27
+ Url,
28
+ undefined,
29
+ EndpointDeclaration['config']['requestSchema']
30
+ >
31
+ : EndpointFunctionArgs<Url, undefined, undefined>
32
+
33
+ export function Stream<
34
+ Method extends HttpMethod = HttpMethod,
35
+ Url extends string = string,
36
+ QuerySchema = undefined,
37
+ RequestSchema = ZodType,
38
+ >(endpoint: {
39
+ config: BaseStreamConfig<Method, Url, QuerySchema, RequestSchema>
40
+ }) {
41
+ return (
42
+ target: (
43
+ params: QuerySchema extends AnyZodObject
44
+ ? RequestSchema extends ZodType
45
+ ? EndpointFunctionArgs<Url, QuerySchema, RequestSchema>
46
+ : EndpointFunctionArgs<Url, QuerySchema, undefined>
47
+ : RequestSchema extends ZodType
48
+ ? EndpointFunctionArgs<Url, undefined, RequestSchema>
49
+ : EndpointFunctionArgs<Url, undefined, undefined>,
50
+ reply: FastifyReply,
51
+ ) => Promise<void>,
52
+ context: ClassMethodDecoratorContext,
53
+ ) => {
54
+ if (typeof target !== 'function') {
55
+ throw new Error(
56
+ '[Navios] Endpoint decorator can only be used on functions.',
57
+ )
58
+ }
59
+ if (context.kind !== 'method') {
60
+ throw new Error(
61
+ '[Navios] Endpoint decorator can only be used on methods.',
62
+ )
63
+ }
64
+ const config = endpoint.config
65
+ if (context.metadata) {
66
+ let endpointMetadata = getEndpointMetadata(target, context)
67
+ if (endpointMetadata.config && endpointMetadata.config.url) {
68
+ throw new Error(
69
+ `[Navios] Endpoint ${config.method} ${config.url} already exists. Please use a different method or url.`,
70
+ )
71
+ }
72
+ // @ts-expect-error We don't need to set correctly in the metadata
73
+ endpointMetadata.config = config
74
+ endpointMetadata.type = EndpointType.Stream
75
+ endpointMetadata.classMethod = target.name
76
+ endpointMetadata.httpMethod = config.method
77
+ endpointMetadata.url = config.url
78
+ }
79
+ return target
80
+ }
81
+ }
@@ -1,4 +1,8 @@
1
- import type { BaseEndpointConfig, HttpMethod } from '@navios/common'
1
+ import type {
2
+ BaseEndpointConfig,
3
+ BaseStreamConfig,
4
+ HttpMethod,
5
+ } from '@navios/common'
2
6
  import type { HttpHeader } from 'fastify/types/utils.js'
3
7
 
4
8
  import type { CanActivate } from '../interfaces/index.mjs'
@@ -11,7 +15,9 @@ export const EndpointMetadataKey = Symbol('EndpointMetadataKey')
11
15
 
12
16
  export enum EndpointType {
13
17
  Unknown = 'unknown',
14
- Config = 'config',
18
+ Endpoint = 'endpoint',
19
+ Stream = 'stream',
20
+ Multipart = 'multipart',
15
21
  Handler = 'handler',
16
22
  }
17
23
 
@@ -22,7 +28,7 @@ export interface EndpointMetadata {
22
28
  type: EndpointType
23
29
  headers: Partial<Record<HttpHeader, number | string | string[] | undefined>>
24
30
  httpMethod: HttpMethod
25
- config: BaseEndpointConfig | null
31
+ config: BaseEndpointConfig | BaseStreamConfig | null
26
32
  guards: Set<
27
33
  ClassTypeWithInstance<CanActivate> | InjectionToken<CanActivate, undefined>
28
34
  >
@@ -50,7 +50,7 @@ export function extractModuleMetadata(target: ClassType): ModuleMetadata {
50
50
  const metadata = target[ModuleMetadataKey] as ModuleMetadata | undefined
51
51
  if (!metadata) {
52
52
  throw new Error(
53
- '[Navios] Module metadata not found. Make sure to use @Module decorator.',
53
+ `[Navios] Module metadata not found for ${target.name}. Make sure to use @Module decorator.`,
54
54
  )
55
55
  }
56
56
  return metadata
@@ -1,4 +1,5 @@
1
1
  import type { FastifyCorsOptions } from '@fastify/cors'
2
+ import type { FastifyMultipartOptions } from '@fastify/multipart'
2
3
  import type {
3
4
  FastifyInstance,
4
5
  FastifyListenOptions,
@@ -50,6 +51,7 @@ export class NaviosApplication {
50
51
  })
51
52
  private server: FastifyInstance | null = null
52
53
  private corsOptions: FastifyCorsOptions | null = null
54
+ private multipartOptions: FastifyMultipartOptions | true | null = null
53
55
  private globalPrefix: string | null = null
54
56
 
55
57
  private appModule: ClassTypeWithInstance<NaviosModule> | null = null
@@ -81,6 +83,10 @@ export class NaviosApplication {
81
83
  await this.server.register(cors, this.corsOptions)
82
84
  }
83
85
 
86
+ if (this.multipartOptions) {
87
+ await this.configureMultipart(this.server, this.multipartOptions)
88
+ }
89
+
84
90
  await this.initModules()
85
91
  await this.server.ready()
86
92
 
@@ -147,6 +153,26 @@ export class NaviosApplication {
147
153
  })
148
154
  }
149
155
 
156
+ async configureMultipart(
157
+ server: FastifyInstance,
158
+ options: FastifyMultipartOptions | true,
159
+ ): Promise<void> {
160
+ if (options) {
161
+ try {
162
+ const multipartModule = await import('@fastify/multipart')
163
+ await server.register(
164
+ multipartModule.default,
165
+ typeof options === 'object' ? options : {},
166
+ )
167
+ } catch (error) {
168
+ this.logger.error(
169
+ `@fastify/multipart is not installed. Please install it.`,
170
+ )
171
+ throw error
172
+ }
173
+ }
174
+ }
175
+
150
176
  private async initModules() {
151
177
  const modules = this.moduleLoader.getAllModules()
152
178
  const promises: PromiseLike<any>[] = []
@@ -183,6 +209,10 @@ export class NaviosApplication {
183
209
  this.corsOptions = options
184
210
  }
185
211
 
212
+ enableMultipart(options: FastifyMultipartOptions) {
213
+ this.multipartOptions = options
214
+ }
215
+
186
216
  setGlobalPrefix(prefix: string) {
187
217
  this.globalPrefix = prefix
188
218
  }
@@ -1,8 +1,12 @@
1
+ import type { BaseEndpointConfig } from '@navios/common'
1
2
  import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'
2
3
  import type { ZodTypeProvider } from 'fastify-type-provider-zod'
4
+ import type { AnyZodObject } from 'zod'
3
5
 
4
6
  import { NaviosException } from '@navios/common'
5
7
 
8
+ import { ZodArray } from 'zod'
9
+
6
10
  import type { EndpointMetadata, ModuleMetadata } from '../metadata/index.mjs'
7
11
  import type { ClassType } from '../service-locator/index.mjs'
8
12
 
@@ -94,12 +98,12 @@ export class ControllerAdapterService {
94
98
  return {}
95
99
  }
96
100
  const { querySchema, requestSchema, responseSchema } =
97
- endpointMetadata.config
101
+ endpointMetadata.config as BaseEndpointConfig
98
102
  const schema: Record<string, any> = {}
99
103
  if (querySchema) {
100
104
  schema.querystring = querySchema
101
105
  }
102
- if (requestSchema) {
106
+ if (requestSchema && endpointMetadata.type !== EndpointType.Multipart) {
103
107
  schema.body = requestSchema
104
108
  }
105
109
  if (responseSchema) {
@@ -122,12 +126,24 @@ export class ControllerAdapterService {
122
126
  `Unknown endpoint type ${endpointMetadata.type} for ${controller.name}:${endpointMetadata.classMethod}`,
123
127
  )
124
128
  throw new NaviosException('Unknown endpoint type')
125
- case EndpointType.Config:
129
+ case EndpointType.Endpoint:
126
130
  return this.provideHandlerForConfig(
127
131
  controller,
128
132
  executionContext,
129
133
  endpointMetadata,
130
134
  )
135
+ case EndpointType.Stream:
136
+ return this.provideHandlerForStream(
137
+ controller,
138
+ executionContext,
139
+ endpointMetadata,
140
+ )
141
+ case EndpointType.Multipart:
142
+ return this.provideHandlerForMultipart(
143
+ controller,
144
+ executionContext,
145
+ endpointMetadata,
146
+ )
131
147
  case EndpointType.Handler:
132
148
  this.logger.error('Not implemented yet')
133
149
  throw new NaviosException('Not implemented yet')
@@ -174,4 +190,115 @@ export class ControllerAdapterService {
174
190
  }
175
191
  }
176
192
  }
193
+
194
+ private provideHandlerForStream(
195
+ controller: ClassType,
196
+ executionContext: ExecutionContext,
197
+ endpointMetadata: EndpointMetadata,
198
+ ): (request: FastifyRequest, reply: FastifyReply) => Promise<void> {
199
+ return async (request, reply) => {
200
+ getServiceLocator().registerInstance(Request, request)
201
+ getServiceLocator().registerInstance(Reply, reply)
202
+ getServiceLocator().registerInstance(
203
+ ExecutionContextToken,
204
+ executionContext,
205
+ )
206
+ executionContext.provideRequest(request)
207
+ executionContext.provideReply(reply)
208
+ const controllerInstance = await inject(controller)
209
+ try {
210
+ const { query, params, body } = request
211
+ const argument: Record<string, any> = {}
212
+ if (query && Object.keys(query).length > 0) {
213
+ argument.params = query
214
+ }
215
+ if (params && Object.keys(params).length > 0) {
216
+ argument.urlParams = params
217
+ }
218
+ if (body) {
219
+ argument.data = body
220
+ }
221
+
222
+ await controllerInstance[endpointMetadata.classMethod](argument, reply)
223
+ } finally {
224
+ getServiceLocator().removeInstance(Request)
225
+ getServiceLocator().removeInstance(Reply)
226
+ getServiceLocator().removeInstance(ExecutionContextToken)
227
+ }
228
+ }
229
+ }
230
+
231
+ private provideHandlerForMultipart(
232
+ controller: ClassType,
233
+ executionContext: ExecutionContext,
234
+ endpointMetadata: EndpointMetadata,
235
+ ): (request: FastifyRequest, reply: FastifyReply) => Promise<void> {
236
+ const config = endpointMetadata.config as BaseEndpointConfig
237
+ const requestSchema = config.requestSchema as unknown as AnyZodObject
238
+ const shape = requestSchema._def.shape()
239
+ return async (request, reply) => {
240
+ getServiceLocator().registerInstance(Request, request)
241
+ getServiceLocator().registerInstance(Reply, reply)
242
+ getServiceLocator().registerInstance(
243
+ ExecutionContextToken,
244
+ executionContext,
245
+ )
246
+ executionContext.provideRequest(request)
247
+ executionContext.provideReply(reply)
248
+ const controllerInstance = await inject(controller)
249
+ try {
250
+ const parts = request.parts()
251
+ const { query, params } = request
252
+ const argument: Record<string, any> = {}
253
+ if (query && Object.keys(query).length > 0) {
254
+ argument.params = query
255
+ }
256
+ if (params && Object.keys(params).length > 0) {
257
+ argument.urlParams = params
258
+ }
259
+ const req: Record<string, any> = {}
260
+ for await (const part of parts) {
261
+ if (!shape[part.fieldname]) {
262
+ throw new NaviosException(
263
+ `Invalid field name ${part.fieldname} for multipart request`,
264
+ )
265
+ }
266
+ const schema = shape[part.fieldname]
267
+ if (part.type === 'file') {
268
+ const file = new File([await part.toBuffer()], part.filename, {
269
+ type: part.mimetype,
270
+ })
271
+ if (schema instanceof ZodArray) {
272
+ if (!req[part.fieldname]) {
273
+ req[part.fieldname] = []
274
+ }
275
+ req[part.fieldname].push(file)
276
+ } else {
277
+ req[part.fieldname] = file
278
+ }
279
+ } else {
280
+ if (schema instanceof ZodArray) {
281
+ if (!req[part.fieldname]) {
282
+ req[part.fieldname] = []
283
+ }
284
+ req[part.fieldname].push(part.value)
285
+ } else {
286
+ req[part.fieldname] = part.value
287
+ }
288
+ }
289
+ }
290
+ argument.body = requestSchema.parse(req)
291
+ const result =
292
+ await controllerInstance[endpointMetadata.classMethod](argument)
293
+ reply
294
+ .status(endpointMetadata.successStatusCode)
295
+ .headers(endpointMetadata.headers)
296
+ .send(result)
297
+ } finally {
298
+ getServiceLocator().removeInstance(Request)
299
+ getServiceLocator().removeInstance(Reply)
300
+ getServiceLocator().removeInstance(ExecutionContextToken)
301
+ }
302
+ }
303
+ }
177
304
  }