@navios/core 0.7.1 → 0.8.0

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 +31 -0
  2. package/lib/{index-DW9EPAE6.d.mts → index-BDNl7j1G.d.cts} +515 -346
  3. package/lib/index-BDNl7j1G.d.cts.map +1 -0
  4. package/lib/{index-pHp-dIGt.d.cts → index-BoP0cWT6.d.mts} +515 -346
  5. package/lib/index-BoP0cWT6.d.mts.map +1 -0
  6. package/lib/index.cjs +12 -3
  7. package/lib/index.d.cts +2 -2
  8. package/lib/index.d.mts +2 -2
  9. package/lib/index.mjs +3 -3
  10. package/lib/legacy-compat/index.cjs +1 -1
  11. package/lib/legacy-compat/index.cjs.map +1 -1
  12. package/lib/legacy-compat/index.d.cts +1 -1
  13. package/lib/legacy-compat/index.d.mts +1 -1
  14. package/lib/legacy-compat/index.mjs +1 -1
  15. package/lib/legacy-compat/index.mjs.map +1 -1
  16. package/lib/{src-QnxR5b7c.cjs → src-B6eISODM.cjs} +465 -47
  17. package/lib/src-B6eISODM.cjs.map +1 -0
  18. package/lib/{src-DyvCDuKO.mjs → src-gBAChVRL.mjs} +445 -45
  19. package/lib/src-gBAChVRL.mjs.map +1 -0
  20. package/lib/testing/index.cjs +2 -2
  21. package/lib/testing/index.d.cts +1 -1
  22. package/lib/testing/index.d.mts +1 -1
  23. package/lib/testing/index.mjs +2 -2
  24. package/lib/{use-guards.decorator-B6q_N0sf.cjs → use-guards.decorator-COR-9mZY.cjs} +20 -94
  25. package/lib/use-guards.decorator-COR-9mZY.cjs.map +1 -0
  26. package/lib/{use-guards.decorator-kZ3lNK8v.mjs → use-guards.decorator-CUww54Nt.mjs} +14 -94
  27. package/lib/use-guards.decorator-CUww54Nt.mjs.map +1 -0
  28. package/package.json +4 -4
  29. package/src/__tests__/controller-resolver.spec.mts +223 -0
  30. package/src/__tests__/controller.spec.mts +1 -1
  31. package/src/decorators/controller.decorator.mts +11 -6
  32. package/src/decorators/endpoint.decorator.mts +60 -12
  33. package/src/decorators/multipart.decorator.mts +67 -24
  34. package/src/decorators/stream.decorator.mts +65 -24
  35. package/src/interfaces/abstract-http-handler-adapter.interface.mts +31 -1
  36. package/src/legacy-compat/decorators/endpoint.decorator.mts +1 -1
  37. package/src/legacy-compat/decorators/multipart.decorator.mts +1 -1
  38. package/src/legacy-compat/decorators/stream.decorator.mts +1 -1
  39. package/src/logger/logger.service.mts +0 -2
  40. package/src/navios.application.mts +14 -0
  41. package/src/navios.factory.mts +19 -0
  42. package/src/services/guard-runner.service.mts +46 -9
  43. package/src/services/index.mts +1 -0
  44. package/src/services/instance-resolver.service.mts +186 -0
  45. package/src/stores/request-id.store.mts +45 -3
  46. package/src/tokens/index.mts +1 -0
  47. package/src/tokens/navios-options.token.mts +6 -0
  48. package/lib/index-DW9EPAE6.d.mts.map +0 -1
  49. package/lib/index-pHp-dIGt.d.cts.map +0 -1
  50. package/lib/src-DyvCDuKO.mjs.map +0 -1
  51. package/lib/src-QnxR5b7c.cjs.map +0 -1
  52. package/lib/use-guards.decorator-B6q_N0sf.cjs.map +0 -1
  53. package/lib/use-guards.decorator-kZ3lNK8v.mjs.map +0 -1
@@ -0,0 +1,223 @@
1
+ import {
2
+ Container,
3
+ getInjectableToken,
4
+ inject,
5
+ Injectable,
6
+ InjectableScope,
7
+ } from '@navios/di'
8
+
9
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
10
+
11
+ import { InstanceResolverService } from '../services/instance-resolver.service.mjs'
12
+
13
+ function createTestSetup() {
14
+ const container = new Container()
15
+
16
+ return { container }
17
+ }
18
+
19
+ describe('InstanceResolverService', () => {
20
+ let container: Container
21
+
22
+ beforeEach(() => {
23
+ const setup = createTestSetup()
24
+ container = setup.container
25
+ })
26
+
27
+ afterEach(async () => {
28
+ await container.dispose()
29
+ })
30
+
31
+ describe('resolve', () => {
32
+ it('should cache singleton controller without request-scoped dependencies', async () => {
33
+ @Injectable()
34
+ class SimpleService {
35
+ value = 'simple'
36
+ }
37
+
38
+ @Injectable()
39
+ class SingletonController {
40
+ private service = inject(SimpleService)
41
+
42
+ getValue() {
43
+ return this.service.value
44
+ }
45
+ }
46
+
47
+ const resolver = await container.get(InstanceResolverService)
48
+ const resolution = await resolver.resolve(SingletonController)
49
+
50
+ expect(resolution.cached).toBe(true)
51
+ expect(resolution.instance).toBeInstanceOf(SingletonController)
52
+ expect((resolution.instance as SingletonController).getValue()).toBe(
53
+ 'simple',
54
+ )
55
+ })
56
+
57
+ it('should not cache controller with request-scoped dependencies', async () => {
58
+ @Injectable({ scope: InjectableScope.Request })
59
+ class RequestScopedService {
60
+ id = Math.random().toString(36).substring(7)
61
+ }
62
+
63
+ @Injectable()
64
+ class ControllerWithRequestDep {
65
+ private service = inject(RequestScopedService)
66
+
67
+ getServiceId() {
68
+ return this.service.id
69
+ }
70
+ }
71
+
72
+ const resolver = await container.get(InstanceResolverService)
73
+ const resolution = await resolver.resolve(ControllerWithRequestDep)
74
+
75
+ expect(resolution.cached).toBe(false)
76
+ expect(resolution.instance).toBeNull()
77
+ expect(typeof resolution.resolve).toBe('function')
78
+ })
79
+
80
+ it('should update controller scope to Request when it has request-scoped dependencies', async () => {
81
+ @Injectable({ scope: InjectableScope.Request })
82
+ class RequestScopedService {
83
+ id = Math.random().toString(36).substring(7)
84
+ }
85
+
86
+ @Injectable()
87
+ class ControllerWithRequestDep {
88
+ private service = inject(RequestScopedService)
89
+
90
+ getServiceId() {
91
+ return this.service.id
92
+ }
93
+ }
94
+
95
+ const resolver = await container.get(InstanceResolverService)
96
+ await resolver.resolve(ControllerWithRequestDep)
97
+
98
+ // Check that the controller's scope was updated
99
+ const token = container
100
+ .getRegistry()
101
+ .get(getInjectableToken(ControllerWithRequestDep))
102
+ expect(token.scope).toBe(InjectableScope.Request)
103
+ })
104
+
105
+ it('should resolve different instances per request when controller has request-scoped deps', async () => {
106
+ let serviceInstanceCount = 0
107
+
108
+ @Injectable({ scope: InjectableScope.Request })
109
+ class RequestScopedService {
110
+ id = ++serviceInstanceCount
111
+ }
112
+
113
+ @Injectable()
114
+ class ControllerWithRequestDep {
115
+ private service = inject(RequestScopedService)
116
+
117
+ getServiceId() {
118
+ return this.service.id
119
+ }
120
+ }
121
+
122
+ const resolver = await container.get(InstanceResolverService)
123
+ const resolution = await resolver.resolve(ControllerWithRequestDep)
124
+
125
+ expect(resolution.cached).toBe(false)
126
+
127
+ // Request 1
128
+ const scoped1 = container.beginRequest('request-1')
129
+ const controller1 = await resolution.resolve(scoped1)
130
+ const id1 = (controller1 as ControllerWithRequestDep).getServiceId()
131
+
132
+ // Request 2
133
+ const scoped2 = container.beginRequest('request-2')
134
+ const controller2 = await resolution.resolve(scoped2)
135
+ const id2 = (controller2 as ControllerWithRequestDep).getServiceId()
136
+
137
+ expect(id1).toBe(1)
138
+ expect(id2).toBe(2)
139
+ expect(controller1).not.toBe(controller2)
140
+
141
+ await scoped1.endRequest()
142
+ await scoped2.endRequest()
143
+ })
144
+
145
+ it('should handle parallel requests with isolated instances', async () => {
146
+ let serviceInstanceCount = 0
147
+
148
+ @Injectable({ scope: InjectableScope.Request })
149
+ class RequestTrackerService {
150
+ id = ++serviceInstanceCount
151
+ data: Record<string, any> = {}
152
+
153
+ addData(key: string, value: any) {
154
+ this.data[key] = value
155
+ }
156
+
157
+ getData(key: string) {
158
+ return this.data[key]
159
+ }
160
+ }
161
+
162
+ @Injectable()
163
+ class ControllerWithTracker {
164
+ private tracker = inject(RequestTrackerService)
165
+
166
+ async handleRequest(data: string) {
167
+ this.tracker.addData('input', data)
168
+ // Simulate async work
169
+ await new Promise((resolve) => setTimeout(resolve, 5))
170
+ return {
171
+ id: this.tracker.id,
172
+ data: this.tracker.getData('input'),
173
+ }
174
+ }
175
+ }
176
+
177
+ const resolver = await container.get(InstanceResolverService)
178
+ const resolution = await resolver.resolve(ControllerWithTracker)
179
+
180
+ expect(resolution.cached).toBe(false)
181
+
182
+ // Create 5 parallel requests
183
+ const requests = ['req1', 'req2', 'req3', 'req4', 'req5'].map(
184
+ async (data, i) => {
185
+ const scoped = container.beginRequest(`request-${i}`)
186
+ const controller = (await resolution.resolve(
187
+ scoped,
188
+ )) as ControllerWithTracker
189
+ const result = await controller.handleRequest(data)
190
+ await scoped.endRequest()
191
+ return result
192
+ },
193
+ )
194
+
195
+ const results = await Promise.all(requests)
196
+
197
+ // Verify each request got its own unique ID
198
+ const ids = results.map((r) => r.id)
199
+ const uniqueIds = new Set(ids)
200
+ expect(uniqueIds.size).toBe(5)
201
+
202
+ // Verify each request returned its own data
203
+ const expectedData = ['req1', 'req2', 'req3', 'req4', 'req5']
204
+ const actualData = results.map((r) => r.data)
205
+ expect(actualData).toEqual(expect.arrayContaining(expectedData))
206
+ })
207
+
208
+ it('should return same cached instance for singleton controllers', async () => {
209
+ @Injectable()
210
+ class SingletonController {
211
+ id = Math.random()
212
+ }
213
+
214
+ const resolver = await container.get(InstanceResolverService)
215
+ const resolution1 = await resolver.resolve(SingletonController)
216
+ const resolution2 = await resolver.resolve(SingletonController)
217
+
218
+ expect(resolution1.cached).toBe(true)
219
+ expect(resolution2.cached).toBe(true)
220
+ expect(resolution1.instance).toBe(resolution2.instance)
221
+ })
222
+ })
223
+ })
@@ -36,7 +36,7 @@ describe('Controller decorator', () => {
36
36
  @Controller()
37
37
  class Test {
38
38
  @Endpoint(endpoint)
39
- async getBar(params: EndpointParams<typeof endpoint>) {
39
+ async getBar() {
40
40
  return {
41
41
  foo: 'bar',
42
42
  }
@@ -1,6 +1,6 @@
1
1
  import type { ClassType } from '@navios/di'
2
2
 
3
- import { Injectable, InjectableScope, InjectionToken } from '@navios/di'
3
+ import { Injectable, InjectionToken, Registry } from '@navios/di'
4
4
 
5
5
  import { getControllerMetadata } from '../metadata/index.mjs'
6
6
 
@@ -13,17 +13,22 @@ export interface ControllerOptions {
13
13
  * Guards are executed in reverse order (last guard first).
14
14
  */
15
15
  guards?: ClassType[] | Set<ClassType>
16
+ /**
17
+ * Registry to use for the controller.
18
+ * Registry is used to store the controller and its endpoints.
19
+ */
20
+ registry?: Registry
16
21
  }
17
22
 
18
23
  /**
19
24
  * Decorator that marks a class as a Navios controller.
20
- *
25
+ *
21
26
  * Controllers handle HTTP requests and define endpoints.
22
27
  * They are request-scoped by default, meaning a new instance is created for each request.
23
- *
28
+ *
24
29
  * @param options - Controller configuration options
25
30
  * @returns A class decorator
26
- *
31
+ *
27
32
  * @example
28
33
  * ```typescript
29
34
  * @Controller({ guards: [AuthGuard] })
@@ -35,7 +40,7 @@ export interface ControllerOptions {
35
40
  * }
36
41
  * ```
37
42
  */
38
- export function Controller({ guards }: ControllerOptions = {}) {
43
+ export function Controller({ guards, registry }: ControllerOptions = {}) {
39
44
  return function (target: ClassType, context: ClassDecoratorContext) {
40
45
  if (context.kind !== 'class') {
41
46
  throw new Error(
@@ -53,7 +58,7 @@ export function Controller({ guards }: ControllerOptions = {}) {
53
58
  }
54
59
  return Injectable({
55
60
  token,
56
- scope: InjectableScope.Request,
61
+ registry,
57
62
  })(target, context)
58
63
  }
59
64
  }
@@ -126,6 +126,51 @@ export type EndpointResult<
126
126
  * }
127
127
  * ```
128
128
  */
129
+ export function Endpoint<
130
+ Method extends HttpMethod = HttpMethod,
131
+ Url extends string = string,
132
+ QuerySchema = undefined,
133
+ ResponseSchema extends ZodType = ZodType,
134
+ RequestSchema = ZodType,
135
+ Params = QuerySchema extends ZodType
136
+ ? RequestSchema extends ZodType
137
+ ? EndpointFunctionArgs<Url, QuerySchema, RequestSchema, true>
138
+ : EndpointFunctionArgs<Url, QuerySchema, undefined, true>
139
+ : RequestSchema extends ZodType
140
+ ? EndpointFunctionArgs<Url, undefined, RequestSchema, true>
141
+ : EndpointFunctionArgs<Url, undefined, undefined, true>,
142
+ >(endpoint: {
143
+ config: BaseEndpointConfig<
144
+ Method,
145
+ Url,
146
+ QuerySchema,
147
+ ResponseSchema,
148
+ RequestSchema
149
+ >
150
+ }): (
151
+ target: (
152
+ params: Params,
153
+ ) => Promise<z.input<ResponseSchema>> | z.input<ResponseSchema>,
154
+ context: ClassMethodDecoratorContext,
155
+ ) => void
156
+ export function Endpoint<
157
+ Method extends HttpMethod = HttpMethod,
158
+ Url extends string = string,
159
+ QuerySchema = undefined,
160
+ ResponseSchema extends ZodType = ZodType,
161
+ RequestSchema = ZodType,
162
+ >(endpoint: {
163
+ config: BaseEndpointConfig<
164
+ Method,
165
+ Url,
166
+ QuerySchema,
167
+ ResponseSchema,
168
+ RequestSchema
169
+ >
170
+ }): (
171
+ target: () => Promise<z.input<ResponseSchema>> | z.input<ResponseSchema>,
172
+ context: ClassMethodDecoratorContext,
173
+ ) => void
129
174
  export function Endpoint<
130
175
  Method extends HttpMethod = HttpMethod,
131
176
  Url extends string = string,
@@ -141,18 +186,21 @@ export function Endpoint<
141
186
  RequestSchema
142
187
  >
143
188
  }) {
144
- return (
145
- target: (
146
- params: QuerySchema extends ZodType
147
- ? RequestSchema extends ZodType
148
- ? EndpointFunctionArgs<Url, QuerySchema, RequestSchema, true>
149
- : EndpointFunctionArgs<Url, QuerySchema, undefined, true>
150
- : RequestSchema extends ZodType
151
- ? EndpointFunctionArgs<Url, undefined, RequestSchema, true>
152
- : EndpointFunctionArgs<Url, undefined, undefined, true>,
153
- ) => Promise<z.input<ResponseSchema>>,
154
- context: ClassMethodDecoratorContext,
155
- ) => {
189
+ type Params = QuerySchema extends ZodType
190
+ ? RequestSchema extends ZodType
191
+ ? EndpointFunctionArgs<Url, QuerySchema, RequestSchema, true>
192
+ : EndpointFunctionArgs<Url, QuerySchema, undefined, true>
193
+ : RequestSchema extends ZodType
194
+ ? EndpointFunctionArgs<Url, undefined, RequestSchema, true>
195
+ : EndpointFunctionArgs<Url, undefined, undefined, true>
196
+
197
+ type Handler =
198
+ | ((
199
+ params: Params,
200
+ ) => Promise<z.input<ResponseSchema>> | z.input<ResponseSchema>)
201
+ | (() => Promise<z.input<ResponseSchema>> | z.input<ResponseSchema>)
202
+
203
+ return (target: Handler, context: ClassMethodDecoratorContext) => {
156
204
  if (context.kind !== 'method') {
157
205
  throw new Error(
158
206
  '[Navios] Endpoint decorator can only be used on methods.',
@@ -13,9 +13,9 @@ import { MultipartAdapterToken } from '../tokens/index.mjs'
13
13
 
14
14
  /**
15
15
  * Extracts the typed parameters for a multipart endpoint handler function.
16
- *
16
+ *
17
17
  * Similar to `EndpointParams`, but specifically for multipart/form-data endpoints.
18
- *
18
+ *
19
19
  * @typeParam EndpointDeclaration - The endpoint declaration from @navios/builder
20
20
  */
21
21
  export type MultipartParams<
@@ -48,7 +48,7 @@ export type MultipartParams<
48
48
 
49
49
  /**
50
50
  * Extracts the typed return value for a multipart endpoint handler function.
51
- *
51
+ *
52
52
  * @typeParam EndpointDeclaration - The endpoint declaration from @navios/builder
53
53
  */
54
54
  export type MultipartResult<
@@ -64,13 +64,13 @@ export type MultipartResult<
64
64
 
65
65
  /**
66
66
  * Decorator that marks a method as a multipart/form-data endpoint.
67
- *
67
+ *
68
68
  * Use this decorator for endpoints that handle file uploads or form data.
69
69
  * The endpoint must be defined using @navios/builder's `declareMultipart` method.
70
- *
70
+ *
71
71
  * @param endpoint - The multipart endpoint declaration from @navios/builder
72
72
  * @returns A method decorator
73
- *
73
+ *
74
74
  * @example
75
75
  * ```typescript
76
76
  * const uploadFileEndpoint = api.declareMultipart({
@@ -79,7 +79,7 @@ export type MultipartResult<
79
79
  * requestSchema: z.object({ file: z.instanceof(File) }),
80
80
  * responseSchema: z.object({ url: z.string() }),
81
81
  * })
82
- *
82
+ *
83
83
  * @Controller()
84
84
  * export class FileController {
85
85
  * @Multipart(uploadFileEndpoint)
@@ -91,6 +91,51 @@ export type MultipartResult<
91
91
  * }
92
92
  * ```
93
93
  */
94
+ export function Multipart<
95
+ Method extends HttpMethod = HttpMethod,
96
+ Url extends string = string,
97
+ QuerySchema = undefined,
98
+ ResponseSchema extends ZodType = ZodType,
99
+ RequestSchema = ZodType,
100
+ Params = QuerySchema extends ZodObject
101
+ ? RequestSchema extends ZodType
102
+ ? EndpointFunctionArgs<Url, QuerySchema, RequestSchema, true>
103
+ : EndpointFunctionArgs<Url, QuerySchema, undefined, true>
104
+ : RequestSchema extends ZodType
105
+ ? EndpointFunctionArgs<Url, undefined, RequestSchema, true>
106
+ : EndpointFunctionArgs<Url, undefined, undefined, true>,
107
+ >(endpoint: {
108
+ config: BaseEndpointConfig<
109
+ Method,
110
+ Url,
111
+ QuerySchema,
112
+ ResponseSchema,
113
+ RequestSchema
114
+ >
115
+ }): (
116
+ target: (
117
+ params: Params,
118
+ ) => Promise<z.input<ResponseSchema>> | z.input<ResponseSchema>,
119
+ context: ClassMethodDecoratorContext,
120
+ ) => void
121
+ export function Multipart<
122
+ Method extends HttpMethod = HttpMethod,
123
+ Url extends string = string,
124
+ QuerySchema = undefined,
125
+ ResponseSchema extends ZodType = ZodType,
126
+ RequestSchema = ZodType,
127
+ >(endpoint: {
128
+ config: BaseEndpointConfig<
129
+ Method,
130
+ Url,
131
+ QuerySchema,
132
+ ResponseSchema,
133
+ RequestSchema
134
+ >
135
+ }): (
136
+ target: () => Promise<z.input<ResponseSchema>> | z.input<ResponseSchema>,
137
+ context: ClassMethodDecoratorContext,
138
+ ) => void
94
139
  export function Multipart<
95
140
  Method extends HttpMethod = HttpMethod,
96
141
  Url extends string = string,
@@ -106,23 +151,21 @@ export function Multipart<
106
151
  RequestSchema
107
152
  >
108
153
  }) {
109
- return (
110
- target: (
111
- params: QuerySchema extends ZodObject
112
- ? RequestSchema extends ZodType
113
- ? EndpointFunctionArgs<Url, QuerySchema, RequestSchema>
114
- : EndpointFunctionArgs<Url, QuerySchema, undefined>
115
- : RequestSchema extends ZodType
116
- ? EndpointFunctionArgs<Url, undefined, RequestSchema>
117
- : EndpointFunctionArgs<Url, undefined, undefined>,
118
- ) => Promise<z.input<ResponseSchema>>,
119
- context: ClassMethodDecoratorContext,
120
- ) => {
121
- if (typeof target !== 'function') {
122
- throw new Error(
123
- '[Navios] Endpoint decorator can only be used on functions.',
124
- )
125
- }
154
+ type Params = QuerySchema extends ZodObject
155
+ ? RequestSchema extends ZodType
156
+ ? EndpointFunctionArgs<Url, QuerySchema, RequestSchema, true>
157
+ : EndpointFunctionArgs<Url, QuerySchema, undefined, true>
158
+ : RequestSchema extends ZodType
159
+ ? EndpointFunctionArgs<Url, undefined, RequestSchema, true>
160
+ : EndpointFunctionArgs<Url, undefined, undefined, true>
161
+
162
+ type Handler =
163
+ | ((
164
+ params: Params,
165
+ ) => Promise<z.input<ResponseSchema>> | z.input<ResponseSchema>)
166
+ | (() => Promise<z.input<ResponseSchema>> | z.input<ResponseSchema>)
167
+
168
+ return (target: Handler, context: ClassMethodDecoratorContext) => {
126
169
  if (context.kind !== 'method') {
127
170
  throw new Error(
128
171
  '[Navios] Endpoint decorator can only be used on methods.',
@@ -11,9 +11,9 @@ import { StreamAdapterToken } from '../tokens/index.mjs'
11
11
 
12
12
  /**
13
13
  * Extracts the typed parameters for a stream endpoint handler function.
14
- *
14
+ *
15
15
  * Similar to `EndpointParams`, but specifically for streaming endpoints.
16
- *
16
+ *
17
17
  * @typeParam EndpointDeclaration - The stream endpoint declaration from @navios/builder
18
18
  */
19
19
  export type StreamParams<
@@ -46,20 +46,20 @@ export type StreamParams<
46
46
 
47
47
  /**
48
48
  * Decorator that marks a method as a streaming endpoint.
49
- *
49
+ *
50
50
  * Use this decorator for endpoints that stream data (e.g., file downloads, SSE).
51
51
  * The endpoint must be defined using @navios/builder's `declareStream` method.
52
- *
52
+ *
53
53
  * @param endpoint - The stream endpoint declaration from @navios/builder
54
54
  * @returns A method decorator
55
- *
55
+ *
56
56
  * @example
57
57
  * ```typescript
58
58
  * const downloadFileEndpoint = api.declareStream({
59
59
  * method: 'get',
60
60
  * url: '/files/$fileId',
61
61
  * })
62
- *
62
+ *
63
63
  * @Controller()
64
64
  * export class FileController {
65
65
  * @Stream(downloadFileEndpoint)
@@ -70,6 +70,51 @@ export type StreamParams<
70
70
  * }
71
71
  * ```
72
72
  */
73
+ export function Stream<
74
+ Method extends HttpMethod = HttpMethod,
75
+ Url extends string = string,
76
+ QuerySchema = undefined,
77
+ RequestSchema = ZodType,
78
+ Params = QuerySchema extends ZodObject
79
+ ? RequestSchema extends ZodType
80
+ ? EndpointFunctionArgs<Url, QuerySchema, RequestSchema, true>
81
+ : EndpointFunctionArgs<Url, QuerySchema, undefined, true>
82
+ : RequestSchema extends ZodType
83
+ ? EndpointFunctionArgs<Url, undefined, RequestSchema, true>
84
+ : EndpointFunctionArgs<Url, undefined, undefined, true>,
85
+ >(endpoint: {
86
+ config: BaseStreamConfig<Method, Url, QuerySchema, RequestSchema>
87
+ }): (
88
+ target: (params: Params, reply: any) => any,
89
+ context: ClassMethodDecoratorContext,
90
+ ) => void
91
+ // Bun doesn't support reply parameter
92
+ export function Stream<
93
+ Method extends HttpMethod = HttpMethod,
94
+ Url extends string = string,
95
+ QuerySchema = undefined,
96
+ RequestSchema = ZodType,
97
+ Params = QuerySchema extends ZodObject
98
+ ? RequestSchema extends ZodType
99
+ ? EndpointFunctionArgs<Url, QuerySchema, RequestSchema, true>
100
+ : EndpointFunctionArgs<Url, QuerySchema, undefined, true>
101
+ : RequestSchema extends ZodType
102
+ ? EndpointFunctionArgs<Url, undefined, RequestSchema, true>
103
+ : EndpointFunctionArgs<Url, undefined, undefined, true>,
104
+ >(endpoint: {
105
+ config: BaseStreamConfig<Method, Url, QuerySchema, RequestSchema>
106
+ }): (
107
+ target: (params: Params) => any,
108
+ context: ClassMethodDecoratorContext,
109
+ ) => void
110
+ export function Stream<
111
+ Method extends HttpMethod = HttpMethod,
112
+ Url extends string = string,
113
+ QuerySchema = undefined,
114
+ RequestSchema = ZodType,
115
+ >(endpoint: {
116
+ config: BaseStreamConfig<Method, Url, QuerySchema, RequestSchema>
117
+ }): (target: () => any, context: ClassMethodDecoratorContext) => void
73
118
  export function Stream<
74
119
  Method extends HttpMethod = HttpMethod,
75
120
  Url extends string = string,
@@ -78,24 +123,20 @@ export function Stream<
78
123
  >(endpoint: {
79
124
  config: BaseStreamConfig<Method, Url, QuerySchema, RequestSchema>
80
125
  }) {
81
- return (
82
- target: (
83
- params: QuerySchema extends ZodObject
84
- ? RequestSchema extends ZodType
85
- ? EndpointFunctionArgs<Url, QuerySchema, RequestSchema>
86
- : EndpointFunctionArgs<Url, QuerySchema, undefined>
87
- : RequestSchema extends ZodType
88
- ? EndpointFunctionArgs<Url, undefined, RequestSchema>
89
- : EndpointFunctionArgs<Url, undefined, undefined>,
90
- reply: any,
91
- ) => Promise<void>,
92
- context: ClassMethodDecoratorContext,
93
- ) => {
94
- if (typeof target !== 'function') {
95
- throw new Error(
96
- '[Navios] Endpoint decorator can only be used on functions.',
97
- )
98
- }
126
+ type Params = QuerySchema extends ZodObject
127
+ ? RequestSchema extends ZodType
128
+ ? EndpointFunctionArgs<Url, QuerySchema, RequestSchema, true>
129
+ : EndpointFunctionArgs<Url, QuerySchema, undefined, true>
130
+ : RequestSchema extends ZodType
131
+ ? EndpointFunctionArgs<Url, undefined, RequestSchema, true>
132
+ : EndpointFunctionArgs<Url, undefined, undefined, true>
133
+
134
+ type Handler =
135
+ | ((params: Params, reply: any) => any)
136
+ | ((params: Params) => any)
137
+ | (() => any)
138
+
139
+ return (target: Handler, context: ClassMethodDecoratorContext) => {
99
140
  if (context.kind !== 'method') {
100
141
  throw new Error(
101
142
  '[Navios] Endpoint decorator can only be used on methods.',
@@ -2,6 +2,36 @@ import type { ClassType, ScopedContainer } from '@navios/di'
2
2
 
3
3
  import type { HandlerMetadata } from '../metadata/index.mjs'
4
4
 
5
+ /**
6
+ * Static handler result - handler can be called without a scoped container.
7
+ * Used when the controller and all its dependencies are singletons.
8
+ */
9
+ export type StaticHandler<TRequest = any, TReply = any> = {
10
+ isStatic: true
11
+ handler: (request: TRequest, reply: TReply) => Promise<any>
12
+ }
13
+
14
+ /**
15
+ * Dynamic handler result - handler requires a scoped container for resolution.
16
+ * Used when the controller or its dependencies need per-request resolution.
17
+ */
18
+ export type DynamicHandler<TRequest = any, TReply = any> = {
19
+ isStatic: false
20
+ handler: (
21
+ scoped: ScopedContainer,
22
+ request: TRequest,
23
+ reply: TReply,
24
+ ) => Promise<any>
25
+ }
26
+
27
+ /**
28
+ * Handler result returned by provideHandler.
29
+ * Can be either static (pre-resolved) or dynamic (needs scoped container).
30
+ */
31
+ export type HandlerResult<TRequest = any, TReply = any> =
32
+ | StaticHandler<TRequest, TReply>
33
+ | DynamicHandler<TRequest, TReply>
34
+
5
35
  export interface AbstractHttpHandlerAdapterInterface {
6
36
  prepareArguments?: (
7
37
  handlerMetadata: HandlerMetadata<any>,
@@ -9,5 +39,5 @@ export interface AbstractHttpHandlerAdapterInterface {
9
39
  provideHandler: (
10
40
  controller: ClassType,
11
41
  handlerMetadata: HandlerMetadata<any>,
12
- ) => (context: ScopedContainer, request: any, reply: any) => Promise<any>
42
+ ) => Promise<HandlerResult>
13
43
  }
@@ -92,7 +92,7 @@ export function Endpoint<
92
92
  // @ts-expect-error - we don't need to type the value
93
93
  const result = originalDecorator(typedDescriptor.value, context)
94
94
  if (result !== typedDescriptor.value) {
95
- typedDescriptor.value = result
95
+ typedDescriptor.value = result as any
96
96
  }
97
97
  return typedDescriptor
98
98
  }