@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.
- package/CHANGELOG.md +31 -0
- package/lib/{index-DW9EPAE6.d.mts → index-BDNl7j1G.d.cts} +515 -346
- package/lib/index-BDNl7j1G.d.cts.map +1 -0
- package/lib/{index-pHp-dIGt.d.cts → index-BoP0cWT6.d.mts} +515 -346
- package/lib/index-BoP0cWT6.d.mts.map +1 -0
- package/lib/index.cjs +12 -3
- package/lib/index.d.cts +2 -2
- package/lib/index.d.mts +2 -2
- package/lib/index.mjs +3 -3
- package/lib/legacy-compat/index.cjs +1 -1
- package/lib/legacy-compat/index.cjs.map +1 -1
- package/lib/legacy-compat/index.d.cts +1 -1
- package/lib/legacy-compat/index.d.mts +1 -1
- package/lib/legacy-compat/index.mjs +1 -1
- package/lib/legacy-compat/index.mjs.map +1 -1
- package/lib/{src-QnxR5b7c.cjs → src-B6eISODM.cjs} +465 -47
- package/lib/src-B6eISODM.cjs.map +1 -0
- package/lib/{src-DyvCDuKO.mjs → src-gBAChVRL.mjs} +445 -45
- package/lib/src-gBAChVRL.mjs.map +1 -0
- package/lib/testing/index.cjs +2 -2
- package/lib/testing/index.d.cts +1 -1
- package/lib/testing/index.d.mts +1 -1
- package/lib/testing/index.mjs +2 -2
- package/lib/{use-guards.decorator-B6q_N0sf.cjs → use-guards.decorator-COR-9mZY.cjs} +20 -94
- package/lib/use-guards.decorator-COR-9mZY.cjs.map +1 -0
- package/lib/{use-guards.decorator-kZ3lNK8v.mjs → use-guards.decorator-CUww54Nt.mjs} +14 -94
- package/lib/use-guards.decorator-CUww54Nt.mjs.map +1 -0
- package/package.json +4 -4
- package/src/__tests__/controller-resolver.spec.mts +223 -0
- package/src/__tests__/controller.spec.mts +1 -1
- package/src/decorators/controller.decorator.mts +11 -6
- package/src/decorators/endpoint.decorator.mts +60 -12
- package/src/decorators/multipart.decorator.mts +67 -24
- package/src/decorators/stream.decorator.mts +65 -24
- package/src/interfaces/abstract-http-handler-adapter.interface.mts +31 -1
- package/src/legacy-compat/decorators/endpoint.decorator.mts +1 -1
- package/src/legacy-compat/decorators/multipart.decorator.mts +1 -1
- package/src/legacy-compat/decorators/stream.decorator.mts +1 -1
- package/src/logger/logger.service.mts +0 -2
- package/src/navios.application.mts +14 -0
- package/src/navios.factory.mts +19 -0
- package/src/services/guard-runner.service.mts +46 -9
- package/src/services/index.mts +1 -0
- package/src/services/instance-resolver.service.mts +186 -0
- package/src/stores/request-id.store.mts +45 -3
- package/src/tokens/index.mts +1 -0
- package/src/tokens/navios-options.token.mts +6 -0
- package/lib/index-DW9EPAE6.d.mts.map +0 -1
- package/lib/index-pHp-dIGt.d.cts.map +0 -1
- package/lib/src-DyvCDuKO.mjs.map +0 -1
- package/lib/src-QnxR5b7c.cjs.map +0 -1
- package/lib/use-guards.decorator-B6q_N0sf.cjs.map +0 -1
- 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
|
+
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ClassType } from '@navios/di'
|
|
2
2
|
|
|
3
|
-
import { Injectable,
|
|
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
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
) =>
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
) =>
|
|
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
|
}
|