stratal 0.0.17 → 0.0.19

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 (200) hide show
  1. package/README.md +8 -8
  2. package/dist/{base-email.provider-DypUAfWm.mjs → base-email.provider-mjynzewK.mjs} +1 -1
  3. package/dist/{base-email.provider-DypUAfWm.mjs.map → base-email.provider-mjynzewK.mjs.map} +1 -1
  4. package/dist/bin/cloudflare-workers-loader.mjs +33 -1
  5. package/dist/bin/cloudflare-workers-loader.mjs.map +1 -1
  6. package/dist/bin/quarry.mjs +169 -7
  7. package/dist/bin/quarry.mjs.map +1 -1
  8. package/dist/cache/index.d.mts +3 -2
  9. package/dist/cache/index.d.mts.map +1 -1
  10. package/dist/cache/index.mjs +3 -10
  11. package/dist/cache/index.mjs.map +1 -1
  12. package/dist/{colors-Y7WIFXs7.mjs → colors-DJaRDXoS.mjs} +1 -1
  13. package/dist/{colors-Y7WIFXs7.mjs.map → colors-DJaRDXoS.mjs.map} +1 -1
  14. package/dist/{command-B1CPgsrU.mjs → command-BgSlsS4M.mjs} +3 -3
  15. package/dist/command-BgSlsS4M.mjs.map +1 -0
  16. package/dist/{command-TnkPYWta.d.mts → command-DsQq56Lp.d.mts} +2 -2
  17. package/dist/{command-TnkPYWta.d.mts.map → command-DsQq56Lp.d.mts.map} +1 -1
  18. package/dist/config/index.d.mts +81 -37
  19. package/dist/config/index.d.mts.map +1 -1
  20. package/dist/config/index.mjs +135 -61
  21. package/dist/config/index.mjs.map +1 -1
  22. package/dist/{consumer-registry-Bymm6ff4.d.mts → consumer-registry-Doom7BEh.d.mts} +1 -1
  23. package/dist/{consumer-registry-Bymm6ff4.d.mts.map → consumer-registry-Doom7BEh.d.mts.map} +1 -1
  24. package/dist/controller.decorator-LZY9aHYG.mjs +66 -0
  25. package/dist/controller.decorator-LZY9aHYG.mjs.map +1 -0
  26. package/dist/cron/index.d.mts +4 -3
  27. package/dist/cron/index.d.mts.map +1 -1
  28. package/dist/cron/index.mjs +1 -3
  29. package/dist/{cron-manager-CFBamKKk.mjs → cron-manager-C30t9UZM.mjs} +29 -19
  30. package/dist/cron-manager-C30t9UZM.mjs.map +1 -0
  31. package/dist/{cron-manager-D7imGwUT.d.mts → cron-manager-RuPtFVLy.d.mts} +28 -14
  32. package/dist/cron-manager-RuPtFVLy.d.mts.map +1 -0
  33. package/dist/di/index.d.mts +2 -2
  34. package/dist/di/index.mjs +3 -3
  35. package/dist/email/index.d.mts +3 -3
  36. package/dist/email/index.mjs +87 -18
  37. package/dist/email/index.mjs.map +1 -1
  38. package/dist/{en-DaewN8hc.mjs → en-rHmW6vD9.mjs} +14 -31
  39. package/dist/en-rHmW6vD9.mjs.map +1 -0
  40. package/dist/env-CamWD-U1.d.mts +25 -0
  41. package/dist/env-CamWD-U1.d.mts.map +1 -0
  42. package/dist/errors/index.d.mts +2 -2
  43. package/dist/errors/index.mjs +2 -3
  44. package/dist/errors-B4pYgYON.mjs +1714 -0
  45. package/dist/errors-B4pYgYON.mjs.map +1 -0
  46. package/dist/{errors-DuAR5Wke.mjs → errors-BUyUfr2Z.mjs} +14 -7
  47. package/dist/errors-BUyUfr2Z.mjs.map +1 -0
  48. package/dist/events/index.d.mts +2 -2
  49. package/dist/events/index.mjs +1 -2
  50. package/dist/{events-CvUSgEuN.mjs → events-COKixqnG.mjs} +2 -2
  51. package/dist/{events-CvUSgEuN.mjs.map → events-COKixqnG.mjs.map} +1 -1
  52. package/dist/{gateway-context-CNOLkLUC.mjs → gateway-context-cqZ8wMoi.mjs} +4 -9
  53. package/dist/gateway-context-cqZ8wMoi.mjs.map +1 -0
  54. package/dist/guards/index.d.mts +14 -5
  55. package/dist/guards/index.d.mts.map +1 -1
  56. package/dist/guards/index.mjs +1 -1
  57. package/dist/{guards-DUk_Kzst.mjs → guards-DMbsAxSX.mjs} +1 -1
  58. package/dist/guards-DMbsAxSX.mjs.map +1 -0
  59. package/dist/http-method.decorator-BT3ufnz8.mjs +96 -0
  60. package/dist/http-method.decorator-BT3ufnz8.mjs.map +1 -0
  61. package/dist/i18n/index.d.mts +3 -3
  62. package/dist/i18n/index.mjs +3 -16
  63. package/dist/i18n/messages/en/index.d.mts +1 -1
  64. package/dist/i18n/messages/en/index.mjs +1 -1
  65. package/dist/i18n/utils/index.d.mts +30 -0
  66. package/dist/i18n/utils/index.d.mts.map +1 -0
  67. package/dist/i18n/utils/index.mjs +2 -0
  68. package/dist/i18n/validation/index.d.mts +1 -1
  69. package/dist/i18n/validation/index.mjs +1 -1
  70. package/dist/i18n.module-CI_prYFD.mjs +2340 -0
  71. package/dist/i18n.module-CI_prYFD.mjs.map +1 -0
  72. package/dist/{index-NGxg-KP_.d.mts → index-B437eK7p.d.mts} +59 -16
  73. package/dist/index-B437eK7p.d.mts.map +1 -0
  74. package/dist/{index-Dp6A5ywM.d.mts → index-CWRS7Ri3.d.mts} +1 -1
  75. package/dist/{index-Dp6A5ywM.d.mts.map → index-CWRS7Ri3.d.mts.map} +1 -1
  76. package/dist/{index-D_w_Rmtd.d.mts → index-DFhEeFfC.d.mts} +13 -30
  77. package/dist/{index-D_w_Rmtd.d.mts.map → index-DFhEeFfC.d.mts.map} +1 -1
  78. package/dist/index-DPFqRs8L.d.mts +4318 -0
  79. package/dist/index-DPFqRs8L.d.mts.map +1 -0
  80. package/dist/{index-DGRe6Yoa.d.mts → index-Dnqm9ZB6.d.mts} +5 -4
  81. package/dist/index-Dnqm9ZB6.d.mts.map +1 -0
  82. package/dist/index-SHx31sBJ.d.mts +101 -0
  83. package/dist/index-SHx31sBJ.d.mts.map +1 -0
  84. package/dist/index.d.mts +5 -3
  85. package/dist/index.d.mts.map +1 -1
  86. package/dist/index.mjs +1 -20
  87. package/dist/{is-command-DJVI6wEJ.mjs → is-command-C6a7WTPw.mjs} +2 -2
  88. package/dist/{is-command-DJVI6wEJ.mjs.map → is-command-C6a7WTPw.mjs.map} +1 -1
  89. package/dist/{is-seeder-D5MIEcdz.mjs → is-seeder-CebjZCDn.mjs} +1 -1
  90. package/dist/{is-seeder-D5MIEcdz.mjs.map → is-seeder-CebjZCDn.mjs.map} +1 -1
  91. package/dist/logger/index.d.mts +1 -1
  92. package/dist/logger/index.mjs +1 -1
  93. package/dist/{logger-CGT91VY6.mjs → logger-V6Ms3QnQ.mjs} +63 -45
  94. package/dist/logger-V6Ms3QnQ.mjs.map +1 -0
  95. package/dist/macroable/index.d.mts +2 -0
  96. package/dist/macroable/index.mjs +2 -0
  97. package/dist/macroable-BmufBshB.mjs +122 -0
  98. package/dist/macroable-BmufBshB.mjs.map +1 -0
  99. package/dist/module/index.d.mts +3 -4
  100. package/dist/module/index.d.mts.map +1 -1
  101. package/dist/module/index.mjs +1 -10
  102. package/dist/module-qGE_1duv.mjs +732 -0
  103. package/dist/module-qGE_1duv.mjs.map +1 -0
  104. package/dist/openapi/index.d.mts +3 -3
  105. package/dist/openapi/index.mjs +2 -15
  106. package/dist/{openapi-tools.service-B3TxYKoQ.mjs → openapi-tools.service-CYWGuhue.mjs} +4 -1
  107. package/dist/{openapi-tools.service-B3TxYKoQ.mjs.map → openapi-tools.service-CYWGuhue.mjs.map} +1 -1
  108. package/dist/{openapi.service-DGnX3Fc4.d.mts → openapi.service-Bv_NioM9.d.mts} +9 -17
  109. package/dist/openapi.service-Bv_NioM9.d.mts.map +1 -0
  110. package/dist/quarry/index.d.mts +26 -12
  111. package/dist/quarry/index.d.mts.map +1 -1
  112. package/dist/quarry/index.mjs +4 -8
  113. package/dist/{quarry-registry-B2rkO-JS.mjs → quarry-registry-DFfRRkA7.mjs} +45 -40
  114. package/dist/quarry-registry-DFfRRkA7.mjs.map +1 -0
  115. package/dist/queue/index.d.mts +2 -2
  116. package/dist/queue/index.mjs +3 -13
  117. package/dist/queue/index.mjs.map +1 -1
  118. package/dist/{queue.module-BtI8f4Jo.mjs → queue.module-P-G-nCYz.mjs} +39 -42
  119. package/dist/queue.module-P-G-nCYz.mjs.map +1 -0
  120. package/dist/r2-storage.provider-LdzK9tfG.mjs +244 -0
  121. package/dist/r2-storage.provider-LdzK9tfG.mjs.map +1 -0
  122. package/dist/{resend.provider-bXMEkdRJ.mjs → resend.provider-bwILp0WI.mjs} +2 -4
  123. package/dist/{resend.provider-bXMEkdRJ.mjs.map → resend.provider-bwILp0WI.mjs.map} +1 -1
  124. package/dist/router/index.d.mts +2 -2
  125. package/dist/router/index.mjs +7 -16
  126. package/dist/seeder/index.d.mts +3 -4
  127. package/dist/seeder/index.d.mts.map +1 -1
  128. package/dist/seeder/index.mjs +2 -6
  129. package/dist/{seeder-R7RXJC35.mjs → seeder-BcqIFa2X.mjs} +5 -5
  130. package/dist/{seeder-R7RXJC35.mjs.map → seeder-BcqIFa2X.mjs.map} +1 -1
  131. package/dist/setup-CtekcwuO.mjs +37 -0
  132. package/dist/setup-CtekcwuO.mjs.map +1 -0
  133. package/dist/signed-url-COX7cCWR.mjs +74 -0
  134. package/dist/signed-url-COX7cCWR.mjs.map +1 -0
  135. package/dist/{smtp.provider-DrbHQztF.mjs → smtp.provider-B07yuARi.mjs} +2 -4
  136. package/dist/{smtp.provider-DrbHQztF.mjs.map → smtp.provider-B07yuARi.mjs.map} +1 -1
  137. package/dist/storage/index.d.mts +39 -17
  138. package/dist/storage/index.d.mts.map +1 -1
  139. package/dist/storage/index.mjs +3 -14
  140. package/dist/storage/providers/index.d.mts +30 -69
  141. package/dist/storage/providers/index.d.mts.map +1 -1
  142. package/dist/storage/providers/index.mjs +2 -5
  143. package/dist/{storage-CZKHOhci.mjs → storage-P6X4h9So.mjs} +102 -29
  144. package/dist/storage-P6X4h9So.mjs.map +1 -0
  145. package/dist/{storage-provider.interface-0IqcdhBf.d.mts → storage-provider.interface-CC1nniHk.d.mts} +20 -15
  146. package/dist/storage-provider.interface-CC1nniHk.d.mts.map +1 -0
  147. package/dist/stratal-BCiwCFN9.mjs +533 -0
  148. package/dist/stratal-BCiwCFN9.mjs.map +1 -0
  149. package/dist/{types-DahElfUw.d.mts → types-DIWemRad.d.mts} +2 -2
  150. package/dist/types-DIWemRad.d.mts.map +1 -0
  151. package/dist/{usage-generator-CVIsENuE.mjs → usage-generator-MBcRo0Q2.mjs} +2 -2
  152. package/dist/{usage-generator-CVIsENuE.mjs.map → usage-generator-MBcRo0Q2.mjs.map} +1 -1
  153. package/dist/{validation-DQTC259A.mjs → validation-Dbg3ehdP.mjs} +2 -2
  154. package/dist/{validation-DQTC259A.mjs.map → validation-Dbg3ehdP.mjs.map} +1 -1
  155. package/dist/websocket/index.d.mts +3 -3
  156. package/dist/websocket/index.d.mts.map +1 -1
  157. package/dist/websocket/index.mjs +1 -4
  158. package/dist/workers/index.d.mts +2 -1
  159. package/dist/workers/index.d.mts.map +1 -1
  160. package/dist/workers/index.mjs +2 -20
  161. package/dist/workers/index.mjs.map +1 -1
  162. package/package.json +41 -50
  163. package/dist/application-DfPtIzxF.d.mts +0 -177
  164. package/dist/application-DfPtIzxF.d.mts.map +0 -1
  165. package/dist/command-B1CPgsrU.mjs.map +0 -1
  166. package/dist/cron-manager-CFBamKKk.mjs.map +0 -1
  167. package/dist/cron-manager-D7imGwUT.d.mts.map +0 -1
  168. package/dist/en-DaewN8hc.mjs.map +0 -1
  169. package/dist/errors-DSKapqD8.mjs +0 -707
  170. package/dist/errors-DSKapqD8.mjs.map +0 -1
  171. package/dist/errors-DuAR5Wke.mjs.map +0 -1
  172. package/dist/gateway-context-CNOLkLUC.mjs.map +0 -1
  173. package/dist/guards-DUk_Kzst.mjs.map +0 -1
  174. package/dist/i18n.module-Dn9SrFdS.mjs +0 -1841
  175. package/dist/i18n.module-Dn9SrFdS.mjs.map +0 -1
  176. package/dist/index-BFCxSp_f.d.mts +0 -2625
  177. package/dist/index-BFCxSp_f.d.mts.map +0 -1
  178. package/dist/index-DGRe6Yoa.d.mts.map +0 -1
  179. package/dist/index-NGxg-KP_.d.mts.map +0 -1
  180. package/dist/logger-CGT91VY6.mjs.map +0 -1
  181. package/dist/middleware/index.d.mts +0 -2
  182. package/dist/middleware/index.mjs +0 -5
  183. package/dist/middleware-Bl-b5pkt.mjs +0 -362
  184. package/dist/middleware-Bl-b5pkt.mjs.map +0 -1
  185. package/dist/module-registry-CmjBX6ol.d.mts +0 -121
  186. package/dist/module-registry-CmjBX6ol.d.mts.map +0 -1
  187. package/dist/module-tUtyVJ5E.mjs +0 -371
  188. package/dist/module-tUtyVJ5E.mjs.map +0 -1
  189. package/dist/openapi.service-DGnX3Fc4.d.mts.map +0 -1
  190. package/dist/quarry-registry-B2rkO-JS.mjs.map +0 -1
  191. package/dist/queue.module-BtI8f4Jo.mjs.map +0 -1
  192. package/dist/router-context-D9R1v2Ac.mjs +0 -267
  193. package/dist/router-context-D9R1v2Ac.mjs.map +0 -1
  194. package/dist/s3-storage.provider-CttzNnDR.mjs +0 -335
  195. package/dist/s3-storage.provider-CttzNnDR.mjs.map +0 -1
  196. package/dist/storage-CZKHOhci.mjs.map +0 -1
  197. package/dist/storage-provider.interface-0IqcdhBf.d.mts.map +0 -1
  198. package/dist/stratal-D5smIU1y.mjs +0 -315
  199. package/dist/stratal-D5smIU1y.mjs.map +0 -1
  200. package/dist/types-DahElfUw.d.mts.map +0 -1
@@ -1,1841 +0,0 @@
1
- import { S as ApplicationError, b as ERROR_CODES, o as I18N_TOKENS, s as Scope, v as ROUTER_TOKENS, y as getHttpStatus } from "./errors-DSKapqD8.mjs";
2
- import { a as __decorate, d as Transient, g as DI_TOKENS, m as getMethodInjections, o as __decorateParam, s as __decorateMetadata } from "./logger-CGT91VY6.mjs";
3
- import { r as Module } from "./module-tUtyVJ5E.mjs";
4
- import { i as getControllerRoute, r as getControllerOptions, t as MiddlewareConfigurationService } from "./middleware-Bl-b5pkt.mjs";
5
- import { a as ROUTER_CONTEXT_KEYS, c as VERSION_NEUTRAL, i as METHOD_STATUS_CODES, n as DEFAULT_CONTENT_TYPE, o as ROUTE_METADATA_KEYS, r as HTTP_METHODS, s as SECURITY_SCHEMES, t as RouterContext } from "./router-context-D9R1v2Ac.mjs";
6
- import { i as z, o as backendErrorMap, r as validation_exports, s as runWithErrorMapContext, t as OpenAPIHono } from "./validation-DQTC259A.mjs";
7
- import { n as OPENAPI_TOKENS } from "./openapi-tools.service-B3TxYKoQ.mjs";
8
- import { t as en_exports } from "./en-DaewN8hc.mjs";
9
- import { i as getMethodGuards, r as getControllerGuards, t as GuardExecutionService } from "./guards-DUk_Kzst.mjs";
10
- import { c as getWsOnMessageMethod, d as isGateway, o as getWsOnCloseMethod, s as getWsOnErrorMethod, t as GatewayContext } from "./gateway-context-CNOLkLUC.mjs";
11
- import { inject } from "tsyringe";
12
- import { compile, createCoreContext, registerMessageCompiler, translate } from "@intlify/core-base";
13
- import { swaggerUI } from "@hono/swagger-ui";
14
- //#region src/i18n/middleware/i18n-context.middleware.ts
15
- /**
16
- * I18n Context Middleware
17
- *
18
- * Sets up AsyncLocalStorage context for Zod i18n validation.
19
- * Must run after LocaleExtractionMiddleware sets the locale.
20
- */
21
- let I18nContextMiddleware = class I18nContextMiddleware {
22
- constructor(i18n) {
23
- this.i18n = i18n;
24
- }
25
- async handle(ctx, next) {
26
- await runWithErrorMapContext({
27
- t: (key, params) => this.i18n.t(key, params),
28
- locale: ctx.getLocale()
29
- }, () => next());
30
- }
31
- };
32
- I18nContextMiddleware = __decorate([
33
- Transient(),
34
- __decorateParam(0, inject(I18N_TOKENS.I18nService)),
35
- __decorateMetadata("design:paramtypes", [Object])
36
- ], I18nContextMiddleware);
37
- //#endregion
38
- //#region src/i18n/middleware/locale-extraction.middleware.ts
39
- /**
40
- * Locale Extraction Middleware
41
- *
42
- * Extracts locale from X-Locale header and sets it on RouterContext.
43
- * Validates against available locales from MessageLoaderService.
44
- */
45
- let LocaleExtractionMiddleware = class LocaleExtractionMiddleware {
46
- constructor(loader) {
47
- this.loader = loader;
48
- }
49
- async handle(ctx, next) {
50
- const locale = this.extractLocale(ctx);
51
- ctx.setLocale(locale);
52
- await next();
53
- }
54
- /**
55
- * Extract and validate locale from X-Locale header
56
- */
57
- extractLocale(ctx) {
58
- const requestedLocale = ctx.header("x-locale")?.toLowerCase();
59
- if (requestedLocale && this.loader.isLocaleSupported(requestedLocale)) return requestedLocale;
60
- return this.loader.getDefaultLocale();
61
- }
62
- };
63
- LocaleExtractionMiddleware = __decorate([
64
- Transient(),
65
- __decorateParam(0, inject(I18N_TOKENS.MessageLoader)),
66
- __decorateMetadata("design:paramtypes", [Object])
67
- ], LocaleExtractionMiddleware);
68
- //#endregion
69
- //#region src/openapi/services/openapi-config.service.ts
70
- let OpenAPIConfigService = class OpenAPIConfigService {
71
- overrides = [];
72
- constructor(baseOptions) {
73
- this.baseOptions = baseOptions;
74
- }
75
- /**
76
- * Add configuration override for this request.
77
- * Overrides are merged in the order they are added.
78
- */
79
- override(config) {
80
- this.overrides.push(config);
81
- }
82
- /** Get effective configuration (base merged with all overrides) */
83
- getEffectiveConfig() {
84
- let effective = {
85
- jsonPath: this.baseOptions?.jsonPath ?? "/api/openapi.json",
86
- ui: this.baseOptions?.ui,
87
- info: {
88
- title: this.baseOptions?.info?.title ?? "API",
89
- version: this.baseOptions?.info?.version ?? "1.0.0",
90
- description: this.baseOptions?.info?.description
91
- },
92
- securitySchemes: this.baseOptions?.securitySchemes
93
- };
94
- for (const override of this.overrides) effective = this.mergeConfig(effective, override);
95
- return effective;
96
- }
97
- /**
98
- * Merge override into effective config.
99
- * Info is shallow-merged, routeFilter is replaced.
100
- */
101
- mergeConfig(base, override) {
102
- return {
103
- ...base,
104
- info: {
105
- ...base.info,
106
- ...override.info && {
107
- title: override.info.title ?? base.info.title,
108
- version: override.info.version ?? base.info.version,
109
- description: override.info.description ?? base.info.description
110
- }
111
- },
112
- routeFilter: override.routeFilter ?? base.routeFilter
113
- };
114
- }
115
- };
116
- OpenAPIConfigService = __decorate([
117
- Transient(OPENAPI_TOKENS.ConfigService),
118
- __decorateParam(0, inject(OPENAPI_TOKENS.Options, { isOptional: true })),
119
- __decorateMetadata("design:paramtypes", [Object])
120
- ], OpenAPIConfigService);
121
- //#endregion
122
- //#region src/i18n/messages/index.ts
123
- /**
124
- * Core Messages
125
- *
126
- * Messages used by packages/modules infrastructure.
127
- * These are automatically merged with application-specific messages.
128
- */
129
- /**
130
- * All locale messages
131
- * Explicitly import and export (no filesystem scanning - Cloudflare Workers compatible)
132
- */
133
- const messages = { en: en_exports };
134
- /**
135
- * Get messages for all locales
136
- */
137
- function getMessages() {
138
- return messages;
139
- }
140
- /**
141
- * Get available locales
142
- */
143
- function getLocales() {
144
- return Object.keys(messages);
145
- }
146
- //#endregion
147
- //#region src/i18n/utils/deep-merge.ts
148
- /**
149
- * Deep merge two objects. Source values override target at leaf level.
150
- */
151
- function deepMerge(target, source) {
152
- const result = { ...target };
153
- for (const key of Object.keys(source)) {
154
- const targetValue = target[key];
155
- const sourceValue = source[key];
156
- if (typeof targetValue === "object" && targetValue !== null && !Array.isArray(targetValue) && typeof sourceValue === "object" && sourceValue !== null && !Array.isArray(sourceValue)) result[key] = deepMerge(targetValue, sourceValue);
157
- else result[key] = sourceValue;
158
- }
159
- return result;
160
- }
161
- //#endregion
162
- //#region src/i18n/services/message-registry.ts
163
- var _MessageRegistry;
164
- let MessageRegistry = class MessageRegistry {
165
- static {
166
- _MessageRegistry = this;
167
- }
168
- static contributions = [];
169
- /**
170
- * Add messages (called statically by I18nModule.registerMessages)
171
- */
172
- static addMessages(messages) {
173
- if (Boolean(messages) && typeof messages === "object" && Object.keys(messages).length > 0) _MessageRegistry.contributions.push(messages);
174
- }
175
- /**
176
- * Get all messages deep-merged in registration order
177
- */
178
- getMergedMessages() {
179
- const merged = {};
180
- for (const contribution of _MessageRegistry.contributions) for (const locale of Object.keys(contribution)) merged[locale] = deepMerge(merged[locale] ?? {}, contribution[locale]);
181
- return merged;
182
- }
183
- /**
184
- * Reset registry (for testing)
185
- * @internal
186
- */
187
- static reset() {
188
- _MessageRegistry.contributions = [];
189
- }
190
- };
191
- MessageRegistry = _MessageRegistry = __decorate([Transient(I18N_TOKENS.MessageRegistry)], MessageRegistry);
192
- //#endregion
193
- //#region src/i18n/setup.ts
194
- /**
195
- * I18n Setup - Message Compiler Registration
196
- *
197
- * Registers a JIT (Just-In-Time) message compiler for @intlify/core-base
198
- * that works in Cloudflare Workers edge runtime.
199
- *
200
- * This must be called ONCE at application startup, before any I18nService instances are created.
201
- */
202
- let isRegistered = false;
203
- /**
204
- * Setup JIT message compiler for i18n
205
- *
206
- * Registers a message compiler that uses JIT compilation mode.
207
- * In @intlify/core-base v10+, JIT mode is enabled by default, which generates
208
- * AST (Abstract Syntax Tree) instead of JavaScript code, making it compatible
209
- * with CSP-restricted environments like Cloudflare Workers.
210
- *
211
- * **IMPORTANT:** Call this function once at application startup before creating
212
- * any I18nService instances. Safe to call multiple times (only registers once).
213
- *
214
- * @example
215
- * ```typescript
216
- * // In application entry point (before app.initialize())
217
- * setupI18nCompiler()
218
- * ```
219
- */
220
- function setupI18nCompiler() {
221
- if (isRegistered) return;
222
- registerMessageCompiler(compile);
223
- isRegistered = true;
224
- }
225
- //#endregion
226
- //#region src/i18n/i18n.options.ts
227
- /**
228
- * Resolve I18n options with defaults
229
- */
230
- function resolveI18nOptions(options) {
231
- return {
232
- defaultLocale: options?.defaultLocale ?? "en",
233
- fallbackLocale: options?.fallbackLocale ?? "en",
234
- locales: options?.locales ?? ["en"]
235
- };
236
- }
237
- //#endregion
238
- //#region src/i18n/errors/locale-not-supported.error.ts
239
- /**
240
- * Locale Not Supported Error
241
- * Thrown when an unsupported locale is requested
242
- *
243
- * HTTP Status: 500 Internal Server Error
244
- * Error Code: 9301
245
- */
246
- var LocaleNotSupportedError = class extends ApplicationError {
247
- constructor(locale, supportedLocales) {
248
- super("errors.localeNotSupported", ERROR_CODES.I18N.LOCALE_NOT_SUPPORTED, {
249
- locale,
250
- supportedLocales: supportedLocales.join(", ")
251
- });
252
- }
253
- };
254
- //#endregion
255
- //#region src/i18n/errors/translation-missing.error.ts
256
- /**
257
- * Translation Missing Error
258
- * Thrown when a translation key is missing from all locales
259
- *
260
- * HTTP Status: 500 Internal Server Error
261
- * Error Code: 9300
262
- */
263
- var TranslationMissingError = class extends ApplicationError {
264
- constructor(key, locale) {
265
- super("errors.translationMissing", ERROR_CODES.I18N.TRANSLATION_MISSING, {
266
- key,
267
- locale
268
- });
269
- }
270
- };
271
- //#endregion
272
- //#region src/router/decorators/http-method.decorator.ts
273
- /**
274
- * Creates an HTTP method decorator factory for the given HTTP method.
275
- *
276
- * The returned decorator stores {@link HttpRouteMetadata} on the method and
277
- * tracks the method name under {@link ROUTE_METADATA_KEYS.HTTP_DECORATED_METHODS}
278
- * on the controller prototype so they can be discovered at registration time.
279
- */
280
- function createHttpMethodDecorator(method) {
281
- return function(path, config) {
282
- return function(target, propertyKey, descriptor) {
283
- const metadata = {
284
- method,
285
- path,
286
- config: config ?? { response: z.any() }
287
- };
288
- Reflect.defineMetadata(ROUTE_METADATA_KEYS.HTTP_ROUTE_CONFIG, metadata, target, propertyKey);
289
- const existing = Reflect.getOwnMetadata(ROUTE_METADATA_KEYS.HTTP_DECORATED_METHODS, target) ?? [];
290
- existing.push(propertyKey);
291
- Reflect.defineMetadata(ROUTE_METADATA_KEYS.HTTP_DECORATED_METHODS, existing, target);
292
- return descriptor;
293
- };
294
- };
295
- }
296
- /**
297
- * Registers a GET route on the controller method.
298
- *
299
- * @param path - Route path relative to the controller base path
300
- * @param config - Optional route configuration (response schema, body, params, etc.)
301
- *
302
- * @example
303
- * ```typescript
304
- * @Controller('/api/v1/users')
305
- * class UsersController {
306
- * @Get('/', { response: z.array(userSchema), summary: 'List users' })
307
- * async list(ctx: RouterContext) { ... }
308
- *
309
- * @Get('/:id', { params: z.object({ id: z.string().uuid() }), response: userSchema })
310
- * async getUser(ctx: RouterContext) { ... }
311
- * }
312
- * ```
313
- */
314
- const Get = createHttpMethodDecorator("get");
315
- /**
316
- * Registers a POST route on the controller method.
317
- *
318
- * @param path - Route path relative to the controller base path
319
- * @param config - Optional route configuration (response schema, body, params, etc.)
320
- *
321
- * @example
322
- * ```typescript
323
- * @Controller('/api/v1/users')
324
- * class UsersController {
325
- * @Post('/', { body: createUserSchema, response: userSchema, statusCode: 201 })
326
- * async createUser(ctx: RouterContext) { ... }
327
- * }
328
- * ```
329
- */
330
- const Post = createHttpMethodDecorator("post");
331
- /**
332
- * Registers a PUT route on the controller method.
333
- *
334
- * @param path - Route path relative to the controller base path
335
- * @param config - Optional route configuration
336
- */
337
- const Put = createHttpMethodDecorator("put");
338
- /**
339
- * Registers a PATCH route on the controller method.
340
- *
341
- * @param path - Route path relative to the controller base path
342
- * @param config - Optional route configuration
343
- */
344
- const Patch = createHttpMethodDecorator("patch");
345
- /**
346
- * Registers a DELETE route on the controller method.
347
- *
348
- * @param path - Route path relative to the controller base path
349
- * @param config - Optional route configuration
350
- */
351
- const Delete = createHttpMethodDecorator("delete");
352
- /**
353
- * Registers an ALL (any HTTP method) route on the controller method.
354
- * Routes using @All are automatically hidden from OpenAPI documentation
355
- * since OpenAPI does not support a catch-all HTTP method.
356
- *
357
- * @param path - Route path relative to the controller base path
358
- * @param config - Optional route configuration
359
- */
360
- const All = createHttpMethodDecorator("all");
361
- /**
362
- * Get the HTTP route metadata from a controller method decorated with
363
- * @Get, @Post, @Put, @Patch, @Delete, or @All.
364
- *
365
- * @param target - Controller prototype
366
- * @param methodName - Name of the method
367
- * @returns HTTP route metadata or undefined if not decorated
368
- */
369
- function getHttpRouteMetadata(target, methodName) {
370
- return Reflect.getMetadata(ROUTE_METADATA_KEYS.HTTP_ROUTE_CONFIG, target, methodName);
371
- }
372
- /**
373
- * Get all methods decorated with HTTP method decorators from a controller class.
374
- *
375
- * @param ControllerClass - Controller class
376
- * @returns Array of method names that have HTTP route metadata
377
- */
378
- function getHttpDecoratedMethods(ControllerClass) {
379
- const prototype = ControllerClass.prototype;
380
- return Reflect.getOwnMetadata(ROUTE_METADATA_KEYS.HTTP_DECORATED_METHODS, prototype) ?? [];
381
- }
382
- //#endregion
383
- //#region src/router/decorators/route.decorator.ts
384
- /**
385
- * Decorator to add OpenAPI metadata to a controller method using convention-based routing.
386
- *
387
- * **Cannot be mixed with HTTP method decorators** (`@Get`, `@Post`, `@Put`, `@Patch`,
388
- * `@Delete`, `@All`) in the same controller. Use one pattern or the other.
389
- *
390
- * Stores route configuration (schemas, response, tags, security) in metadata.
391
- * HTTP method, path, and success status code are auto-derived from the method name:
392
- * - index() → GET /base-path → 200
393
- * - show() → GET /base-path/:id → 200
394
- * - create() → POST /base-path → 201
395
- * - update() → PUT /base-path/:id → 200
396
- * - patch() → PATCH /base-path/:id → 200
397
- * - destroy() → DELETE /base-path/:id → 200
398
- *
399
- * @param config - Route configuration (schemas, response, tags, security)
400
- *
401
- * @example
402
- * ```typescript
403
- * @Controller('/api/v1/notes', {
404
- * tags: ['Notes'],
405
- * security: ['bearerAuth']
406
- * })
407
- * export class NotesController implements Controller {
408
- * @Route({
409
- * body: createNoteSchema,
410
- * response: noteSchema, // 201 auto-derived from 'create' method
411
- * tags: ['Mutations'],
412
- * description: 'Create a new note'
413
- * })
414
- * async create(ctx: RouterContext): Promise<Response> {
415
- * // POST /api/v1/notes (auto-derived from method name)
416
- * // Body schema: createNoteSchema (auto-validated)
417
- * // Response: 201 → noteSchema (status auto-derived)
418
- * // Tags: ['Notes', 'Mutations'] (merged with controller)
419
- * // Security: ['bearerAuth'] (inherited from controller)
420
- * const body = ctx.body()
421
- * const note = await this.notesService.create(body)
422
- * return ctx.json(note, 201)
423
- * }
424
- *
425
- * @Route({
426
- * query: paginationSchema,
427
- * response: z.array(noteSchema) // 200 auto-derived from 'index' method
428
- * })
429
- * async index(ctx: RouterContext): Promise<Response> {
430
- * // GET /api/v1/notes (auto-derived)
431
- * // Query params auto-validated
432
- * const notes = await this.notesService.list()
433
- * return ctx.json(notes)
434
- * }
435
- *
436
- * @Route({
437
- * params: z.object({ id: z.string().uuid() }),
438
- * response: {
439
- * schema: noteSchema,
440
- * description: 'Note details'
441
- * },
442
- * security: [] // Override to make public
443
- * })
444
- * async show(ctx: RouterContext): Promise<Response> {
445
- * // GET /api/v1/notes/:id (auto-derived)
446
- * // URL params auto-validated
447
- * // Response: 200 → noteSchema (status auto-derived)
448
- * // Security: [] (public route, override controller security)
449
- * const id = ctx.param('id')
450
- * const note = await this.notesService.findById(id)
451
- * return ctx.json(note)
452
- * }
453
- * }
454
- * ```
455
- */
456
- function Route(config) {
457
- return function(target, propertyKey, descriptor) {
458
- Reflect.defineMetadata(ROUTE_METADATA_KEYS.ROUTE_CONFIG, config, target, propertyKey);
459
- return descriptor;
460
- };
461
- }
462
- /**
463
- * Get the route configuration from a controller method
464
- *
465
- * @param target - Controller instance or prototype
466
- * @param methodName - Name of the method
467
- * @returns Route configuration or undefined if not decorated
468
- */
469
- function getRouteConfig(target, methodName) {
470
- return Reflect.getMetadata(ROUTE_METADATA_KEYS.ROUTE_CONFIG, target, methodName);
471
- }
472
- /**
473
- * Get all methods with @Route() decorator from a controller
474
- *
475
- * @param ControllerClass - Controller class
476
- * @returns Array of method names that have route config
477
- */
478
- function getDecoratedMethods(ControllerClass) {
479
- const prototype = ControllerClass.prototype;
480
- return Object.getOwnPropertyNames(prototype).filter((name) => name !== "constructor" && typeof prototype[name] === "function").filter((methodName) => Reflect.hasMetadata(ROUTE_METADATA_KEYS.ROUTE_CONFIG, prototype, methodName));
481
- }
482
- //#endregion
483
- //#region src/openapi/services/openapi.service.ts
484
- let OpenAPIService = class OpenAPIService {
485
- routeInfoMap = /* @__PURE__ */ new Map();
486
- /**
487
- * Generate a filtered OpenAPI spec using the user's config.
488
- * Usable from both HTTP handlers and CLI commands.
489
- */
490
- getSpec(app, container) {
491
- const configService = container.resolve(OPENAPI_TOKENS.ConfigService);
492
- const i18n = container.resolve(I18N_TOKENS.I18nService);
493
- const config = configService.getEffectiveConfig();
494
- const fullSpec = app.getOpenAPIDocument({
495
- openapi: "3.0.0",
496
- info: {
497
- version: config.info.version,
498
- title: config.info.title,
499
- description: config.info.description
500
- }
501
- });
502
- fullSpec.components ??= {};
503
- fullSpec.components.securitySchemes = this.getSecuritySchemeDefinitions(i18n);
504
- fullSpec.paths = this.filterRoutes(fullSpec.paths, config);
505
- if (fullSpec.components.schemas) fullSpec.components.schemas = this.filterSchemas(fullSpec);
506
- return fullSpec;
507
- }
508
- /**
509
- * Setup OpenAPI documentation endpoints
510
- */
511
- setupEndpoints(app, controllers, container) {
512
- this.buildRouteInfoMap(controllers);
513
- const config = container.resolve(OPENAPI_TOKENS.ConfigService).getEffectiveConfig();
514
- app.get(config.jsonPath, (c) => {
515
- const requestContainer = c.get(ROUTER_CONTEXT_KEYS.REQUEST_CONTAINER);
516
- const fullSpec = this.getSpec(app, requestContainer);
517
- const url = new URL(c.req.raw.url);
518
- const i18n = requestContainer.resolve(I18N_TOKENS.I18nService);
519
- fullSpec.servers = [{
520
- url: `${url.protocol}//${url.host}`,
521
- description: i18n.t("common.api.serverDescription")
522
- }];
523
- return c.json(fullSpec);
524
- });
525
- this.nameLastHandler(app, "OpenAPI", "spec");
526
- if (config.ui !== false) {
527
- const uiPath = config.ui?.path ?? "/api/docs";
528
- const uiRenderer = config.ui?.renderer;
529
- app.get(uiPath, (c, next) => {
530
- const effectiveConfig = c.get(ROUTER_CONTEXT_KEYS.REQUEST_CONTAINER).resolve(OPENAPI_TOKENS.ConfigService).getEffectiveConfig();
531
- const uiContext = {
532
- specUrl: effectiveConfig.jsonPath,
533
- title: effectiveConfig.info.title
534
- };
535
- if (uiRenderer) return uiRenderer(uiContext)(c, next);
536
- return swaggerUI({ url: uiContext.specUrl })(c, next);
537
- });
538
- this.nameLastHandler(app, "OpenAPI", "docs");
539
- }
540
- }
541
- nameLastHandler(app, controller, method) {
542
- const last = app.routes[app.routes.length - 1];
543
- Object.defineProperty(last.handler, "name", { value: `http:${controller}.${method}` });
544
- }
545
- /**
546
- * Get localized security scheme definitions
547
- */
548
- getSecuritySchemeDefinitions(i18n) {
549
- return {
550
- [SECURITY_SCHEMES.BEARER_AUTH]: {
551
- type: "http",
552
- scheme: "bearer",
553
- bearerFormat: "JWT",
554
- description: i18n.t("common.api.security.bearerAuth")
555
- },
556
- [SECURITY_SCHEMES.API_KEY]: {
557
- type: "apiKey",
558
- in: "header",
559
- name: "X-API-Key",
560
- description: i18n.t("common.api.security.apiKey")
561
- },
562
- [SECURITY_SCHEMES.SESSION_COOKIE]: {
563
- type: "apiKey",
564
- in: "cookie",
565
- name: "session",
566
- description: i18n.t("common.api.security.sessionCookie")
567
- }
568
- };
569
- }
570
- /**
571
- * Build route info map from controllers
572
- * Maps route prefixes to their hideFromDocs flag
573
- */
574
- buildRouteInfoMap(controllers) {
575
- for (const ControllerClass of controllers) {
576
- const route = getControllerRoute(ControllerClass);
577
- const options = getControllerOptions(ControllerClass);
578
- if (route) this.routeInfoMap.set(route, { hideFromDocs: options?.hideFromDocs ?? false });
579
- }
580
- }
581
- /**
582
- * Filter OpenAPI paths based on hideFromDocs and custom routeFilter
583
- */
584
- filterRoutes(paths, config) {
585
- const filteredPaths = {};
586
- for (const [path, pathItem] of Object.entries(paths)) {
587
- if (this.getRouteInfo(path).hideFromDocs) continue;
588
- if (config.routeFilter) {
589
- if (!config.routeFilter(path, pathItem)) continue;
590
- }
591
- filteredPaths[path] = pathItem;
592
- }
593
- return filteredPaths;
594
- }
595
- /**
596
- * Get route info by matching path against controller routes
597
- */
598
- getRouteInfo(path) {
599
- for (const [route, info] of this.routeInfoMap.entries()) if (path === route || path.startsWith(`${route}/`)) return info;
600
- return { hideFromDocs: false };
601
- }
602
- /**
603
- * Filter unreferenced schemas from OpenAPI spec
604
- */
605
- filterSchemas(spec) {
606
- const referencedSchemas = /* @__PURE__ */ new Set();
607
- this.collectSchemaRefs(spec.paths, referencedSchemas);
608
- const filteredSchemas = {};
609
- const components = spec.components;
610
- if (components?.schemas) {
611
- const allSchemas = components.schemas;
612
- let prevSize = 0;
613
- while (referencedSchemas.size > prevSize) {
614
- prevSize = referencedSchemas.size;
615
- for (const [schemaName, schemaValue] of Object.entries(allSchemas)) if (referencedSchemas.has(schemaName) && !filteredSchemas[schemaName]) {
616
- filteredSchemas[schemaName] = schemaValue;
617
- this.collectSchemaRefs(schemaValue, referencedSchemas);
618
- }
619
- }
620
- }
621
- return filteredSchemas;
622
- }
623
- /**
624
- * Recursively collect all schema references from an object
625
- */
626
- collectSchemaRefs(obj, refs) {
627
- if (!obj || typeof obj !== "object") return;
628
- const record = obj;
629
- if (record.$ref && typeof record.$ref === "string") {
630
- const match = /^#\/components\/schemas\/(.+)$/.exec(record.$ref);
631
- if (match) refs.add(match[1]);
632
- }
633
- if (Array.isArray(obj)) for (const item of obj) this.collectSchemaRefs(item, refs);
634
- else for (const value of Object.values(record)) this.collectSchemaRefs(value, refs);
635
- }
636
- };
637
- OpenAPIService = __decorate([Transient(OPENAPI_TOKENS.OpenAPIService)], OpenAPIService);
638
- //#endregion
639
- //#region src/openapi/openapi.module.ts
640
- /**
641
- * OpenAPI Module
642
- *
643
- * Provides configurable OpenAPI documentation endpoints with runtime override support.
644
- *
645
- * Features:
646
- * - Configurable paths for /openapi.json and /docs
647
- * - Runtime config overrides via middleware
648
- * - i18n support for titles and descriptions
649
- * - Route filtering via hideFromDocs and custom routeFilter
650
- *
651
- * @example Basic usage
652
- * ```typescript
653
- * @Module({
654
- * imports: [
655
- * OpenAPIModule.forRoot({
656
- * info: { title: 'My API', version: '1.0.0' }
657
- * })
658
- * ]
659
- * })
660
- * export class AppModule {}
661
- * ```
662
- *
663
- * @example With runtime override in middleware
664
- * ```typescript
665
- * // In RouteAccessMiddleware
666
- * constructor(
667
- * @inject(OPENAPI_TOKENS.ConfigService) private openAPIConfig: IOpenAPIConfigService
668
- * ) {}
669
- *
670
- * async handle(ctx, next) {
671
- * this.openAPIConfig.override({
672
- * info: { title: 'Custom API' },
673
- * routeFilter: (path) => this.shouldInclude(path)
674
- * })
675
- * await next()
676
- * }
677
- * ```
678
- */
679
- var _OpenAPIModule;
680
- /** Default options when none provided */
681
- const DEFAULT_OPTIONS = {
682
- jsonPath: "/api/openapi.json",
683
- info: {
684
- title: "API",
685
- version: "1.0.0"
686
- }
687
- };
688
- let OpenAPIModule = _OpenAPIModule = class OpenAPIModule {
689
- /**
690
- * Configure OpenAPI module with static options
691
- *
692
- * @param options - OpenAPI configuration (paths, info, security schemes)
693
- * @returns DynamicModule with options provider
694
- */
695
- static forRoot(options = {}) {
696
- const mergedOptions = {
697
- ...DEFAULT_OPTIONS,
698
- ...options,
699
- info: {
700
- ...DEFAULT_OPTIONS.info,
701
- ...options.info,
702
- title: options.info?.title ?? DEFAULT_OPTIONS.info?.title ?? "API",
703
- version: options.info?.version ?? DEFAULT_OPTIONS.info?.version ?? "1.0.0"
704
- }
705
- };
706
- return {
707
- module: _OpenAPIModule,
708
- providers: [{
709
- provide: OPENAPI_TOKENS.Options,
710
- useValue: mergedOptions
711
- }]
712
- };
713
- }
714
- static forRootAsync(options) {
715
- return {
716
- module: _OpenAPIModule,
717
- providers: [{
718
- provide: OPENAPI_TOKENS.Options,
719
- useFactory: options.useFactory,
720
- inject: options.inject
721
- }]
722
- };
723
- }
724
- };
725
- OpenAPIModule = _OpenAPIModule = __decorate([Module({ providers: [{
726
- provide: OPENAPI_TOKENS.ConfigService,
727
- useClass: OpenAPIConfigService,
728
- scope: Scope.Request
729
- }, {
730
- provide: OPENAPI_TOKENS.OpenAPIService,
731
- useClass: OpenAPIService,
732
- scope: Scope.Singleton
733
- }] })], OpenAPIModule);
734
- //#endregion
735
- //#region src/router/errors/controller-method-not-found.error.ts
736
- /**
737
- * ControllerMethodNotFoundError
738
- *
739
- * Thrown when a controller method is registered but doesn't exist on the controller instance.
740
- * This typically indicates a mismatch between route registration and controller implementation.
741
- */
742
- var ControllerMethodNotFoundError = class extends ApplicationError {
743
- constructor(methodName, controllerName) {
744
- super("errors.controllerMethodNotFound", ERROR_CODES.ROUTER.CONTROLLER_METHOD_NOT_FOUND, {
745
- methodName,
746
- controllerName
747
- });
748
- }
749
- };
750
- //#endregion
751
- //#region src/router/errors/hono-app-already-configured.error.ts
752
- /**
753
- * Error thrown when HonoApp.configure() is called more than once.
754
- *
755
- * HonoApp can only be configured a single time during application bootstrap.
756
- */
757
- var HonoAppAlreadyConfiguredError = class extends ApplicationError {
758
- constructor() {
759
- super("errors.honoAppAlreadyConfigured", ERROR_CODES.SYSTEM.CONFIGURATION_ERROR);
760
- }
761
- };
762
- //#endregion
763
- //#region src/router/errors/controller-registration.error.ts
764
- /**
765
- * Error thrown when a controller fails to register
766
- *
767
- * This typically happens when:
768
- * - Controller is missing the `@Controller` decorator
769
- * - Controller route metadata is not set
770
- * - Controller class name is invalid
771
- *
772
- * Error Code: 9005
773
- */
774
- var ControllerRegistrationError = class extends ApplicationError {
775
- constructor(controllerName, reason) {
776
- super("errors.controllerRegistration", ERROR_CODES.ROUTER.CONTROLLER_REGISTRATION_ERROR, {
777
- controllerName,
778
- reason
779
- });
780
- }
781
- };
782
- //#endregion
783
- //#region src/router/errors/openapi-route-registration.error.ts
784
- /**
785
- * OpenAPIRouteRegistrationError
786
- *
787
- * Thrown when an OpenAPI route fails to register properly
788
- * This indicates a configuration issue with route decorators or metadata
789
- * Uses i18n key for localized error messages
790
- *
791
- * @example
792
- * ```typescript
793
- * throw new OpenAPIRouteRegistrationError('/api/v1/users', 'Missing response schema')
794
- * ```
795
- */
796
- var OpenAPIRouteRegistrationError = class extends ApplicationError {
797
- constructor(path, reason) {
798
- super("errors.openapiRouteRegistration", ERROR_CODES.ROUTER.OPENAPI_ROUTE_REGISTRATION, {
799
- path,
800
- reason
801
- });
802
- }
803
- };
804
- //#endregion
805
- //#region src/router/errors/openapi-validation.error.ts
806
- /**
807
- * OpenAPIValidationError
808
- *
809
- * Thrown when OpenAPI request/response validation fails
810
- * Uses i18n key for localized error messages
811
- *
812
- * HTTP Status: 400 Bad Request
813
- * Error Code: 1004
814
- *
815
- * @example
816
- * ```typescript
817
- * throw new OpenAPIValidationError('Request body missing required field: email')
818
- * ```
819
- */
820
- var OpenAPIValidationError = class extends ApplicationError {
821
- constructor(details) {
822
- super("errors.openapiValidation", ERROR_CODES.VALIDATION.REQUEST_VALIDATION, { details });
823
- }
824
- };
825
- //#endregion
826
- //#region src/router/errors/route-not-found.error.ts
827
- /**
828
- * Error thrown when a requested route is not found
829
- *
830
- * HTTP Status: 404 Not Found
831
- * Error Code: 4004
832
- */
833
- var RouteNotFoundError = class extends ApplicationError {
834
- constructor(path, method) {
835
- super("errors.routeNotFound", ERROR_CODES.RESOURCE.ROUTE_NOT_FOUND, {
836
- path,
837
- method
838
- });
839
- }
840
- };
841
- //#endregion
842
- //#region src/router/errors/schema-validation.error.ts
843
- /**
844
- * SchemaValidationError
845
- *
846
- * Thrown when Zod schema validation fails
847
- */
848
- var SchemaValidationError = class extends ApplicationError {
849
- constructor(zodError) {
850
- const issues = zodError.issues.map((err) => ({
851
- path: err.path.join("."),
852
- message: err.message,
853
- code: err.code
854
- }));
855
- super("errors.schemaValidation", ERROR_CODES.VALIDATION.SCHEMA_VALIDATION, { issues });
856
- }
857
- };
858
- //#endregion
859
- //#region src/router/middleware/logger.middleware.ts
860
- /**
861
- * Create a Hono middleware that logs HTTP requests using our Logger service
862
- *
863
- * Logs request method, path, status code, and duration in milliseconds.
864
- * Format: [HTTP] METHOD /path -> STATUS (duration ms)
865
- *
866
- * @param logger - Logger service instance
867
- * @returns Hono middleware handler
868
- *
869
- * @example
870
- * ```typescript
871
- * const logger = container.resolve<LoggerService>(LOGGER_TOKENS.LoggerService)
872
- * app.use('*', createLoggerMiddleware(logger))
873
- * ```
874
- */
875
- function createLoggerMiddleware(logger) {
876
- return async (c, next) => {
877
- const start = Date.now();
878
- const method = c.req.method;
879
- const path = c.req.path;
880
- await next();
881
- const duration = Date.now() - start;
882
- const status = c.res.status;
883
- logger.info(`[HTTP] ${method} ${path} -> ${status}`, {
884
- method,
885
- path,
886
- status,
887
- duration
888
- });
889
- };
890
- }
891
- //#endregion
892
- //#region src/router/schemas/common.schemas.ts
893
- /**
894
- * Common OpenAPI Schemas
895
- *
896
- * Reusable schema definitions for common API patterns:
897
- * - Error responses
898
- * - Pagination
899
- * - Common parameters
900
- */
901
- /**
902
- * Generic error response schema
903
- * Used for all error responses (4xx, 5xx)
904
- * Matches ApplicationError.toErrorResponse() structure
905
- */
906
- const errorResponseSchema = z.object({
907
- code: z.number().int().describe("Application error code"),
908
- message: z.string().describe("Human-readable error message"),
909
- timestamp: z.string().datetime().describe("ISO timestamp when error occurred"),
910
- metadata: z.record(z.string(), z.unknown()).optional().describe("Additional error context"),
911
- stack: z.string().optional().describe("Stack trace (development only)")
912
- }).openapi("ErrorResponse");
913
- /**
914
- * Validation error response schema
915
- * Used for 400 Bad Request with validation failures
916
- * Matches ApplicationError.toErrorResponse() structure with validation-specific metadata
917
- */
918
- const validationErrorResponseSchema = z.object({
919
- code: z.number().int().describe("Application error code"),
920
- message: z.string().describe("Human-readable error message"),
921
- timestamp: z.string().datetime().describe("ISO timestamp when error occurred"),
922
- metadata: z.object({ issues: z.array(z.object({
923
- path: z.string().describe("Field path that failed validation"),
924
- message: z.string().describe("Validation failure message"),
925
- code: z.string().describe("Zod validation error code")
926
- })) }).describe("Validation error details"),
927
- stack: z.string().optional().describe("Stack trace (development only)")
928
- }).openapi("ValidationErrorResponse");
929
- /**
930
- * Pagination query parameters schema
931
- * Used for list endpoints
932
- */
933
- const paginationQuerySchema = z.object({
934
- page: z.coerce.number().int().positive().default(1).describe("Page number (1-indexed)"),
935
- limit: z.coerce.number().int().positive().max(100).default(20).describe("Items per page (max 100)")
936
- }).openapi("PaginationQuery");
937
- /**
938
- * Paginated response wrapper schema
939
- * Generic wrapper for paginated list responses
940
- */
941
- const paginatedResponseSchema = (itemSchema) => z.object({
942
- data: z.array(itemSchema).describe("Array of items for current page"),
943
- pagination: z.object({
944
- page: z.number().int().positive().describe("Current page number"),
945
- limit: z.number().int().positive().describe("Items per page"),
946
- total: z.number().int().nonnegative().describe("Total number of items"),
947
- totalPages: z.number().int().nonnegative().describe("Total number of pages")
948
- })
949
- });
950
- /**
951
- * UUID parameter schema
952
- * Used for :id parameters in RESTful routes
953
- */
954
- const uuidParamSchema = z.object({ id: z.string().uuid().describe("Resource UUID") }).openapi("UUIDParam");
955
- /**
956
- * Success message response schema
957
- * Used for operations that don't return data (e.g., DELETE)
958
- */
959
- const successMessageSchema = z.object({
960
- message: z.string().describe("Success message"),
961
- data: z.record(z.string(), z.unknown()).optional().describe("Optional additional data")
962
- }).openapi("SuccessMessage");
963
- /**
964
- * Common HTTP status error schemas
965
- * Pre-configured for standard error responses
966
- */
967
- const commonErrorSchemas = {
968
- 400: {
969
- schema: validationErrorResponseSchema,
970
- description: "Validation error"
971
- },
972
- 401: {
973
- schema: errorResponseSchema,
974
- description: "Unauthorized"
975
- },
976
- 403: {
977
- schema: errorResponseSchema,
978
- description: "Forbidden"
979
- },
980
- 404: {
981
- schema: errorResponseSchema,
982
- description: "Not found"
983
- },
984
- 409: {
985
- schema: errorResponseSchema,
986
- description: "Conflict"
987
- },
988
- 500: {
989
- schema: errorResponseSchema,
990
- description: "Internal server error"
991
- }
992
- };
993
- //#endregion
994
- //#region src/router/services/route-registration.service.ts
995
- const invokeHandler = (instance, method, ...args) => {
996
- try {
997
- return Promise.resolve(instance[method](...args));
998
- } catch (err) {
999
- return Promise.reject(err);
1000
- }
1001
- };
1002
- /**
1003
- * Route registration service
1004
- * Manages controller and route registration with OpenAPI support
1005
- *
1006
- * Responsibilities:
1007
- * - Register RESTful controllers with OpenAPI metadata
1008
- * - Auto-derive HTTP methods/paths from controller method names
1009
- * - Build OpenAPI route configurations with guard execution
1010
- * - Validate all controllers have access decorators (strict mode)
1011
- * - Create controller handlers with DI resolution
1012
- */
1013
- var RouteRegistrationService = class {
1014
- controllerClasses = /* @__PURE__ */ new Map();
1015
- constructor(logger, versioningOptions = null) {
1016
- this.logger = logger;
1017
- this.versioningOptions = versioningOptions;
1018
- }
1019
- /**
1020
- * Configure router with controllers
1021
- *
1022
- * @param app - OpenAPIHono application instance
1023
- * @param controllers - Array of controller classes from modules
1024
- */
1025
- async configure(app, controllers) {
1026
- this.logger.info("Registering controllers", { controllerCount: controllers.length });
1027
- const sortedControllers = [...controllers].sort((a, b) => {
1028
- const aHasHandle = "handle" in a.prototype;
1029
- const bHasHandle = "handle" in b.prototype;
1030
- if (aHasHandle && !bHasHandle) return 1;
1031
- if (!aHasHandle && bHasHandle) return -1;
1032
- return 0;
1033
- });
1034
- const gateways = [];
1035
- const regulars = [];
1036
- for (const c of sortedControllers) if (isGateway(c)) gateways.push(c);
1037
- else regulars.push(c);
1038
- for (const GatewayClass of gateways) await this.registerGateway(app, GatewayClass);
1039
- for (const ControllerClass of regulars) this.registerController(app, ControllerClass);
1040
- this.logger.info("Controller registration complete");
1041
- }
1042
- /**
1043
- * Register a WebSocket gateway
1044
- * Applies versioning and guards, then registers an upgradeWebSocket handler
1045
- */
1046
- async registerGateway(app, GatewayClass) {
1047
- const route = getControllerRoute(GatewayClass);
1048
- if (!route) throw new ControllerRegistrationError(GatewayClass.name, "Missing @Gateway decorator or route metadata");
1049
- const controllerOpts = getControllerOptions(GatewayClass);
1050
- const paths = this.versioningOptions ? this.resolveVersionedPaths(route, controllerOpts) : [route];
1051
- for (const fullPath of paths) await this.registerGatewayRoute(app, GatewayClass, fullPath);
1052
- }
1053
- /**
1054
- * Register a single WebSocket gateway route
1055
- */
1056
- async registerGatewayRoute(app, GatewayClass, fullPath) {
1057
- const controllerGuards = getControllerGuards(GatewayClass)?.guards ?? [];
1058
- const { upgradeWebSocket } = await import("hono/cloudflare-workers");
1059
- const onMsgMethod = getWsOnMessageMethod(GatewayClass);
1060
- const onCloseMethod = getWsOnCloseMethod(GatewayClass);
1061
- const onErrMethod = getWsOnErrorMethod(GatewayClass);
1062
- const wsHandler = upgradeWebSocket((c) => {
1063
- const gateway = new RouterContext(c).getContainer().resolve(GatewayClass);
1064
- const events = {};
1065
- const bindWsHandler = (method, onCatch) => {
1066
- return (evt, ws) => {
1067
- const ctx = new GatewayContext(c, ws);
1068
- c.executionCtx.waitUntil(invokeHandler(gateway, method, evt, ctx).catch((err) => {
1069
- this.logger.error(`WebSocket ${method} handler error`, {
1070
- gateway: GatewayClass.name,
1071
- error: err instanceof Error ? err.message : String(err)
1072
- });
1073
- onCatch?.(err, ws);
1074
- }));
1075
- };
1076
- };
1077
- if (onMsgMethod) events.onMessage = bindWsHandler(onMsgMethod, (_err, ws) => ws.close(1011, "Internal Error"));
1078
- if (onCloseMethod) events.onClose = bindWsHandler(onCloseMethod);
1079
- if (onErrMethod) events.onError = bindWsHandler(onErrMethod);
1080
- return events;
1081
- });
1082
- this.nameHandler(wsHandler, GatewayClass.name, onMsgMethod ?? "[anonymous]", "ws");
1083
- this.logger.info("Registering WebSocket gateway", {
1084
- gateway: GatewayClass.name,
1085
- path: fullPath
1086
- });
1087
- const handlers = [];
1088
- if (controllerGuards.length > 0) {
1089
- this.logger.info("Gateway guards", {
1090
- gateway: GatewayClass.name,
1091
- path: fullPath,
1092
- guardCount: controllerGuards.length
1093
- });
1094
- handlers.push(this.createGuardMiddleware(controllerGuards));
1095
- }
1096
- handlers.push(wsHandler);
1097
- app.get(fullPath, ...handlers);
1098
- }
1099
- /**
1100
- * Register a single controller with all its routes
1101
- * Validates that controller has access decorator (strict mode)
1102
- */
1103
- registerController(app, ControllerClass) {
1104
- const route = getControllerRoute(ControllerClass);
1105
- if (!route) throw new ControllerRegistrationError(ControllerClass.name, "Missing @Controller decorator or route metadata");
1106
- const className = ControllerClass.name;
1107
- this.controllerClasses.set(className, ControllerClass);
1108
- const prototype = ControllerClass.prototype;
1109
- const controllerOpts = getControllerOptions(ControllerClass);
1110
- if (prototype.handle) {
1111
- if (!this.versioningOptions) this.registerWildcardRoute(app, ControllerClass, route);
1112
- else for (const versionedRoute of this.resolveVersionedPaths(route, controllerOpts)) this.registerWildcardRoute(app, ControllerClass, versionedRoute);
1113
- return;
1114
- }
1115
- const decoratedMethods = getDecoratedMethods(ControllerClass);
1116
- const httpDecoratedMethods = getHttpDecoratedMethods(ControllerClass);
1117
- if (decoratedMethods.length > 0 && httpDecoratedMethods.length > 0) 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.");
1118
- if (!this.versioningOptions) {
1119
- this.dispatchRoutes(app, ControllerClass, route, decoratedMethods, httpDecoratedMethods, controllerOpts, prototype);
1120
- return;
1121
- }
1122
- for (const versionedRoute of this.resolveVersionedPaths(route, controllerOpts)) this.dispatchRoutes(app, ControllerClass, versionedRoute, decoratedMethods, httpDecoratedMethods, controllerOpts, prototype);
1123
- }
1124
- /**
1125
- * Dispatch route registration based on decorator type
1126
- */
1127
- dispatchRoutes(app, ControllerClass, path, decoratedMethods, httpDecoratedMethods, controllerOpts, prototype) {
1128
- if (httpDecoratedMethods.length > 0) this.registerHttpRoutes(app, ControllerClass, path, httpDecoratedMethods, controllerOpts);
1129
- else if (decoratedMethods.length > 0) this.registerOpenAPIRoutes(app, ControllerClass, path, decoratedMethods, controllerOpts);
1130
- else this.registerRESTfulRoutes(app, ControllerClass, path, prototype);
1131
- }
1132
- /**
1133
- * Resolve versioned paths for a controller based on versioning configuration.
1134
- *
1135
- * @param basePath - The base path from @Controller decorator
1136
- * @param controllerOpts - Controller options (may contain version)
1137
- * @returns Array of resolved paths (with version prefix if applicable)
1138
- */
1139
- resolveVersionedPaths(basePath, controllerOpts) {
1140
- if (!this.versioningOptions) return [basePath];
1141
- const version = controllerOpts?.version;
1142
- if (version === VERSION_NEUTRAL) return [basePath];
1143
- const prefix = this.versioningOptions.prefix ?? "v";
1144
- if (version !== void 0) return (Array.isArray(version) ? version : [version]).map((v) => `/${prefix}${v}${basePath}`);
1145
- if (this.versioningOptions.defaultVersion !== void 0) return (Array.isArray(this.versioningOptions.defaultVersion) ? this.versioningOptions.defaultVersion : [this.versioningOptions.defaultVersion]).map((v) => `/${prefix}${v}${basePath}`);
1146
- return [basePath];
1147
- }
1148
- /**
1149
- * Create a guard execution middleware
1150
- *
1151
- * This middleware executes all guards for a route before the handler.
1152
- * Guards are executed in order; all must pass for the request to proceed.
1153
- *
1154
- * @param guards - Array of guards to execute
1155
- * @returns Hono middleware function
1156
- */
1157
- createGuardMiddleware(guards) {
1158
- const guardService = new GuardExecutionService(this.logger);
1159
- return async (c, next) => {
1160
- const ctx = new RouterContext(c);
1161
- const container = ctx.getContainer();
1162
- await guardService.executeGuards(guards, ctx, container);
1163
- await next();
1164
- };
1165
- }
1166
- /**
1167
- * Register wildcard route for non-RESTful controllers
1168
- */
1169
- registerWildcardRoute(app, ControllerClass, route) {
1170
- this.logger.info(`Registering wildcard route`, {
1171
- controller: ControllerClass.name,
1172
- route: `${route}/:path{.+}`,
1173
- method: "ALL"
1174
- });
1175
- const handler = this.createControllerHandler(ControllerClass, "handle");
1176
- app.all(route, handler);
1177
- app.all(`${route}/:path{.+}`, handler);
1178
- }
1179
- /**
1180
- * Register OpenAPI routes with metadata
1181
- */
1182
- registerOpenAPIRoutes(app, ControllerClass, route, decoratedMethods, controllerOpts) {
1183
- const className = ControllerClass.name;
1184
- const prototype = ControllerClass.prototype;
1185
- const controllerHidden = controllerOpts?.hideFromDocs ?? false;
1186
- const controllerGuards = getControllerGuards(ControllerClass)?.guards ?? [];
1187
- for (const methodName of decoratedMethods) {
1188
- const routeConfig = getRouteConfig(prototype, methodName);
1189
- if (!routeConfig) continue;
1190
- const derived = this.deriveHttpMethodAndPath(methodName, route);
1191
- if (!derived) {
1192
- this.logger.warn(`Cannot derive HTTP method/path for ${className}.${methodName}`);
1193
- continue;
1194
- }
1195
- if (routeConfig.hideFromDocs ?? controllerHidden) {
1196
- this.logger.info(`Registering hidden route (no OpenAPI)`, {
1197
- controller: className,
1198
- method: derived.method.toUpperCase(),
1199
- path: derived.path,
1200
- methodName
1201
- });
1202
- this.registerRouteWithoutOpenAPI(app, ControllerClass, methodName, derived);
1203
- continue;
1204
- }
1205
- const methodGuards = getMethodGuards(prototype, methodName)?.guards ?? [];
1206
- const allGuards = [...controllerGuards, ...methodGuards];
1207
- if (allGuards.length > 0) this.logger.info(`Route guards`, {
1208
- controller: className,
1209
- method: derived.method.toUpperCase(),
1210
- path: derived.path,
1211
- methodName,
1212
- guardCount: allGuards.length
1213
- });
1214
- const metadata = this.mergeMetadata(controllerOpts, routeConfig, ControllerClass, methodName);
1215
- const openApiRoute = this.buildOpenAPIRoute(derived.method, derived.path, routeConfig, metadata, allGuards, methodName);
1216
- this.logger.info(`Registering OpenAPI route`, {
1217
- controller: className,
1218
- method: derived.method.toUpperCase(),
1219
- path: derived.path,
1220
- methodName,
1221
- tags: metadata.tags
1222
- });
1223
- const handler = async (c) => {
1224
- const ctx = new RouterContext(c);
1225
- const requestContainer = ctx.getContainer();
1226
- const controller = requestContainer.resolve(ControllerClass);
1227
- const method = controller[methodName];
1228
- if (typeof method === "function") {
1229
- const injectedArgs = this.resolveMethodInjections(prototype, methodName, requestContainer);
1230
- return await method.call(controller, ctx, ...injectedArgs);
1231
- }
1232
- throw new ControllerMethodNotFoundError(methodName, className);
1233
- };
1234
- this.nameHandler(handler, className, methodName);
1235
- app.openapi(openApiRoute, handler);
1236
- }
1237
- }
1238
- /**
1239
- * Register routes using HTTP method decorators (@Get, @Post, etc.)
1240
- * These use explicit method + path instead of convention-based derivation
1241
- */
1242
- registerHttpRoutes(app, ControllerClass, basePath, httpDecoratedMethods, controllerOpts) {
1243
- const className = ControllerClass.name;
1244
- const prototype = ControllerClass.prototype;
1245
- const controllerHidden = controllerOpts?.hideFromDocs ?? false;
1246
- const controllerGuards = getControllerGuards(ControllerClass)?.guards ?? [];
1247
- for (const methodName of httpDecoratedMethods) {
1248
- const httpMeta = getHttpRouteMetadata(prototype, methodName);
1249
- if (!httpMeta) continue;
1250
- const fullPath = this.joinPaths(basePath, httpMeta.path);
1251
- const routeConfig = httpMeta.config;
1252
- const hideFromDocs = routeConfig.hideFromDocs ?? controllerHidden;
1253
- if (httpMeta.method === "all" || hideFromDocs) {
1254
- this.logger.info(`Registering route (no OpenAPI)`, {
1255
- controller: className,
1256
- method: httpMeta.method.toUpperCase(),
1257
- path: fullPath,
1258
- methodName
1259
- });
1260
- this.registerRouteWithoutOpenAPI(app, ControllerClass, methodName, {
1261
- method: httpMeta.method,
1262
- path: fullPath
1263
- });
1264
- continue;
1265
- }
1266
- const methodGuards = getMethodGuards(prototype, methodName)?.guards ?? [];
1267
- const allGuards = [...controllerGuards, ...methodGuards];
1268
- if (allGuards.length > 0) this.logger.info(`Route guards`, {
1269
- controller: className,
1270
- method: httpMeta.method.toUpperCase(),
1271
- path: fullPath,
1272
- methodName,
1273
- guardCount: allGuards.length
1274
- });
1275
- const metadata = this.mergeMetadata(controllerOpts, routeConfig, ControllerClass, methodName);
1276
- const statusCode = routeConfig.statusCode ?? 200;
1277
- const openApiRoute = this.buildOpenAPIRoute(httpMeta.method, fullPath, routeConfig, metadata, allGuards, void 0, statusCode);
1278
- this.logger.info(`Registering OpenAPI route`, {
1279
- controller: className,
1280
- method: httpMeta.method.toUpperCase(),
1281
- path: fullPath,
1282
- methodName,
1283
- tags: metadata.tags
1284
- });
1285
- const handler = async (c) => {
1286
- const ctx = new RouterContext(c);
1287
- const requestContainer = ctx.getContainer();
1288
- const controller = requestContainer.resolve(ControllerClass);
1289
- const method = controller[methodName];
1290
- if (typeof method === "function") {
1291
- const injectedArgs = this.resolveMethodInjections(prototype, methodName, requestContainer);
1292
- return await method.call(controller, ctx, ...injectedArgs);
1293
- }
1294
- throw new ControllerMethodNotFoundError(methodName, className);
1295
- };
1296
- this.nameHandler(handler, className, methodName);
1297
- app.openapi(openApiRoute, handler);
1298
- }
1299
- }
1300
- /**
1301
- * Join a base path and a route path, normalizing slashes
1302
- */
1303
- joinPaths(basePath, routePath) {
1304
- if (routePath === "/") return basePath;
1305
- return basePath + routePath;
1306
- }
1307
- /**
1308
- * Register route without OpenAPI metadata (for hidden routes)
1309
- * Route is functional but won't appear in documentation
1310
- */
1311
- registerRouteWithoutOpenAPI(app, ControllerClass, methodName, derived) {
1312
- const prototype = ControllerClass.prototype;
1313
- const handler = async (c) => {
1314
- const ctx = new RouterContext(c);
1315
- const requestContainer = ctx.getContainer();
1316
- const controller = requestContainer.resolve(ControllerClass);
1317
- const method = controller[methodName];
1318
- if (typeof method === "function") {
1319
- const injectedArgs = this.resolveMethodInjections(prototype, methodName, requestContainer);
1320
- return await method.call(controller, ctx, ...injectedArgs);
1321
- }
1322
- throw new ControllerMethodNotFoundError(methodName, ControllerClass.name);
1323
- };
1324
- this.nameHandler(handler, ControllerClass.name, methodName);
1325
- switch (derived.method) {
1326
- case "get":
1327
- app.get(derived.path, handler);
1328
- break;
1329
- case "post":
1330
- app.post(derived.path, handler);
1331
- break;
1332
- case "put":
1333
- app.put(derived.path, handler);
1334
- break;
1335
- case "patch":
1336
- app.patch(derived.path, handler);
1337
- break;
1338
- case "delete":
1339
- app.delete(derived.path, handler);
1340
- break;
1341
- case "all":
1342
- app.all(derived.path, handler);
1343
- break;
1344
- default:
1345
- app.all(derived.path, handler);
1346
- break;
1347
- }
1348
- }
1349
- /**
1350
- * Register traditional RESTful routes without OpenAPI
1351
- */
1352
- registerRESTfulRoutes(app, ControllerClass, route, prototype) {
1353
- if (prototype.index) app.get(route, this.createControllerHandler(ControllerClass, "index"));
1354
- if (prototype.show) app.get(`${route}/:id`, this.createControllerHandler(ControllerClass, "show"));
1355
- if (prototype.create) app.post(route, this.createControllerHandler(ControllerClass, "create"));
1356
- if (prototype.update) app.put(`${route}/:id`, this.createControllerHandler(ControllerClass, "update"));
1357
- if (prototype.patch) app.patch(`${route}/:id`, this.createControllerHandler(ControllerClass, "patch"));
1358
- if (prototype.destroy) app.delete(`${route}/:id`, this.createControllerHandler(ControllerClass, "destroy"));
1359
- }
1360
- /**
1361
- * Auto-derive HTTP method and path from controller method name
1362
- * Uses HTTP_METHODS constant for RESTful convention mapping
1363
- */
1364
- deriveHttpMethodAndPath(methodName, basePath) {
1365
- if (!(methodName in HTTP_METHODS)) return null;
1366
- const mapping = HTTP_METHODS[methodName];
1367
- return {
1368
- method: mapping.method,
1369
- path: basePath + mapping.path
1370
- };
1371
- }
1372
- /**
1373
- * Merge controller-level and route-level metadata
1374
- * Tags are merged (appended), security is merged (union)
1375
- * Guards automatically add sessionCookie security if present
1376
- */
1377
- mergeMetadata(controllerOpts, routeConfig, ControllerClass, methodName) {
1378
- const tags = [...controllerOpts?.tags ?? [], ...routeConfig.tags ?? []];
1379
- const prototype = ControllerClass.prototype;
1380
- const hasMethodGuards = (getMethodGuards(prototype, methodName)?.guards.length ?? 0) > 0;
1381
- const hasControllerGuards = (getControllerGuards(ControllerClass)?.guards.length ?? 0) > 0;
1382
- const requiresAuth = hasMethodGuards || hasControllerGuards;
1383
- let security = [];
1384
- if (routeConfig.security !== void 0) security = [...controllerOpts?.security ?? [], ...routeConfig.security];
1385
- else if (controllerOpts?.security) security = controllerOpts.security;
1386
- if (requiresAuth && !security.includes(SECURITY_SCHEMES.SESSION_COOKIE)) security.push(SECURITY_SCHEMES.SESSION_COOKIE);
1387
- return {
1388
- tags,
1389
- security: security.length > 0 ? security.map((scheme) => ({ [scheme]: [] })) : []
1390
- };
1391
- }
1392
- /**
1393
- * Build OpenAPI route configuration from metadata
1394
- * Creates a route definition compatible with @hono/zod-openapi
1395
- * Includes guard execution for proper access control
1396
- *
1397
- * Execution order: Global middlewares → Guards → Handler
1398
- */
1399
- buildOpenAPIRoute(method, path, routeConfig, metadata, guards, methodName, statusCodeOverride) {
1400
- try {
1401
- const route = {
1402
- method,
1403
- path,
1404
- request: {},
1405
- responses: {}
1406
- };
1407
- if (guards.length > 0) route.middleware = [this.createGuardMiddleware(guards)];
1408
- if (routeConfig.body) {
1409
- const bodySchema = this.isRouteBodyObject(routeConfig.body) ? routeConfig.body.schema : routeConfig.body;
1410
- const bodyContentType = this.isRouteBodyObject(routeConfig.body) ? routeConfig.body.contentType ?? "application/json" : DEFAULT_CONTENT_TYPE;
1411
- route.request = {
1412
- ...route.request,
1413
- body: { content: { [bodyContentType]: { schema: bodySchema } } }
1414
- };
1415
- }
1416
- if (routeConfig.query) route.request = {
1417
- ...route.request,
1418
- query: routeConfig.query
1419
- };
1420
- if (routeConfig.params) route.request = {
1421
- ...route.request,
1422
- params: routeConfig.params
1423
- };
1424
- const successStatus = statusCodeOverride ?? (methodName && METHOD_STATUS_CODES[methodName]) ?? 200;
1425
- const responses = {};
1426
- const responseDef = routeConfig.response;
1427
- if (responseDef) if (typeof responseDef === "object" && "schema" in responseDef) responses[successStatus] = {
1428
- content: { [responseDef.contentType ?? "application/json"]: { schema: responseDef.schema } },
1429
- description: responseDef.description ?? `Response ${successStatus}`
1430
- };
1431
- else responses[successStatus] = {
1432
- content: { [DEFAULT_CONTENT_TYPE]: { schema: responseDef } },
1433
- description: `Response ${successStatus}`
1434
- };
1435
- for (const [statusStr, schema] of Object.entries(commonErrorSchemas)) {
1436
- const status = parseInt(statusStr);
1437
- responses[status] ??= schema;
1438
- }
1439
- route.responses = responses;
1440
- if (metadata.tags.length > 0) route.tags = metadata.tags;
1441
- if (metadata.security.length > 0) route.security = metadata.security;
1442
- if (routeConfig.description) route.description = routeConfig.description;
1443
- if (routeConfig.summary) route.summary = routeConfig.summary;
1444
- return (0, validation_exports.createRoute)(route);
1445
- } catch (error) {
1446
- throw new OpenAPIRouteRegistrationError(path, error instanceof Error ? error.message : String(error));
1447
- }
1448
- }
1449
- /**
1450
- * Check if a body definition is a RouteBodyObject (has schema key) vs bare ZodType
1451
- */
1452
- isRouteBodyObject(body) {
1453
- return typeof body === "object" && "schema" in body;
1454
- }
1455
- /**
1456
- * Resolve method parameter injections from the container
1457
- *
1458
- * @param prototype - Controller prototype
1459
- * @param methodName - Method name to get injections for
1460
- * @param container - Request-scoped container
1461
- * @returns Array of resolved dependencies in parameter order
1462
- */
1463
- resolveMethodInjections(prototype, methodName, container) {
1464
- const injections = getMethodInjections(prototype, methodName);
1465
- if (!injections.length) return [];
1466
- return injections.map((inj) => container.resolve(inj.token));
1467
- }
1468
- /**
1469
- * Name a handler function so Hono's inspectRoutes() can identify it.
1470
- * Format: `{type}:{Controller}.{method}` (e.g. `http:UsersController.create`)
1471
- */
1472
- nameHandler(fn, controller, method, type = "http") {
1473
- Object.defineProperty(fn, "name", { value: `${type}:${controller}.${method}` });
1474
- }
1475
- /**
1476
- * Create controller handler that resolves controller from request-scoped container
1477
- * This ensures each request gets a fresh controller with request-scoped context
1478
- */
1479
- createControllerHandler(ControllerClass, methodName) {
1480
- const handler = async (c) => {
1481
- this.logger.info("Handler invoked", {
1482
- path: c.req.path,
1483
- method: c.req.method,
1484
- controller: ControllerClass.name,
1485
- methodName
1486
- });
1487
- try {
1488
- const ctx = new RouterContext(c);
1489
- const requestContainer = ctx.getContainer();
1490
- const controller = requestContainer.resolve(ControllerClass);
1491
- const method = controller[methodName];
1492
- if (typeof method === "function") {
1493
- const injectedArgs = this.resolveMethodInjections(ControllerClass.prototype, methodName, requestContainer);
1494
- return await method.apply(controller, [ctx, ...injectedArgs]);
1495
- }
1496
- throw new ControllerMethodNotFoundError(methodName, ControllerClass.name);
1497
- } catch (error) {
1498
- this.logger.error("Error in controller handler", {
1499
- controller: ControllerClass.name,
1500
- methodName,
1501
- error: error instanceof Error ? error.message : String(error),
1502
- stack: error instanceof Error ? error.stack : void 0
1503
- });
1504
- throw error;
1505
- }
1506
- };
1507
- this.nameHandler(handler, ControllerClass.name, methodName);
1508
- return handler;
1509
- }
1510
- };
1511
- //#endregion
1512
- //#region src/router/hono-app.ts
1513
- const isMiddlewareClass = (arg) => typeof arg === "function" && arg.prototype && "handle" in arg.prototype;
1514
- /**
1515
- * HonoApp — extends OpenAPIHono with Stratal-specific setup
1516
- *
1517
- * Absorbs all Hono-related setup from the former RouterService and RequestScopeService:
1518
- * - Request scope middleware (child container per request)
1519
- * - Global middleware (CORS, logging, error handling)
1520
- * - defaultHook for validation errors
1521
- * - `use()` overload for Stratal middleware classes
1522
- * - `configure()` for module middleware, OpenAPI, routes, and 404
1523
- */
1524
- var HonoApp = class extends OpenAPIHono {
1525
- configured = false;
1526
- _container;
1527
- _logger;
1528
- /**
1529
- * Reference to the original Hono `use` implementation.
1530
- * Captured in constructor after super() sets it as an instance property.
1531
- * Used by private methods to register middleware without going through the override.
1532
- */
1533
- nativeUse;
1534
- constructor(container, logger) {
1535
- super({ defaultHook: (result, c) => {
1536
- if (!result.success) {
1537
- const errorHandler = c.get(ROUTER_CONTEXT_KEYS.REQUEST_CONTAINER).resolve(DI_TOKENS.ErrorHandler);
1538
- const validationError = new SchemaValidationError(result.error);
1539
- const errorResponse = errorHandler.handle(validationError);
1540
- return c.json(errorResponse, getHttpStatus(errorResponse.code));
1541
- }
1542
- } });
1543
- this._container = container;
1544
- this._logger = logger;
1545
- this.nativeUse = this.use;
1546
- this.use = ((...args) => {
1547
- if (isMiddlewareClass(args[0])) {
1548
- this.applyMiddlewareClasses("*", args);
1549
- return this;
1550
- }
1551
- if (typeof args[0] === "string" && args.length > 1 && isMiddlewareClass(args[1])) {
1552
- this.applyMiddlewareClasses(args[0], args.slice(1));
1553
- return this;
1554
- }
1555
- return this.nativeUse(...args);
1556
- });
1557
- this.setupRequestScope();
1558
- this.setupGlobalMiddleware();
1559
- }
1560
- /**
1561
- * Configure module middleware, OpenAPI endpoints, controller routes, and 404 handler.
1562
- * Called once by Application.initialize().
1563
- */
1564
- async configure(middlewareConfigs, controllers, versioningOptions) {
1565
- if (this.configured) throw new HonoAppAlreadyConfiguredError();
1566
- new MiddlewareConfigurationService(this._logger, versioningOptions ?? null).applyMiddlewares(this, middlewareConfigs, controllers, this._container);
1567
- this._container.resolve(OPENAPI_TOKENS.OpenAPIService).setupEndpoints(this, controllers, this._container);
1568
- await new RouteRegistrationService(this._logger, versioningOptions ?? null).configure(this, controllers);
1569
- this.notFound((c) => {
1570
- throw new RouteNotFoundError(c.req.path, c.req.method);
1571
- });
1572
- this.configured = true;
1573
- }
1574
- setupRequestScope() {
1575
- this.nativeUse("*", async (c, next) => {
1576
- const routerContext = new RouterContext(c);
1577
- const requestContainer = this._container.createRequestScope(routerContext);
1578
- c.set(ROUTER_CONTEXT_KEYS.REQUEST_CONTAINER, requestContainer);
1579
- try {
1580
- await next();
1581
- } finally {
1582
- await requestContainer.dispose();
1583
- }
1584
- });
1585
- }
1586
- setupGlobalMiddleware() {
1587
- this.nativeUse("*", createLoggerMiddleware(this._logger));
1588
- this.onError((err, c) => {
1589
- const errorResponse = c.get(ROUTER_CONTEXT_KEYS.REQUEST_CONTAINER).resolve(DI_TOKENS.ErrorHandler).handle(err);
1590
- return c.json(errorResponse, getHttpStatus(errorResponse.code));
1591
- });
1592
- }
1593
- applyMiddlewareClasses(path, classes) {
1594
- this.nativeUse(path, async (c, next) => {
1595
- const requestContainer = c.get(ROUTER_CONTEXT_KEYS.REQUEST_CONTAINER);
1596
- const ctx = new RouterContext(c);
1597
- let current = next;
1598
- for (let i = classes.length - 1; i >= 0; i--) {
1599
- const prevNext = current;
1600
- const middleware = requestContainer.resolve(classes[i]);
1601
- current = () => middleware.handle(ctx, prevNext);
1602
- }
1603
- await current();
1604
- });
1605
- return this;
1606
- }
1607
- };
1608
- //#endregion
1609
- //#region src/i18n/services/i18n.service.ts
1610
- /**
1611
- * I18n Service
1612
- *
1613
- * Request-scoped service for translations.
1614
- * Injects RouterContext to access request-specific locale.
1615
- * Uses pre-built CoreContext from MessageLoaderService (singleton) for zero-cost lookups.
1616
- */
1617
- var _ref;
1618
- let I18nService = class I18nService {
1619
- constructor(loader, routerContext) {
1620
- this.loader = loader;
1621
- this.routerContext = routerContext;
1622
- }
1623
- /**
1624
- * Translate a message key
1625
- *
1626
- * @param key - Message key (e.g., 'common.actions.save')
1627
- * @param params - Optional parameters for interpolation
1628
- * @returns Translated string
1629
- */
1630
- t(key, params) {
1631
- const context = this.loader.getCoreContext(this.getLocale());
1632
- const result = params !== void 0 ? translate(context, key, params) : translate(context, key);
1633
- return typeof result === "string" ? result : key;
1634
- }
1635
- /**
1636
- * Get current locale
1637
- *
1638
- * @returns Current locale code from RouterContext or default locale
1639
- */
1640
- getLocale() {
1641
- return this.routerContext?.getLocale() ?? "en";
1642
- }
1643
- };
1644
- I18nService = __decorate([
1645
- Transient(I18N_TOKENS.I18nService),
1646
- __decorateParam(0, inject(I18N_TOKENS.MessageLoader)),
1647
- __decorateParam(1, inject(ROUTER_TOKENS.RouterContext, { isOptional: true })),
1648
- __decorateMetadata("design:paramtypes", [Object, typeof (_ref = typeof RouterContext !== "undefined" && RouterContext) === "function" ? _ref : Object])
1649
- ], I18nService);
1650
- //#endregion
1651
- //#region src/i18n/services/message-loader.service.ts
1652
- let MessageLoaderService = class MessageLoaderService {
1653
- cache;
1654
- contextCache;
1655
- locales;
1656
- defaultLocale;
1657
- constructor(registry, options) {
1658
- this.registry = registry;
1659
- this.options = options;
1660
- this.defaultLocale = this.options?.defaultLocale ?? "en";
1661
- this.cache = /* @__PURE__ */ new Map();
1662
- this.contextCache = /* @__PURE__ */ new Map();
1663
- const coreMessages = getMessages();
1664
- const coreLocales = getLocales();
1665
- const registryMessages = this.registry.getMergedMessages();
1666
- const registryLocales = Object.keys(registryMessages);
1667
- const allLocales = [...new Set([...coreLocales, ...registryLocales])];
1668
- this.locales = allLocales;
1669
- for (const locale of allLocales) {
1670
- const merged = deepMerge(coreMessages[locale] ?? {}, registryMessages[locale] ?? {});
1671
- this.cache.set(locale, merged);
1672
- }
1673
- }
1674
- /**
1675
- * Get CoreContext for a locale (lazily built and cached on first access)
1676
- * Falls back to default locale if locale not found
1677
- */
1678
- getCoreContext(locale) {
1679
- const cached = this.contextCache.get(locale);
1680
- if (cached) return cached;
1681
- const effectiveLocale = this.cache.has(locale) ? locale : this.defaultLocale;
1682
- const cachedEffective = this.contextCache.get(effectiveLocale);
1683
- if (cachedEffective) return cachedEffective;
1684
- const messages = this.cache.get(effectiveLocale) ?? {};
1685
- const flattened = this.flattenMessages(messages);
1686
- const ctx = createCoreContext({
1687
- locale: effectiveLocale,
1688
- messages: { [effectiveLocale]: flattened },
1689
- missingWarn: false,
1690
- fallbackWarn: false
1691
- });
1692
- this.contextCache.set(effectiveLocale, ctx);
1693
- return ctx;
1694
- }
1695
- /**
1696
- * Get messages for a specific locale.
1697
- * Falls back to default locale if not found.
1698
- */
1699
- getMessages(locale) {
1700
- return this.cache.get(locale) ?? this.cache.get(this.defaultLocale) ?? {};
1701
- }
1702
- /** Get list of available locale codes */
1703
- getAvailableLocales() {
1704
- return this.locales;
1705
- }
1706
- /** Check if a locale is supported */
1707
- isLocaleSupported(locale) {
1708
- return this.cache.has(locale);
1709
- }
1710
- /** Get default locale */
1711
- getDefaultLocale() {
1712
- return this.defaultLocale;
1713
- }
1714
- /**
1715
- * Flatten nested messages to dot-notation.
1716
- * e.g. `{ a: { b: 'hello' } }` → `{ 'a.b': 'hello' }`
1717
- */
1718
- flattenMessages(messages, prefix = "") {
1719
- const result = {};
1720
- for (const key of Object.keys(messages)) {
1721
- const value = messages[key];
1722
- const newKey = prefix ? `${prefix}.${key}` : key;
1723
- if (typeof value === "object" && value !== null && !Array.isArray(value)) Object.assign(result, this.flattenMessages(value, newKey));
1724
- else result[newKey] = String(value);
1725
- }
1726
- return result;
1727
- }
1728
- };
1729
- MessageLoaderService = __decorate([
1730
- Transient(I18N_TOKENS.MessageLoader),
1731
- __decorateParam(0, inject(I18N_TOKENS.MessageRegistry)),
1732
- __decorateParam(1, inject(I18N_TOKENS.Options, { isOptional: true })),
1733
- __decorateMetadata("design:paramtypes", [Object, Object])
1734
- ], MessageLoaderService);
1735
- //#endregion
1736
- //#region src/i18n/i18n.module.ts
1737
- /**
1738
- * I18n Module
1739
- *
1740
- * Core infrastructure module for internationalization.
1741
- * Provides message translation and locale handling.
1742
- *
1743
- * - `forRoot()` configures locale settings (call once in root module)
1744
- * - `registerMessages()` adds translations (call from any module, as many times as needed)
1745
- *
1746
- * @example
1747
- * ```typescript
1748
- * @Module({
1749
- * imports: [
1750
- * I18nModule.forRoot({ defaultLocale: 'en', locales: ['en', 'fr'] }),
1751
- * I18nModule.registerMessages(appMessages),
1752
- * ],
1753
- * })
1754
- * export class AppModule {}
1755
- * ```
1756
- *
1757
- * @example Package contributing messages
1758
- * ```typescript
1759
- * @Module({
1760
- * imports: [
1761
- * I18nModule.registerMessages(tenancyMessages),
1762
- * ],
1763
- * })
1764
- * export class TenancyModule {}
1765
- * ```
1766
- */
1767
- var _I18nModule;
1768
- setupI18nCompiler();
1769
- z.config({ customError: backendErrorMap });
1770
- let I18nModule = _I18nModule = class I18nModule {
1771
- /**
1772
- * Configure I18n locale settings
1773
- *
1774
- * Call once in the root module. Does not accept messages —
1775
- * use `registerMessages()` to add translations.
1776
- *
1777
- * @param options - Locale configuration (defaultLocale, fallbackLocale, locales)
1778
- */
1779
- static forRoot(options = {}) {
1780
- return {
1781
- module: _I18nModule,
1782
- providers: [{
1783
- provide: I18N_TOKENS.Options,
1784
- useValue: options
1785
- }]
1786
- };
1787
- }
1788
- /**
1789
- * Register i18n messages
1790
- *
1791
- * Can be called from any module, as many times as needed.
1792
- * Messages are deep-merged in registration order — later calls override earlier ones at leaf level.
1793
- *
1794
- * @param messages - Messages keyed by locale code
1795
- *
1796
- * @example App-level messages
1797
- * ```typescript
1798
- * I18nModule.registerMessages({
1799
- * en: { common: { hello: 'Hello' }, errors: { notFound: 'Not found' } },
1800
- * fr: { common: { hello: 'Bonjour' }, errors: { notFound: 'Introuvable' } },
1801
- * })
1802
- * ```
1803
- *
1804
- * @example Package-level messages
1805
- * ```typescript
1806
- * I18nModule.registerMessages({
1807
- * en: { tenancy: { tenantNotFound: 'Tenant not found' } },
1808
- * })
1809
- * ```
1810
- */
1811
- static registerMessages(messages) {
1812
- MessageRegistry.addMessages(messages);
1813
- return {
1814
- module: _I18nModule,
1815
- providers: []
1816
- };
1817
- }
1818
- configure(consumer) {
1819
- consumer.apply(LocaleExtractionMiddleware, I18nContextMiddleware).forRoutes("*");
1820
- }
1821
- };
1822
- I18nModule = _I18nModule = __decorate([Module({ providers: [
1823
- {
1824
- provide: I18N_TOKENS.MessageRegistry,
1825
- useClass: MessageRegistry,
1826
- scope: Scope.Singleton
1827
- },
1828
- {
1829
- provide: I18N_TOKENS.MessageLoader,
1830
- useClass: MessageLoaderService,
1831
- scope: Scope.Singleton
1832
- },
1833
- {
1834
- provide: I18N_TOKENS.I18nService,
1835
- useClass: I18nService
1836
- }
1837
- ] })], I18nModule);
1838
- //#endregion
1839
- export { LocaleNotSupportedError as A, Get as C, getHttpDecoratedMethods as D, Put as E, getMessages as F, messages as I, OpenAPIConfigService as L, setupI18nCompiler as M, MessageRegistry as N, getHttpRouteMetadata as O, getLocales as P, LocaleExtractionMiddleware as R, Delete as S, Post as T, OpenAPIService as _, errorResponseSchema as a, getRouteConfig as b, successMessageSchema as c, RouteNotFoundError as d, OpenAPIValidationError as f, OpenAPIModule as g, HonoAppAlreadyConfiguredError as h, commonErrorSchemas as i, resolveI18nOptions as j, TranslationMissingError as k, uuidParamSchema as l, ControllerRegistrationError as m, HonoApp as n, paginatedResponseSchema as o, OpenAPIRouteRegistrationError as p, RouteRegistrationService as r, paginationQuerySchema as s, I18nModule as t, validationErrorResponseSchema as u, Route as v, Patch as w, All as x, getDecoratedMethods as y, I18nContextMiddleware as z };
1840
-
1841
- //# sourceMappingURL=i18n.module-Dn9SrFdS.mjs.map