@navios/core 0.6.0 → 0.7.1

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 (102) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/README.md +18 -1
  3. package/docs/README.md +1 -0
  4. package/docs/legacy-compat.md +320 -0
  5. package/docs/testing.md +140 -17
  6. package/lib/index-DW9EPAE6.d.mts +2156 -0
  7. package/lib/index-DW9EPAE6.d.mts.map +1 -0
  8. package/lib/index-pHp-dIGt.d.cts +2156 -0
  9. package/lib/index-pHp-dIGt.d.cts.map +1 -0
  10. package/lib/index.cjs +157 -0
  11. package/lib/index.d.cts +3 -0
  12. package/lib/index.d.mts +3 -190
  13. package/lib/index.mjs +4 -1459
  14. package/lib/legacy-compat/index.cjs +315 -0
  15. package/lib/legacy-compat/index.cjs.map +1 -0
  16. package/lib/legacy-compat/index.d.cts +219 -0
  17. package/lib/legacy-compat/index.d.cts.map +1 -0
  18. package/lib/legacy-compat/index.d.mts +219 -0
  19. package/lib/legacy-compat/index.d.mts.map +1 -0
  20. package/lib/legacy-compat/index.mjs +308 -0
  21. package/lib/legacy-compat/index.mjs.map +1 -0
  22. package/lib/src-DyvCDuKO.mjs +5443 -0
  23. package/lib/src-DyvCDuKO.mjs.map +1 -0
  24. package/lib/src-QnxR5b7c.cjs +5800 -0
  25. package/lib/src-QnxR5b7c.cjs.map +1 -0
  26. package/lib/testing/index.cjs +106 -0
  27. package/lib/testing/index.cjs.map +1 -0
  28. package/lib/testing/index.d.cts +156 -0
  29. package/lib/testing/index.d.cts.map +1 -0
  30. package/lib/testing/index.d.mts +156 -0
  31. package/lib/testing/index.d.mts.map +1 -0
  32. package/lib/testing/index.mjs +100 -0
  33. package/lib/testing/index.mjs.map +1 -0
  34. package/lib/use-guards.decorator-B6q_N0sf.cjs +622 -0
  35. package/lib/use-guards.decorator-B6q_N0sf.cjs.map +1 -0
  36. package/lib/use-guards.decorator-kZ3lNK8v.mjs +454 -0
  37. package/lib/use-guards.decorator-kZ3lNK8v.mjs.map +1 -0
  38. package/package.json +28 -8
  39. package/project.json +2 -2
  40. package/src/attribute.factory.mts +154 -0
  41. package/src/config/config-service.interface.mts +31 -0
  42. package/src/config/config.provider.mts +36 -0
  43. package/src/config/config.service.mts +94 -4
  44. package/src/decorators/controller.decorator.mts +28 -0
  45. package/src/decorators/endpoint.decorator.mts +76 -0
  46. package/src/decorators/header.decorator.mts +19 -0
  47. package/src/decorators/http-code.decorator.mts +20 -0
  48. package/src/decorators/module.decorator.mts +34 -0
  49. package/src/decorators/multipart.decorator.mts +41 -0
  50. package/src/decorators/stream.decorator.mts +33 -0
  51. package/src/decorators/use-guards.decorator.mts +29 -0
  52. package/src/exceptions/bad-request.exception.mts +21 -0
  53. package/src/exceptions/conflict.exception.mts +24 -0
  54. package/src/exceptions/forbidden.exception.mts +23 -0
  55. package/src/exceptions/http.exception.mts +26 -0
  56. package/src/exceptions/internal-server-error.exception.mts +26 -0
  57. package/src/exceptions/not-found.exception.mts +23 -0
  58. package/src/exceptions/unauthorized.exception.mts +23 -0
  59. package/src/index.mts +1 -0
  60. package/src/interfaces/abstract-execution-context.inteface.mts +35 -0
  61. package/src/interfaces/abstract-http-adapter.interface.mts +52 -0
  62. package/src/interfaces/abstract-http-handler-adapter.interface.mts +2 -2
  63. package/src/interfaces/can-activate.mts +31 -0
  64. package/src/interfaces/index.mts +1 -0
  65. package/src/interfaces/navios-module.mts +25 -0
  66. package/src/interfaces/plugin.interface.mts +105 -0
  67. package/src/legacy-compat/__type-tests__/legacy-decorators.spec-d.mts +420 -0
  68. package/src/legacy-compat/__type-tests__/tsconfig.json +15 -0
  69. package/src/legacy-compat/context-compat.mts +93 -0
  70. package/src/legacy-compat/decorators/controller.decorator.mts +31 -0
  71. package/src/legacy-compat/decorators/endpoint.decorator.mts +99 -0
  72. package/src/legacy-compat/decorators/header.decorator.mts +42 -0
  73. package/src/legacy-compat/decorators/http-code.decorator.mts +38 -0
  74. package/src/legacy-compat/decorators/index.mts +9 -0
  75. package/src/legacy-compat/decorators/module.decorator.mts +37 -0
  76. package/src/legacy-compat/decorators/multipart.decorator.mts +93 -0
  77. package/src/legacy-compat/decorators/stream.decorator.mts +76 -0
  78. package/src/legacy-compat/decorators/use-guards.decorator.mts +80 -0
  79. package/src/legacy-compat/index.mts +40 -0
  80. package/src/logger/console-logger.service.mts +15 -2
  81. package/src/logger/log-levels.mts +9 -0
  82. package/src/logger/logger.service.mts +21 -0
  83. package/src/logger/logger.tokens.mts +23 -0
  84. package/src/navios.application.mts +228 -4
  85. package/src/navios.factory.mts +60 -1
  86. package/src/services/guard-runner.service.mts +12 -11
  87. package/src/services/module-loader.service.mts +118 -12
  88. package/src/stores/index.mts +1 -0
  89. package/src/stores/request-id.store.mts +43 -0
  90. package/src/testing/index.mts +2 -0
  91. package/src/testing/testing-module.mts +231 -0
  92. package/tsconfig.lib.json +1 -1
  93. package/tsconfig.spec.json +3 -0
  94. package/tsdown.config.mts +35 -0
  95. package/vitest.config.mts +6 -0
  96. package/lib/_tsup-dts-rollup.d.mts +0 -1365
  97. package/lib/_tsup-dts-rollup.d.ts +0 -1365
  98. package/lib/index.d.ts +0 -190
  99. package/lib/index.js +0 -1540
  100. package/lib/index.js.map +0 -1
  101. package/lib/index.mjs.map +0 -1
  102. package/tsup.config.mts +0 -13
@@ -2,19 +2,71 @@ import type { ModuleMetadata } from '../metadata/index.mjs'
2
2
  import type { AbstractHttpCorsOptions } from './abstract-http-cors-options.interface.mjs'
3
3
  import type { AbstractHttpListenOptions } from './abstract-http-listen-options.interface.mjs'
4
4
 
5
+ /**
6
+ * Abstract interface for HTTP adapters.
7
+ *
8
+ * Adapters implement this interface to provide runtime-specific HTTP server
9
+ * functionality (Fastify, Bun, etc.).
10
+ *
11
+ * @typeParam ServerInstance - The underlying server type (e.g., FastifyInstance)
12
+ * @typeParam CorsOptions - CORS configuration options type
13
+ * @typeParam Options - Server setup options type
14
+ * @typeParam MultipartOptions - Multipart form handling options type
15
+ */
5
16
  export interface AbstractHttpAdapterInterface<
6
17
  ServerInstance,
7
18
  CorsOptions = AbstractHttpCorsOptions,
8
19
  Options = {},
9
20
  MultipartOptions = {},
10
21
  > {
22
+ /**
23
+ * Sets up the HTTP server with the provided options.
24
+ */
11
25
  setupHttpServer(options: Options): Promise<void>
26
+
27
+ /**
28
+ * Called after all modules are loaded to register routes.
29
+ */
12
30
  onModulesInit(modules: Map<string, ModuleMetadata>): Promise<void>
31
+
32
+ /**
33
+ * Signals that the server is ready to accept requests.
34
+ */
13
35
  ready(): Promise<void>
36
+
37
+ /**
38
+ * Returns the underlying HTTP server instance.
39
+ */
14
40
  getServer(): ServerInstance
41
+
42
+ /**
43
+ * Sets a global prefix for all routes.
44
+ */
15
45
  setGlobalPrefix(prefix: string): void
46
+
47
+ /**
48
+ * Gets the current global prefix.
49
+ * Returns empty string if no prefix is set.
50
+ */
51
+ getGlobalPrefix(): string
52
+
53
+ /**
54
+ * Enables CORS with the specified options.
55
+ */
16
56
  enableCors(options: CorsOptions): void
57
+
58
+ /**
59
+ * Enables multipart form data handling.
60
+ */
17
61
  enableMultipart(options: MultipartOptions): void
62
+
63
+ /**
64
+ * Starts the server and listens for incoming requests.
65
+ */
18
66
  listen(options: AbstractHttpListenOptions): Promise<string>
67
+
68
+ /**
69
+ * Disposes of the server and cleans up resources.
70
+ */
19
71
  dispose(): Promise<void>
20
72
  }
@@ -1,4 +1,4 @@
1
- import type { ClassType, RequestContextHolder } from '@navios/di'
1
+ import type { ClassType, ScopedContainer } from '@navios/di'
2
2
 
3
3
  import type { HandlerMetadata } from '../metadata/index.mjs'
4
4
 
@@ -9,5 +9,5 @@ export interface AbstractHttpHandlerAdapterInterface {
9
9
  provideHandler: (
10
10
  controller: ClassType,
11
11
  handlerMetadata: HandlerMetadata<any>,
12
- ) => (context: RequestContextHolder, request: any, reply: any) => Promise<any>
12
+ ) => (context: ScopedContainer, request: any, reply: any) => Promise<any>
13
13
  }
@@ -1,6 +1,37 @@
1
1
  import type { AbstractExecutionContext } from '../interfaces/index.mjs'
2
2
 
3
+ /**
4
+ * Interface that guards must implement to control access to endpoints.
5
+ *
6
+ * Guards are used for authentication, authorization, and request validation.
7
+ * They are executed before the endpoint handler and can prevent the request
8
+ * from proceeding by returning `false` or throwing an exception.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * @Injectable()
13
+ * export class AuthGuard implements CanActivate {
14
+ * async canActivate(context: AbstractExecutionContext): Promise<boolean> {
15
+ * const request = context.getRequest()
16
+ * const token = request.headers.authorization
17
+ *
18
+ * if (!token) {
19
+ * throw new UnauthorizedException('Authentication required')
20
+ * }
21
+ *
22
+ * // Validate token
23
+ * return true
24
+ * }
25
+ * }
26
+ * ```
27
+ */
3
28
  export interface CanActivate {
29
+ /**
30
+ * Determines if the current request can proceed to the endpoint handler.
31
+ *
32
+ * @param executionContext - The execution context containing request, reply, and metadata
33
+ * @returns `true` if the request can proceed, `false` otherwise. Can also throw an exception.
34
+ */
4
35
  canActivate(
5
36
  executionContext: AbstractExecutionContext,
6
37
  ): Promise<boolean> | boolean
@@ -6,3 +6,4 @@ export * from './abstract-http-listen-options.interface.mjs'
6
6
  export * from './can-activate.mjs'
7
7
  export * from './http-header.mjs'
8
8
  export * from './navios-module.mjs'
9
+ export * from './plugin.interface.mjs'
@@ -1,3 +1,28 @@
1
+ /**
2
+ * Interface that all Navios modules must implement.
3
+ *
4
+ * Modules decorated with @Module() should implement this interface to receive
5
+ * lifecycle hooks.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * @Module({
10
+ * controllers: [UserController],
11
+ * })
12
+ * export class AppModule implements NaviosModule {
13
+ * async onModuleInit() {
14
+ * console.log('AppModule initialized')
15
+ * // Perform initialization logic
16
+ * }
17
+ * }
18
+ * ```
19
+ */
1
20
  export interface NaviosModule {
21
+ /**
22
+ * Optional lifecycle hook called after the module is initialized.
23
+ *
24
+ * This is called after all modules are loaded and the HTTP server is set up
25
+ * (if an adapter is configured), but before the server starts listening.
26
+ */
2
27
  onModuleInit?: () => Promise<void> | void
3
28
  }
@@ -0,0 +1,105 @@
1
+ import type { Container } from '@navios/di'
2
+
3
+ import type { ModuleMetadata } from '../metadata/index.mjs'
4
+ import type { ModuleLoaderService } from '../services/module-loader.service.mjs'
5
+
6
+ /**
7
+ * Context provided to plugins during registration.
8
+ *
9
+ * This context gives plugins access to the application's modules,
10
+ * server instance, DI container, and configuration.
11
+ */
12
+ export interface PluginContext {
13
+ /**
14
+ * All loaded modules with their metadata.
15
+ * Keys are module class names, values are their metadata.
16
+ */
17
+ modules: Map<string, ModuleMetadata>
18
+
19
+ /**
20
+ * The underlying HTTP server instance.
21
+ * Type depends on the adapter used (Fastify, Bun, etc.)
22
+ */
23
+ server: any
24
+
25
+ /**
26
+ * The dependency injection container.
27
+ */
28
+ container: Container
29
+
30
+ /**
31
+ * Global route prefix (e.g., '/api/v1').
32
+ * Empty string if no prefix is set.
33
+ */
34
+ globalPrefix: string
35
+
36
+ /**
37
+ * Module loader service for extending the module tree.
38
+ * Use `moduleLoader.extendModules()` to add controllers dynamically.
39
+ */
40
+ moduleLoader: ModuleLoaderService
41
+ }
42
+
43
+ /**
44
+ * Base interface for Navios plugins.
45
+ *
46
+ * Plugins are registered using `app.usePlugin()` and are initialized
47
+ * after all modules are loaded but before the server starts listening.
48
+ *
49
+ * @typeParam TOptions - The type of options the plugin accepts
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * const myPlugin: NaviosPlugin<{ enabled: boolean }> = {
54
+ * name: 'my-plugin',
55
+ * register: async (context, options) => {
56
+ * if (options.enabled) {
57
+ * // Register routes, services, etc.
58
+ * }
59
+ * },
60
+ * }
61
+ * ```
62
+ */
63
+ export interface NaviosPlugin<TOptions = unknown> {
64
+ /**
65
+ * Plugin name for identification and logging.
66
+ */
67
+ name: string
68
+
69
+ /**
70
+ * Called after modules are loaded but before the server starts listening.
71
+ *
72
+ * @param context - The plugin context with access to modules and server
73
+ * @param options - Plugin-specific configuration options
74
+ */
75
+ register(context: PluginContext, options: TOptions): Promise<void> | void
76
+ }
77
+
78
+ /**
79
+ * Plugin definition combining a plugin with its options.
80
+ *
81
+ * This is the type returned by plugin factory functions like `defineOpenApiPlugin()`.
82
+ *
83
+ * @typeParam TOptions - The type of options the plugin accepts
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * function defineMyPlugin(options: MyPluginOptions): PluginDefinition<MyPluginOptions> {
88
+ * return {
89
+ * plugin: myPlugin,
90
+ * options,
91
+ * }
92
+ * }
93
+ * ```
94
+ */
95
+ export interface PluginDefinition<TOptions = unknown> {
96
+ /**
97
+ * The plugin instance.
98
+ */
99
+ plugin: NaviosPlugin<TOptions>
100
+
101
+ /**
102
+ * Options to pass to the plugin's register function.
103
+ */
104
+ options: TOptions
105
+ }
@@ -0,0 +1,420 @@
1
+ // oxlint-disable no-unused-vars
2
+ import { builder } from '@navios/builder'
3
+
4
+ import { describe, expectTypeOf, test } from 'vitest'
5
+ import { z } from 'zod/v4'
6
+
7
+ import type {
8
+ EndpointParams,
9
+ EndpointResult,
10
+ MultipartParams,
11
+ MultipartResult,
12
+ StreamParams,
13
+ } from '../index.mjs'
14
+
15
+ import {
16
+ Controller,
17
+ Endpoint,
18
+ Header,
19
+ HttpCode,
20
+ Module,
21
+ Multipart,
22
+ Stream,
23
+ UseGuards,
24
+ } from '../index.mjs'
25
+
26
+ // Create a test API builder
27
+ const api = builder()
28
+
29
+ // Test schemas
30
+ const responseSchema = z.object({
31
+ id: z.string(),
32
+ name: z.string(),
33
+ })
34
+
35
+ const querySchema = z.object({
36
+ page: z.number(),
37
+ limit: z.number(),
38
+ })
39
+
40
+ const requestSchema = z.object({
41
+ name: z.string(),
42
+ email: z.string(),
43
+ })
44
+
45
+ const multipartRequestSchema = z.object({
46
+ file: z.instanceof(File),
47
+ description: z.string(),
48
+ })
49
+
50
+ // Test endpoint declarations
51
+ const getUserEndpoint = api.declareEndpoint({
52
+ method: 'GET',
53
+ url: '/users/$userId',
54
+ querySchema,
55
+ responseSchema,
56
+ })
57
+
58
+ const createUserEndpoint = api.declareEndpoint({
59
+ method: 'POST',
60
+ url: '/users',
61
+ requestSchema,
62
+ responseSchema,
63
+ })
64
+
65
+ const uploadFileEndpoint = api.declareMultipart({
66
+ method: 'POST',
67
+ url: '/upload',
68
+ requestSchema: multipartRequestSchema,
69
+ responseSchema,
70
+ })
71
+
72
+ const downloadFileEndpoint = api.declareStream({
73
+ method: 'GET',
74
+ url: '/files/$fileId',
75
+ querySchema,
76
+ })
77
+
78
+ // Mock guard for testing
79
+ class AuthGuard {
80
+ canActivate() {
81
+ return true
82
+ }
83
+ }
84
+
85
+ describe('Legacy Decorators Type Safety', () => {
86
+ describe('Module decorator', () => {
87
+ test('should accept valid module options', () => {
88
+ @Module({
89
+ controllers: [],
90
+ imports: [],
91
+ guards: [],
92
+ })
93
+ class TestModule {}
94
+
95
+ expectTypeOf(TestModule).toBeConstructibleWith()
96
+ })
97
+
98
+ test('should accept controllers in module', () => {
99
+ @Controller()
100
+ class TestController {}
101
+
102
+ @Module({
103
+ controllers: [TestController],
104
+ })
105
+ class TestModule {}
106
+
107
+ expectTypeOf(TestModule).toBeConstructibleWith()
108
+ })
109
+ })
110
+
111
+ describe('Controller decorator', () => {
112
+ test('should accept valid controller options', () => {
113
+ @Controller()
114
+ class TestController {}
115
+
116
+ expectTypeOf(TestController).toBeConstructibleWith()
117
+ })
118
+
119
+ test('should accept guards in controller', () => {
120
+ @Controller({
121
+ guards: [AuthGuard],
122
+ })
123
+ class TestController {}
124
+
125
+ expectTypeOf(TestController).toBeConstructibleWith()
126
+ })
127
+ })
128
+
129
+ describe('Endpoint decorator', () => {
130
+ test('should enforce correct parameter type', () => {
131
+ @Controller()
132
+ class UserController {
133
+ @Endpoint(getUserEndpoint)
134
+ async getUser(
135
+ request: EndpointParams<typeof getUserEndpoint>,
136
+ ): EndpointResult<typeof getUserEndpoint> {
137
+ // TypeScript should infer:
138
+ // - request.urlParams.userId: string | number
139
+ // - request.params.page: number
140
+ // - request.params.limit: number
141
+ expectTypeOf(request.urlParams.userId).toEqualTypeOf<
142
+ string | number
143
+ >()
144
+ expectTypeOf(request.params.page).toEqualTypeOf<number>()
145
+ expectTypeOf(request.params.limit).toEqualTypeOf<number>()
146
+ return {
147
+ id: request.urlParams.userId.toString(),
148
+ name: 'Test',
149
+ }
150
+ }
151
+ }
152
+
153
+ expectTypeOf(UserController).toBeConstructibleWith()
154
+ })
155
+
156
+ test('should enforce correct return type', () => {
157
+ @Controller()
158
+ class UserController {
159
+ @Endpoint(getUserEndpoint)
160
+ async getUser(
161
+ request: EndpointParams<typeof getUserEndpoint>,
162
+ ): EndpointResult<typeof getUserEndpoint> {
163
+ // Return type should match responseSchema
164
+ return {
165
+ id: '1',
166
+ name: 'John',
167
+ }
168
+ }
169
+ }
170
+
171
+ expectTypeOf(UserController).toBeConstructibleWith()
172
+ })
173
+
174
+ test('should reject incorrect parameter type', () => {
175
+ @Controller()
176
+ class UserController {
177
+ // @ts-expect-error - wrong parameter type
178
+ @Endpoint(getUserEndpoint)
179
+ async getUser(request: {
180
+ wrong: string
181
+ }): EndpointResult<typeof getUserEndpoint> {
182
+ return { id: '1', name: 'John' }
183
+ }
184
+ }
185
+ })
186
+
187
+ test('should reject incorrect return type', () => {
188
+ @Controller()
189
+ class UserController {
190
+ @Endpoint(getUserEndpoint)
191
+ async getUser(
192
+ request: EndpointParams<typeof getUserEndpoint>,
193
+ ): EndpointResult<typeof getUserEndpoint> {
194
+ // @ts-expect-error - wrong return type
195
+ return { wrong: 'value' }
196
+ }
197
+ }
198
+ })
199
+
200
+ test('should work with POST endpoint with request body', () => {
201
+ @Controller()
202
+ class UserController {
203
+ @Endpoint(createUserEndpoint)
204
+ async createUser(
205
+ request: EndpointParams<typeof createUserEndpoint>,
206
+ ): EndpointResult<typeof createUserEndpoint> {
207
+ // TypeScript should infer:
208
+ // - request.data.name: string
209
+ // - request.data.email: string
210
+ expectTypeOf(request.data.name).toEqualTypeOf<string>()
211
+ expectTypeOf(request.data.email).toEqualTypeOf<string>()
212
+ return {
213
+ id: '1',
214
+ name: request.data.name,
215
+ }
216
+ }
217
+ }
218
+
219
+ expectTypeOf(UserController).toBeConstructibleWith()
220
+ })
221
+ })
222
+
223
+ describe('Multipart decorator', () => {
224
+ test('should enforce correct parameter type', () => {
225
+ @Controller()
226
+ class FileController {
227
+ @Multipart(uploadFileEndpoint)
228
+ async uploadFile(
229
+ request: MultipartParams<typeof uploadFileEndpoint>,
230
+ ): MultipartResult<typeof uploadFileEndpoint> {
231
+ // TypeScript should infer:
232
+ // - request.data.file: File
233
+ // - request.data.description: string
234
+ expectTypeOf(request.data.file).toEqualTypeOf<File>()
235
+ expectTypeOf(request.data.description).toEqualTypeOf<string>()
236
+ return {
237
+ id: '1',
238
+ name: 'uploaded.jpg',
239
+ }
240
+ }
241
+ }
242
+
243
+ expectTypeOf(FileController).toBeConstructibleWith()
244
+ })
245
+
246
+ test('should reject incorrect parameter type', () => {
247
+ @Controller()
248
+ class FileController {
249
+ // @ts-expect-error - wrong parameter type
250
+ @Multipart(uploadFileEndpoint)
251
+ async uploadFile(request: {
252
+ wrong: string
253
+ }): MultipartResult<typeof uploadFileEndpoint> {
254
+ return { id: '1', name: 'test.jpg' }
255
+ }
256
+ }
257
+ })
258
+ })
259
+
260
+ describe('Stream decorator', () => {
261
+ test('should enforce correct parameter type', () => {
262
+ @Controller()
263
+ class FileController {
264
+ @Stream(downloadFileEndpoint)
265
+ async downloadFile(
266
+ request: StreamParams<typeof downloadFileEndpoint>,
267
+ reply: any,
268
+ ): Promise<void> {
269
+ // TypeScript should infer:
270
+ // - request.urlParams.fileId: string | number
271
+ // - request.params.page: number
272
+ // - request.params.limit: number
273
+ expectTypeOf(request.urlParams.fileId).toEqualTypeOf<
274
+ string | number
275
+ >()
276
+ expectTypeOf(request.params.page).toEqualTypeOf<number>()
277
+ expectTypeOf(request.params.limit).toEqualTypeOf<number>()
278
+ }
279
+ }
280
+
281
+ expectTypeOf(FileController).toBeConstructibleWith()
282
+ })
283
+
284
+ test('should require reply parameter', () => {
285
+ @Controller()
286
+ class FileController {
287
+ // @ts-expect-error - missing reply parameter
288
+ @Stream(downloadFileEndpoint)
289
+ async downloadFile(
290
+ request: StreamParams<typeof downloadFileEndpoint>,
291
+ ): Promise<void> {
292
+ // Stream methods must have reply parameter
293
+ }
294
+ }
295
+ })
296
+ })
297
+
298
+ describe('UseGuards decorator', () => {
299
+ test('should work on class level', () => {
300
+ @Controller()
301
+ @UseGuards(AuthGuard)
302
+ class ProtectedController {
303
+ @Endpoint(getUserEndpoint)
304
+ async getUser(
305
+ request: EndpointParams<typeof getUserEndpoint>,
306
+ ): EndpointResult<typeof getUserEndpoint> {
307
+ return { id: '1', name: 'John' }
308
+ }
309
+ }
310
+
311
+ expectTypeOf(ProtectedController).toBeConstructibleWith()
312
+ })
313
+
314
+ test('should work on method level', () => {
315
+ @Controller()
316
+ class UserController {
317
+ @Endpoint(getUserEndpoint)
318
+ @UseGuards(AuthGuard)
319
+ async getUser(
320
+ request: EndpointParams<typeof getUserEndpoint>,
321
+ ): EndpointResult<typeof getUserEndpoint> {
322
+ return { id: '1', name: 'John' }
323
+ }
324
+ }
325
+
326
+ expectTypeOf(UserController).toBeConstructibleWith()
327
+ })
328
+ })
329
+
330
+ describe('Header decorator', () => {
331
+ test('should work with string value', () => {
332
+ @Controller()
333
+ class UserController {
334
+ @Endpoint(getUserEndpoint)
335
+ @Header('Cache-Control', 'max-age=3600')
336
+ async getUser(
337
+ request: EndpointParams<typeof getUserEndpoint>,
338
+ ): EndpointResult<typeof getUserEndpoint> {
339
+ return { id: '1', name: 'John' }
340
+ }
341
+ }
342
+
343
+ expectTypeOf(UserController).toBeConstructibleWith()
344
+ })
345
+
346
+ test('should work with number value', () => {
347
+ @Controller()
348
+ class UserController {
349
+ @Endpoint(getUserEndpoint)
350
+ @Header('Content-Length', 100)
351
+ async getUser(
352
+ request: EndpointParams<typeof getUserEndpoint>,
353
+ ): EndpointResult<typeof getUserEndpoint> {
354
+ return { id: '1', name: 'John' }
355
+ }
356
+ }
357
+
358
+ expectTypeOf(UserController).toBeConstructibleWith()
359
+ })
360
+ })
361
+
362
+ describe('HttpCode decorator', () => {
363
+ test('should accept valid status codes', () => {
364
+ @Controller()
365
+ class UserController {
366
+ @Endpoint(createUserEndpoint)
367
+ @HttpCode(201)
368
+ async createUser(
369
+ request: EndpointParams<typeof createUserEndpoint>,
370
+ ): EndpointResult<typeof createUserEndpoint> {
371
+ return { id: '1', name: request.data.name }
372
+ }
373
+ }
374
+
375
+ expectTypeOf(UserController).toBeConstructibleWith()
376
+ })
377
+ })
378
+
379
+ describe('Integration test - full controller', () => {
380
+ test('should work with all decorators together', () => {
381
+ @Controller({
382
+ guards: [AuthGuard],
383
+ })
384
+ @UseGuards(AuthGuard)
385
+ class UserController {
386
+ @Endpoint(getUserEndpoint)
387
+ @UseGuards(AuthGuard)
388
+ @Header('Cache-Control', 'max-age=3600')
389
+ @HttpCode(200)
390
+ async getUser(
391
+ request: EndpointParams<typeof getUserEndpoint>,
392
+ ): EndpointResult<typeof getUserEndpoint> {
393
+ return {
394
+ id: request.urlParams.userId.toString(),
395
+ name: 'John',
396
+ }
397
+ }
398
+
399
+ @Endpoint(createUserEndpoint)
400
+ @HttpCode(201)
401
+ async createUser(
402
+ request: EndpointParams<typeof createUserEndpoint>,
403
+ ): EndpointResult<typeof createUserEndpoint> {
404
+ return {
405
+ id: '1',
406
+ name: request.data.name,
407
+ }
408
+ }
409
+ }
410
+
411
+ @Module({
412
+ controllers: [UserController],
413
+ })
414
+ class AppModule {}
415
+
416
+ expectTypeOf(UserController).toBeConstructibleWith()
417
+ expectTypeOf(AppModule).toBeConstructibleWith()
418
+ })
419
+ })
420
+ })
@@ -0,0 +1,15 @@
1
+ {
2
+ "extends": "../../../../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "experimentalDecorators": true,
5
+ "emitDecoratorMetadata": false,
6
+ "outDir": "../../../dist/legacy-compat/__type-tests__"
7
+ },
8
+ "include": ["**/*.spec-d.mts"],
9
+ "exclude": [],
10
+ "references": [
11
+ {
12
+ "path": "../../../tsconfig.lib.json"
13
+ }
14
+ ]
15
+ }