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.
Files changed (107) hide show
  1. package/README.md +1 -1
  2. package/dist/application.d.ts +6 -0
  3. package/dist/application.d.ts.map +1 -1
  4. package/dist/application.js +1 -1
  5. package/dist/application.js.map +1 -1
  6. package/dist/errors/application-error.d.ts +4 -3
  7. package/dist/errors/application-error.d.ts.map +1 -1
  8. package/dist/errors/application-error.js +12 -7
  9. package/dist/errors/application-error.js.map +1 -1
  10. package/dist/errors/error-codes.d.ts +4 -0
  11. package/dist/errors/error-codes.d.ts.map +1 -1
  12. package/dist/errors/error-codes.js +4 -0
  13. package/dist/errors/error-codes.js.map +1 -1
  14. package/dist/i18n/messages/en/errors.d.ts +2 -0
  15. package/dist/i18n/messages/en/errors.d.ts.map +1 -1
  16. package/dist/i18n/messages/en/errors.js +3 -0
  17. package/dist/i18n/messages/en/errors.js.map +1 -1
  18. package/dist/middleware/middleware-configuration.service.d.ts +8 -2
  19. package/dist/middleware/middleware-configuration.service.d.ts.map +1 -1
  20. package/dist/middleware/middleware-configuration.service.js +32 -3
  21. package/dist/middleware/middleware-configuration.service.js.map +1 -1
  22. package/dist/middleware/types.d.ts +2 -0
  23. package/dist/middleware/types.d.ts.map +1 -1
  24. package/dist/openapi/index.d.ts +1 -1
  25. package/dist/openapi/index.d.ts.map +1 -1
  26. package/dist/openapi/index.js.map +1 -1
  27. package/dist/openapi/openapi.module.d.ts.map +1 -1
  28. package/dist/openapi/openapi.module.js +0 -1
  29. package/dist/openapi/openapi.module.js.map +1 -1
  30. package/dist/openapi/services/openapi-config.service.js +1 -1
  31. package/dist/openapi/services/openapi-config.service.js.map +1 -1
  32. package/dist/openapi/services/openapi.service.d.ts.map +1 -1
  33. package/dist/openapi/services/openapi.service.js +16 -13
  34. package/dist/openapi/services/openapi.service.js.map +1 -1
  35. package/dist/openapi/types.d.ts +26 -3
  36. package/dist/openapi/types.d.ts.map +1 -1
  37. package/dist/router/constants.d.ts +15 -0
  38. package/dist/router/constants.d.ts.map +1 -1
  39. package/dist/router/constants.js +16 -1
  40. package/dist/router/constants.js.map +1 -1
  41. package/dist/router/decorators/controller.decorator.d.ts +8 -1
  42. package/dist/router/decorators/controller.decorator.d.ts.map +1 -1
  43. package/dist/router/decorators/controller.decorator.js +10 -0
  44. package/dist/router/decorators/controller.decorator.js.map +1 -1
  45. package/dist/router/decorators/http-method.decorator.d.ts +83 -0
  46. package/dist/router/decorators/http-method.decorator.d.ts.map +1 -0
  47. package/dist/router/decorators/http-method.decorator.js +113 -0
  48. package/dist/router/decorators/http-method.decorator.js.map +1 -0
  49. package/dist/router/decorators/index.d.ts +1 -0
  50. package/dist/router/decorators/index.d.ts.map +1 -1
  51. package/dist/router/decorators/index.js +1 -0
  52. package/dist/router/decorators/index.js.map +1 -1
  53. package/dist/router/decorators/route.decorator.d.ts +5 -2
  54. package/dist/router/decorators/route.decorator.d.ts.map +1 -1
  55. package/dist/router/decorators/route.decorator.js +4 -1
  56. package/dist/router/decorators/route.decorator.js.map +1 -1
  57. package/dist/router/hono-app.d.ts +2 -2
  58. package/dist/router/hono-app.d.ts.map +1 -1
  59. package/dist/router/hono-app.js +4 -4
  60. package/dist/router/hono-app.js.map +1 -1
  61. package/dist/router/index.d.ts +6 -3
  62. package/dist/router/index.d.ts.map +1 -1
  63. package/dist/router/index.js +3 -2
  64. package/dist/router/index.js.map +1 -1
  65. package/dist/router/router-context.d.ts +29 -3
  66. package/dist/router/router-context.d.ts.map +1 -1
  67. package/dist/router/router-context.js +34 -0
  68. package/dist/router/router-context.js.map +1 -1
  69. package/dist/router/services/route-registration.service.d.ts +38 -3
  70. package/dist/router/services/route-registration.service.d.ts.map +1 -1
  71. package/dist/router/services/route-registration.service.js +269 -20
  72. package/dist/router/services/route-registration.service.js.map +1 -1
  73. package/dist/router/types.d.ts +66 -9
  74. package/dist/router/types.d.ts.map +1 -1
  75. package/dist/websocket/decorators/gateway.decorator.d.ts +39 -0
  76. package/dist/websocket/decorators/gateway.decorator.d.ts.map +1 -0
  77. package/dist/websocket/decorators/gateway.decorator.js +55 -0
  78. package/dist/websocket/decorators/gateway.decorator.js.map +1 -0
  79. package/dist/websocket/decorators/index.d.ts +3 -0
  80. package/dist/websocket/decorators/index.d.ts.map +1 -0
  81. package/dist/websocket/decorators/index.js +3 -0
  82. package/dist/websocket/decorators/index.js.map +1 -0
  83. package/dist/websocket/decorators/ws-event.decorator.d.ts +59 -0
  84. package/dist/websocket/decorators/ws-event.decorator.d.ts.map +1 -0
  85. package/dist/websocket/decorators/ws-event.decorator.js +94 -0
  86. package/dist/websocket/decorators/ws-event.decorator.js.map +1 -0
  87. package/dist/websocket/errors/websocket-body-not-available.error.d.ts +5 -0
  88. package/dist/websocket/errors/websocket-body-not-available.error.d.ts.map +1 -0
  89. package/dist/websocket/errors/websocket-body-not-available.error.js +7 -0
  90. package/dist/websocket/errors/websocket-body-not-available.error.js.map +1 -0
  91. package/dist/websocket/errors/websocket-duplicate-event-handler.error.d.ts +5 -0
  92. package/dist/websocket/errors/websocket-duplicate-event-handler.error.d.ts.map +1 -0
  93. package/dist/websocket/errors/websocket-duplicate-event-handler.error.js +7 -0
  94. package/dist/websocket/errors/websocket-duplicate-event-handler.error.js.map +1 -0
  95. package/dist/websocket/gateway-context.d.ts +51 -0
  96. package/dist/websocket/gateway-context.d.ts.map +1 -0
  97. package/dist/websocket/gateway-context.js +66 -0
  98. package/dist/websocket/gateway-context.js.map +1 -0
  99. package/dist/websocket/index.d.ts +7 -0
  100. package/dist/websocket/index.d.ts.map +1 -0
  101. package/dist/websocket/index.js +5 -0
  102. package/dist/websocket/index.js.map +1 -0
  103. package/dist/websocket/types.d.ts +7 -0
  104. package/dist/websocket/types.d.ts.map +1 -0
  105. package/dist/websocket/types.js +2 -0
  106. package/dist/websocket/types.js.map +1 -0
  107. package/package.json +21 -15
@@ -1,8 +1,10 @@
1
1
  import type { Context } from 'hono';
2
- import { type ContentfulStatusCode, type RedirectStatusCode } from 'hono/utils/http-status';
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,KAAK,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAC3F,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAGhD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAExC,KAAK,kBAAkB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAAE,CAAC,SAAS,MAAM,GAAG,SAAS,IAAI,CAAC,SAAS,MAAM,GAAG,MAAM,GAAG,CAAC,CAAA;AAEpI;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBAAa,aAAa,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS;aAMtC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IAL/B;;;OAGG;gBAEe,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IAG/B;;;;;OAKG;IACH,YAAY,IAAI,SAAS;IAQzB;;;;;OAKG;IACH,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAI/B;;;;OAIG;IACH,SAAS,IAAI,MAAM;IAKnB;;;;;OAKG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,oBAAoB,GAAG,QAAQ;IAI3D;;;;OAIG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAI1B;;;;OAIG;IACH,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,SAAS,EAAE,CAAC,SAAS,MAAM,GAAG,SAAS,GAAG,SAAS,EAAE,GAAG,CAAC,EAAE,CAAC,GAAG,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC;IAK7I;;;;OAIG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIxC;;;;;OAKG;IACH,IAAI,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC;IAMrB;;;;;OAKG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,oBAAoB,GAAG,QAAQ;IAI3D;;;;;OAKG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,oBAAoB,GAAG,QAAQ;IAI3D;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,kBAAkB,GAAG,QAAQ;CAG7D"}
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,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAErD,OAAO,KAAK,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AACtF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAGhD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAExC,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAAE,CAAC,SAAS,MAAM,GAAG,SAAS,IAAI,CAAC,SAAS,MAAM,GAAG,MAAM,GAAG,CAAC,SAAS,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,CAAA;AAE3L;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBAAa,aAAa,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS;aAMtC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IAL/B;;;OAGG;gBAEe,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IAG/B;;;;;OAKG;IACH,YAAY,IAAI,SAAS;IAQzB;;;;;OAKG;IACH,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAI/B;;;;OAIG;IACH,SAAS,IAAI,MAAM;IAKnB;;;;;OAKG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,oBAAoB,GAAG,QAAQ;IAI3D;;;;OAIG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAI1B;;;;OAIG;IACH,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,SAAS,EAAE,CAAC,SAAS,MAAM,GAAG,SAAS,GAAG,SAAS,EAAE,GAAG,CAAC,EAAE,CAAC,GAAG,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC;IAK7I;;;;OAIG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIxC;;;;;OAKG;IACH,IAAI,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC;IAMrB;;;;;OAKG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,oBAAoB,GAAG,QAAQ;IAI3D;;;;;OAKG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,oBAAoB,GAAG,QAAQ;IAI3D;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,kBAAkB,GAAG,QAAQ;IAI5D;;;;;OAKG;IACH,MAAM,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,QAAQ;IAIlI;;;;;;;OAOG;IACH,UAAU,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,QAAQ;IAKtI;;;;;;;OAOG;IACH,SAAS,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,QAAQ;CAI5I"}
@@ -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":"AAGA,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;CACF"}
1
+ {"version":3,"file":"router-context.js","sourceRoot":"","sources":["../../src/router/router-context.ts"],"names":[],"mappings":"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"}
@@ -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":"AAQA,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;AAE9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAchD,OAAO,KAAK,EAKV,SAAS,EAEV,MAAM,UAAU,CAAA;AAEjB;;;;;;;;;;GAUG;AACH,qBAAa,wBAAwB;IAIjC,OAAO,CAAC,MAAM;IAHhB,OAAO,CAAC,iBAAiB,CAA8C;gBAG7D,MAAM,EAAE,aAAa;IAG/B;;;;;OAKG;IACH,SAAS,CACP,GAAG,EAAE,WAAW,CAAC,SAAS,CAAC,EAC3B,WAAW,EAAE,WAAW,CAAC,WAAW,CAAC,EAAE,GACtC,IAAI;IAwBP;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IAkC1B;;;;;;;;OAQG;IACH,OAAO,CAAC,qBAAqB;IAe7B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAkB7B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IA6F7B;;;OAGG;IACH,OAAO,CAAC,2BAA2B;IA+CnC;;OAEG;IACH,OAAO,CAAC,qBAAqB;IA0B7B;;;OAGG;IACH,OAAO,CAAC,uBAAuB;IAU/B;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAuCrB;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAiHzB;;;;;;;OAOG;IACH,OAAO,CAAC,uBAAuB;IAW/B;;;OAGG;IACH,OAAO,CAAC,uBAAuB;CAsChC"}
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 { HTTP_METHODS, METHOD_STATUS_CODES, SECURITY_SCHEMES } from '../constants';
5
- import { getControllerOptions, getControllerRoute, getDecoratedMethods, getRouteConfig } from '../decorators';
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 sortedControllers) {
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.registerWildcardRoute(app, ControllerClass, route);
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
- // Check for OpenAPI decorated methods
180
+ // Compute decorated methods once (loop-invariant)
73
181
  const decoratedMethods = getDecoratedMethods(ControllerClass);
74
- if (decoratedMethods.length > 0) {
75
- // Register OpenAPI routes
76
- this.registerOpenAPIRoutes(app, ControllerClass, route, decoratedMethods, controllerOpts);
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
- // Fallback to traditional RESTful method registration (no OpenAPI)
80
- this.registerRESTfulRoutes(app, ControllerClass, route, prototype);
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
- 'application/json': {
326
- schema: routeConfig.body,
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 = (methodName && METHOD_STATUS_CODES[methodName]) ?? 200;
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
- 'application/json': { schema: responseDef.schema },
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
- 'application/json': { schema: responseDef },
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
  *