@navios/core 1.0.0-alpha.2 → 1.0.0-alpha.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.
Files changed (53) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/lib/{index-BJjk2X1S.d.mts → index-BISYCYEG.d.mts} +435 -180
  3. package/lib/index-BISYCYEG.d.mts.map +1 -0
  4. package/lib/{index-DZ6NU03y.d.cts → index-CP80H6Dh.d.cts} +435 -180
  5. package/lib/index-CP80H6Dh.d.cts.map +1 -0
  6. package/lib/index.cjs +3 -2
  7. package/lib/index.d.cts +2 -2
  8. package/lib/index.d.mts +2 -2
  9. package/lib/index.mjs +2 -2
  10. package/lib/legacy-compat/index.cjs +38 -124
  11. package/lib/legacy-compat/index.cjs.map +1 -1
  12. package/lib/legacy-compat/index.d.cts +4 -60
  13. package/lib/legacy-compat/index.d.cts.map +1 -1
  14. package/lib/legacy-compat/index.d.mts +4 -60
  15. package/lib/legacy-compat/index.d.mts.map +1 -1
  16. package/lib/legacy-compat/index.mjs +12 -118
  17. package/lib/legacy-compat/index.mjs.map +1 -1
  18. package/lib/{src-C46ePe3d.cjs → src-CC5lmk_Q.cjs} +167 -2
  19. package/lib/src-CC5lmk_Q.cjs.map +1 -0
  20. package/lib/{src-K2k0riYJ.mjs → src-j1cBuAgy.mjs} +162 -3
  21. package/lib/src-j1cBuAgy.mjs.map +1 -0
  22. package/lib/testing/index.cjs +2 -2
  23. package/lib/testing/index.d.cts +1 -1
  24. package/lib/testing/index.d.mts +1 -1
  25. package/lib/testing/index.mjs +1 -1
  26. package/lib/{use-guards.decorator-B6tghdxM.cjs → use-guards.decorator-DtCGXcWZ.cjs} +2 -2
  27. package/lib/{use-guards.decorator-B6tghdxM.cjs.map → use-guards.decorator-DtCGXcWZ.cjs.map} +1 -1
  28. package/package.json +2 -2
  29. package/src/__tests__/attribute.factory.spec.mts +300 -0
  30. package/src/__tests__/guard-runner.service.spec.mts +399 -0
  31. package/src/__tests__/logger.service.spec.mts +147 -0
  32. package/src/__tests__/responders.spec.mts +6 -5
  33. package/src/interfaces/abstract-http-handler-adapter.interface.mts +86 -2
  34. package/src/legacy-compat/attribute.factory.mts +2 -2
  35. package/src/legacy-compat/decorators/controller.decorator.mts +1 -1
  36. package/src/legacy-compat/decorators/endpoint.decorator.mts +1 -1
  37. package/src/legacy-compat/decorators/header.decorator.mts +2 -1
  38. package/src/legacy-compat/decorators/http-code.decorator.mts +2 -1
  39. package/src/legacy-compat/decorators/index.mts +2 -2
  40. package/src/legacy-compat/decorators/module.decorator.mts +1 -1
  41. package/src/legacy-compat/decorators/multipart.decorator.mts +1 -1
  42. package/src/legacy-compat/decorators/stream.decorator.mts +1 -1
  43. package/src/legacy-compat/decorators/use-guards.decorator.mts +1 -1
  44. package/src/legacy-compat/index.mts +10 -5
  45. package/src/services/abstract-handler-adapter.service.mts +366 -0
  46. package/src/services/index.mts +1 -0
  47. package/lib/index-BJjk2X1S.d.mts.map +0 -1
  48. package/lib/index-DZ6NU03y.d.cts.map +0 -1
  49. package/lib/src-C46ePe3d.cjs.map +0 -1
  50. package/lib/src-K2k0riYJ.mjs.map +0 -1
  51. package/src/legacy-compat/context-compat.mts +0 -95
  52. package/src/legacy-compat/decorators/factory.decorator.mts +0 -37
  53. package/src/legacy-compat/decorators/injectable.decorator.mts +0 -41
@@ -0,0 +1,399 @@
1
+ import type { ScopedContainer } from '@navios/di'
2
+
3
+ import { Container, Injectable, InjectionToken } from '@navios/di'
4
+
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
6
+
7
+ import type {
8
+ AbstractExecutionContext,
9
+ CanActivate,
10
+ } from '../interfaces/index.mjs'
11
+ import type {
12
+ ControllerMetadata,
13
+ HandlerMetadata,
14
+ ModuleMetadata,
15
+ } from '../metadata/index.mjs'
16
+
17
+ import { HttpException } from '../exceptions/index.mjs'
18
+ import { LoggerOutput } from '../logger/logger.tokens.mjs'
19
+ import {
20
+ ForbiddenResponderToken,
21
+ InternalServerErrorResponderToken,
22
+ NotFoundResponderToken,
23
+ ValidationErrorResponderToken,
24
+ } from '../responders/tokens/responder.tokens.mjs'
25
+ import { GuardRunnerService } from '../services/guard-runner.service.mjs'
26
+
27
+ // Mock responders
28
+ const createMockResponder = (statusCode: number, message: string) => ({
29
+ getResponse: vi.fn().mockReturnValue({
30
+ statusCode,
31
+ payload: { message },
32
+ headers: { 'content-type': 'application/problem+json' },
33
+ }),
34
+ })
35
+
36
+ // Mock logger output
37
+ const mockLoggerOutput = {
38
+ log: vi.fn(),
39
+ error: vi.fn(),
40
+ warn: vi.fn(),
41
+ debug: vi.fn(),
42
+ verbose: vi.fn(),
43
+ fatal: vi.fn(),
44
+ }
45
+
46
+ // Mock execution context
47
+ const createMockExecutionContext = (): AbstractExecutionContext => {
48
+ const mockReply = {
49
+ status: vi.fn().mockReturnThis(),
50
+ send: vi.fn().mockReturnThis(),
51
+ }
52
+ return {
53
+ getRequest: vi.fn().mockReturnValue({}),
54
+ getReply: vi.fn().mockReturnValue(mockReply),
55
+ getHandler: vi.fn(),
56
+ getModule: vi.fn(),
57
+ getController: vi.fn(),
58
+ }
59
+ }
60
+
61
+ // Mock guard
62
+ const createMockGuard = (
63
+ canActivateResult: boolean | Promise<boolean>,
64
+ ): CanActivate => ({
65
+ canActivate: vi.fn().mockImplementation(() => canActivateResult),
66
+ })
67
+
68
+ describe('GuardRunnerService', () => {
69
+ let container: Container
70
+ let mockForbiddenResponder: ReturnType<typeof createMockResponder>
71
+ let mockInternalErrorResponder: ReturnType<typeof createMockResponder>
72
+ let mockNotFoundResponder: ReturnType<typeof createMockResponder>
73
+ let mockValidationErrorResponder: ReturnType<typeof createMockResponder>
74
+
75
+ beforeEach(() => {
76
+ container = new Container()
77
+ vi.clearAllMocks()
78
+
79
+ // Create mock responders
80
+ mockForbiddenResponder = createMockResponder(403, 'Forbidden')
81
+ mockInternalErrorResponder = createMockResponder(
82
+ 500,
83
+ 'Internal Server Error',
84
+ )
85
+ mockNotFoundResponder = createMockResponder(404, 'Not Found')
86
+ mockValidationErrorResponder = createMockResponder(400, 'Validation Error')
87
+
88
+ // Register all required dependencies
89
+ container.addInstance(ForbiddenResponderToken, mockForbiddenResponder)
90
+ container.addInstance(
91
+ InternalServerErrorResponderToken,
92
+ mockInternalErrorResponder,
93
+ )
94
+ container.addInstance(NotFoundResponderToken, mockNotFoundResponder)
95
+ container.addInstance(
96
+ ValidationErrorResponderToken,
97
+ mockValidationErrorResponder,
98
+ )
99
+ container.addInstance(LoggerOutput, mockLoggerOutput)
100
+ })
101
+
102
+ afterEach(async () => {
103
+ await container.dispose()
104
+ })
105
+
106
+ describe('runGuardsStatic', () => {
107
+ it('should return true when all guards pass', async () => {
108
+ const service = await container.get(GuardRunnerService)
109
+ const guards = [createMockGuard(true), createMockGuard(true)]
110
+ const context = createMockExecutionContext()
111
+
112
+ const result = await service.runGuardsStatic(guards, context)
113
+
114
+ expect(result).toBe(true)
115
+ expect(guards[0].canActivate).toHaveBeenCalledWith(context)
116
+ expect(guards[1].canActivate).toHaveBeenCalledWith(context)
117
+ })
118
+
119
+ it('should return false when a guard returns false', async () => {
120
+ const service = await container.get(GuardRunnerService)
121
+ const guards = [createMockGuard(true), createMockGuard(false)]
122
+ const context = createMockExecutionContext()
123
+
124
+ const result = await service.runGuardsStatic(guards, context)
125
+
126
+ expect(result).toBe(false)
127
+ expect(context.getReply().status).toHaveBeenCalledWith(403)
128
+ })
129
+
130
+ it('should stop execution when a guard fails', async () => {
131
+ const service = await container.get(GuardRunnerService)
132
+ const firstGuard = createMockGuard(false)
133
+ const secondGuard = createMockGuard(true)
134
+ const guards = [firstGuard, secondGuard]
135
+ const context = createMockExecutionContext()
136
+
137
+ await service.runGuardsStatic(guards, context)
138
+
139
+ expect(firstGuard.canActivate).toHaveBeenCalled()
140
+ expect(secondGuard.canActivate).not.toHaveBeenCalled()
141
+ })
142
+
143
+ it('should handle HttpException from guard', async () => {
144
+ const service = await container.get(GuardRunnerService)
145
+ const httpException = new HttpException(401, 'Unauthorized')
146
+ const guard = {
147
+ canActivate: vi.fn().mockRejectedValue(httpException),
148
+ }
149
+ const context = createMockExecutionContext()
150
+
151
+ const result = await service.runGuardsStatic([guard], context)
152
+
153
+ expect(result).toBe(false)
154
+ expect(context.getReply().status).toHaveBeenCalledWith(401)
155
+ expect(context.getReply().send).toHaveBeenCalledWith('Unauthorized')
156
+ })
157
+
158
+ it('should handle unknown errors from guard', async () => {
159
+ const service = await container.get(GuardRunnerService)
160
+ const guard = {
161
+ canActivate: vi.fn().mockRejectedValue(new Error('Unknown error')),
162
+ }
163
+ const context = createMockExecutionContext()
164
+
165
+ const result = await service.runGuardsStatic([guard], context)
166
+
167
+ expect(result).toBe(false)
168
+ expect(mockLoggerOutput.error).toHaveBeenCalled()
169
+ expect(context.getReply().status).toHaveBeenCalledWith(500)
170
+ })
171
+
172
+ it('should handle async guards', async () => {
173
+ const service = await container.get(GuardRunnerService)
174
+ const asyncGuard = createMockGuard(Promise.resolve(true))
175
+ const context = createMockExecutionContext()
176
+
177
+ const result = await service.runGuardsStatic([asyncGuard], context)
178
+
179
+ expect(result).toBe(true)
180
+ })
181
+
182
+ it('should work with empty guard array', async () => {
183
+ const service = await container.get(GuardRunnerService)
184
+ const context = createMockExecutionContext()
185
+
186
+ const result = await service.runGuardsStatic([], context)
187
+
188
+ expect(result).toBe(true)
189
+ })
190
+ })
191
+
192
+ describe('runGuards', () => {
193
+ it('should resolve guards from scoped container', async () => {
194
+ @Injectable()
195
+ class TestGuard implements CanActivate {
196
+ canActivate = vi.fn().mockReturnValue(true)
197
+ }
198
+
199
+ const service = await container.get(GuardRunnerService)
200
+ const testGuardInstance = new TestGuard()
201
+
202
+ const mockScopedContainer = {
203
+ get: vi.fn().mockResolvedValue(testGuardInstance),
204
+ } as unknown as ScopedContainer
205
+
206
+ const guards = new Set([TestGuard])
207
+ const context = createMockExecutionContext()
208
+
209
+ const result = await service.runGuards(
210
+ guards,
211
+ context,
212
+ mockScopedContainer,
213
+ )
214
+
215
+ expect(result).toBe(true)
216
+ expect(mockScopedContainer.get).toHaveBeenCalled()
217
+ expect(testGuardInstance.canActivate).toHaveBeenCalledWith(context)
218
+ })
219
+
220
+ it('should throw error for guard without canActivate', async () => {
221
+ @Injectable()
222
+ class InvalidGuard {
223
+ // Missing canActivate
224
+ }
225
+
226
+ const service = await container.get(GuardRunnerService)
227
+ const mockScopedContainer = {
228
+ get: vi.fn().mockResolvedValue(new InvalidGuard()),
229
+ } as unknown as ScopedContainer
230
+
231
+ const guards = new Set([InvalidGuard as any])
232
+ const context = createMockExecutionContext()
233
+
234
+ await expect(
235
+ service.runGuards(guards, context, mockScopedContainer),
236
+ ).rejects.toThrow('does not implement canActivate')
237
+ })
238
+
239
+ it('should reverse guard order (module -> controller -> endpoint)', async () => {
240
+ const callOrder: string[] = []
241
+
242
+ @Injectable()
243
+ class Guard1 implements CanActivate {
244
+ canActivate() {
245
+ callOrder.push('guard1')
246
+ return true
247
+ }
248
+ }
249
+
250
+ @Injectable()
251
+ class Guard2 implements CanActivate {
252
+ canActivate() {
253
+ callOrder.push('guard2')
254
+ return true
255
+ }
256
+ }
257
+
258
+ @Injectable()
259
+ class Guard3 implements CanActivate {
260
+ canActivate() {
261
+ callOrder.push('guard3')
262
+ return true
263
+ }
264
+ }
265
+
266
+ const service = await container.get(GuardRunnerService)
267
+
268
+ const mockScopedContainer = {
269
+ get: vi.fn().mockImplementation((token) => {
270
+ if (token === Guard1) return new Guard1()
271
+ if (token === Guard2) return new Guard2()
272
+ if (token === Guard3) return new Guard3()
273
+ }),
274
+ } as unknown as ScopedContainer
275
+
276
+ // Order in Set: Guard1, Guard2, Guard3
277
+ // Should execute in reverse: Guard3, Guard2, Guard1
278
+ const guards = new Set([Guard1, Guard2, Guard3])
279
+ const context = createMockExecutionContext()
280
+
281
+ await service.runGuards(guards, context, mockScopedContainer)
282
+
283
+ expect(callOrder).toEqual(['guard3', 'guard2', 'guard1'])
284
+ })
285
+ })
286
+
287
+ describe('makeContext', () => {
288
+ it('should merge guards from module, controller, and endpoint', async () => {
289
+ const service = await container.get(GuardRunnerService)
290
+
291
+ class ModuleGuard {}
292
+ class ControllerGuard {}
293
+ class EndpointGuard {}
294
+
295
+ const moduleMetadata = {
296
+ guards: new Set([ModuleGuard]),
297
+ } as unknown as ModuleMetadata
298
+
299
+ const controllerMetadata = {
300
+ guards: new Set([ControllerGuard]),
301
+ } as unknown as ControllerMetadata
302
+
303
+ const endpointMetadata = {
304
+ guards: new Set([EndpointGuard]),
305
+ } as unknown as HandlerMetadata<any>
306
+
307
+ const result = service.makeContext(
308
+ moduleMetadata,
309
+ controllerMetadata,
310
+ endpointMetadata,
311
+ )
312
+
313
+ expect(result.size).toBe(3)
314
+ expect(result.has(ModuleGuard as any)).toBe(true)
315
+ expect(result.has(ControllerGuard as any)).toBe(true)
316
+ expect(result.has(EndpointGuard as any)).toBe(true)
317
+ })
318
+
319
+ it('should handle empty guards at each level', async () => {
320
+ const service = await container.get(GuardRunnerService)
321
+
322
+ const moduleMetadata = {
323
+ guards: new Set(),
324
+ } as unknown as ModuleMetadata
325
+
326
+ const controllerMetadata = {
327
+ guards: new Set(),
328
+ } as unknown as ControllerMetadata
329
+
330
+ const endpointMetadata = {
331
+ guards: new Set(),
332
+ } as unknown as HandlerMetadata<any>
333
+
334
+ const result = service.makeContext(
335
+ moduleMetadata,
336
+ controllerMetadata,
337
+ endpointMetadata,
338
+ )
339
+
340
+ expect(result.size).toBe(0)
341
+ })
342
+
343
+ it('should deduplicate guards', async () => {
344
+ const service = await container.get(GuardRunnerService)
345
+
346
+ class SharedGuard {}
347
+
348
+ const moduleMetadata = {
349
+ guards: new Set([SharedGuard]),
350
+ } as unknown as ModuleMetadata
351
+
352
+ const controllerMetadata = {
353
+ guards: new Set([SharedGuard]), // Same guard
354
+ } as unknown as ControllerMetadata
355
+
356
+ const endpointMetadata = {
357
+ guards: new Set([SharedGuard]), // Same guard
358
+ } as unknown as HandlerMetadata<any>
359
+
360
+ const result = service.makeContext(
361
+ moduleMetadata,
362
+ controllerMetadata,
363
+ endpointMetadata,
364
+ )
365
+
366
+ // Set deduplicates automatically
367
+ expect(result.size).toBe(1)
368
+ })
369
+
370
+ it('should handle injection tokens as guards', async () => {
371
+ const service = await container.get(GuardRunnerService)
372
+
373
+ const GuardToken = InjectionToken.create<CanActivate>(
374
+ Symbol.for('GuardToken'),
375
+ )
376
+
377
+ const moduleMetadata = {
378
+ guards: new Set([GuardToken]),
379
+ } as unknown as ModuleMetadata
380
+
381
+ const controllerMetadata = {
382
+ guards: new Set(),
383
+ } as unknown as ControllerMetadata
384
+
385
+ const endpointMetadata = {
386
+ guards: new Set(),
387
+ } as unknown as HandlerMetadata<any>
388
+
389
+ const result = service.makeContext(
390
+ moduleMetadata,
391
+ controllerMetadata,
392
+ endpointMetadata,
393
+ )
394
+
395
+ expect(result.size).toBe(1)
396
+ expect(result.has(GuardToken)).toBe(true)
397
+ })
398
+ })
399
+ })
@@ -0,0 +1,147 @@
1
+ import { Container } from '@navios/di'
2
+
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
4
+
5
+ import type { LoggerService } from '../index.mjs'
6
+
7
+ import { LoggerInstance } from '../logger/logger.service.mjs'
8
+ import { LoggerOutput } from '../logger/logger.tokens.mjs'
9
+
10
+ describe('LoggerInstance', () => {
11
+ let container: Container
12
+ let mockLoggerOutput: {
13
+ log: ReturnType<typeof vi.fn>
14
+ error: ReturnType<typeof vi.fn>
15
+ warn: ReturnType<typeof vi.fn>
16
+ debug: ReturnType<typeof vi.fn>
17
+ verbose: ReturnType<typeof vi.fn>
18
+ fatal: ReturnType<typeof vi.fn>
19
+ }
20
+
21
+ beforeEach(() => {
22
+ container = new Container()
23
+ mockLoggerOutput = {
24
+ log: vi.fn(),
25
+ error: vi.fn(),
26
+ warn: vi.fn(),
27
+ debug: vi.fn(),
28
+ verbose: vi.fn(),
29
+ fatal: vi.fn(),
30
+ }
31
+ })
32
+
33
+ afterEach(async () => {
34
+ await container.dispose()
35
+ })
36
+
37
+ describe('log', () => {
38
+ it('should call localInstance.log with message', async () => {
39
+ container.addInstance(LoggerOutput, mockLoggerOutput as LoggerService)
40
+ const logger = await container.get(LoggerInstance)
41
+
42
+ logger.log('Test message')
43
+
44
+ expect(mockLoggerOutput.log).toHaveBeenCalledWith('Test message')
45
+ })
46
+ })
47
+
48
+ describe('error', () => {
49
+ it('should call localInstance.error with message', async () => {
50
+ container.addInstance(LoggerOutput, mockLoggerOutput as LoggerService)
51
+ const logger = await container.get(LoggerInstance)
52
+
53
+ logger.error('Error message')
54
+
55
+ expect(mockLoggerOutput.error).toHaveBeenCalledWith('Error message')
56
+ })
57
+ })
58
+
59
+ describe('warn', () => {
60
+ it('should call localInstance.warn with message', async () => {
61
+ container.addInstance(LoggerOutput, mockLoggerOutput as LoggerService)
62
+ const logger = await container.get(LoggerInstance)
63
+
64
+ logger.warn('Warning message')
65
+
66
+ expect(mockLoggerOutput.warn).toHaveBeenCalledWith('Warning message')
67
+ })
68
+ })
69
+
70
+ describe('debug', () => {
71
+ it('should call localInstance.debug with message', async () => {
72
+ container.addInstance(LoggerOutput, mockLoggerOutput as LoggerService)
73
+ const logger = await container.get(LoggerInstance)
74
+
75
+ logger.debug('Debug message')
76
+
77
+ expect(mockLoggerOutput.debug).toHaveBeenCalledWith('Debug message')
78
+ })
79
+
80
+ it('should handle missing debug method gracefully', async () => {
81
+ const loggerWithoutDebug = {
82
+ log: vi.fn(),
83
+ error: vi.fn(),
84
+ warn: vi.fn(),
85
+ // No debug method
86
+ }
87
+
88
+ container.addInstance(LoggerOutput, loggerWithoutDebug as LoggerService)
89
+ const logger = await container.get(LoggerInstance)
90
+
91
+ // Should not throw
92
+ expect(() => logger.debug('Debug message')).not.toThrow()
93
+ })
94
+ })
95
+
96
+ describe('verbose', () => {
97
+ it('should call localInstance.verbose with message', async () => {
98
+ container.addInstance(LoggerOutput, mockLoggerOutput as LoggerService)
99
+ const logger = await container.get(LoggerInstance)
100
+
101
+ logger.verbose('Verbose message')
102
+
103
+ expect(mockLoggerOutput.verbose).toHaveBeenCalledWith('Verbose message')
104
+ })
105
+
106
+ it('should handle missing verbose method gracefully', async () => {
107
+ const loggerWithoutVerbose = {
108
+ log: vi.fn(),
109
+ error: vi.fn(),
110
+ warn: vi.fn(),
111
+ // No verbose method
112
+ }
113
+
114
+ container.addInstance(LoggerOutput, loggerWithoutVerbose as LoggerService)
115
+ const logger = await container.get(LoggerInstance)
116
+
117
+ // Should not throw
118
+ expect(() => logger.verbose('Verbose message')).not.toThrow()
119
+ })
120
+ })
121
+
122
+ describe('fatal', () => {
123
+ it('should call localInstance.fatal with message', async () => {
124
+ container.addInstance(LoggerOutput, mockLoggerOutput as LoggerService)
125
+ const logger = await container.get(LoggerInstance)
126
+
127
+ logger.fatal('Fatal message')
128
+
129
+ expect(mockLoggerOutput.fatal).toHaveBeenCalledWith('Fatal message')
130
+ })
131
+
132
+ it('should handle missing fatal method gracefully', async () => {
133
+ const loggerWithoutFatal = {
134
+ log: vi.fn(),
135
+ error: vi.fn(),
136
+ warn: vi.fn(),
137
+ // No fatal method
138
+ }
139
+
140
+ container.addInstance(LoggerOutput, loggerWithoutFatal as LoggerService)
141
+ const logger = await container.get(LoggerInstance)
142
+
143
+ // Should not throw
144
+ expect(() => logger.fatal('Fatal message')).not.toThrow()
145
+ })
146
+ })
147
+ })
@@ -9,10 +9,11 @@ import type { ErrorResponse } from '../responders/interfaces/error-response.inte
9
9
  import { NotFoundException } from '../exceptions/not-found.exception.mjs'
10
10
  import { FrameworkError } from '../responders/enums/framework-error.enum.mjs'
11
11
  import { ErrorResponseProducerService } from '../responders/services/error-response-producer.service.mjs'
12
- import { ForbiddenResponderService } from '../responders/services/forbidden-responder.service.mjs'
13
- import { InternalServerErrorResponderService } from '../responders/services/internal-server-error-responder.service.mjs'
14
- import { NotFoundResponderService } from '../responders/services/not-found-responder.service.mjs'
15
- import { ValidationErrorResponderService } from '../responders/services/validation-error-responder.service.mjs'
12
+ // Import services for side-effects (registers @Injectable decorators)
13
+ import '../responders/services/forbidden-responder.service.mjs'
14
+ import '../responders/services/internal-server-error-responder.service.mjs'
15
+ import '../responders/services/not-found-responder.service.mjs'
16
+ import '../responders/services/validation-error-responder.service.mjs'
16
17
  import {
17
18
  ForbiddenResponderToken,
18
19
  InternalServerErrorResponderToken,
@@ -309,7 +310,7 @@ describe('Responders', () => {
309
310
  token: NotFoundResponderToken,
310
311
  priority: 0, // Higher than default -10
311
312
  })
312
- class CustomNotFoundResponder implements ErrorResponder {
313
+ class _CustomNotFoundResponder implements ErrorResponder {
313
314
  getResponse(_error: unknown, description?: string): ErrorResponse {
314
315
  return {
315
316
  statusCode: 404,
@@ -32,10 +32,94 @@ export type HandlerResult<TRequest = any, TReply = any> =
32
32
  | StaticHandler<TRequest, TReply>
33
33
  | DynamicHandler<TRequest, TReply>
34
34
 
35
- export interface AbstractHttpHandlerAdapterInterface {
35
+ /**
36
+ * Function type for argument getters that extract data from requests.
37
+ * Each getter populates a target object with data from the request.
38
+ */
39
+ export type ArgumentGetterFn<TRequest = any> = (
40
+ target: Record<string, any>,
41
+ request: TRequest,
42
+ ) => void | Promise<void>
43
+
44
+ /**
45
+ * Function type for formatting arguments from a request.
46
+ * Built from argument getters, optimized for sync/async handling.
47
+ */
48
+ export type FormatArgumentsFn<TRequest = any> = (
49
+ request: TRequest,
50
+ ) => Record<string, any> | Promise<Record<string, any>>
51
+
52
+ /**
53
+ * Interface for HTTP handler adapter services.
54
+ *
55
+ * Adapters handle different types of HTTP requests (REST, streaming, multipart)
56
+ * and are responsible for:
57
+ * - Parsing and validating request data
58
+ * - Creating handler functions
59
+ * - Formatting responses
60
+ * - Providing schema information (for frameworks like Fastify)
61
+ */
62
+ export interface AbstractHttpHandlerAdapterInterface<TRequest = any> {
63
+ /**
64
+ * Prepares argument getters for parsing request data.
65
+ *
66
+ * Creates functions that extract and validate data from the request,
67
+ * populating a target object with validated arguments.
68
+ *
69
+ * @param handlerMetadata - The handler metadata with schemas and configuration.
70
+ * @returns An array of getter functions that populate request arguments.
71
+ */
36
72
  prepareArguments?: (
37
73
  handlerMetadata: HandlerMetadata<any>,
38
- ) => ((target: Record<string, any>, request: any) => Promise<void> | void)[]
74
+ ) => ArgumentGetterFn<TRequest>[]
75
+
76
+ /**
77
+ * Builds a formatArguments function from argument getters.
78
+ *
79
+ * Automatically detects sync vs async getters and optimizes accordingly:
80
+ * - If all getters are sync: returns sync function (no Promise overhead)
81
+ * - If any getter is async: returns async function with Promise.all
82
+ * - If no getters: returns frozen empty object (zero allocation)
83
+ *
84
+ * This method is useful for composition-based adapters that need to
85
+ * build formatArguments without duplicating the optimization logic.
86
+ *
87
+ * @param getters - Array of argument getter functions
88
+ * @returns Function to format arguments from request
89
+ */
90
+ buildFormatArguments?: (
91
+ getters: ArgumentGetterFn<TRequest>[],
92
+ ) => FormatArgumentsFn<TRequest>
93
+
94
+ /**
95
+ * Checks if the handler has any validation schemas defined.
96
+ *
97
+ * @param handlerMetadata - The handler metadata containing configuration.
98
+ * @returns `true` if the handler has any schemas (request, query, response).
99
+ */
100
+ hasSchema?: (handlerMetadata: HandlerMetadata<any>) => boolean
101
+
102
+ /**
103
+ * Provides schema information for the framework's validation system.
104
+ *
105
+ * For frameworks like Fastify, this returns route schema objects.
106
+ * For frameworks like Bun, this typically returns an empty object.
107
+ *
108
+ * @param handlerMetadata - The handler metadata containing configuration.
109
+ * @returns Schema information for framework registration.
110
+ */
111
+ provideSchema?: (handlerMetadata: HandlerMetadata<any>) => Record<string, any>
112
+
113
+ /**
114
+ * Creates a request handler function for the endpoint.
115
+ *
116
+ * This is the core method that generates the actual handler function
117
+ * that will be called when a request matches the endpoint.
118
+ *
119
+ * @param controller - The controller class containing the handler method.
120
+ * @param handlerMetadata - The handler metadata with configuration and schemas.
121
+ * @returns A handler result that is either static or dynamic.
122
+ */
39
123
  provideHandler: (
40
124
  controller: ClassType,
41
125
  handlerMetadata: HandlerMetadata<any>,
@@ -1,5 +1,6 @@
1
- import type { z, ZodType } from 'zod/v4'
2
1
  import type { ClassType } from '@navios/di'
2
+ import { createClassContext, createMethodContext } from '@navios/di/legacy-compat'
3
+ import type { z, ZodType } from 'zod/v4'
3
4
 
4
5
  import type {
5
6
  ControllerMetadata,
@@ -18,7 +19,6 @@ import {
18
19
  getManagedMetadata,
19
20
  hasManagedMetadata,
20
21
  } from '../metadata/navios-managed.metadata.mjs'
21
- import { createClassContext, createMethodContext } from './context-compat.mjs'
22
22
 
23
23
  /**
24
24
  * Type for a legacy class/method attribute decorator without a value.
@@ -1,9 +1,9 @@
1
1
  import type { ClassType } from '@navios/di'
2
+ import { createClassContext } from '@navios/di/legacy-compat'
2
3
 
3
4
  import type { ControllerOptions } from '../../decorators/controller.decorator.mjs'
4
5
 
5
6
  import { Controller as OriginalController } from '../../decorators/controller.decorator.mjs'
6
- import { createClassContext } from '../context-compat.mjs'
7
7
 
8
8
  /**
9
9
  * Legacy-compatible Controller decorator.