stratal 0.0.11 → 0.0.13
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/README.md +1 -1
- package/dist/application.d.ts +6 -0
- package/dist/application.d.ts.map +1 -1
- package/dist/application.js +1 -1
- package/dist/application.js.map +1 -1
- package/dist/errors/application-error.d.ts +4 -3
- package/dist/errors/application-error.d.ts.map +1 -1
- package/dist/errors/application-error.js +12 -7
- package/dist/errors/application-error.js.map +1 -1
- package/dist/errors/error-codes.d.ts +4 -0
- package/dist/errors/error-codes.d.ts.map +1 -1
- package/dist/errors/error-codes.js +4 -0
- package/dist/errors/error-codes.js.map +1 -1
- package/dist/i18n/messages/en/errors.d.ts +2 -0
- package/dist/i18n/messages/en/errors.d.ts.map +1 -1
- package/dist/i18n/messages/en/errors.js +3 -0
- package/dist/i18n/messages/en/errors.js.map +1 -1
- package/dist/middleware/middleware-configuration.service.d.ts +8 -2
- package/dist/middleware/middleware-configuration.service.d.ts.map +1 -1
- package/dist/middleware/middleware-configuration.service.js +32 -3
- package/dist/middleware/middleware-configuration.service.js.map +1 -1
- package/dist/middleware/types.d.ts +2 -0
- package/dist/middleware/types.d.ts.map +1 -1
- package/dist/openapi/index.d.ts +1 -1
- package/dist/openapi/index.d.ts.map +1 -1
- package/dist/openapi/index.js.map +1 -1
- package/dist/openapi/openapi.module.d.ts.map +1 -1
- package/dist/openapi/openapi.module.js +0 -1
- package/dist/openapi/openapi.module.js.map +1 -1
- package/dist/openapi/services/openapi-config.service.js +1 -1
- package/dist/openapi/services/openapi-config.service.js.map +1 -1
- package/dist/openapi/services/openapi.service.d.ts.map +1 -1
- package/dist/openapi/services/openapi.service.js +16 -13
- package/dist/openapi/services/openapi.service.js.map +1 -1
- package/dist/openapi/types.d.ts +26 -3
- package/dist/openapi/types.d.ts.map +1 -1
- package/dist/router/constants.d.ts +15 -0
- package/dist/router/constants.d.ts.map +1 -1
- package/dist/router/constants.js +16 -1
- package/dist/router/constants.js.map +1 -1
- package/dist/router/decorators/controller.decorator.d.ts +8 -1
- package/dist/router/decorators/controller.decorator.d.ts.map +1 -1
- package/dist/router/decorators/controller.decorator.js +10 -0
- package/dist/router/decorators/controller.decorator.js.map +1 -1
- package/dist/router/decorators/http-method.decorator.d.ts +83 -0
- package/dist/router/decorators/http-method.decorator.d.ts.map +1 -0
- package/dist/router/decorators/http-method.decorator.js +113 -0
- package/dist/router/decorators/http-method.decorator.js.map +1 -0
- package/dist/router/decorators/index.d.ts +1 -0
- package/dist/router/decorators/index.d.ts.map +1 -1
- package/dist/router/decorators/index.js +1 -0
- package/dist/router/decorators/index.js.map +1 -1
- package/dist/router/decorators/route.decorator.d.ts +5 -2
- package/dist/router/decorators/route.decorator.d.ts.map +1 -1
- package/dist/router/decorators/route.decorator.js +4 -1
- package/dist/router/decorators/route.decorator.js.map +1 -1
- package/dist/router/hono-app.d.ts +2 -2
- package/dist/router/hono-app.d.ts.map +1 -1
- package/dist/router/hono-app.js +4 -4
- package/dist/router/hono-app.js.map +1 -1
- package/dist/router/index.d.ts +6 -3
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +3 -2
- package/dist/router/index.js.map +1 -1
- package/dist/router/router-context.d.ts +29 -3
- package/dist/router/router-context.d.ts.map +1 -1
- package/dist/router/router-context.js +34 -0
- package/dist/router/router-context.js.map +1 -1
- package/dist/router/services/route-registration.service.d.ts +38 -3
- package/dist/router/services/route-registration.service.d.ts.map +1 -1
- package/dist/router/services/route-registration.service.js +269 -20
- package/dist/router/services/route-registration.service.js.map +1 -1
- package/dist/router/types.d.ts +66 -9
- package/dist/router/types.d.ts.map +1 -1
- package/dist/websocket/decorators/gateway.decorator.d.ts +39 -0
- package/dist/websocket/decorators/gateway.decorator.d.ts.map +1 -0
- package/dist/websocket/decorators/gateway.decorator.js +55 -0
- package/dist/websocket/decorators/gateway.decorator.js.map +1 -0
- package/dist/websocket/decorators/index.d.ts +3 -0
- package/dist/websocket/decorators/index.d.ts.map +1 -0
- package/dist/websocket/decorators/index.js +3 -0
- package/dist/websocket/decorators/index.js.map +1 -0
- package/dist/websocket/decorators/ws-event.decorator.d.ts +59 -0
- package/dist/websocket/decorators/ws-event.decorator.d.ts.map +1 -0
- package/dist/websocket/decorators/ws-event.decorator.js +94 -0
- package/dist/websocket/decorators/ws-event.decorator.js.map +1 -0
- package/dist/websocket/errors/websocket-body-not-available.error.d.ts +5 -0
- package/dist/websocket/errors/websocket-body-not-available.error.d.ts.map +1 -0
- package/dist/websocket/errors/websocket-body-not-available.error.js +7 -0
- package/dist/websocket/errors/websocket-body-not-available.error.js.map +1 -0
- package/dist/websocket/errors/websocket-duplicate-event-handler.error.d.ts +5 -0
- package/dist/websocket/errors/websocket-duplicate-event-handler.error.d.ts.map +1 -0
- package/dist/websocket/errors/websocket-duplicate-event-handler.error.js +7 -0
- package/dist/websocket/errors/websocket-duplicate-event-handler.error.js.map +1 -0
- package/dist/websocket/gateway-context.d.ts +51 -0
- package/dist/websocket/gateway-context.d.ts.map +1 -0
- package/dist/websocket/gateway-context.js +66 -0
- package/dist/websocket/gateway-context.js.map +1 -0
- package/dist/websocket/index.d.ts +7 -0
- package/dist/websocket/index.d.ts.map +1 -0
- package/dist/websocket/index.js +5 -0
- package/dist/websocket/index.js.map +1 -0
- package/dist/websocket/types.d.ts +7 -0
- package/dist/websocket/types.d.ts.map +1 -0
- package/dist/websocket/types.js +2 -0
- package/dist/websocket/types.js.map +1 -0
- package/package.json +21 -15
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { Context } from 'hono';
|
|
2
|
-
import
|
|
2
|
+
import type { SSEStreamingApi } from 'hono/streaming';
|
|
3
|
+
import type { ContentfulStatusCode, RedirectStatusCode } from 'hono/utils/http-status';
|
|
4
|
+
import type { StreamingApi } from 'hono/utils/stream';
|
|
3
5
|
import type { Container } from '../di/container';
|
|
4
6
|
import type { RouterEnv } from './types';
|
|
5
|
-
type ContextQueryResult<R extends Record<string, unknown> | undefined, K extends string | undefined> = K extends string ? string : R;
|
|
7
|
+
export type ContextQueryResult<R extends Record<string, unknown> | undefined, K extends string | undefined> = K extends string ? string : R extends undefined ? Record<string, unknown> : R;
|
|
6
8
|
/**
|
|
7
9
|
* Router context wrapper with helper methods
|
|
8
10
|
*
|
|
@@ -112,6 +114,30 @@ export declare class RouterContext<T extends RouterEnv = RouterEnv> {
|
|
|
112
114
|
* @param status - HTTP status code (default: 302)
|
|
113
115
|
*/
|
|
114
116
|
redirect(url: string, status?: RedirectStatusCode): Response;
|
|
117
|
+
/**
|
|
118
|
+
* Return a streaming response (binary/generic)
|
|
119
|
+
*
|
|
120
|
+
* @param callback - Async function that writes to the stream
|
|
121
|
+
* @param onError - Optional error handler called if an error occurs during streaming
|
|
122
|
+
*/
|
|
123
|
+
stream(callback: (stream: StreamingApi) => Promise<void>, onError?: (err: Error, stream: StreamingApi) => Promise<void>): Response;
|
|
124
|
+
/**
|
|
125
|
+
* Return a streaming text response
|
|
126
|
+
*
|
|
127
|
+
* Automatically sets `Content-Encoding: Identity` for Cloudflare Workers compatibility.
|
|
128
|
+
*
|
|
129
|
+
* @param callback - Async function that writes text to the stream
|
|
130
|
+
* @param onError - Optional error handler called if an error occurs during streaming
|
|
131
|
+
*/
|
|
132
|
+
streamText(callback: (stream: StreamingApi) => Promise<void>, onError?: (err: Error, stream: StreamingApi) => Promise<void>): Response;
|
|
133
|
+
/**
|
|
134
|
+
* Return a Server-Sent Events (SSE) streaming response
|
|
135
|
+
*
|
|
136
|
+
* Automatically sets `Content-Encoding: Identity` for Cloudflare Workers compatibility.
|
|
137
|
+
*
|
|
138
|
+
* @param callback - Async function that writes SSE events to the stream
|
|
139
|
+
* @param onError - Optional error handler called if an error occurs during streaming
|
|
140
|
+
*/
|
|
141
|
+
streamSSE(callback: (stream: SSEStreamingApi) => Promise<void>, onError?: (err: Error, stream: SSEStreamingApi) => Promise<void>): Response;
|
|
115
142
|
}
|
|
116
|
-
export {};
|
|
117
143
|
//# sourceMappingURL=router-context.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router-context.d.ts","sourceRoot":"","sources":["../../src/router/router-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,EAAE,KAAK,oBAAoB,EAAE,
|
|
1
|
+
{"version":3,"file":"router-context.d.ts","sourceRoot":"","sources":["../../src/router/router-context.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { stream as honoStream, streamSSE as honoStreamSSE, streamText as honoStreamText } from 'hono/streaming';
|
|
1
2
|
import { RequestContainerNotInitializedError } from '../errors';
|
|
2
3
|
import { ROUTER_CONTEXT_KEYS } from './constants';
|
|
3
4
|
/**
|
|
@@ -141,5 +142,38 @@ export class RouterContext {
|
|
|
141
142
|
redirect(url, status) {
|
|
142
143
|
return this.c.redirect(url, status);
|
|
143
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Return a streaming response (binary/generic)
|
|
147
|
+
*
|
|
148
|
+
* @param callback - Async function that writes to the stream
|
|
149
|
+
* @param onError - Optional error handler called if an error occurs during streaming
|
|
150
|
+
*/
|
|
151
|
+
stream(callback, onError) {
|
|
152
|
+
return honoStream(this.c, callback, onError);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Return a streaming text response
|
|
156
|
+
*
|
|
157
|
+
* Automatically sets `Content-Encoding: Identity` for Cloudflare Workers compatibility.
|
|
158
|
+
*
|
|
159
|
+
* @param callback - Async function that writes text to the stream
|
|
160
|
+
* @param onError - Optional error handler called if an error occurs during streaming
|
|
161
|
+
*/
|
|
162
|
+
streamText(callback, onError) {
|
|
163
|
+
this.c.header('Content-Encoding', 'Identity');
|
|
164
|
+
return honoStreamText(this.c, callback, onError);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Return a Server-Sent Events (SSE) streaming response
|
|
168
|
+
*
|
|
169
|
+
* Automatically sets `Content-Encoding: Identity` for Cloudflare Workers compatibility.
|
|
170
|
+
*
|
|
171
|
+
* @param callback - Async function that writes SSE events to the stream
|
|
172
|
+
* @param onError - Optional error handler called if an error occurs during streaming
|
|
173
|
+
*/
|
|
174
|
+
streamSSE(callback, onError) {
|
|
175
|
+
this.c.header('Content-Encoding', 'Identity');
|
|
176
|
+
return honoStreamSSE(this.c, callback, onError);
|
|
177
|
+
}
|
|
144
178
|
}
|
|
145
179
|
//# sourceMappingURL=router-context.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router-context.js","sourceRoot":"","sources":["../../src/router/router-context.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"router-context.js","sourceRoot":"","sources":["../../src/router/router-context.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,IAAI,UAAU,EAAE,SAAS,IAAI,aAAa,EAAE,UAAU,IAAI,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAI/G,OAAO,EAAE,mCAAmC,EAAE,MAAM,WAAW,CAAA;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAKjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,OAAO,aAAa;IAMN;IALlB;;;OAGG;IACH,YACkB,CAAa;QAAb,MAAC,GAAD,CAAC,CAAY;IAC3B,CAAC;IAEL;;;;;OAKG;IACH,YAAY;QACV,MAAM,SAAS,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,mBAAmB,CAAC,iBAAiB,CAAC,CAAA;QACnE,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,mCAAmC,EAAE,CAAA;QACjD,CAAC;QACD,OAAO,SAAsB,CAAA;IAC/B,CAAC;IAED;;;;;OAKG;IACH,SAAS,CAAC,MAAc;QACtB,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChD,CAAC;IAED;;;;OAIG;IACH,SAAS;QACP,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAA;QACrD,OAAQ,MAAiB,IAAI,IAAI,CAAA;IACnC,CAAC;IAED;;;;;OAKG;IACH,IAAI,CAAC,IAAY,EAAE,MAA6B;QAC9C,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IAClC,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,GAAW;QACf,OAAQ,IAAI,CAAC,CAAC,CAAC,GAAqE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAA;IAC1G,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAsG,GAAO;QAChH,MAAM,SAAS,GAAI,IAAI,CAAC,CAAC,CAAC,GAAsE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QAC/G,OAAO,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAA6B,CAAC,CAAC,CAAC,SAAqC,CAAA;IACjG,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,IAAY;QACjB,OAAO,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAChC,CAAC;IAED;;;;;OAKG;IACH,IAAI;QACF,mEAAmE;QACnE,mEAAmE;QACnE,OAAQ,IAAI,CAAC,CAAC,CAAC,GAAwD,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;IACvF,CAAC;IAED;;;;;OAKG;IACH,IAAI,CAAC,IAAY,EAAE,MAA6B;QAC9C,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IAClC,CAAC;IAED;;;;;OAKG;IACH,IAAI,CAAC,IAAY,EAAE,MAA6B;QAC9C,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IAClC,CAAC;IAED;;;;;OAKG;IACH,QAAQ,CAAC,GAAW,EAAE,MAA2B;QAC/C,OAAO,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;IACrC,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,QAAiD,EAAE,OAA6D;QACrH,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAA;IAC9C,CAAC;IAED;;;;;;;OAOG;IACH,UAAU,CAAC,QAAiD,EAAE,OAA6D;QACzH,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,kBAAkB,EAAE,UAAU,CAAC,CAAA;QAC7C,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAA;IAClD,CAAC;IAED;;;;;;;OAOG;IACH,SAAS,CAAC,QAAoD,EAAE,OAAgE;QAC9H,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,kBAAkB,EAAE,UAAU,CAAC,CAAA;QAC7C,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAA;IACjD,CAAC;CACF"}
|
|
@@ -2,7 +2,7 @@ import type { OpenAPIHono } from '../../i18n/validation';
|
|
|
2
2
|
import { type LoggerService } from '../../logger';
|
|
3
3
|
import type { Constructor } from '../../types';
|
|
4
4
|
import type { IController } from '../controller';
|
|
5
|
-
import type { RouterEnv } from '../types';
|
|
5
|
+
import type { RouterEnv, VersioningOptions } from '../types';
|
|
6
6
|
/**
|
|
7
7
|
* Route registration service
|
|
8
8
|
* Manages controller and route registration with OpenAPI support
|
|
@@ -16,20 +16,42 @@ import type { RouterEnv } from '../types';
|
|
|
16
16
|
*/
|
|
17
17
|
export declare class RouteRegistrationService {
|
|
18
18
|
private logger;
|
|
19
|
+
private versioningOptions;
|
|
19
20
|
private controllerClasses;
|
|
20
|
-
constructor(logger: LoggerService);
|
|
21
|
+
constructor(logger: LoggerService, versioningOptions?: VersioningOptions | null);
|
|
21
22
|
/**
|
|
22
23
|
* Configure router with controllers
|
|
23
24
|
*
|
|
24
25
|
* @param app - OpenAPIHono application instance
|
|
25
26
|
* @param controllers - Array of controller classes from modules
|
|
26
27
|
*/
|
|
27
|
-
configure(app: OpenAPIHono<RouterEnv>, controllers: Constructor<IController>[]): void
|
|
28
|
+
configure(app: OpenAPIHono<RouterEnv>, controllers: Constructor<IController>[]): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Register a WebSocket gateway
|
|
31
|
+
* Applies versioning and guards, then registers an upgradeWebSocket handler
|
|
32
|
+
*/
|
|
33
|
+
private registerGateway;
|
|
34
|
+
/**
|
|
35
|
+
* Register a single WebSocket gateway route
|
|
36
|
+
*/
|
|
37
|
+
private registerGatewayRoute;
|
|
28
38
|
/**
|
|
29
39
|
* Register a single controller with all its routes
|
|
30
40
|
* Validates that controller has access decorator (strict mode)
|
|
31
41
|
*/
|
|
32
42
|
private registerController;
|
|
43
|
+
/**
|
|
44
|
+
* Dispatch route registration based on decorator type
|
|
45
|
+
*/
|
|
46
|
+
private dispatchRoutes;
|
|
47
|
+
/**
|
|
48
|
+
* Resolve versioned paths for a controller based on versioning configuration.
|
|
49
|
+
*
|
|
50
|
+
* @param basePath - The base path from @Controller decorator
|
|
51
|
+
* @param controllerOpts - Controller options (may contain version)
|
|
52
|
+
* @returns Array of resolved paths (with version prefix if applicable)
|
|
53
|
+
*/
|
|
54
|
+
private resolveVersionedPaths;
|
|
33
55
|
/**
|
|
34
56
|
* Create a guard execution middleware
|
|
35
57
|
*
|
|
@@ -48,6 +70,15 @@ export declare class RouteRegistrationService {
|
|
|
48
70
|
* Register OpenAPI routes with metadata
|
|
49
71
|
*/
|
|
50
72
|
private registerOpenAPIRoutes;
|
|
73
|
+
/**
|
|
74
|
+
* Register routes using HTTP method decorators (@Get, @Post, etc.)
|
|
75
|
+
* These use explicit method + path instead of convention-based derivation
|
|
76
|
+
*/
|
|
77
|
+
private registerHttpRoutes;
|
|
78
|
+
/**
|
|
79
|
+
* Join a base path and a route path, normalizing slashes
|
|
80
|
+
*/
|
|
81
|
+
private joinPaths;
|
|
51
82
|
/**
|
|
52
83
|
* Register route without OpenAPI metadata (for hidden routes)
|
|
53
84
|
* Route is functional but won't appear in documentation
|
|
@@ -76,6 +107,10 @@ export declare class RouteRegistrationService {
|
|
|
76
107
|
* Execution order: Global middlewares → Guards → Handler
|
|
77
108
|
*/
|
|
78
109
|
private buildOpenAPIRoute;
|
|
110
|
+
/**
|
|
111
|
+
* Check if a body definition is a RouteBodyObject (has schema key) vs bare ZodType
|
|
112
|
+
*/
|
|
113
|
+
private isRouteBodyObject;
|
|
79
114
|
/**
|
|
80
115
|
* Resolve method parameter injections from the container
|
|
81
116
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-registration.service.d.ts","sourceRoot":"","sources":["../../../src/router/services/route-registration.service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"route-registration.service.d.ts","sourceRoot":"","sources":["../../../src/router/services/route-registration.service.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAExD,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAA;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAI9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAgBhD,OAAO,KAAK,EAMV,SAAS,EAET,iBAAiB,EAClB,MAAM,UAAU,CAAA;AAUjB;;;;;;;;;;GAUG;AACH,qBAAa,wBAAwB;IAIjC,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,iBAAiB;IAJ3B,OAAO,CAAC,iBAAiB,CAA8C;gBAG7D,MAAM,EAAE,aAAa,EACrB,iBAAiB,GAAE,iBAAiB,GAAG,IAAW;IAG5D;;;;;OAKG;IACG,SAAS,CACb,GAAG,EAAE,WAAW,CAAC,SAAS,CAAC,EAC3B,WAAW,EAAE,WAAW,CAAC,WAAW,CAAC,EAAE,GACtC,OAAO,CAAC,IAAI,CAAC;IAyChB;;;OAGG;YACW,eAAe;IAoB7B;;OAEG;YACW,oBAAoB;IA4ElC;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IAoD1B;;OAEG;IACH,OAAO,CAAC,cAAc;IAkBtB;;;;;;OAMG;IACH,OAAO,CAAC,qBAAqB;IAiC7B;;;;;;;;OAQG;IACH,OAAO,CAAC,qBAAqB;IAe7B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAkB7B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IA6F7B;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IAsF1B;;OAEG;IACH,OAAO,CAAC,SAAS;IAKjB;;;OAGG;IACH,OAAO,CAAC,2BAA2B;IAkDnC;;OAEG;IACH,OAAO,CAAC,qBAAqB;IA0B7B;;;OAGG;IACH,OAAO,CAAC,uBAAuB;IAU/B;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAuCrB;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAuHzB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAIzB;;;;;;;OAOG;IACH,OAAO,CAAC,uBAAuB;IAW/B;;;OAGG;IACH,OAAO,CAAC,uBAAuB;CAsChC"}
|
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import { getMethodInjections } from '../../di';
|
|
2
2
|
import { getControllerGuards, getMethodGuards, GuardExecutionService, } from '../../guards';
|
|
3
3
|
import { createRoute } from '../../i18n/validation';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { getWsOnCloseMethod, getWsOnErrorMethod, getWsOnMessageMethod, isGateway } from '../../websocket/decorators';
|
|
5
|
+
import { GatewayContext } from '../../websocket/gateway-context';
|
|
6
|
+
import { DEFAULT_CONTENT_TYPE, HTTP_METHODS, METHOD_STATUS_CODES, SECURITY_SCHEMES, VERSION_NEUTRAL } from '../constants';
|
|
7
|
+
import { getControllerOptions, getControllerRoute, getDecoratedMethods, getHttpDecoratedMethods, getHttpRouteMetadata, getRouteConfig, } from '../decorators';
|
|
6
8
|
import { ControllerMethodNotFoundError, ControllerRegistrationError, OpenAPIRouteRegistrationError, } from '../errors';
|
|
7
9
|
import { RouterContext } from '../router-context';
|
|
8
10
|
import { commonErrorSchemas } from '../schemas/common.schemas';
|
|
11
|
+
const invokeHandler = (instance, method, ...args) => {
|
|
12
|
+
try {
|
|
13
|
+
return Promise.resolve(instance[method](...args));
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
return Promise.reject(err);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
9
19
|
/**
|
|
10
20
|
* Route registration service
|
|
11
21
|
* Manages controller and route registration with OpenAPI support
|
|
@@ -19,9 +29,11 @@ import { commonErrorSchemas } from '../schemas/common.schemas';
|
|
|
19
29
|
*/
|
|
20
30
|
export class RouteRegistrationService {
|
|
21
31
|
logger;
|
|
32
|
+
versioningOptions;
|
|
22
33
|
controllerClasses = new Map();
|
|
23
|
-
constructor(logger) {
|
|
34
|
+
constructor(logger, versioningOptions = null) {
|
|
24
35
|
this.logger = logger;
|
|
36
|
+
this.versioningOptions = versioningOptions;
|
|
25
37
|
}
|
|
26
38
|
/**
|
|
27
39
|
* Configure router with controllers
|
|
@@ -29,7 +41,7 @@ export class RouteRegistrationService {
|
|
|
29
41
|
* @param app - OpenAPIHono application instance
|
|
30
42
|
* @param controllers - Array of controller classes from modules
|
|
31
43
|
*/
|
|
32
|
-
configure(app, controllers) {
|
|
44
|
+
async configure(app, controllers) {
|
|
33
45
|
this.logger.info('Registering controllers', {
|
|
34
46
|
controllerCount: controllers.length,
|
|
35
47
|
});
|
|
@@ -45,12 +57,101 @@ export class RouteRegistrationService {
|
|
|
45
57
|
return -1; // a goes before b
|
|
46
58
|
return 0; // maintain relative order
|
|
47
59
|
});
|
|
60
|
+
// Single-pass partition: gateways vs regular controllers
|
|
61
|
+
const gateways = [];
|
|
62
|
+
const regulars = [];
|
|
63
|
+
for (const c of sortedControllers) {
|
|
64
|
+
if (isGateway(c)) {
|
|
65
|
+
gateways.push(c);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
regulars.push(c);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Register WebSocket gateways
|
|
72
|
+
for (const GatewayClass of gateways) {
|
|
73
|
+
await this.registerGateway(app, GatewayClass);
|
|
74
|
+
}
|
|
48
75
|
// Register controllers
|
|
49
|
-
for (const ControllerClass of
|
|
76
|
+
for (const ControllerClass of regulars) {
|
|
50
77
|
this.registerController(app, ControllerClass);
|
|
51
78
|
}
|
|
52
79
|
this.logger.info('Controller registration complete');
|
|
53
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Register a WebSocket gateway
|
|
83
|
+
* Applies versioning and guards, then registers an upgradeWebSocket handler
|
|
84
|
+
*/
|
|
85
|
+
async registerGateway(app, GatewayClass) {
|
|
86
|
+
const route = getControllerRoute(GatewayClass);
|
|
87
|
+
if (!route) {
|
|
88
|
+
throw new ControllerRegistrationError(GatewayClass.name, 'Missing @Gateway decorator or route metadata');
|
|
89
|
+
}
|
|
90
|
+
const controllerOpts = getControllerOptions(GatewayClass);
|
|
91
|
+
const paths = this.versioningOptions
|
|
92
|
+
? this.resolveVersionedPaths(route, controllerOpts)
|
|
93
|
+
: [route];
|
|
94
|
+
for (const fullPath of paths) {
|
|
95
|
+
await this.registerGatewayRoute(app, GatewayClass, fullPath);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Register a single WebSocket gateway route
|
|
100
|
+
*/
|
|
101
|
+
async registerGatewayRoute(app, GatewayClass, fullPath) {
|
|
102
|
+
// Collect class-level guards
|
|
103
|
+
const controllerGuards = getControllerGuards(GatewayClass)?.guards ?? [];
|
|
104
|
+
// Dynamic import: only load hono/cloudflare-workers when gateways exist
|
|
105
|
+
const { upgradeWebSocket } = await import('hono/cloudflare-workers');
|
|
106
|
+
// Cache WS metadata once at registration time (not per-connection)
|
|
107
|
+
const onMsgMethod = getWsOnMessageMethod(GatewayClass);
|
|
108
|
+
const onCloseMethod = getWsOnCloseMethod(GatewayClass);
|
|
109
|
+
const onErrMethod = getWsOnErrorMethod(GatewayClass);
|
|
110
|
+
const wsHandler = upgradeWebSocket((c) => {
|
|
111
|
+
const routerCtx = new RouterContext(c);
|
|
112
|
+
const container = routerCtx.getContainer();
|
|
113
|
+
const gateway = container.resolve(GatewayClass);
|
|
114
|
+
// Cloudflare Workers doesn't support the `onOpen` WebSocket event;
|
|
115
|
+
// the upgrade callback itself serves as the open context.
|
|
116
|
+
const events = {};
|
|
117
|
+
const bindWsHandler = (method, onCatch) => {
|
|
118
|
+
return (evt, ws) => {
|
|
119
|
+
const ctx = new GatewayContext(c, ws);
|
|
120
|
+
c.executionCtx.waitUntil(invokeHandler(gateway, method, evt, ctx).catch((err) => {
|
|
121
|
+
this.logger.error(`WebSocket ${method} handler error`, { gateway: GatewayClass.name, error: err instanceof Error ? err.message : String(err) });
|
|
122
|
+
onCatch?.(err, ws);
|
|
123
|
+
}));
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
if (onMsgMethod) {
|
|
127
|
+
events.onMessage = bindWsHandler(onMsgMethod, (_err, ws) => ws.close(1011, 'Internal Error'));
|
|
128
|
+
}
|
|
129
|
+
if (onCloseMethod) {
|
|
130
|
+
events.onClose = bindWsHandler(onCloseMethod);
|
|
131
|
+
}
|
|
132
|
+
if (onErrMethod) {
|
|
133
|
+
events.onError = bindWsHandler(onErrMethod);
|
|
134
|
+
}
|
|
135
|
+
return events;
|
|
136
|
+
});
|
|
137
|
+
this.logger.info('Registering WebSocket gateway', {
|
|
138
|
+
gateway: GatewayClass.name,
|
|
139
|
+
path: fullPath,
|
|
140
|
+
});
|
|
141
|
+
const handlers = [];
|
|
142
|
+
if (controllerGuards.length > 0) {
|
|
143
|
+
this.logger.info('Gateway guards', {
|
|
144
|
+
gateway: GatewayClass.name,
|
|
145
|
+
path: fullPath,
|
|
146
|
+
guardCount: controllerGuards.length,
|
|
147
|
+
});
|
|
148
|
+
handlers.push(this.createGuardMiddleware(controllerGuards));
|
|
149
|
+
}
|
|
150
|
+
handlers.push(wsHandler);
|
|
151
|
+
// Type assertion needed because Hono's overloaded .get() signatures
|
|
152
|
+
// don't accept a spread of MiddlewareHandler[] alongside upgradeWebSocket's output type
|
|
153
|
+
app.get(fullPath, ...handlers);
|
|
154
|
+
}
|
|
54
155
|
/**
|
|
55
156
|
* Register a single controller with all its routes
|
|
56
157
|
* Validates that controller has access decorator (strict mode)
|
|
@@ -64,21 +165,81 @@ export class RouteRegistrationService {
|
|
|
64
165
|
this.controllerClasses.set(className, ControllerClass);
|
|
65
166
|
const prototype = ControllerClass.prototype;
|
|
66
167
|
const controllerOpts = getControllerOptions(ControllerClass);
|
|
67
|
-
// Handle wildcard routes (non-RESTful controllers)
|
|
168
|
+
// Handle wildcard routes (non-RESTful controllers) — check once, not per version
|
|
68
169
|
if (prototype.handle) {
|
|
69
|
-
this.
|
|
170
|
+
if (!this.versioningOptions) {
|
|
171
|
+
this.registerWildcardRoute(app, ControllerClass, route);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
for (const versionedRoute of this.resolveVersionedPaths(route, controllerOpts)) {
|
|
175
|
+
this.registerWildcardRoute(app, ControllerClass, versionedRoute);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
70
178
|
return;
|
|
71
179
|
}
|
|
72
|
-
//
|
|
180
|
+
// Compute decorated methods once (loop-invariant)
|
|
73
181
|
const decoratedMethods = getDecoratedMethods(ControllerClass);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
182
|
+
const httpDecoratedMethods = getHttpDecoratedMethods(ControllerClass);
|
|
183
|
+
// Enforce mutual exclusivity once
|
|
184
|
+
if (decoratedMethods.length > 0 && httpDecoratedMethods.length > 0) {
|
|
185
|
+
throw new ControllerRegistrationError(ControllerClass.name, 'Cannot mix @Route() with HTTP method decorators (@Get, @Post, etc.) in the same controller. Use one pattern or the other.');
|
|
186
|
+
}
|
|
187
|
+
// Fast path: no versioning — inline dispatch, no array allocation, no loop
|
|
188
|
+
if (!this.versioningOptions) {
|
|
189
|
+
this.dispatchRoutes(app, ControllerClass, route, decoratedMethods, httpDecoratedMethods, controllerOpts, prototype);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
// Versioning path: resolve versioned paths and register each
|
|
193
|
+
for (const versionedRoute of this.resolveVersionedPaths(route, controllerOpts)) {
|
|
194
|
+
this.dispatchRoutes(app, ControllerClass, versionedRoute, decoratedMethods, httpDecoratedMethods, controllerOpts, prototype);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Dispatch route registration based on decorator type
|
|
199
|
+
*/
|
|
200
|
+
dispatchRoutes(app, ControllerClass, path, decoratedMethods, httpDecoratedMethods, controllerOpts, prototype) {
|
|
201
|
+
if (httpDecoratedMethods.length > 0) {
|
|
202
|
+
this.registerHttpRoutes(app, ControllerClass, path, httpDecoratedMethods, controllerOpts);
|
|
203
|
+
}
|
|
204
|
+
else if (decoratedMethods.length > 0) {
|
|
205
|
+
this.registerOpenAPIRoutes(app, ControllerClass, path, decoratedMethods, controllerOpts);
|
|
77
206
|
}
|
|
78
207
|
else {
|
|
79
|
-
|
|
80
|
-
|
|
208
|
+
this.registerRESTfulRoutes(app, ControllerClass, path, prototype);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Resolve versioned paths for a controller based on versioning configuration.
|
|
213
|
+
*
|
|
214
|
+
* @param basePath - The base path from @Controller decorator
|
|
215
|
+
* @param controllerOpts - Controller options (may contain version)
|
|
216
|
+
* @returns Array of resolved paths (with version prefix if applicable)
|
|
217
|
+
*/
|
|
218
|
+
resolveVersionedPaths(basePath, controllerOpts) {
|
|
219
|
+
// Versioning disabled — always return base path
|
|
220
|
+
if (!this.versioningOptions) {
|
|
221
|
+
return [basePath];
|
|
222
|
+
}
|
|
223
|
+
const version = controllerOpts?.version;
|
|
224
|
+
// VERSION_NEUTRAL — explicitly opt out of versioning
|
|
225
|
+
if (version === VERSION_NEUTRAL) {
|
|
226
|
+
return [basePath];
|
|
227
|
+
}
|
|
228
|
+
const prefix = this.versioningOptions.prefix ?? 'v';
|
|
229
|
+
// Explicit version(s) on the controller
|
|
230
|
+
if (version !== undefined) {
|
|
231
|
+
const versions = Array.isArray(version) ? version : [version];
|
|
232
|
+
return versions.map(v => `/${prefix}${v}${basePath}`);
|
|
81
233
|
}
|
|
234
|
+
// No explicit version — apply defaultVersion if set
|
|
235
|
+
if (this.versioningOptions.defaultVersion !== undefined) {
|
|
236
|
+
const defaults = Array.isArray(this.versioningOptions.defaultVersion)
|
|
237
|
+
? this.versioningOptions.defaultVersion
|
|
238
|
+
: [this.versioningOptions.defaultVersion];
|
|
239
|
+
return defaults.map(v => `/${prefix}${v}${basePath}`);
|
|
240
|
+
}
|
|
241
|
+
// Versioning enabled but no version and no default — no prefix
|
|
242
|
+
return [basePath];
|
|
82
243
|
}
|
|
83
244
|
/**
|
|
84
245
|
* Create a guard execution middleware
|
|
@@ -185,6 +346,80 @@ export class RouteRegistrationService {
|
|
|
185
346
|
});
|
|
186
347
|
}
|
|
187
348
|
}
|
|
349
|
+
/**
|
|
350
|
+
* Register routes using HTTP method decorators (@Get, @Post, etc.)
|
|
351
|
+
* These use explicit method + path instead of convention-based derivation
|
|
352
|
+
*/
|
|
353
|
+
registerHttpRoutes(app, ControllerClass, basePath, httpDecoratedMethods, controllerOpts) {
|
|
354
|
+
const className = ControllerClass.name;
|
|
355
|
+
const prototype = ControllerClass.prototype;
|
|
356
|
+
const controllerHidden = controllerOpts?.hideFromDocs ?? false;
|
|
357
|
+
const controllerGuards = getControllerGuards(ControllerClass)?.guards ?? [];
|
|
358
|
+
for (const methodName of httpDecoratedMethods) {
|
|
359
|
+
const httpMeta = getHttpRouteMetadata(prototype, methodName);
|
|
360
|
+
if (!httpMeta)
|
|
361
|
+
continue;
|
|
362
|
+
const fullPath = this.joinPaths(basePath, httpMeta.path);
|
|
363
|
+
const routeConfig = httpMeta.config;
|
|
364
|
+
const hideFromDocs = routeConfig.hideFromDocs ?? controllerHidden;
|
|
365
|
+
// @All routes or hidden routes are registered without OpenAPI
|
|
366
|
+
if (httpMeta.method === 'all' || hideFromDocs) {
|
|
367
|
+
this.logger.info(`Registering route (no OpenAPI)`, {
|
|
368
|
+
controller: className,
|
|
369
|
+
method: httpMeta.method.toUpperCase(),
|
|
370
|
+
path: fullPath,
|
|
371
|
+
methodName,
|
|
372
|
+
});
|
|
373
|
+
this.registerRouteWithoutOpenAPI(app, ControllerClass, methodName, {
|
|
374
|
+
method: httpMeta.method,
|
|
375
|
+
path: fullPath,
|
|
376
|
+
});
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
// Collect method-level guards
|
|
380
|
+
const methodGuards = getMethodGuards(prototype, methodName)?.guards ?? [];
|
|
381
|
+
const allGuards = [...controllerGuards, ...methodGuards];
|
|
382
|
+
if (allGuards.length > 0) {
|
|
383
|
+
this.logger.info(`Route guards`, {
|
|
384
|
+
controller: className,
|
|
385
|
+
method: httpMeta.method.toUpperCase(),
|
|
386
|
+
path: fullPath,
|
|
387
|
+
methodName,
|
|
388
|
+
guardCount: allGuards.length,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
// Build and register OpenAPI route
|
|
392
|
+
const metadata = this.mergeMetadata(controllerOpts, routeConfig, ControllerClass, methodName);
|
|
393
|
+
const statusCode = routeConfig.statusCode ?? 200;
|
|
394
|
+
const openApiRoute = this.buildOpenAPIRoute(httpMeta.method, fullPath, routeConfig, metadata, allGuards, undefined, statusCode);
|
|
395
|
+
this.logger.info(`Registering OpenAPI route`, {
|
|
396
|
+
controller: className,
|
|
397
|
+
method: httpMeta.method.toUpperCase(),
|
|
398
|
+
path: fullPath,
|
|
399
|
+
methodName,
|
|
400
|
+
tags: metadata.tags,
|
|
401
|
+
});
|
|
402
|
+
app.openapi(openApiRoute, async (c) => {
|
|
403
|
+
const ctx = new RouterContext(c);
|
|
404
|
+
const requestContainer = ctx.getContainer();
|
|
405
|
+
const controller = requestContainer.resolve(ControllerClass);
|
|
406
|
+
const method = controller[methodName];
|
|
407
|
+
if (typeof method === 'function') {
|
|
408
|
+
const injectedArgs = this.resolveMethodInjections(prototype, methodName, requestContainer);
|
|
409
|
+
return await method.call(controller, ctx, ...injectedArgs);
|
|
410
|
+
}
|
|
411
|
+
throw new ControllerMethodNotFoundError(methodName, className);
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Join a base path and a route path, normalizing slashes
|
|
417
|
+
*/
|
|
418
|
+
joinPaths(basePath, routePath) {
|
|
419
|
+
if (routePath === '/')
|
|
420
|
+
return basePath;
|
|
421
|
+
return basePath + routePath;
|
|
422
|
+
}
|
|
188
423
|
/**
|
|
189
424
|
* Register route without OpenAPI metadata (for hidden routes)
|
|
190
425
|
* Route is functional but won't appear in documentation
|
|
@@ -222,6 +457,9 @@ export class RouteRegistrationService {
|
|
|
222
457
|
case 'delete':
|
|
223
458
|
app.delete(derived.path, handler);
|
|
224
459
|
break;
|
|
460
|
+
case 'all':
|
|
461
|
+
app.all(derived.path, handler);
|
|
462
|
+
break;
|
|
225
463
|
default:
|
|
226
464
|
// For head, options, trace, or any other method, use all()
|
|
227
465
|
app.all(derived.path, handler);
|
|
@@ -304,7 +542,7 @@ export class RouteRegistrationService {
|
|
|
304
542
|
*
|
|
305
543
|
* Execution order: Global middlewares → Guards → Handler
|
|
306
544
|
*/
|
|
307
|
-
buildOpenAPIRoute(method, path, routeConfig, metadata, guards, methodName) {
|
|
545
|
+
buildOpenAPIRoute(method, path, routeConfig, metadata, guards, methodName, statusCodeOverride) {
|
|
308
546
|
try {
|
|
309
547
|
const route = {
|
|
310
548
|
method,
|
|
@@ -318,12 +556,14 @@ export class RouteRegistrationService {
|
|
|
318
556
|
}
|
|
319
557
|
// Add request body if defined
|
|
320
558
|
if (routeConfig.body) {
|
|
559
|
+
const bodySchema = this.isRouteBodyObject(routeConfig.body) ? routeConfig.body.schema : routeConfig.body;
|
|
560
|
+
const bodyContentType = this.isRouteBodyObject(routeConfig.body) ? routeConfig.body.contentType ?? DEFAULT_CONTENT_TYPE : DEFAULT_CONTENT_TYPE;
|
|
321
561
|
route.request = {
|
|
322
562
|
...route.request,
|
|
323
563
|
body: {
|
|
324
564
|
content: {
|
|
325
|
-
|
|
326
|
-
schema:
|
|
565
|
+
[bodyContentType]: {
|
|
566
|
+
schema: bodySchema,
|
|
327
567
|
},
|
|
328
568
|
},
|
|
329
569
|
},
|
|
@@ -343,8 +583,10 @@ export class RouteRegistrationService {
|
|
|
343
583
|
params: routeConfig.params,
|
|
344
584
|
};
|
|
345
585
|
}
|
|
346
|
-
// Derive success status code from method name
|
|
347
|
-
const successStatus =
|
|
586
|
+
// Derive success status code from method name or use override
|
|
587
|
+
const successStatus = statusCodeOverride
|
|
588
|
+
?? (methodName && METHOD_STATUS_CODES[methodName])
|
|
589
|
+
?? 200;
|
|
348
590
|
// Build responses object with auto-derived status
|
|
349
591
|
const responses = {};
|
|
350
592
|
// Add success response with derived status code
|
|
@@ -352,9 +594,10 @@ export class RouteRegistrationService {
|
|
|
352
594
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- response may be undefined at runtime
|
|
353
595
|
if (responseDef) {
|
|
354
596
|
if (typeof responseDef === 'object' && 'schema' in responseDef) {
|
|
597
|
+
const responseContentType = responseDef.contentType ?? DEFAULT_CONTENT_TYPE;
|
|
355
598
|
responses[successStatus] = {
|
|
356
599
|
content: {
|
|
357
|
-
|
|
600
|
+
[responseContentType]: { schema: responseDef.schema },
|
|
358
601
|
},
|
|
359
602
|
description: responseDef.description ?? `Response ${successStatus}`,
|
|
360
603
|
};
|
|
@@ -362,7 +605,7 @@ export class RouteRegistrationService {
|
|
|
362
605
|
else {
|
|
363
606
|
responses[successStatus] = {
|
|
364
607
|
content: {
|
|
365
|
-
|
|
608
|
+
[DEFAULT_CONTENT_TYPE]: { schema: responseDef },
|
|
366
609
|
},
|
|
367
610
|
description: `Response ${successStatus}`,
|
|
368
611
|
};
|
|
@@ -397,6 +640,12 @@ export class RouteRegistrationService {
|
|
|
397
640
|
throw new OpenAPIRouteRegistrationError(path, error instanceof Error ? error.message : String(error));
|
|
398
641
|
}
|
|
399
642
|
}
|
|
643
|
+
/**
|
|
644
|
+
* Check if a body definition is a RouteBodyObject (has schema key) vs bare ZodType
|
|
645
|
+
*/
|
|
646
|
+
isRouteBodyObject(body) {
|
|
647
|
+
return typeof body === 'object' && 'schema' in body;
|
|
648
|
+
}
|
|
400
649
|
/**
|
|
401
650
|
* Resolve method parameter injections from the container
|
|
402
651
|
*
|