stratal 0.0.21 → 0.0.23

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 (296) hide show
  1. package/README.md +2 -2
  2. package/dist/bin/cloudflare-workers-loader.mjs +80 -7
  3. package/dist/bin/cloudflare-workers-loader.mjs.map +1 -1
  4. package/dist/bin/quarry.mjs +84 -160
  5. package/dist/bin/quarry.mjs.map +1 -1
  6. package/dist/cache/index.d.mts +8 -46
  7. package/dist/cache/index.d.mts.map +1 -1
  8. package/dist/cache/index.mjs +134 -97
  9. package/dist/cache/index.mjs.map +1 -1
  10. package/dist/{cache.service-DsnKuNyO.d.mts → cache.service-uElmBtdS.d.mts} +29 -39
  11. package/dist/cache.service-uElmBtdS.d.mts.map +1 -0
  12. package/dist/{command-BgSlsS4M.mjs → command-BvmUAPPQ.mjs} +15 -4
  13. package/dist/command-BvmUAPPQ.mjs.map +1 -0
  14. package/dist/{command-Cmmf0oHX.d.mts → command-CPhFHjG3.d.mts} +3 -2
  15. package/dist/command-CPhFHjG3.d.mts.map +1 -0
  16. package/dist/command-not-found.error-ONAZ2Bpk.mjs +14 -0
  17. package/dist/command-not-found.error-ONAZ2Bpk.mjs.map +1 -0
  18. package/dist/config/index.d.mts +24 -11
  19. package/dist/config/index.d.mts.map +1 -1
  20. package/dist/config/index.mjs +32 -57
  21. package/dist/config/index.mjs.map +1 -1
  22. package/dist/{consumer-registry-B7yUNh0q.d.mts → consumer-registry-D3iMTSdy.d.mts} +54 -22
  23. package/dist/consumer-registry-D3iMTSdy.d.mts.map +1 -0
  24. package/dist/container-storage-BmOJ4_Na.mjs +52 -0
  25. package/dist/container-storage-BmOJ4_Na.mjs.map +1 -0
  26. package/dist/{controller.decorator-B9vwn0zK.mjs → controller.decorator-C5UVeJS3.mjs} +8 -8
  27. package/dist/controller.decorator-C5UVeJS3.mjs.map +1 -0
  28. package/dist/cron/index.d.mts +103 -7
  29. package/dist/cron/index.d.mts.map +1 -1
  30. package/dist/cron/index.mjs +2 -2
  31. package/dist/cron-job-NesZRk8F.d.mts +58 -0
  32. package/dist/cron-job-NesZRk8F.d.mts.map +1 -0
  33. package/dist/{cron-manager-DQSK8uoV.mjs → cron.module-Bgzq5hiT.mjs} +47 -17
  34. package/dist/cron.module-Bgzq5hiT.mjs.map +1 -0
  35. package/dist/decorate-CuAoSZvs.mjs +16 -0
  36. package/dist/deep-merge-ByiAOZ3r.mjs +18 -0
  37. package/dist/deep-merge-ByiAOZ3r.mjs.map +1 -0
  38. package/dist/di/index.d.mts +2 -2
  39. package/dist/di/index.mjs +4 -3
  40. package/dist/di-DseMn-z9.mjs +524 -0
  41. package/dist/di-DseMn-z9.mjs.map +1 -0
  42. package/dist/email/index.d.mts +40 -122
  43. package/dist/email/index.d.mts.map +1 -1
  44. package/dist/email/index.mjs +446 -131
  45. package/dist/email/index.mjs.map +1 -1
  46. package/dist/en-CDZBMcc1.mjs +202 -0
  47. package/dist/en-CDZBMcc1.mjs.map +1 -0
  48. package/dist/{env-D1rcZ8_r.d.mts → env-ug22bJj7.d.mts} +1 -1
  49. package/dist/env-ug22bJj7.d.mts.map +1 -0
  50. package/dist/errors/index.d.mts +2 -2
  51. package/dist/errors/index.mjs +4 -2
  52. package/dist/errors-mXYxG0XB.mjs +333 -0
  53. package/dist/errors-mXYxG0XB.mjs.map +1 -0
  54. package/dist/events/index.d.mts +14 -3
  55. package/dist/events/index.d.mts.map +1 -1
  56. package/dist/events/index.mjs +2 -2
  57. package/dist/{events-CzCV8jI8.mjs → events-BXJGZjpG.mjs} +23 -13
  58. package/dist/events-BXJGZjpG.mjs.map +1 -0
  59. package/dist/exception-context-kEoMFwze.mjs +429 -0
  60. package/dist/exception-context-kEoMFwze.mjs.map +1 -0
  61. package/dist/{gateway-context-CXmXtaUP.mjs → gateway-context-TMu_AlJt.mjs} +38 -31
  62. package/dist/gateway-context-TMu_AlJt.mjs.map +1 -0
  63. package/dist/guards/index.d.mts +3 -3
  64. package/dist/guards/index.d.mts.map +1 -1
  65. package/dist/guards/index.mjs +1 -1
  66. package/dist/{guards-DU1_J9YA.mjs → guards-DALPXy3_.mjs} +6 -5
  67. package/dist/guards-DALPXy3_.mjs.map +1 -0
  68. package/dist/hono-app-CvV3hOfT.mjs +161 -0
  69. package/dist/hono-app-CvV3hOfT.mjs.map +1 -0
  70. package/dist/{http-method.decorator-BrgHMdLQ.mjs → http-method.decorator-ByWZb9DO.mjs} +7 -6
  71. package/dist/http-method.decorator-ByWZb9DO.mjs.map +1 -0
  72. package/dist/i18n/index.d.mts +238 -3
  73. package/dist/i18n/index.d.mts.map +1 -0
  74. package/dist/i18n/index.mjs +39 -3
  75. package/dist/i18n/index.mjs.map +1 -0
  76. package/dist/i18n/messages/en/index.d.mts +2 -2
  77. package/dist/i18n/messages/en/index.mjs +2 -2
  78. package/dist/i18n/utils/index.d.mts +4 -26
  79. package/dist/i18n/utils/index.d.mts.map +1 -1
  80. package/dist/i18n/utils/index.mjs +2 -2
  81. package/dist/i18n/validation/index.d.mts +3 -2
  82. package/dist/i18n/validation/index.mjs +4 -2
  83. package/dist/i18n.module-DRQAZoSZ.mjs +222 -0
  84. package/dist/i18n.module-DRQAZoSZ.mjs.map +1 -0
  85. package/dist/i18n.tokens-CZ_v8oyS.mjs +19 -0
  86. package/dist/i18n.tokens-CZ_v8oyS.mjs.map +1 -0
  87. package/dist/{index-7-hU3GTV.d.mts → index-0ItCjaqw.d.mts} +1 -1
  88. package/dist/index-0ItCjaqw.d.mts.map +1 -0
  89. package/dist/index-B5JBRcWD.d.mts +544 -0
  90. package/dist/index-B5JBRcWD.d.mts.map +1 -0
  91. package/dist/index-BUt92sAE.d.mts +124 -0
  92. package/dist/index-BUt92sAE.d.mts.map +1 -0
  93. package/dist/{index-ByOyTmqf.d.mts → index-B_JoEl3V.d.mts} +751 -2229
  94. package/dist/index-B_JoEl3V.d.mts.map +1 -0
  95. package/dist/index-DtBNIFuP.d.mts +42 -0
  96. package/dist/index-DtBNIFuP.d.mts.map +1 -0
  97. package/dist/{index-C1KvMncZ.d.mts → index-HgOLNruQ.d.mts} +3 -108
  98. package/dist/index-HgOLNruQ.d.mts.map +1 -0
  99. package/dist/index.d.mts +6 -43
  100. package/dist/index.mjs +3 -2
  101. package/dist/{is-command-C6a7WTPw.mjs → is-command-CEPO9n8c.mjs} +2 -2
  102. package/dist/{is-command-C6a7WTPw.mjs.map → is-command-CEPO9n8c.mjs.map} +1 -1
  103. package/dist/{is-seeder-CebjZCDn.mjs → is-seeder-Gvh_AM71.mjs} +1 -1
  104. package/dist/{is-seeder-CebjZCDn.mjs.map → is-seeder-Gvh_AM71.mjs.map} +1 -1
  105. package/dist/lazy-module-loader-Ib383jH_.d.mts +60 -0
  106. package/dist/lazy-module-loader-Ib383jH_.d.mts.map +1 -0
  107. package/dist/locale-path.service-D-dHiIPc.mjs +165 -0
  108. package/dist/locale-path.service-D-dHiIPc.mjs.map +1 -0
  109. package/dist/locale-url-nZrZxqJP.mjs +44 -0
  110. package/dist/locale-url-nZrZxqJP.mjs.map +1 -0
  111. package/dist/locale-url.service-C2EWmGdq.mjs +41 -0
  112. package/dist/locale-url.service-C2EWmGdq.mjs.map +1 -0
  113. package/dist/logger/index.d.mts +2 -2
  114. package/dist/logger/index.mjs +170 -2
  115. package/dist/logger/index.mjs.map +1 -0
  116. package/dist/macroable/index.d.mts +2 -2
  117. package/dist/macroable/index.mjs +1 -1
  118. package/dist/{macroable-BmufBshB.mjs → macroable-cvDTFZ_A.mjs} +1 -1
  119. package/dist/{macroable-BmufBshB.mjs.map → macroable-cvDTFZ_A.mjs.map} +1 -1
  120. package/dist/metadata-DzzprcID.mjs +39 -0
  121. package/dist/metadata-DzzprcID.mjs.map +1 -0
  122. package/dist/module/index.d.mts +7 -24
  123. package/dist/module/index.d.mts.map +1 -1
  124. package/dist/module/index.mjs +10 -2
  125. package/dist/module/index.mjs.map +1 -0
  126. package/dist/module-registry-Dm-pqHd3.mjs +554 -0
  127. package/dist/module-registry-Dm-pqHd3.mjs.map +1 -0
  128. package/dist/module.decorator-CYHY6pG5.mjs +19 -0
  129. package/dist/module.decorator-CYHY6pG5.mjs.map +1 -0
  130. package/dist/openapi/index.d.mts +44 -8
  131. package/dist/openapi/index.d.mts.map +1 -1
  132. package/dist/openapi/index.mjs +3 -3
  133. package/dist/openapi-CstuTM8S.mjs +309 -0
  134. package/dist/openapi-CstuTM8S.mjs.map +1 -0
  135. package/dist/{openapi-tools.service-Zs-Ewv7F.mjs → openapi-tools.service-BC5EC3R3.mjs} +8 -2
  136. package/dist/openapi-tools.service-BC5EC3R3.mjs.map +1 -0
  137. package/dist/{openapi.service-Bt9bCIrd.d.mts → openapi.service-YhTiJ1bO.d.mts} +3 -3
  138. package/dist/openapi.service-YhTiJ1bO.d.mts.map +1 -0
  139. package/dist/quarry/index.d.mts +14 -163
  140. package/dist/quarry/index.d.mts.map +1 -1
  141. package/dist/quarry/index.mjs +6 -5
  142. package/dist/quarry/runner.d.mts +184 -0
  143. package/dist/quarry/runner.d.mts.map +1 -0
  144. package/dist/quarry/runner.mjs +945 -0
  145. package/dist/quarry/runner.mjs.map +1 -0
  146. package/dist/quarry-registry-CXg0RFXq.d.mts +69 -0
  147. package/dist/quarry-registry-CXg0RFXq.d.mts.map +1 -0
  148. package/dist/quarry.module-BuRPGMDm.mjs +312 -0
  149. package/dist/quarry.module-BuRPGMDm.mjs.map +1 -0
  150. package/dist/queue/index.d.mts +3 -3
  151. package/dist/queue/index.mjs +57 -48
  152. package/dist/queue/index.mjs.map +1 -1
  153. package/dist/queue.module-nddvxzCB.mjs +613 -0
  154. package/dist/queue.module-nddvxzCB.mjs.map +1 -0
  155. package/dist/queue.tokens-DjHnFmre.mjs +11 -0
  156. package/dist/queue.tokens-DjHnFmre.mjs.map +1 -0
  157. package/dist/{r2-storage.provider-DuonKeYm.mjs → r2-storage.provider-DCxQt9dD.mjs} +6 -6
  158. package/dist/r2-storage.provider-DCxQt9dD.mjs.map +1 -0
  159. package/dist/{rate-limit.decorator-6qzNcSOt.mjs → rate-limit.decorator-BPAie_p3.mjs} +6 -11
  160. package/dist/rate-limit.decorator-BPAie_p3.mjs.map +1 -0
  161. package/dist/rate-limiter/index.d.mts +11 -50
  162. package/dist/rate-limiter/index.d.mts.map +1 -1
  163. package/dist/rate-limiter/index.mjs +33 -42
  164. package/dist/rate-limiter/index.mjs.map +1 -1
  165. package/dist/route-name-DGoBOfPg.mjs +171 -0
  166. package/dist/route-name-DGoBOfPg.mjs.map +1 -0
  167. package/dist/route-registration.service-D6vSwiKP.mjs +918 -0
  168. package/dist/route-registration.service-D6vSwiKP.mjs.map +1 -0
  169. package/dist/route-registry-CYqLp2Nj.mjs +123 -0
  170. package/dist/route-registry-CYqLp2Nj.mjs.map +1 -0
  171. package/dist/router/index.d.mts +2 -2
  172. package/dist/router/index.mjs +18 -7
  173. package/dist/router-CWGBD-Bg.mjs +78 -0
  174. package/dist/router-CWGBD-Bg.mjs.map +1 -0
  175. package/dist/router-resolver-D4YlPNlm.mjs +88 -0
  176. package/dist/router-resolver-D4YlPNlm.mjs.map +1 -0
  177. package/dist/seeder/index.d.mts +16 -11
  178. package/dist/seeder/index.d.mts.map +1 -1
  179. package/dist/seeder/index.mjs +5 -3
  180. package/dist/seeder-7ubkms-Y.mjs +81 -0
  181. package/dist/seeder-7ubkms-Y.mjs.map +1 -0
  182. package/dist/seeder-registry-CyUmKsJq.mjs +57 -0
  183. package/dist/seeder-registry-CyUmKsJq.mjs.map +1 -0
  184. package/dist/seeder.module-CYYwk3Qk.mjs +15 -0
  185. package/dist/seeder.module-CYYwk3Qk.mjs.map +1 -0
  186. package/dist/{signed-url-BQPbv2In.mjs → signed-url-DIU0sK_6.mjs} +1 -1
  187. package/dist/{signed-url-BQPbv2In.mjs.map → signed-url-DIU0sK_6.mjs.map} +1 -1
  188. package/dist/storage/index.d.mts +15 -39
  189. package/dist/storage/index.d.mts.map +1 -1
  190. package/dist/storage/index.mjs +3 -3
  191. package/dist/storage/providers/index.d.mts +2 -2
  192. package/dist/storage/providers/index.d.mts.map +1 -1
  193. package/dist/storage/providers/index.mjs +1 -1
  194. package/dist/{storage-D8CBP72Z.mjs → storage-MDZypIE9.mjs} +66 -59
  195. package/dist/storage-MDZypIE9.mjs.map +1 -0
  196. package/dist/{storage-provider.interface-Bd6vA4ak.d.mts → storage-provider.interface-ClUwxz4S.d.mts} +2 -3
  197. package/dist/storage-provider.interface-ClUwxz4S.d.mts.map +1 -0
  198. package/dist/storage.error-Dnib4VHc.mjs +8 -0
  199. package/dist/storage.error-Dnib4VHc.mjs.map +1 -0
  200. package/dist/stratal-DL9M38_s.mjs +383 -0
  201. package/dist/stratal-DL9M38_s.mjs.map +1 -0
  202. package/dist/stratal-DwDJPY9N.d.mts +43 -0
  203. package/dist/stratal-DwDJPY9N.d.mts.map +1 -0
  204. package/dist/tiered-cache.service-Dv3BhxxE.d.mts +79 -0
  205. package/dist/tiered-cache.service-Dv3BhxxE.d.mts.map +1 -0
  206. package/dist/trailing-slash-CFyw8nYu.mjs +34 -0
  207. package/dist/trailing-slash-CFyw8nYu.mjs.map +1 -0
  208. package/dist/{types-cySNS_lp.d.mts → types-CmV_9xBD.d.mts} +1 -1
  209. package/dist/types-CmV_9xBD.d.mts.map +1 -0
  210. package/dist/uri-h7Q8Jug9.mjs +251 -0
  211. package/dist/uri-h7Q8Jug9.mjs.map +1 -0
  212. package/dist/{usage-generator-BUdlhnCK.mjs → usage-generator-DAWYasuP.mjs} +7 -4
  213. package/dist/usage-generator-DAWYasuP.mjs.map +1 -0
  214. package/dist/validation-CpOjviyT.mjs +49 -0
  215. package/dist/validation-CpOjviyT.mjs.map +1 -0
  216. package/dist/validation.context-CRvmrhq7.mjs +117 -0
  217. package/dist/validation.context-CRvmrhq7.mjs.map +1 -0
  218. package/dist/versioning.service-C6aHky8-.mjs +36 -0
  219. package/dist/versioning.service-C6aHky8-.mjs.map +1 -0
  220. package/dist/websocket/index.d.mts +16 -14
  221. package/dist/websocket/index.d.mts.map +1 -1
  222. package/dist/websocket/index.mjs +2 -2
  223. package/dist/workers/index.d.mts +2 -2
  224. package/dist/workers/index.d.mts.map +1 -1
  225. package/dist/workers/index.mjs +3 -2
  226. package/dist/workers/index.mjs.map +1 -1
  227. package/dist/zod-eKqqhZ5_.mjs +72 -0
  228. package/dist/zod-eKqqhZ5_.mjs.map +1 -0
  229. package/dist/{index-Bnpfq6uk.d.mts → zod-wecrEVAs.d.mts} +63 -133
  230. package/dist/zod-wecrEVAs.d.mts.map +1 -0
  231. package/package.json +28 -39
  232. package/dist/base-email.provider-CfQCA08m.mjs +0 -42
  233. package/dist/base-email.provider-CfQCA08m.mjs.map +0 -1
  234. package/dist/cache.service-DsnKuNyO.d.mts.map +0 -1
  235. package/dist/cache.tokens-B7Rw1C9Q.mjs +0 -6
  236. package/dist/cache.tokens-B7Rw1C9Q.mjs.map +0 -1
  237. package/dist/colors-DJaRDXoS.mjs +0 -16
  238. package/dist/colors-DJaRDXoS.mjs.map +0 -1
  239. package/dist/command-BgSlsS4M.mjs.map +0 -1
  240. package/dist/command-Cmmf0oHX.d.mts.map +0 -1
  241. package/dist/consumer-registry-B7yUNh0q.d.mts.map +0 -1
  242. package/dist/controller.decorator-B9vwn0zK.mjs.map +0 -1
  243. package/dist/cron-manager-CmTimEjf.d.mts +0 -131
  244. package/dist/cron-manager-CmTimEjf.d.mts.map +0 -1
  245. package/dist/cron-manager-DQSK8uoV.mjs.map +0 -1
  246. package/dist/en-DSH_bhh6.mjs +0 -308
  247. package/dist/en-DSH_bhh6.mjs.map +0 -1
  248. package/dist/env-D1rcZ8_r.d.mts.map +0 -1
  249. package/dist/errors-COW9-Mar.mjs +0 -1739
  250. package/dist/errors-COW9-Mar.mjs.map +0 -1
  251. package/dist/errors-ORxu1-Bb.mjs +0 -74
  252. package/dist/errors-ORxu1-Bb.mjs.map +0 -1
  253. package/dist/events-CzCV8jI8.mjs.map +0 -1
  254. package/dist/gateway-context-CXmXtaUP.mjs.map +0 -1
  255. package/dist/guards-DU1_J9YA.mjs.map +0 -1
  256. package/dist/http-method.decorator-BrgHMdLQ.mjs.map +0 -1
  257. package/dist/i18n.module-CzXLW9Hy.mjs +0 -2532
  258. package/dist/i18n.module-CzXLW9Hy.mjs.map +0 -1
  259. package/dist/index-7-hU3GTV.d.mts.map +0 -1
  260. package/dist/index-Bnpfq6uk.d.mts.map +0 -1
  261. package/dist/index-ByOyTmqf.d.mts.map +0 -1
  262. package/dist/index-C1KvMncZ.d.mts.map +0 -1
  263. package/dist/index-DBd_2wv8.d.mts +0 -263
  264. package/dist/index-DBd_2wv8.d.mts.map +0 -1
  265. package/dist/index-DUzWs0z7.d.mts +0 -494
  266. package/dist/index-DUzWs0z7.d.mts.map +0 -1
  267. package/dist/index.d.mts.map +0 -1
  268. package/dist/logger-DlV7NtvD.mjs +0 -440
  269. package/dist/logger-DlV7NtvD.mjs.map +0 -1
  270. package/dist/module-BzLg57FK.mjs +0 -866
  271. package/dist/module-BzLg57FK.mjs.map +0 -1
  272. package/dist/openapi-tools.service-Zs-Ewv7F.mjs.map +0 -1
  273. package/dist/openapi.service-Bt9bCIrd.d.mts.map +0 -1
  274. package/dist/quarry-registry-BwY2hOxm.mjs +0 -699
  275. package/dist/quarry-registry-BwY2hOxm.mjs.map +0 -1
  276. package/dist/queue.module-BhCjZp6H.mjs +0 -409
  277. package/dist/queue.module-BhCjZp6H.mjs.map +0 -1
  278. package/dist/r2-storage.provider-DuonKeYm.mjs.map +0 -1
  279. package/dist/rate-limit.decorator-6qzNcSOt.mjs.map +0 -1
  280. package/dist/resend.provider-DB4IlFjG.mjs +0 -68
  281. package/dist/resend.provider-DB4IlFjG.mjs.map +0 -1
  282. package/dist/seeder-zoEfEw9i.mjs +0 -138
  283. package/dist/seeder-zoEfEw9i.mjs.map +0 -1
  284. package/dist/setup-CefZKV_e.mjs +0 -37
  285. package/dist/setup-CefZKV_e.mjs.map +0 -1
  286. package/dist/smtp.provider-B6D7zuWX.mjs +0 -76
  287. package/dist/smtp.provider-B6D7zuWX.mjs.map +0 -1
  288. package/dist/storage-D8CBP72Z.mjs.map +0 -1
  289. package/dist/storage-provider.interface-Bd6vA4ak.d.mts.map +0 -1
  290. package/dist/stratal-CNwpbSZl.mjs +0 -535
  291. package/dist/stratal-CNwpbSZl.mjs.map +0 -1
  292. package/dist/types-cySNS_lp.d.mts.map +0 -1
  293. package/dist/usage-generator-BUdlhnCK.mjs.map +0 -1
  294. package/dist/validation-DtJwAv7O.mjs +0 -248
  295. package/dist/validation-DtJwAv7O.mjs.map +0 -1
  296. /package/dist/{chunk-D1SwGrFN.mjs → chunk-BBjsoOtd.mjs} +0 -0
@@ -1,2532 +0,0 @@
1
- import { A as Scope, D as runWithContainer, E as getContainer, H as ApplicationError, V as ROUTER_TOKENS, a as createHttpExceptionContext, c as DEFAULT_CONTENT_TYPE, d as ROUTER_CONTEXT_KEYS, f as ROUTE_METADATA_KEYS, k as ERROR_CODES, l as HTTP_METHODS, m as VERSION_NEUTRAL, p as SECURITY_SCHEMES, s as RouterContext, u as METHOD_STATUS_CODES, w as I18N_TOKENS } from "./errors-COW9-Mar.mjs";
2
- import { a as __decorate, d as CONTAINER_TOKEN, f as DI_TOKENS, g as getMethodInjections, o as __decorateParam, p as Transient, s as __decorateMetadata, u as LOGGER_TOKENS } from "./logger-DlV7NtvD.mjs";
3
- import { S as createThrottleMiddleware, b as ControllerRegistrationError, c as InvalidSignatureError, d as MissingRouteParamError, f as ResponseValidationError, g as RouteNotFoundError, h as SchemaValidationError, k as Module, l as MiddlewareNextCalledMultipleTimesError, o as DomainMismatchError, p as RouteNameNotFoundError, s as DuplicateRouteNameError, u as MissingEnvironmentVariableError, v as OpenAPIRouteRegistrationError, x as ControllerMethodNotFoundError, y as HonoAppAlreadyConfiguredError } from "./module-BzLg57FK.mjs";
4
- import { c as backendErrorMap, i as z, l as runWithErrorMapContext, r as validation_exports, t as OpenAPIHono } from "./validation-DtJwAv7O.mjs";
5
- import { n as OPENAPI_TOKENS } from "./openapi-tools.service-Zs-Ewv7F.mjs";
6
- import { t as en_exports } from "./en-DSH_bhh6.mjs";
7
- import { i as getMethodGuards, r as getControllerGuards, t as GuardExecutionService } from "./guards-DU1_J9YA.mjs";
8
- import { n as getControllerOptions, r as getControllerRoute } from "./controller.decorator-B9vwn0zK.mjs";
9
- import { c as getWsOnMessageMethod, d as isGateway, o as getWsOnCloseMethod, s as getWsOnErrorMethod, t as GatewayContext } from "./gateway-context-CXmXtaUP.mjs";
10
- import "./http-method.decorator-BrgHMdLQ.mjs";
11
- import { n as getRateLimits } from "./rate-limit.decorator-6qzNcSOt.mjs";
12
- import { n as verifySignedUrl, t as signUrl } from "./signed-url-BQPbv2In.mjs";
13
- import { t as setupI18nCompiler } from "./setup-CefZKV_e.mjs";
14
- import { inject } from "tsyringe";
15
- import { createCoreContext, translate } from "@intlify/core-base";
16
- import { swaggerUI } from "@hono/swagger-ui";
17
- import { languageDetector } from "hono/language";
18
- //#region src/i18n/middleware/i18n-context.middleware.ts
19
- /**
20
- * I18n Context Middleware
21
- *
22
- * Sets up AsyncLocalStorage context for Zod i18n validation.
23
- * Must run after LocaleExtractionMiddleware sets the locale.
24
- */
25
- let I18nContextMiddleware = class I18nContextMiddleware {
26
- i18n;
27
- constructor(i18n) {
28
- this.i18n = i18n;
29
- }
30
- async handle(ctx, next) {
31
- await runWithErrorMapContext({
32
- t: (key, params) => this.i18n.t(key, params),
33
- locale: ctx.getLocale()
34
- }, () => next());
35
- }
36
- };
37
- I18nContextMiddleware = __decorate([
38
- Transient(),
39
- __decorateParam(0, inject(I18N_TOKENS.I18nService)),
40
- __decorateMetadata("design:paramtypes", [Object])
41
- ], I18nContextMiddleware);
42
- //#endregion
43
- //#region src/openapi/services/openapi-config.service.ts
44
- let OpenAPIConfigService = class OpenAPIConfigService {
45
- baseOptions;
46
- overrides = [];
47
- constructor(baseOptions) {
48
- this.baseOptions = baseOptions;
49
- }
50
- /**
51
- * Add configuration override for this request.
52
- * Overrides are merged in the order they are added.
53
- */
54
- override(config) {
55
- this.overrides.push(config);
56
- }
57
- /** Get effective configuration (base merged with all overrides) */
58
- getEffectiveConfig() {
59
- let effective = {
60
- jsonPath: this.baseOptions?.jsonPath ?? "/api/openapi.json",
61
- ui: this.baseOptions?.ui,
62
- info: {
63
- title: this.baseOptions?.info?.title ?? "API",
64
- version: this.baseOptions?.info?.version ?? "1.0.0",
65
- description: this.baseOptions?.info?.description
66
- },
67
- securitySchemes: this.baseOptions?.securitySchemes
68
- };
69
- for (const override of this.overrides) effective = this.mergeConfig(effective, override);
70
- return effective;
71
- }
72
- /**
73
- * Merge override into effective config.
74
- * Info is shallow-merged, routeFilter is replaced.
75
- */
76
- mergeConfig(base, override) {
77
- return {
78
- ...base,
79
- info: {
80
- ...base.info,
81
- ...override.info && {
82
- title: override.info.title ?? base.info.title,
83
- version: override.info.version ?? base.info.version,
84
- description: override.info.description ?? base.info.description
85
- }
86
- },
87
- routeFilter: override.routeFilter ?? base.routeFilter
88
- };
89
- }
90
- };
91
- OpenAPIConfigService = __decorate([
92
- Transient(OPENAPI_TOKENS.ConfigService),
93
- __decorateParam(0, inject(OPENAPI_TOKENS.Options, { isOptional: true })),
94
- __decorateMetadata("design:paramtypes", [Object])
95
- ], OpenAPIConfigService);
96
- //#endregion
97
- //#region src/i18n/errors/locale-not-supported.error.ts
98
- /**
99
- * Locale Not Supported Error
100
- * Thrown when an unsupported locale is requested
101
- *
102
- * HTTP Status: 500 Internal Server Error
103
- * Error Code: 9301
104
- */
105
- var LocaleNotSupportedError = class extends ApplicationError {
106
- constructor(locale, supportedLocales) {
107
- super("errors.localeNotSupported", ERROR_CODES.I18N.LOCALE_NOT_SUPPORTED, {
108
- locale,
109
- supportedLocales: supportedLocales.join(", ")
110
- });
111
- }
112
- };
113
- //#endregion
114
- //#region src/i18n/errors/translation-missing.error.ts
115
- /**
116
- * Translation Missing Error
117
- * Thrown when a translation key is missing from all locales
118
- *
119
- * HTTP Status: 500 Internal Server Error
120
- * Error Code: 9300
121
- */
122
- var TranslationMissingError = class extends ApplicationError {
123
- constructor(key, locale) {
124
- super("errors.translationMissing", ERROR_CODES.I18N.TRANSLATION_MISSING, {
125
- key,
126
- locale
127
- });
128
- }
129
- };
130
- //#endregion
131
- //#region src/i18n/i18n.options.ts
132
- /**
133
- * Resolve I18n options with defaults
134
- */
135
- function resolveI18nOptions(options) {
136
- const detection = options?.detection;
137
- const enabled = detection ? detection.enabled !== false : true;
138
- const strategy = detection && "strategy" in detection ? detection.strategy ?? "cookie" : "cookie";
139
- const prefixDefaultLocale = detection && "prefixDefaultLocale" in detection && detection.prefixDefaultLocale !== void 0 ? detection.prefixDefaultLocale : false;
140
- return {
141
- defaultLocale: options?.defaultLocale ?? "en",
142
- fallbackLocale: options?.fallbackLocale ?? "en",
143
- locales: options?.locales ?? ["en"],
144
- detection: {
145
- enabled,
146
- strategy,
147
- prefixDefaultLocale
148
- }
149
- };
150
- }
151
- /**
152
- * Build Hono languageDetector options from I18n module options
153
- */
154
- function buildDetectorOptions(options) {
155
- const resolved = resolveI18nOptions(options);
156
- const strategy = resolved.detection.strategy;
157
- const detectorOptions = {
158
- order: [strategy],
159
- fallbackLanguage: resolved.defaultLocale,
160
- supportedLanguages: resolved.locales,
161
- lookupCookie: "locale",
162
- lookupQueryString: "locale",
163
- lookupFromPathIndex: 0,
164
- ignoreCase: true
165
- };
166
- if (strategy === "cookie") {
167
- detectorOptions.caches = ["cookie"];
168
- if (options?.detection && "cookieOptions" in options.detection && options.detection.cookieOptions) detectorOptions.cookieOptions = options.detection.cookieOptions;
169
- } else detectorOptions.caches = false;
170
- return detectorOptions;
171
- }
172
- //#endregion
173
- //#region src/i18n/messages/index.ts
174
- /**
175
- * Core Messages
176
- *
177
- * Messages used by packages/modules infrastructure.
178
- * These are automatically merged with application-specific messages.
179
- */
180
- /**
181
- * All locale messages
182
- * Explicitly import and export (no filesystem scanning - Cloudflare Workers compatible)
183
- */
184
- const messages = { en: en_exports };
185
- /**
186
- * Get messages for all locales
187
- */
188
- function getMessages() {
189
- return messages;
190
- }
191
- /**
192
- * Get available locales
193
- */
194
- function getLocales() {
195
- return Object.keys(messages);
196
- }
197
- //#endregion
198
- //#region src/i18n/utils/deep-merge.ts
199
- /**
200
- * Deep merge two objects. Source values override target at leaf level.
201
- */
202
- function deepMerge(target, source) {
203
- const result = { ...target };
204
- for (const key of Object.keys(source)) {
205
- const targetValue = target[key];
206
- const sourceValue = source[key];
207
- if (typeof targetValue === "object" && targetValue !== null && !Array.isArray(targetValue) && typeof sourceValue === "object" && sourceValue !== null && !Array.isArray(sourceValue)) result[key] = deepMerge(targetValue, sourceValue);
208
- else result[key] = sourceValue;
209
- }
210
- return result;
211
- }
212
- //#endregion
213
- //#region src/i18n/services/message-loader.service.ts
214
- let MessageLoaderService = class MessageLoaderService {
215
- registry;
216
- options;
217
- cache;
218
- contextCache;
219
- locales;
220
- defaultLocale;
221
- constructor(registry, options) {
222
- this.registry = registry;
223
- this.options = options;
224
- this.defaultLocale = this.options?.defaultLocale ?? "en";
225
- this.cache = /* @__PURE__ */ new Map();
226
- this.contextCache = /* @__PURE__ */ new Map();
227
- const coreMessages = getMessages();
228
- const coreLocales = getLocales();
229
- const registryMessages = this.registry.getMergedMessages();
230
- const registryLocales = Object.keys(registryMessages);
231
- const allLocales = [...new Set([...coreLocales, ...registryLocales])];
232
- this.locales = allLocales;
233
- for (const locale of allLocales) {
234
- const merged = deepMerge(coreMessages[locale] ?? {}, registryMessages[locale] ?? {});
235
- this.cache.set(locale, merged);
236
- }
237
- }
238
- /**
239
- * Get CoreContext for a locale (lazily built and cached on first access)
240
- * Falls back to default locale if locale not found
241
- */
242
- getCoreContext(locale) {
243
- const cached = this.contextCache.get(locale);
244
- if (cached) return cached;
245
- const effectiveLocale = this.cache.has(locale) ? locale : this.defaultLocale;
246
- const cachedEffective = this.contextCache.get(effectiveLocale);
247
- if (cachedEffective) return cachedEffective;
248
- const messages = this.cache.get(effectiveLocale) ?? {};
249
- const flattened = this.flattenMessages(messages);
250
- const ctx = createCoreContext({
251
- locale: effectiveLocale,
252
- messages: { [effectiveLocale]: flattened },
253
- missingWarn: false,
254
- fallbackWarn: false
255
- });
256
- this.contextCache.set(effectiveLocale, ctx);
257
- return ctx;
258
- }
259
- /**
260
- * Get messages for a specific locale.
261
- * Falls back to default locale if not found.
262
- */
263
- getMessages(locale) {
264
- return this.cache.get(locale) ?? this.cache.get(this.defaultLocale) ?? {};
265
- }
266
- /** Get list of available locale codes */
267
- getAvailableLocales() {
268
- return this.locales;
269
- }
270
- /** Check if a locale is supported */
271
- isLocaleSupported(locale) {
272
- return this.cache.has(locale);
273
- }
274
- /** Get default locale */
275
- getDefaultLocale() {
276
- return this.defaultLocale;
277
- }
278
- /**
279
- * Get flattened (dot-notation) messages for a locale, optionally filtered by namespace prefixes.
280
- *
281
- * Returns flat key-value pairs matching the format used by `@intlify/core-base`'s
282
- * `createCoreContext`. Requires `registerMessageCompiler(compile)` to be called
283
- * before `translate()` can resolve these flat keys.
284
- *
285
- * @param locale - Locale code (falls back to default locale if not found)
286
- * @param options - Optional filter configuration
287
- * @param options.only - Dot-notation prefixes to include (e.g., `['common', 'nav.sidebar']`)
288
- * @returns Flattened messages as `{ 'key.path': 'translated value' }`
289
- *
290
- * @example
291
- * ```typescript
292
- * // All messages for the locale
293
- * loader.getFilteredMessages('en')
294
- *
295
- * // Only 'common' and 'nav' namespaces
296
- * loader.getFilteredMessages('en', { only: ['common', 'nav'] })
297
- *
298
- * // Deeply nested prefix
299
- * loader.getFilteredMessages('en', { only: ['common.actions'] })
300
- * ```
301
- */
302
- getFilteredMessages(locale, options) {
303
- const messages = this.getMessages(locale);
304
- const flattened = this.flattenMessages(messages);
305
- if (!options?.only?.length) return flattened;
306
- const result = {};
307
- for (const [key, value] of Object.entries(flattened)) if (options.only.some((prefix) => key === prefix || key.startsWith(`${prefix}.`))) result[key] = value;
308
- return result;
309
- }
310
- /**
311
- * Flatten nested messages to dot-notation.
312
- * e.g. `{ a: { b: 'hello' } }` → `{ 'a.b': 'hello' }`
313
- */
314
- flattenMessages(messages, prefix = "") {
315
- const result = {};
316
- for (const key of Object.keys(messages)) {
317
- const value = messages[key];
318
- const newKey = prefix ? `${prefix}.${key}` : key;
319
- if (typeof value === "object" && value !== null && !Array.isArray(value)) Object.assign(result, this.flattenMessages(value, newKey));
320
- else result[newKey] = String(value);
321
- }
322
- return result;
323
- }
324
- };
325
- MessageLoaderService = __decorate([
326
- Transient(I18N_TOKENS.MessageLoader),
327
- __decorateParam(0, inject(I18N_TOKENS.MessageRegistry)),
328
- __decorateParam(1, inject(I18N_TOKENS.Options, { isOptional: true })),
329
- __decorateMetadata("design:paramtypes", [Object, Object])
330
- ], MessageLoaderService);
331
- //#endregion
332
- //#region src/i18n/services/message-registry.ts
333
- /**
334
- * Global key for the shared contributions array.
335
- *
336
- * When stratal is installed via portal/symlink (e.g., in monorepos), bundlers
337
- * like esbuild may inline multiple copies of this module. Each copy gets its
338
- * own static class fields, so messages registered by one copy are invisible
339
- * to another. Using a `Symbol.for()` key on `globalThis` ensures all copies
340
- * share the same contributions array.
341
- */
342
- const CONTRIBUTIONS_KEY = Symbol.for("stratal:i18n:message-registry:contributions");
343
- function getContributions() {
344
- const g = globalThis;
345
- g[CONTRIBUTIONS_KEY] ??= [];
346
- return g[CONTRIBUTIONS_KEY];
347
- }
348
- let MessageRegistry = class MessageRegistry {
349
- /**
350
- * Add messages (called statically by I18nModule.registerMessages)
351
- */
352
- static addMessages(messages) {
353
- if (Boolean(messages) && typeof messages === "object" && Object.keys(messages).length > 0) getContributions().push(messages);
354
- }
355
- /**
356
- * Get all messages deep-merged in registration order
357
- */
358
- getMergedMessages() {
359
- const merged = {};
360
- for (const contribution of getContributions()) for (const locale of Object.keys(contribution)) merged[locale] = deepMerge(merged[locale] ?? {}, contribution[locale]);
361
- return merged;
362
- }
363
- /**
364
- * Reset registry (for testing)
365
- * @internal
366
- */
367
- static reset() {
368
- globalThis[CONTRIBUTIONS_KEY] = [];
369
- }
370
- };
371
- MessageRegistry = __decorate([Transient(I18N_TOKENS.MessageRegistry)], MessageRegistry);
372
- //#endregion
373
- //#region src/openapi/services/openapi.service.ts
374
- let OpenAPIService = class OpenAPIService {
375
- /**
376
- * Generate a filtered OpenAPI spec using the user's config.
377
- * Usable from both HTTP handlers and CLI commands.
378
- */
379
- getSpec(app, container) {
380
- const configService = container.resolve(OPENAPI_TOKENS.ConfigService);
381
- const i18n = container.resolve(I18N_TOKENS.I18nService);
382
- const config = configService.getEffectiveConfig();
383
- const fullSpec = app.getOpenAPIDocument({
384
- openapi: "3.0.0",
385
- info: {
386
- version: config.info.version,
387
- title: config.info.title,
388
- description: config.info.description
389
- }
390
- });
391
- fullSpec.components ??= {};
392
- fullSpec.components.securitySchemes = this.getSecuritySchemeDefinitions(i18n);
393
- if (config.routeFilter) fullSpec.paths = this.filterRoutes(fullSpec.paths, config);
394
- if (fullSpec.components.schemas) fullSpec.components.schemas = this.filterSchemas(fullSpec);
395
- return fullSpec;
396
- }
397
- /**
398
- * Setup OpenAPI documentation endpoints
399
- */
400
- setupEndpoints(app, container) {
401
- const config = container.resolve(OPENAPI_TOKENS.ConfigService).getEffectiveConfig();
402
- app.get(config.jsonPath, (c) => {
403
- const requestContainer = c.get(ROUTER_CONTEXT_KEYS.REQUEST_CONTAINER);
404
- const fullSpec = this.getSpec(app, requestContainer);
405
- const url = new URL(c.req.raw.url);
406
- const i18n = requestContainer.resolve(I18N_TOKENS.I18nService);
407
- fullSpec.servers = [{
408
- url: `${url.protocol}//${url.host}`,
409
- description: i18n.t("common.api.serverDescription")
410
- }];
411
- return c.json(fullSpec);
412
- });
413
- this.nameLastHandler(app, "OpenAPI", "spec");
414
- if (config.ui !== false) {
415
- const uiPath = config.ui?.path ?? "/api/docs";
416
- const uiRenderer = config.ui?.renderer;
417
- app.get(uiPath, (c, next) => {
418
- const effectiveConfig = c.get(ROUTER_CONTEXT_KEYS.REQUEST_CONTAINER).resolve(OPENAPI_TOKENS.ConfigService).getEffectiveConfig();
419
- const uiContext = {
420
- specUrl: effectiveConfig.jsonPath,
421
- title: effectiveConfig.info.title
422
- };
423
- if (uiRenderer) return uiRenderer(uiContext)(c, next);
424
- return swaggerUI({ url: uiContext.specUrl })(c, next);
425
- });
426
- this.nameLastHandler(app, "OpenAPI", "docs");
427
- }
428
- }
429
- nameLastHandler(app, controller, method) {
430
- const last = app.routes[app.routes.length - 1];
431
- Object.defineProperty(last.handler, "name", { value: `http:${controller}.${method}` });
432
- }
433
- /**
434
- * Get localized security scheme definitions
435
- */
436
- getSecuritySchemeDefinitions(i18n) {
437
- return {
438
- [SECURITY_SCHEMES.BEARER_AUTH]: {
439
- type: "http",
440
- scheme: "bearer",
441
- bearerFormat: "JWT",
442
- description: i18n.t("common.api.security.bearerAuth")
443
- },
444
- [SECURITY_SCHEMES.API_KEY]: {
445
- type: "apiKey",
446
- in: "header",
447
- name: "X-API-Key",
448
- description: i18n.t("common.api.security.apiKey")
449
- },
450
- [SECURITY_SCHEMES.SESSION_COOKIE]: {
451
- type: "apiKey",
452
- in: "cookie",
453
- name: "session",
454
- description: i18n.t("common.api.security.sessionCookie")
455
- }
456
- };
457
- }
458
- /**
459
- * Filter OpenAPI paths using custom routeFilter
460
- */
461
- filterRoutes(paths, config) {
462
- const filteredPaths = {};
463
- for (const [path, pathItem] of Object.entries(paths)) {
464
- if (config.routeFilter && !config.routeFilter(path, pathItem)) continue;
465
- filteredPaths[path] = pathItem;
466
- }
467
- return filteredPaths;
468
- }
469
- /**
470
- * Filter unreferenced schemas from OpenAPI spec
471
- */
472
- filterSchemas(spec) {
473
- const referencedSchemas = /* @__PURE__ */ new Set();
474
- this.collectSchemaRefs(spec.paths, referencedSchemas);
475
- const filteredSchemas = {};
476
- const components = spec.components;
477
- if (components?.schemas) {
478
- const allSchemas = components.schemas;
479
- let prevSize = 0;
480
- while (referencedSchemas.size > prevSize) {
481
- prevSize = referencedSchemas.size;
482
- for (const [schemaName, schemaValue] of Object.entries(allSchemas)) if (referencedSchemas.has(schemaName) && !filteredSchemas[schemaName]) {
483
- filteredSchemas[schemaName] = schemaValue;
484
- this.collectSchemaRefs(schemaValue, referencedSchemas);
485
- }
486
- }
487
- }
488
- return filteredSchemas;
489
- }
490
- /**
491
- * Recursively collect all schema references from an object
492
- */
493
- collectSchemaRefs(obj, refs) {
494
- if (!obj || typeof obj !== "object") return;
495
- const record = obj;
496
- if (record.$ref && typeof record.$ref === "string") {
497
- const match = /^#\/components\/schemas\/(.+)$/.exec(record.$ref);
498
- if (match) refs.add(match[1]);
499
- }
500
- if (Array.isArray(obj)) for (const item of obj) this.collectSchemaRefs(item, refs);
501
- else for (const value of Object.values(record)) this.collectSchemaRefs(value, refs);
502
- }
503
- };
504
- OpenAPIService = __decorate([Transient(OPENAPI_TOKENS.OpenAPIService)], OpenAPIService);
505
- //#endregion
506
- //#region src/openapi/openapi.module.ts
507
- /**
508
- * OpenAPI Module
509
- *
510
- * Provides configurable OpenAPI documentation endpoints with runtime override support.
511
- *
512
- * Features:
513
- * - Configurable paths for /openapi.json and /docs
514
- * - Runtime config overrides via middleware
515
- * - i18n support for titles and descriptions
516
- * - Route filtering via hideFromDocs and custom routeFilter
517
- *
518
- * @example Basic usage
519
- * ```typescript
520
- * @Module({
521
- * imports: [
522
- * OpenAPIModule.forRoot({
523
- * info: { title: 'My API', version: '1.0.0' }
524
- * })
525
- * ]
526
- * })
527
- * export class AppModule {}
528
- * ```
529
- *
530
- * @example With runtime override in middleware
531
- * ```typescript
532
- * // In RouteAccessMiddleware
533
- * constructor(
534
- * @inject(OPENAPI_TOKENS.ConfigService) private openAPIConfig: IOpenAPIConfigService
535
- * ) {}
536
- *
537
- * async handle(ctx, next) {
538
- * this.openAPIConfig.override({
539
- * info: { title: 'Custom API' },
540
- * routeFilter: (path) => this.shouldInclude(path)
541
- * })
542
- * await next()
543
- * }
544
- * ```
545
- */
546
- var _OpenAPIModule;
547
- /** Default options when none provided */
548
- const DEFAULT_OPTIONS = {
549
- jsonPath: "/api/openapi.json",
550
- info: {
551
- title: "API",
552
- version: "1.0.0"
553
- }
554
- };
555
- let OpenAPIModule = _OpenAPIModule = class OpenAPIModule {
556
- /**
557
- * Configure OpenAPI module with static options
558
- *
559
- * @param options - OpenAPI configuration (paths, info, security schemes)
560
- * @returns DynamicModule with options provider
561
- */
562
- static forRoot(options = {}) {
563
- const mergedOptions = {
564
- ...DEFAULT_OPTIONS,
565
- ...options,
566
- info: {
567
- ...DEFAULT_OPTIONS.info,
568
- ...options.info,
569
- title: options.info?.title ?? DEFAULT_OPTIONS.info?.title ?? "API",
570
- version: options.info?.version ?? DEFAULT_OPTIONS.info?.version ?? "1.0.0"
571
- }
572
- };
573
- return {
574
- module: _OpenAPIModule,
575
- providers: [{
576
- provide: OPENAPI_TOKENS.Options,
577
- useValue: mergedOptions
578
- }]
579
- };
580
- }
581
- static forRootAsync(options) {
582
- return {
583
- module: _OpenAPIModule,
584
- providers: [{
585
- provide: OPENAPI_TOKENS.Options,
586
- useFactory: options.useFactory,
587
- inject: options.inject
588
- }]
589
- };
590
- }
591
- };
592
- OpenAPIModule = _OpenAPIModule = __decorate([Module({ providers: [{
593
- provide: OPENAPI_TOKENS.ConfigService,
594
- useClass: OpenAPIConfigService,
595
- scope: Scope.Request
596
- }, {
597
- provide: OPENAPI_TOKENS.OpenAPIService,
598
- useClass: OpenAPIService,
599
- scope: Scope.Singleton
600
- }] })], OpenAPIModule);
601
- //#endregion
602
- //#region src/router/middleware/logger.middleware.ts
603
- /**
604
- * Create a Hono middleware that logs HTTP requests using our Logger service
605
- *
606
- * Logs request method, path, status code, and duration in milliseconds.
607
- * Format: [HTTP] METHOD /path -> STATUS (duration ms)
608
- *
609
- * @param logger - Logger service instance
610
- * @returns Hono middleware handler
611
- *
612
- * @example
613
- * ```typescript
614
- * const logger = container.resolve<LoggerService>(LOGGER_TOKENS.LoggerService)
615
- * app.use('*', createLoggerMiddleware(logger))
616
- * ```
617
- */
618
- function createLoggerMiddleware(logger) {
619
- return async (c, next) => {
620
- const start = Date.now();
621
- const method = c.req.method;
622
- const path = c.req.path;
623
- await next();
624
- const duration = Date.now() - start;
625
- const status = c.res.status;
626
- logger.info(`[HTTP] ${method} ${path} -> ${status}`, {
627
- method,
628
- path,
629
- status,
630
- duration
631
- });
632
- };
633
- }
634
- //#endregion
635
- //#region src/router/middleware/domain.middleware.ts
636
- /**
637
- * Parse a domain pattern into a regex and extract parameter names.
638
- *
639
- * @example
640
- * parseDomainPattern('{tenant}.example.com')
641
- * // => { regex: /^([^.]+)\.example\.com$/, paramNames: ['tenant'] }
642
- *
643
- * parseDomainPattern('{region}.{tenant}.example.com')
644
- * // => { regex: /^([^.]+)\.([^.]+)\.example\.com$/, paramNames: ['region', 'tenant'] }
645
- */
646
- function parseDomainPattern(pattern) {
647
- const paramNames = [];
648
- const escaped = pattern.replace(/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g, (_match, paramName) => {
649
- paramNames.push(paramName);
650
- return "([^.]+)";
651
- }).replace(/\./g, "\\.");
652
- return {
653
- regex: new RegExp(`^${escaped}$`),
654
- paramNames
655
- };
656
- }
657
- /**
658
- * Strip port number from a host header value.
659
- * 'example.com:8787' => 'example.com'
660
- */
661
- function stripPort(host) {
662
- const colonIdx = host.lastIndexOf(":");
663
- if (colonIdx === -1) return host;
664
- const afterColon = host.slice(colonIdx + 1);
665
- return /^\d+$/.test(afterColon) ? host.slice(0, colonIdx) : host;
666
- }
667
- /**
668
- * Create a Hono middleware that matches the request host against a domain pattern.
669
- *
670
- * When the host matches, domain parameters are extracted and stored in context
671
- * variables accessible via `ctx.domain(key)`.
672
- *
673
- * When the host does NOT match, throws `DomainMismatchError` (404).
674
- *
675
- * @param pattern - Domain pattern with `{param}` placeholders (e.g., '{tenant}.myapp.com')
676
- *
677
- * @example
678
- * ```typescript
679
- * // Applied automatically by RouteRegistrationService for controllers with domain config
680
- * @Controller('/dashboard', { domain: '{tenant}.myapp.com' })
681
- * export class DashboardController {
682
- * async index(ctx: RouterContext) {
683
- * const tenant = ctx.domain('tenant')
684
- * }
685
- * }
686
- * ```
687
- */
688
- function createDomainMiddleware(pattern) {
689
- const { regex, paramNames } = parseDomainPattern(pattern);
690
- return async (c, next) => {
691
- const host = stripPort(c.req.header("host") ?? "");
692
- const match = regex.exec(host);
693
- if (!match) throw new DomainMismatchError();
694
- for (let i = 0; i < paramNames.length; i++) c.set(`domain:${paramNames[i]}`, match[i + 1]);
695
- await next();
696
- };
697
- }
698
- //#endregion
699
- //#region src/router/middleware/middleware-chain.ts
700
- /**
701
- * Create a Hono middleware handler that executes a chain of Stratal middleware classes.
702
- *
703
- * Each middleware is resolved from the request-scoped container per request,
704
- * then executed in order (first registered = outermost in the chain).
705
- *
706
- * @param classes - Middleware classes to chain
707
- * @returns Hono middleware handler
708
- */
709
- function createMiddlewareChain(classes) {
710
- return async (c, next) => {
711
- const requestContainer = c.get(ROUTER_CONTEXT_KEYS.REQUEST_CONTAINER);
712
- const ctx = new RouterContext(c);
713
- let current = next;
714
- for (let i = classes.length - 1; i >= 0; i--) {
715
- const prevNext = current;
716
- const middlewareClass = classes[i];
717
- current = () => {
718
- const middleware = requestContainer.resolve(middlewareClass);
719
- let called = false;
720
- const guardedNext = () => {
721
- if (called) {
722
- const err = new MiddlewareNextCalledMultipleTimesError(middlewareClass.name ?? "anonymous");
723
- console.error("[STRATAL DEBUG] next() called multiple times for " + middlewareClass.name);
724
- console.error("[STRATAL DEBUG] Stack trace:", (/* @__PURE__ */ new Error()).stack);
725
- return Promise.reject(err);
726
- }
727
- called = true;
728
- return prevNext();
729
- };
730
- return middleware.handle(ctx, guardedNext);
731
- };
732
- }
733
- const result = await current();
734
- if (result instanceof Response) return result;
735
- };
736
- }
737
- //#endregion
738
- //#region src/router/trailing-slash.ts
739
- /**
740
- * Apply a trailing-slash mode to a URL or path.
741
- *
742
- * - `'ignore'` — return as-is.
743
- * - `'always'` — append `/` to the pathname unless it already has one.
744
- * Skipped when the last segment contains `.` (file-like paths) and for the
745
- * root `/` path.
746
- * - `'never'` — strip a trailing `/` from the pathname. Skipped for root.
747
- *
748
- * Preserves query string and hash. Handles both relative paths
749
- * (`/foo?x=1`) and absolute URLs (`https://host/foo?x=1`).
750
- *
751
- * Used by URL-generation helpers and the redirect middleware so canonical
752
- * form is computed in one place.
753
- */
754
- function applyTrailingSlash(url, mode) {
755
- if (mode === "ignore") return url;
756
- const isAbsolute = /^https?:\/\//i.test(url);
757
- const parsed = isAbsolute ? new URL(url) : new URL(url, "http://placeholder.local");
758
- const path = parsed.pathname;
759
- if (path === "/") return url;
760
- const hasTrailing = path.endsWith("/");
761
- if (mode === "always" && !hasTrailing) {
762
- if (path.slice(path.lastIndexOf("/") + 1).includes(".")) return url;
763
- parsed.pathname = `${path}/`;
764
- } else if (mode === "never" && hasTrailing) parsed.pathname = path.slice(0, -1);
765
- else return url;
766
- return isAbsolute ? parsed.toString() : `${parsed.pathname}${parsed.search}${parsed.hash}`;
767
- }
768
- //#endregion
769
- //#region src/router/middleware/trailing-slash-redirect.ts
770
- const REDIRECT_STATUS = 308;
771
- /**
772
- * Create a Hono middleware that canonicalises trailing slashes via 308 redirects.
773
- *
774
- * - `'ignore'` — returns `null`; routes match both `/foo` and `/foo/` natively
775
- * (Hono handles this when constructed with `strict: false`).
776
- * - `'always'` — non-trailing requests redirect to the trailing-slash form.
777
- * Paths whose last segment contains `.` (e.g. `/api/openapi.json`) are skipped.
778
- * - `'never'` — trailing requests redirect to the non-trailing form.
779
- *
780
- * Root (`/`) is always passed through unchanged.
781
- *
782
- * 308 is used so that POST/PUT/PATCH bodies survive the redirect.
783
- *
784
- * Location headers are emitted as path-relative URIs so the user agent
785
- * resolves them against the effective request URI — sidestepping scheme
786
- * mismatches behind HTTPS-terminating proxies that proxy HTTPS pages to an
787
- * HTTP-speaking backend (which would otherwise produce a mixed-content block).
788
- */
789
- function createTrailingSlashRedirect(mode) {
790
- if (mode === "ignore") return null;
791
- return async (c, next) => {
792
- const url = new URL(c.req.url);
793
- const canonicalPath = applyTrailingSlash(url.pathname, mode);
794
- if (canonicalPath === url.pathname) return next();
795
- return c.redirect(`${canonicalPath}${url.search}`, REDIRECT_STATUS);
796
- };
797
- }
798
- //#endregion
799
- //#region src/router/decorators/route.decorator.ts
800
- /**
801
- * Decorator to add OpenAPI metadata to a controller method using convention-based routing.
802
- *
803
- * **Cannot be mixed with HTTP method decorators** (`@Get`, `@Post`, `@Put`, `@Patch`,
804
- * `@Delete`, `@All`) in the same controller. Use one pattern or the other.
805
- *
806
- * Stores route configuration (schemas, response, tags, security) in metadata.
807
- * HTTP method, path, and success status code are auto-derived from the method name:
808
- * - index() → GET /base-path → 200
809
- * - show() → GET /base-path/:id → 200
810
- * - create() → POST /base-path → 201
811
- * - update() → PUT /base-path/:id → 200
812
- * - patch() → PATCH /base-path/:id → 200
813
- * - destroy() → DELETE /base-path/:id → 200
814
- *
815
- * @param config - Route configuration (schemas, response, tags, security)
816
- *
817
- * @example
818
- * ```typescript
819
- * @Controller('/api/v1/notes', {
820
- * tags: ['Notes'],
821
- * security: ['bearerAuth']
822
- * })
823
- * export class NotesController implements Controller {
824
- * @Route({
825
- * body: createNoteSchema,
826
- * response: noteSchema, // 201 auto-derived from 'create' method
827
- * tags: ['Mutations'],
828
- * description: 'Create a new note'
829
- * })
830
- * async create(ctx: RouterContext): Promise<Response> {
831
- * // POST /api/v1/notes (auto-derived from method name)
832
- * // Body schema: createNoteSchema (auto-validated)
833
- * // Response: 201 → noteSchema (status auto-derived)
834
- * // Tags: ['Notes', 'Mutations'] (merged with controller)
835
- * // Security: ['bearerAuth'] (inherited from controller)
836
- * const body = ctx.body()
837
- * const note = await this.notesService.create(body)
838
- * return ctx.json(note, 201)
839
- * }
840
- *
841
- * @Route({
842
- * query: paginationSchema,
843
- * response: z.array(noteSchema) // 200 auto-derived from 'index' method
844
- * })
845
- * async index(ctx: RouterContext): Promise<Response> {
846
- * // GET /api/v1/notes (auto-derived)
847
- * // Query params auto-validated
848
- * const notes = await this.notesService.list()
849
- * return ctx.json(notes)
850
- * }
851
- *
852
- * @Route({
853
- * params: z.object({ id: z.string().uuid() }),
854
- * response: {
855
- * schema: noteSchema,
856
- * description: 'Note details'
857
- * },
858
- * security: [] // Override to make public
859
- * })
860
- * async show(ctx: RouterContext): Promise<Response> {
861
- * // GET /api/v1/notes/:id (auto-derived)
862
- * // URL params auto-validated
863
- * // Response: 200 → noteSchema (status auto-derived)
864
- * // Security: [] (public route, override controller security)
865
- * const id = ctx.param('id')
866
- * const note = await this.notesService.findById(id)
867
- * return ctx.json(note)
868
- * }
869
- * }
870
- * ```
871
- */
872
- function Route(config) {
873
- return function(target, propertyKey, descriptor) {
874
- const metadata = {
875
- type: "convention",
876
- config
877
- };
878
- Reflect.defineMetadata(ROUTE_METADATA_KEYS.ROUTE_CONFIG, metadata, target, propertyKey);
879
- const existing = Reflect.getOwnMetadata(ROUTE_METADATA_KEYS.DECORATED_METHODS, target) ?? [];
880
- existing.push(propertyKey);
881
- Reflect.defineMetadata(ROUTE_METADATA_KEYS.DECORATED_METHODS, existing, target);
882
- return descriptor;
883
- };
884
- }
885
- /**
886
- * Get the route metadata from a controller method
887
- *
888
- * @param target - Controller instance or prototype
889
- * @param methodName - Name of the method
890
- * @returns Route metadata or undefined if not decorated
891
- */
892
- function getRouteMetadata(target, methodName) {
893
- return Reflect.getMetadata(ROUTE_METADATA_KEYS.ROUTE_CONFIG, target, methodName);
894
- }
895
- /**
896
- * Get all methods with route decorators (@Route, @Get, @Post, etc.) from a controller
897
- *
898
- * @param ControllerClass - Controller class
899
- * @returns Array of method names that have route metadata
900
- */
901
- function getRouteDecoratedMethods(ControllerClass) {
902
- const methods = /* @__PURE__ */ new Set();
903
- let proto = ControllerClass.prototype;
904
- while (proto && proto !== Object.prototype) {
905
- const own = Reflect.getOwnMetadata(ROUTE_METADATA_KEYS.DECORATED_METHODS, proto);
906
- if (own) for (const m of own) methods.add(m);
907
- proto = Object.getPrototypeOf(proto);
908
- }
909
- return [...methods];
910
- }
911
- //#endregion
912
- //#region src/router/schemas/common.schemas.ts
913
- /**
914
- * Common OpenAPI Schemas
915
- *
916
- * Reusable schema definitions for common API patterns:
917
- * - Error responses
918
- * - Pagination
919
- * - Common parameters
920
- */
921
- /**
922
- * Generic error response schema
923
- * Used for all error responses (4xx, 5xx)
924
- * Matches ApplicationError.toErrorResponse() structure
925
- */
926
- const errorResponseSchema = z.object({
927
- code: z.number().int().describe("Application error code"),
928
- message: z.string().describe("Human-readable error message"),
929
- timestamp: z.string().datetime().describe("ISO timestamp when error occurred"),
930
- metadata: z.record(z.string(), z.unknown()).optional().describe("Additional error context"),
931
- stack: z.string().optional().describe("Stack trace (development only)")
932
- }).openapi("ErrorResponse");
933
- /**
934
- * Validation error response schema
935
- * Used for 400 Bad Request with validation failures
936
- * Matches ApplicationError.toErrorResponse() structure with validation-specific metadata
937
- */
938
- const validationErrorResponseSchema = z.object({
939
- code: z.number().int().describe("Application error code"),
940
- message: z.string().describe("Human-readable error message"),
941
- timestamp: z.string().datetime().describe("ISO timestamp when error occurred"),
942
- metadata: z.object({ issues: z.array(z.object({
943
- path: z.string().describe("Field path that failed validation"),
944
- message: z.string().describe("Validation failure message"),
945
- code: z.string().describe("Zod validation error code")
946
- })) }).describe("Validation error details"),
947
- stack: z.string().optional().describe("Stack trace (development only)")
948
- }).openapi("ValidationErrorResponse");
949
- /**
950
- * Pagination query parameters schema
951
- * Used for list endpoints
952
- */
953
- const paginationQuerySchema = z.object({
954
- page: z.coerce.number().int().positive().default(1).describe("Page number (1-indexed)"),
955
- limit: z.coerce.number().int().positive().max(100).default(20).describe("Items per page (max 100)")
956
- }).openapi("PaginationQuery");
957
- /**
958
- * Paginated response wrapper schema
959
- * Generic wrapper for paginated list responses
960
- */
961
- const paginatedResponseSchema = (itemSchema) => z.object({
962
- data: z.array(itemSchema).describe("Array of items for current page"),
963
- pagination: z.object({
964
- page: z.number().int().positive().describe("Current page number"),
965
- limit: z.number().int().positive().describe("Items per page"),
966
- total: z.number().int().nonnegative().describe("Total number of items"),
967
- totalPages: z.number().int().nonnegative().describe("Total number of pages")
968
- })
969
- });
970
- /**
971
- * UUID parameter schema
972
- * Used for :id parameters in RESTful routes
973
- */
974
- const uuidParamSchema = z.object({ id: z.string().uuid().describe("Resource UUID") }).openapi("UUIDParam");
975
- /**
976
- * Success message response schema
977
- * Used for operations that don't return data (e.g., DELETE)
978
- */
979
- const successMessageSchema = z.object({
980
- message: z.string().describe("Success message"),
981
- data: z.record(z.string(), z.unknown()).optional().describe("Optional additional data")
982
- }).openapi("SuccessMessage");
983
- /**
984
- * Common HTTP status error schemas
985
- * Pre-configured for standard error responses
986
- */
987
- const commonErrorSchemas = {
988
- 400: {
989
- schema: validationErrorResponseSchema,
990
- description: "Validation error"
991
- },
992
- 401: {
993
- schema: errorResponseSchema,
994
- description: "Unauthorized"
995
- },
996
- 403: {
997
- schema: errorResponseSchema,
998
- description: "Forbidden"
999
- },
1000
- 404: {
1001
- schema: errorResponseSchema,
1002
- description: "Not found"
1003
- },
1004
- 409: {
1005
- schema: errorResponseSchema,
1006
- description: "Conflict"
1007
- },
1008
- 500: {
1009
- schema: errorResponseSchema,
1010
- description: "Internal server error"
1011
- }
1012
- };
1013
- //#endregion
1014
- //#region src/router/utils/path.ts
1015
- /**
1016
- * Path normalization and route ordering utilities.
1017
- *
1018
- * Users always write Hono-style `:param` paths (`:companyId`, `:id`).
1019
- * OpenAPI requires `{param}` style — conversion happens only at registration time.
1020
- */
1021
- /**
1022
- * Convert Hono-style `:param` path segments to OpenAPI-style `{param}`.
1023
- * Strips regex constraints (e.g., `:locale{sw}` → `{locale}`).
1024
- *
1025
- * @example
1026
- * toOpenAPIPath('/users/:id') // '/users/{id}'
1027
- * toOpenAPIPath('/:companyId/users/:userId') // '/{companyId}/users/{userId}'
1028
- * toOpenAPIPath('/users/:id/posts') // '/users/{id}/posts'
1029
- * toOpenAPIPath('/:locale{en|fr}/users') // '/{locale}/users'
1030
- */
1031
- function toOpenAPIPath(path) {
1032
- return path.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)(\{[^}]*\})?/g, "{$1}");
1033
- }
1034
- /**
1035
- * Convert Hono-style `:param` path segments to OpenAPI-style `{param}`,
1036
- * preserving regex constraints.
1037
- *
1038
- * Used for Hono route registration via `app.openapi()`. The non-greedy
1039
- * regex in `@hono/zod-openapi` (`\/{(.+?)}/g`) converts `{param}` back
1040
- * to `:param` while leaving the constraint suffix intact.
1041
- *
1042
- * @example
1043
- * toRoutingOpenAPIPath('/:locale{sw}/users/:id') // '/{locale}{sw}/users/{id}'
1044
- * toRoutingOpenAPIPath('/users/:id') // '/users/{id}'
1045
- */
1046
- function toRoutingOpenAPIPath(path) {
1047
- return path.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)(\{[^}]*\})?/g, (_, name, constraint) => constraint ? `{${name}}${constraint}` : `{${name}}`);
1048
- }
1049
- /**
1050
- * Compute a packed specificity key for route ordering.
1051
- * Encodes both score and segment count into a single number to avoid object allocation.
1052
- *
1053
- * Lower score = higher priority (registered first in Hono).
1054
- * Scoring: static = 0, `:param{constraint}` = 5, `:param` = 10, wildcard `{.+}` / `{.*}` = 100.
1055
- *
1056
- * Packed as: score * 10000 - segmentCount (negative segment count so more segments = lower key = higher priority)
1057
- *
1058
- * Locale variants score against the path with the leading `/:locale{…}` segment
1059
- * stripped — the variant's score therefore matches its primary, but its larger
1060
- * segment count makes it sort just before the primary. Without this, a primary
1061
- * catch-all (e.g. `/:slug{.+}`) gobbles locale-prefixed URLs because Hono picks
1062
- * whichever matching route was registered first.
1063
- */
1064
- function getPathSpecificityKey(route) {
1065
- const segmentCount = countSegments(route.path);
1066
- const scoringPath = route.isLocaleVariant ? route.path.replace(/^\/:locale\{[^}]*\}/, "") || "/" : route.path;
1067
- let score = 0;
1068
- let i = 0;
1069
- while (i < scoringPath.length) {
1070
- if (scoringPath.charCodeAt(i) === 47) {
1071
- i++;
1072
- continue;
1073
- }
1074
- let end = scoringPath.indexOf("/", i);
1075
- if (end === -1) end = scoringPath.length;
1076
- const segment = scoringPath.substring(i, end);
1077
- if (segment.includes("{.+}") || segment.includes("{.*}")) score += 100;
1078
- else if (segment.charCodeAt(0) === 58) score += segment.includes("{") ? 5 : 10;
1079
- i = end;
1080
- }
1081
- return score * 1e4 - segmentCount;
1082
- }
1083
- function countSegments(path) {
1084
- let count = 0;
1085
- let i = 0;
1086
- while (i < path.length) {
1087
- if (path.charCodeAt(i) === 47) {
1088
- i++;
1089
- continue;
1090
- }
1091
- let end = path.indexOf("/", i);
1092
- if (end === -1) end = path.length;
1093
- count++;
1094
- i = end;
1095
- }
1096
- return count;
1097
- }
1098
- /**
1099
- * Compute a specificity score for route ordering.
1100
- * Lower score = higher priority (registered first in Hono).
1101
- *
1102
- * Scoring: static = 0, `:param{constraint}` = 5, `:param` = 10, wildcard `{.+}` / `{.*}` = 100.
1103
- */
1104
- function getPathSpecificityScore(path) {
1105
- const segments = path.split("/").filter(Boolean);
1106
- let score = 0;
1107
- for (const segment of segments) if (segment.includes("{.+}") || segment.includes("{.*}")) score += 100;
1108
- else if (segment.startsWith(":") && segment.includes("{")) score += 5;
1109
- else if (segment.startsWith(":")) score += 10;
1110
- return score;
1111
- }
1112
- /**
1113
- * Sort routes by specificity so Hono registers them in the correct order.
1114
- *
1115
- * 1. Static paths before parameterized before wildcards
1116
- * 2. More segments = more specific (tie-breaker)
1117
- * 3. Locale-prefixed variants before their primary (so a locale-prefixed
1118
- * request matches the variant first; a primary catch-all would otherwise
1119
- * swallow the locale prefix into its param)
1120
- */
1121
- function sortRoutesBySpecificity(routes) {
1122
- const keys = /* @__PURE__ */ new Map();
1123
- for (const route of routes) keys.set(route, getPathSpecificityKey(route));
1124
- const copy = routes.slice();
1125
- copy.sort((a, b) => keys.get(a) - keys.get(b));
1126
- return copy;
1127
- }
1128
- //#endregion
1129
- //#region src/router/utils/route-name.ts
1130
- /**
1131
- * Route naming utilities.
1132
- *
1133
- * Extracts parameter names from paths and domains,
1134
- * and generates convention-based route names.
1135
- */
1136
- /**
1137
- * Extract parameter names from a Hono-style path.
1138
- *
1139
- * @example
1140
- * extractParamNames('/users/:id') // ['id']
1141
- * extractParamNames('/:companyId/users/:userId') // ['companyId', 'userId']
1142
- * extractParamNames('/users') // []
1143
- */
1144
- function extractParamNames(path) {
1145
- if (!path.includes(":")) return [];
1146
- return [...path.matchAll(/:([a-zA-Z_][a-zA-Z0-9_]*)/g)].map((m) => m[1]);
1147
- }
1148
- /**
1149
- * Extract parameter names from a domain pattern.
1150
- *
1151
- * @example
1152
- * extractDomainParamNames('{tenant}.example.com') // ['tenant']
1153
- * extractDomainParamNames('{region}.{tenant}.example.com') // ['region', 'tenant']
1154
- * extractDomainParamNames('example.com') // []
1155
- */
1156
- function extractDomainParamNames(domain) {
1157
- if (!domain.includes("{")) return [];
1158
- return [...domain.matchAll(/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g)].map((m) => m[1]);
1159
- }
1160
- /**
1161
- * Auto-generate a route name for convention-based `@Route` methods.
1162
- *
1163
- * Strips common prefixes (`/api/`, `/v{N}/`) and parameter segments,
1164
- * then joins remaining static segments with dots and appends the method name.
1165
- *
1166
- * @example
1167
- * generateConventionRouteName('/users', 'index') // 'users.index'
1168
- * generateConventionRouteName('/users', 'show') // 'users.show'
1169
- * generateConventionRouteName('/api/v1/users', 'create') // 'users.create'
1170
- * generateConventionRouteName('/api/v1/users/:userId/notes', 'index') // 'users.notes.index'
1171
- * generateConventionRouteName('/:companyId/users', 'index') // 'users.index'
1172
- * generateConventionRouteName('/users/:userId/notes/:noteId/tags', 'index') // 'users.notes.tags.index'
1173
- */
1174
- function generateConventionRouteName(basePath, methodName) {
1175
- const parts = basePath.split("/");
1176
- const segments = [];
1177
- for (const s of parts) if (s && s !== "api" && !s.startsWith(":") && !/^v\d+$/.test(s)) segments.push(s);
1178
- if (segments.length === 0) return methodName;
1179
- return `${segments.join(".")}.${methodName}`;
1180
- }
1181
- //#endregion
1182
- //#region src/router/services/route-registration.service.ts
1183
- const invokeHandler = (instance, method, ...args) => {
1184
- try {
1185
- return Promise.resolve(instance[method](...args));
1186
- } catch (err) {
1187
- return Promise.reject(err);
1188
- }
1189
- };
1190
- let RouteRegistrationService = class RouteRegistrationService {
1191
- logger;
1192
- registry;
1193
- routerResolver;
1194
- localePathService;
1195
- app;
1196
- moduleRegistry;
1197
- controllerClasses = /* @__PURE__ */ new Map();
1198
- upgradeWebSocketFn = null;
1199
- constructor(logger, registry, routerResolver, localePathService, app, moduleRegistry) {
1200
- this.logger = logger;
1201
- this.registry = registry;
1202
- this.routerResolver = routerResolver;
1203
- this.localePathService = localePathService;
1204
- this.app = app;
1205
- this.moduleRegistry = moduleRegistry;
1206
- }
1207
- /**
1208
- * Configure router with controllers and global middleware.
1209
- * Resolves controllers from ModuleRegistry and global middleware from RouterResolver.
1210
- */
1211
- async configure() {
1212
- const controllers = this.moduleRegistry.getAllControllers();
1213
- const globalMiddleware = this.routerResolver?.getGlobalMiddleware() ?? [];
1214
- this.logger.info("Registering controllers", { controllerCount: controllers.length });
1215
- if (globalMiddleware.length > 0) this.app.use("*", createMiddlewareChain(globalMiddleware));
1216
- if (controllers.some(isGateway)) {
1217
- const { upgradeWebSocket } = await import("hono/cloudflare-workers");
1218
- this.upgradeWebSocketFn = upgradeWebSocket;
1219
- }
1220
- const actions = /* @__PURE__ */ new WeakMap();
1221
- for (const ControllerClass of controllers) this.collectRoutes(ControllerClass, actions);
1222
- for (const route of this.registry.all()) actions.get(route)?.();
1223
- this.logger.info("Controller registration complete");
1224
- }
1225
- /**
1226
- * Pass 1: Collect routes from a controller into RouteRegistry and store Hono actions.
1227
- * Versioning and locale expansion are handled by RouteRegistry.register().
1228
- */
1229
- collectRoutes(ControllerClass, actions) {
1230
- const isWsGateway = isGateway(ControllerClass);
1231
- const controllerRoute = getControllerRoute(ControllerClass);
1232
- if (!controllerRoute) throw new ControllerRegistrationError(ControllerClass.name, isWsGateway ? "Missing @Gateway decorator or route metadata" : "Missing @Controller decorator or route metadata");
1233
- const controllerOpts = getControllerOptions(ControllerClass);
1234
- const controllerGuards = getControllerGuards(ControllerClass)?.guards ?? [];
1235
- const routerConfig = this.routerResolver?.resolveForController(ControllerClass) ?? { middleware: [] };
1236
- const classThrottleMiddleware = Array.from(new Set(getRateLimits(ControllerClass).map(createThrottleMiddleware)));
1237
- const basePath = routerConfig.prefix ? this.joinPaths(routerConfig.prefix, controllerRoute) : controllerRoute;
1238
- const effectiveVersion = controllerOpts?.version ?? routerConfig.version;
1239
- const effectiveDomain = controllerOpts?.domain ?? routerConfig.domain;
1240
- if (isWsGateway) {
1241
- const wsMiddleware = [...routerConfig.middleware, ...classThrottleMiddleware];
1242
- const expandedRoutes = this.registry.register({
1243
- method: "ws",
1244
- basePath,
1245
- version: effectiveVersion,
1246
- domain: effectiveDomain,
1247
- controller: ControllerClass.name,
1248
- action: "ws",
1249
- hidden: routerConfig.hideFromDocs ?? false,
1250
- middleware: wsMiddleware.map((m) => m.name)
1251
- });
1252
- for (const route of expandedRoutes) actions.set(route, () => {
1253
- if (wsMiddleware.length > 0) this.app.use(route.path, createMiddlewareChain(wsMiddleware));
1254
- if (effectiveDomain) {
1255
- const domainHandler = createDomainMiddleware(effectiveDomain);
1256
- this.app.use(route.path, domainHandler);
1257
- this.app.use(`${route.path}/*`, domainHandler);
1258
- }
1259
- this.registerGatewayForPath(ControllerClass, route.path, controllerGuards);
1260
- });
1261
- return;
1262
- }
1263
- const className = ControllerClass.name;
1264
- this.controllerClasses.set(className, ControllerClass);
1265
- const prototype = ControllerClass.prototype;
1266
- if (prototype.handle) {
1267
- const wildcardMiddleware = [...routerConfig.middleware, ...classThrottleMiddleware];
1268
- const expandedRoutes = this.registry.register({
1269
- method: "all",
1270
- basePath,
1271
- version: effectiveVersion,
1272
- domain: effectiveDomain,
1273
- controller: className,
1274
- action: "handle",
1275
- hidden: routerConfig.hideFromDocs ?? false,
1276
- middleware: wildcardMiddleware.map((m) => m.name)
1277
- });
1278
- for (const route of expandedRoutes) actions.set(route, () => {
1279
- if (wildcardMiddleware.length > 0) this.app.use(route.path, createMiddlewareChain(wildcardMiddleware));
1280
- this.registerWildcardRoute(ControllerClass, route.path);
1281
- });
1282
- return;
1283
- }
1284
- const decoratedMethods = getRouteDecoratedMethods(ControllerClass);
1285
- if (decoratedMethods.length === 0) throw new ControllerRegistrationError(ControllerClass.name, "No route decorators found. Use @Route() or HTTP method decorators (@Get, @Post, etc.) on controller methods.");
1286
- const methodMetadata = [];
1287
- let hasConvention = false;
1288
- let hasExplicit = false;
1289
- for (const m of decoratedMethods) {
1290
- const meta = getRouteMetadata(prototype, m);
1291
- if (!meta) continue;
1292
- methodMetadata.push({
1293
- method: m,
1294
- meta
1295
- });
1296
- if (meta.type === "convention") hasConvention = true;
1297
- else if (meta.type === "explicit") hasExplicit = true;
1298
- }
1299
- if (hasConvention && hasExplicit) 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.");
1300
- const routerHidden = routerConfig.hideFromDocs;
1301
- const controllerHidden = controllerOpts?.hideFromDocs ?? false;
1302
- const routerName = routerConfig.name;
1303
- const controllerName = controllerOpts?.name;
1304
- const effectiveNamePrefix = routerName && controllerName ? `${routerName}${controllerName}` : routerName ?? controllerName;
1305
- for (const { method: methodName, meta } of methodMetadata) {
1306
- const resolved = this.resolveMethodAndPath(meta, methodName, basePath, className);
1307
- if (!resolved) continue;
1308
- const methodThrottleMiddleware = getRateLimits(prototype, methodName).map(createThrottleMiddleware);
1309
- const effectiveMiddleware = Array.from(new Set([
1310
- ...routerConfig.middleware,
1311
- ...classThrottleMiddleware,
1312
- ...methodThrottleMiddleware
1313
- ]));
1314
- const middlewareNames = effectiveMiddleware.map((m) => m.name);
1315
- const { httpMethod, fullPath, routeConfig: rawRouteConfig, statusCodeOverride } = resolved;
1316
- let mergedParams = rawRouteConfig.params;
1317
- if (routerConfig.params) {
1318
- const prefixShape = routerConfig.params.shape;
1319
- mergedParams = mergedParams ? mergedParams.extend(prefixShape) : routerConfig.params.extend({});
1320
- }
1321
- const routeConfig = mergedParams === rawRouteConfig.params ? rawRouteConfig : {
1322
- ...rawRouteConfig,
1323
- params: mergedParams
1324
- };
1325
- const hideFromDocs = routeConfig.hideFromDocs ?? routerHidden ?? controllerHidden;
1326
- let routeName;
1327
- if (routeConfig.name) routeName = effectiveNamePrefix ? `${effectiveNamePrefix}${routeConfig.name}` : routeConfig.name;
1328
- else if (meta.type === "convention") {
1329
- const autoName = generateConventionRouteName(basePath, methodName);
1330
- routeName = effectiveNamePrefix ? `${effectiveNamePrefix}${autoName}` : autoName;
1331
- }
1332
- const expandedRoutes = this.registry.register({
1333
- name: routeName,
1334
- method: httpMethod,
1335
- basePath: fullPath,
1336
- version: effectiveVersion,
1337
- domain: effectiveDomain,
1338
- controller: className,
1339
- action: methodName,
1340
- hidden: hideFromDocs,
1341
- middleware: middlewareNames
1342
- });
1343
- const methodGuards = getMethodGuards(prototype, methodName)?.guards ?? [];
1344
- const allGuards = methodGuards.length > 0 ? [...controllerGuards, ...methodGuards] : controllerGuards;
1345
- const responseSchema = httpMethod !== "all" ? this.extractResponseSchema(routeConfig) : null;
1346
- const handler = this.createControllerHandler(ControllerClass, methodName, responseSchema);
1347
- for (const route of expandedRoutes) actions.set(route, () => {
1348
- if (effectiveDomain) {
1349
- const domainHandler = createDomainMiddleware(effectiveDomain);
1350
- this.app.use(route.path, domainHandler);
1351
- this.app.use(`${route.path}/*`, domainHandler);
1352
- }
1353
- if (allGuards.length > 0) this.logger.info(`Route guards`, {
1354
- controller: className,
1355
- method: httpMethod.toUpperCase(),
1356
- path: route.path,
1357
- methodName,
1358
- guardCount: allGuards.length
1359
- });
1360
- if (httpMethod === "all") {
1361
- this.logger.info(`Registering @All route`, {
1362
- controller: className,
1363
- path: route.path,
1364
- methodName
1365
- });
1366
- if (effectiveMiddleware.length > 0) this.app.use(route.path, createMiddlewareChain(effectiveMiddleware));
1367
- if (allGuards.length > 0) this.app.use(route.path, this.createGuardMiddleware(allGuards));
1368
- this.app.all(route.path, handler);
1369
- return;
1370
- }
1371
- const metadata = this.mergeMetadata(controllerOpts, routeConfig, ControllerClass, methodName);
1372
- const openApiRoute = this.buildOpenAPIRoute(httpMethod, route.path, routeConfig, metadata, meta.type === "convention" ? methodName : void 0, statusCodeOverride, route.isLocaleVariant ?? false);
1373
- this.logger.info(`Registering route`, {
1374
- controller: className,
1375
- method: httpMethod.toUpperCase(),
1376
- path: route.path,
1377
- methodName,
1378
- tags: metadata.tags,
1379
- hidden: route.hidden
1380
- });
1381
- const wrappedHandler = this.wrapHandlerWithChain(handler, effectiveMiddleware, allGuards);
1382
- this.app.openapi(openApiRoute, wrappedHandler);
1383
- if (!route.hidden) {
1384
- const { hide: _, ...specRoute } = openApiRoute;
1385
- this.app.openAPIRegistry.registerPath({
1386
- ...specRoute,
1387
- path: toOpenAPIPath(route.path)
1388
- });
1389
- }
1390
- });
1391
- }
1392
- }
1393
- /**
1394
- * Register a single WebSocket gateway route
1395
- */
1396
- registerGatewayForPath(GatewayClass, fullPath, guards) {
1397
- const onMsgMethod = getWsOnMessageMethod(GatewayClass);
1398
- const onCloseMethod = getWsOnCloseMethod(GatewayClass);
1399
- const onErrMethod = getWsOnErrorMethod(GatewayClass);
1400
- const wsHandler = this.upgradeWebSocketFn((c) => {
1401
- const gateway = new RouterContext(c).getContainer().resolve(GatewayClass);
1402
- const events = {};
1403
- const bindWsHandler = (method, onCatch) => {
1404
- return (evt, ws) => {
1405
- invokeHandler(gateway, method, evt, new GatewayContext(c, ws)).catch((err) => {
1406
- this.logger.error(`WebSocket ${method} handler error`, {
1407
- gateway: GatewayClass.name,
1408
- error: err instanceof Error ? err.message : String(err)
1409
- });
1410
- onCatch?.(err, ws);
1411
- });
1412
- };
1413
- };
1414
- if (onMsgMethod) events.onMessage = bindWsHandler(onMsgMethod, (_err, ws) => ws.close(1011, "Internal Error"));
1415
- if (onCloseMethod) events.onClose = bindWsHandler(onCloseMethod);
1416
- if (onErrMethod) events.onError = bindWsHandler(onErrMethod);
1417
- return events;
1418
- });
1419
- this.nameHandler(wsHandler, GatewayClass.name, onMsgMethod ?? "[anonymous]", "ws");
1420
- this.logger.info("Registering WebSocket gateway", {
1421
- gateway: GatewayClass.name,
1422
- path: fullPath
1423
- });
1424
- const handlers = [];
1425
- if (guards.length > 0) {
1426
- this.logger.info("Gateway guards", {
1427
- gateway: GatewayClass.name,
1428
- path: fullPath,
1429
- guardCount: guards.length
1430
- });
1431
- handlers.push(this.createGuardMiddleware(guards));
1432
- }
1433
- handlers.push(wsHandler);
1434
- this.app.get(fullPath, ...handlers);
1435
- }
1436
- /**
1437
- * Create a guard execution middleware
1438
- *
1439
- * This middleware executes all guards for a route before the handler.
1440
- * Guards are executed in order; all must pass for the request to proceed.
1441
- *
1442
- * @param guards - Array of guards to execute
1443
- * @returns Hono middleware function
1444
- */
1445
- createGuardMiddleware(guards) {
1446
- const guardService = new GuardExecutionService(this.logger);
1447
- return async (c, next) => {
1448
- const ctx = new RouterContext(c);
1449
- const container = ctx.getContainer();
1450
- await guardService.executeGuards(guards, ctx, container);
1451
- await next();
1452
- };
1453
- }
1454
- /**
1455
- * Wrap a controller handler with a `scopedMiddleware → guards → handler`
1456
- * chain that runs *inside* the Hono route handler — after request
1457
- * validators have populated `c.req.valid(...)`. This is the only place
1458
- * we can run user middleware after `@hono/zod-openapi`'s validators in
1459
- * the same pipeline.
1460
- *
1461
- * Returns a Hono handler with the same signature as the original so
1462
- * `app.openapi(route, wrapped)` works transparently.
1463
- */
1464
- wrapHandlerWithChain(handler, scopedMiddleware, guards) {
1465
- if (scopedMiddleware.length === 0 && guards.length === 0) return handler;
1466
- const scopedChain = scopedMiddleware.length > 0 ? createMiddlewareChain(scopedMiddleware) : null;
1467
- const guardChain = guards.length > 0 ? this.createGuardMiddleware(guards) : null;
1468
- return async (c) => {
1469
- let captured;
1470
- const runHandler = async () => {
1471
- captured = await handler(c);
1472
- };
1473
- const runGuards = guardChain ? () => guardChain(c, runHandler) : runHandler;
1474
- const result = await (scopedChain ? () => scopedChain(c, runGuards) : runGuards)();
1475
- if (result instanceof Response) return result;
1476
- return captured;
1477
- };
1478
- }
1479
- /**
1480
- * Register wildcard route for non-RESTful controllers
1481
- */
1482
- registerWildcardRoute(ControllerClass, route) {
1483
- this.logger.info(`Registering wildcard route`, {
1484
- controller: ControllerClass.name,
1485
- route: `${route}/:path{.+}`,
1486
- method: "ALL"
1487
- });
1488
- const handler = this.createControllerHandler(ControllerClass, "handle");
1489
- this.app.all(route, handler);
1490
- this.app.all(`${route}/:path{.+}`, handler);
1491
- }
1492
- /**
1493
- * Resolve HTTP method, path, route config, and status code from route metadata.
1494
- */
1495
- resolveMethodAndPath(meta, methodName, basePath, className) {
1496
- if (meta.type === "convention") {
1497
- const derived = this.deriveHttpMethodAndPath(methodName, basePath);
1498
- if (!derived) throw new ControllerRegistrationError(`Cannot derive HTTP method/path for convention-based route "${className}.${methodName}". Ensure the method name follows the naming convention (e.g., index, create, show).`);
1499
- return {
1500
- httpMethod: derived.method,
1501
- fullPath: derived.path,
1502
- routeConfig: meta.config,
1503
- statusCodeOverride: meta.config.statusCode
1504
- };
1505
- }
1506
- return {
1507
- httpMethod: meta.method,
1508
- fullPath: this.joinPaths(basePath, meta.path),
1509
- routeConfig: meta.config,
1510
- statusCodeOverride: meta.config.statusCode
1511
- };
1512
- }
1513
- /**
1514
- * Join a base path and a route path, normalizing slashes
1515
- */
1516
- joinPaths(basePath, routePath) {
1517
- if (basePath.endsWith("/")) basePath = basePath.slice(0, -1);
1518
- if (routePath === "/" || routePath === "") return basePath || "/";
1519
- if (!routePath.startsWith("/")) routePath = "/" + routePath;
1520
- return basePath + routePath;
1521
- }
1522
- /**
1523
- * Auto-derive HTTP method and path from controller method name
1524
- * Uses HTTP_METHODS constant for RESTful convention mapping
1525
- */
1526
- deriveHttpMethodAndPath(methodName, basePath) {
1527
- if (!(methodName in HTTP_METHODS)) return null;
1528
- const mapping = HTTP_METHODS[methodName];
1529
- return {
1530
- method: mapping.method,
1531
- path: basePath + mapping.path
1532
- };
1533
- }
1534
- /**
1535
- * Merge controller-level and route-level metadata
1536
- * Tags are merged (appended), security is merged (union)
1537
- * Guards automatically add sessionCookie security if present
1538
- */
1539
- mergeMetadata(controllerOpts, routeConfig, ControllerClass, methodName) {
1540
- const tags = [...controllerOpts?.tags ?? [], ...routeConfig.tags ?? []];
1541
- const prototype = ControllerClass.prototype;
1542
- const hasMethodGuards = (getMethodGuards(prototype, methodName)?.guards.length ?? 0) > 0;
1543
- const hasControllerGuards = (getControllerGuards(ControllerClass)?.guards.length ?? 0) > 0;
1544
- const requiresAuth = hasMethodGuards || hasControllerGuards;
1545
- let security = [];
1546
- if (routeConfig.security !== void 0) security = [...controllerOpts?.security ?? [], ...routeConfig.security];
1547
- else if (controllerOpts?.security) security = controllerOpts.security;
1548
- if (requiresAuth && !security.includes(SECURITY_SCHEMES.SESSION_COOKIE)) security.push(SECURITY_SCHEMES.SESSION_COOKIE);
1549
- return {
1550
- tags,
1551
- security: security.length > 0 ? security.map((scheme) => ({ [scheme]: [] })) : []
1552
- };
1553
- }
1554
- /**
1555
- * Build OpenAPI route configuration from metadata
1556
- * Creates a route definition compatible with @hono/zod-openapi.
1557
- *
1558
- * Scoped middleware and guards are NOT attached to `route.middleware`
1559
- * here — they're composed into a wrapped handler in `collectRoutes` so
1560
- * they run after Hono's request validators. See `wrapHandlerWithChain`.
1561
- */
1562
- buildOpenAPIRoute(method, path, routeConfig, metadata, methodName, statusCodeOverride, hasLocaleParam = false) {
1563
- try {
1564
- const route = {
1565
- method,
1566
- path: toRoutingOpenAPIPath(path),
1567
- request: {},
1568
- responses: {},
1569
- hide: true
1570
- };
1571
- if (routeConfig.body) {
1572
- const bodySchema = this.isRouteBodyObject(routeConfig.body) ? routeConfig.body.schema : routeConfig.body;
1573
- const bodyContentType = this.isRouteBodyObject(routeConfig.body) ? routeConfig.body.contentType ?? "application/json" : DEFAULT_CONTENT_TYPE;
1574
- route.request = {
1575
- ...route.request,
1576
- body: { content: { [bodyContentType]: { schema: bodySchema } } }
1577
- };
1578
- }
1579
- if (routeConfig.query) route.request = {
1580
- ...route.request,
1581
- query: routeConfig.query
1582
- };
1583
- if (routeConfig.params) route.request = {
1584
- ...route.request,
1585
- params: routeConfig.params
1586
- };
1587
- const localeConfig = this.localePathService.localePathConfig;
1588
- if (hasLocaleParam && localeConfig) {
1589
- const localeParam = z.object({ locale: z.enum(localeConfig.prefixedLocales).openapi({ param: {
1590
- name: "locale",
1591
- in: "path"
1592
- } }).optional() });
1593
- route.request = {
1594
- ...route.request,
1595
- params: route.request.params ? route.request.params.extend(localeParam.shape) : localeParam
1596
- };
1597
- }
1598
- const successStatus = statusCodeOverride ?? (methodName && METHOD_STATUS_CODES[methodName]) ?? 200;
1599
- const responses = {};
1600
- const responseDef = routeConfig.response;
1601
- if (responseDef) if (typeof responseDef === "object" && "schema" in responseDef) responses[successStatus] = {
1602
- content: { [responseDef.contentType ?? "application/json"]: { schema: responseDef.schema } },
1603
- description: responseDef.description ?? `Response ${successStatus}`
1604
- };
1605
- else responses[successStatus] = {
1606
- content: { [DEFAULT_CONTENT_TYPE]: { schema: responseDef } },
1607
- description: `Response ${successStatus}`
1608
- };
1609
- for (const [statusStr, schema] of Object.entries(commonErrorSchemas)) {
1610
- const status = parseInt(statusStr);
1611
- responses[status] ??= schema;
1612
- }
1613
- route.responses = responses;
1614
- if (metadata.tags.length > 0) route.tags = metadata.tags;
1615
- if (metadata.security.length > 0) route.security = metadata.security;
1616
- if (routeConfig.description) route.description = routeConfig.description;
1617
- if (routeConfig.summary) route.summary = routeConfig.summary;
1618
- return (0, validation_exports.createRoute)(route);
1619
- } catch (error) {
1620
- throw new OpenAPIRouteRegistrationError(path, error instanceof Error ? error.message : String(error));
1621
- }
1622
- }
1623
- /**
1624
- * Check if a body definition is a RouteBodyObject (has schema key) vs bare ZodType
1625
- */
1626
- isRouteBodyObject(body) {
1627
- return typeof body === "object" && "schema" in body;
1628
- }
1629
- /**
1630
- * Resolve method parameter injections from the container
1631
- *
1632
- * @param prototype - Controller prototype
1633
- * @param methodName - Method name to get injections for
1634
- * @param container - Request-scoped container
1635
- * @returns Array of resolved dependencies in parameter order
1636
- */
1637
- resolveMethodInjections(prototype, methodName, container) {
1638
- const injections = getMethodInjections(prototype, methodName);
1639
- if (!injections.length) return [];
1640
- return injections.map((inj) => container.resolve(inj.token));
1641
- }
1642
- /**
1643
- * Name a handler function so Hono's inspectRoutes() can identify it.
1644
- * Format: `{type}:{Controller}.{method}` (e.g. `http:UsersController.create`)
1645
- */
1646
- nameHandler(fn, controller, method, type = "http") {
1647
- Object.defineProperty(fn, "name", { value: `${type}:${controller}.${method}` });
1648
- }
1649
- /**
1650
- * Create controller handler that resolves controller from request-scoped container
1651
- * This ensures each request gets a fresh controller with request-scoped context
1652
- */
1653
- createControllerHandler(ControllerClass, methodName, responseSchema = null) {
1654
- const handler = async (c) => {
1655
- const ctx = new RouterContext(c);
1656
- const requestContainer = ctx.getContainer();
1657
- const controller = requestContainer.resolve(ControllerClass);
1658
- const method = controller[methodName];
1659
- if (typeof method === "function") {
1660
- const injectedArgs = this.resolveMethodInjections(ControllerClass.prototype, methodName, requestContainer);
1661
- const response = await method.apply(controller, [ctx, ...injectedArgs]);
1662
- if (responseSchema && c.env.ENVIRONMENT !== "production") return this.validateResponse(response, responseSchema);
1663
- return response;
1664
- }
1665
- throw new ControllerMethodNotFoundError(methodName, ControllerClass.name);
1666
- };
1667
- this.nameHandler(handler, ControllerClass.name, methodName);
1668
- return handler;
1669
- }
1670
- /**
1671
- * Extract the Zod schema from a RouteResponse definition.
1672
- * Returns null for non-JSON content types or when no response is defined.
1673
- */
1674
- extractResponseSchema(routeConfig) {
1675
- const responseDef = routeConfig.response;
1676
- if (!responseDef) return null;
1677
- if (this.isRouteResponseObject(responseDef)) {
1678
- if (!(responseDef.contentType ?? "application/json").includes("application/json")) return null;
1679
- return responseDef.schema;
1680
- }
1681
- return responseDef;
1682
- }
1683
- /**
1684
- * Check if a response definition is a RouteResponseObject (has schema key) vs bare ZodType
1685
- */
1686
- isRouteResponseObject(response) {
1687
- return typeof response === "object" && "schema" in response;
1688
- }
1689
- /**
1690
- * Validate a Response body against its declared Zod schema.
1691
- *
1692
- * Skips validation for:
1693
- * - Non-JSON content types
1694
- * - Empty bodies (204 No Content, 304 Not Modified)
1695
- *
1696
- * Clones the response to read the body without consuming the original stream.
1697
- */
1698
- async validateResponse(response, schema) {
1699
- const contentType = response.headers.get("content-type");
1700
- if (!contentType || !contentType.includes("application/json")) return response;
1701
- if (response.status === 204 || response.status === 304) return response;
1702
- const cloned = response.clone();
1703
- let body;
1704
- try {
1705
- body = await cloned.json();
1706
- } catch {
1707
- return response;
1708
- }
1709
- const result = schema.safeParse(body);
1710
- if (!result.success) throw new ResponseValidationError(result.error);
1711
- return response;
1712
- }
1713
- };
1714
- RouteRegistrationService = __decorate([
1715
- Transient(),
1716
- __decorateParam(0, inject(LOGGER_TOKENS.LoggerService)),
1717
- __decorateParam(1, inject(ROUTER_TOKENS.RouteRegistry)),
1718
- __decorateParam(2, inject(ROUTER_TOKENS.RouterResolver)),
1719
- __decorateParam(3, inject(ROUTER_TOKENS.LocalePathService)),
1720
- __decorateParam(4, inject(ROUTER_TOKENS.HonoApp)),
1721
- __decorateParam(5, inject(DI_TOKENS.ModuleRegistry)),
1722
- __decorateMetadata("design:paramtypes", [
1723
- Object,
1724
- Object,
1725
- Object,
1726
- Object,
1727
- Object,
1728
- Object
1729
- ])
1730
- ], RouteRegistrationService);
1731
- //#endregion
1732
- //#region src/router/hono-app.ts
1733
- const isMiddlewareClass = (arg) => typeof arg === "function" && arg.prototype && "handle" in arg.prototype;
1734
- let HonoApp = class HonoApp extends OpenAPIHono {
1735
- configured = false;
1736
- _container;
1737
- _logger;
1738
- /**
1739
- * Reference to the original Hono `use` implementation.
1740
- * Captured in constructor after super() sets it as an instance property.
1741
- * Used by private methods to register middleware without going through the override.
1742
- */
1743
- nativeUse;
1744
- constructor(container, logger, application) {
1745
- const trailingSlash = application.config.trailingSlash ?? "ignore";
1746
- super({
1747
- strict: false,
1748
- defaultHook: (result, c) => {
1749
- if (!result.success) throw new SchemaValidationError(result.error);
1750
- const override = c.get("validationSuccessResponse");
1751
- if (override) return override;
1752
- }
1753
- });
1754
- this._container = container;
1755
- this._logger = logger;
1756
- this.nativeUse = this.use;
1757
- this.use = ((...args) => {
1758
- if (isMiddlewareClass(args[0])) {
1759
- this.nativeUse("*", createMiddlewareChain(args));
1760
- return this;
1761
- }
1762
- if (typeof args[0] === "string" && args.length > 1 && isMiddlewareClass(args[1])) {
1763
- this.nativeUse(args[0], createMiddlewareChain(args.slice(1)));
1764
- return this;
1765
- }
1766
- return this.nativeUse(...args);
1767
- });
1768
- const trailingSlashRedirect = createTrailingSlashRedirect(trailingSlash);
1769
- if (trailingSlashRedirect) this.nativeUse("*", trailingSlashRedirect);
1770
- this.setupRequestScope();
1771
- this.applyGlobalMiddleware();
1772
- }
1773
- /**
1774
- * Apply global middleware (logger + error handler).
1775
- * Called by Application after locale middleware is applied by LocalePathService.
1776
- */
1777
- applyGlobalMiddleware() {
1778
- this.nativeUse("*", createLoggerMiddleware(this._logger));
1779
- this.onError((err, c) => this.handleException(c, err));
1780
- }
1781
- /**
1782
- * Configure OpenAPI endpoints, controller routes, and 404 handler.
1783
- * Called once by Application.initialize().
1784
- */
1785
- async configure() {
1786
- if (this.configured) throw new HonoAppAlreadyConfiguredError();
1787
- this._container.resolve(OPENAPI_TOKENS.OpenAPIService).setupEndpoints(this, this._container);
1788
- await this._container.resolve(RouteRegistrationService).configure();
1789
- this.notFound((c) => {
1790
- throw new RouteNotFoundError(c.req.path, c.req.method);
1791
- });
1792
- this.configured = true;
1793
- }
1794
- setupRequestScope() {
1795
- this.nativeUse("*", async (c, next) => {
1796
- const routerContext = new RouterContext(c);
1797
- const requestContainer = this._container.createRequestScope(routerContext);
1798
- c.set(ROUTER_CONTEXT_KEYS.REQUEST_CONTAINER, requestContainer);
1799
- await runWithContainer(requestContainer, next);
1800
- });
1801
- }
1802
- handleException(c, err) {
1803
- const handler = (c.get(ROUTER_CONTEXT_KEYS.REQUEST_CONTAINER) ?? this._container).resolve(DI_TOKENS.ExceptionHandler);
1804
- const ctx = createHttpExceptionContext(c);
1805
- return handler.handle(err, ctx);
1806
- }
1807
- };
1808
- HonoApp = __decorate([
1809
- Transient(),
1810
- __decorateParam(0, inject(CONTAINER_TOKEN)),
1811
- __decorateParam(1, inject(LOGGER_TOKENS.LoggerService)),
1812
- __decorateParam(2, inject(DI_TOKENS.Application)),
1813
- __decorateMetadata("design:paramtypes", [
1814
- Object,
1815
- Object,
1816
- Object
1817
- ])
1818
- ], HonoApp);
1819
- //#endregion
1820
- //#region src/router/services/locale-path.service.ts
1821
- let LocalePathService = class LocalePathService {
1822
- honoApp;
1823
- _config;
1824
- _pathDetectionEnabled;
1825
- _prefixDefaultLocale;
1826
- constructor(container, honoApp) {
1827
- this.honoApp = honoApp;
1828
- const i18nOptions = container.isRegistered(I18N_TOKENS.Options) ? container.resolve(I18N_TOKENS.Options) : void 0;
1829
- const detection = i18nOptions?.detection;
1830
- const detectionEnabled = detection ? detection.enabled !== false : true;
1831
- const strategy = (detection && "strategy" in detection && detection.strategy) ?? "cookie";
1832
- this._pathDetectionEnabled = detectionEnabled && strategy === "path";
1833
- this._prefixDefaultLocale = detection && "prefixDefaultLocale" in detection && detection.prefixDefaultLocale !== void 0 ? detection.prefixDefaultLocale : false;
1834
- if (this._pathDetectionEnabled) {
1835
- const allLocales = i18nOptions?.locales ?? ["en"];
1836
- const defaultLocale = i18nOptions?.defaultLocale ?? "en";
1837
- this._config = this._prefixDefaultLocale === true ? {
1838
- allLocales,
1839
- prefixedLocales: allLocales,
1840
- defaultLocale: null
1841
- } : {
1842
- allLocales,
1843
- prefixedLocales: allLocales.filter((l) => l !== defaultLocale),
1844
- defaultLocale
1845
- };
1846
- } else this._config = null;
1847
- if (detectionEnabled) this.setupLanguageDetection(i18nOptions);
1848
- if (this._config?.defaultLocale && this._prefixDefaultLocale === "redirect") this.setupDefaultLocaleRedirect(this._config.defaultLocale);
1849
- }
1850
- /** Whether path-based locale detection is enabled */
1851
- get enabled() {
1852
- return this._pathDetectionEnabled;
1853
- }
1854
- /** The computed locale path config, or null if path detection is disabled */
1855
- get localePathConfig() {
1856
- return this._config;
1857
- }
1858
- /** The prefixDefaultLocale setting (false, true, or 'redirect') */
1859
- get prefixDefaultLocale() {
1860
- return this._prefixDefaultLocale;
1861
- }
1862
- /**
1863
- * Expand a path into primary + locale-prefixed variants.
1864
- *
1865
- * @param path - The base path to expand
1866
- * @returns Array of resolved paths with locale metadata
1867
- */
1868
- resolve(path) {
1869
- if (!this._config) return [{
1870
- path,
1871
- isLocaleVariant: false
1872
- }];
1873
- const constraint = this.buildLocaleConstraint();
1874
- const suffix = path === "/" ? "" : path;
1875
- if (this._config.defaultLocale === null) return [{
1876
- path: `/:locale${constraint}${suffix}`,
1877
- isLocaleVariant: true
1878
- }];
1879
- const result = [{
1880
- path,
1881
- isLocaleVariant: false
1882
- }];
1883
- if (this._config.prefixedLocales.length > 0) result.push({
1884
- path: `/:locale${constraint}${suffix}`,
1885
- isLocaleVariant: true
1886
- });
1887
- return result;
1888
- }
1889
- /**
1890
- * Build a Hono regex constraint from prefixed locales.
1891
- * e.g., `{en|de|fr}` — restricts `:locale` to only match known values.
1892
- */
1893
- buildLocaleConstraint() {
1894
- return `{${(this._config.defaultLocale === null ? this._config.allLocales : this._config.prefixedLocales).join("|")}}`;
1895
- }
1896
- /**
1897
- * Apply Hono's languageDetector middleware and bridge the detected language
1898
- * to Stratal's LOCALE context variable.
1899
- */
1900
- setupLanguageDetection(i18nOptions) {
1901
- const detectorOptions = buildDetectorOptions(i18nOptions);
1902
- this.honoApp.use("*", languageDetector(detectorOptions));
1903
- this.honoApp.use("*", async (c, next) => {
1904
- const language = c.get("language");
1905
- if (language) c.set(ROUTER_CONTEXT_KEYS.LOCALE, language);
1906
- await next();
1907
- });
1908
- }
1909
- /**
1910
- * Redirect requests that include the default locale prefix to the unprefixed path.
1911
- * For example, `/en/users` → 301 redirect to `/users`.
1912
- *
1913
- * Only active when `prefixDefaultLocale` is `'redirect'`.
1914
- */
1915
- setupDefaultLocaleRedirect(defaultLocale) {
1916
- const prefix = `/${defaultLocale}`;
1917
- this.honoApp.use("*", async (c, next) => {
1918
- const path = new URL(c.req.url).pathname;
1919
- if (path === prefix || path.startsWith(`${prefix}/`)) {
1920
- const stripped = path.slice(prefix.length) || "/";
1921
- return c.redirect(stripped, 301);
1922
- }
1923
- await next();
1924
- });
1925
- }
1926
- };
1927
- LocalePathService = __decorate([
1928
- Transient(),
1929
- __decorateParam(0, inject(CONTAINER_TOKEN)),
1930
- __decorateParam(1, inject(ROUTER_TOKENS.HonoApp)),
1931
- __decorateMetadata("design:paramtypes", [Object, Object])
1932
- ], LocalePathService);
1933
- //#endregion
1934
- //#region src/router/services/versioning.service.ts
1935
- let VersioningService = class VersioningService {
1936
- options;
1937
- constructor(app) {
1938
- this.options = app.config.versioning ?? null;
1939
- }
1940
- /** Whether versioning is enabled */
1941
- get enabled() {
1942
- return this.options !== null;
1943
- }
1944
- /**
1945
- * Resolve versioned paths for a base path.
1946
- *
1947
- * @param basePath - The base path (e.g., '/users')
1948
- * @param version - Explicit version from controller/router config
1949
- * @returns Array of versioned path strings (e.g., ['/v1/users', '/v2/users'])
1950
- */
1951
- resolve(basePath, version) {
1952
- if (!this.options) return [basePath];
1953
- if (version === VERSION_NEUTRAL) return [basePath];
1954
- const prefix = this.options.prefix ?? "v";
1955
- if (version !== void 0) return (Array.isArray(version) ? version : [version]).map((v) => `/${prefix}${v}${basePath}`);
1956
- if (this.options.defaultVersion !== void 0) return (Array.isArray(this.options.defaultVersion) ? this.options.defaultVersion : [this.options.defaultVersion]).map((v) => `/${prefix}${v}${basePath}`);
1957
- return [basePath];
1958
- }
1959
- };
1960
- VersioningService = __decorate([
1961
- Transient(),
1962
- __decorateParam(0, inject(DI_TOKENS.Application)),
1963
- __decorateMetadata("design:paramtypes", [Object])
1964
- ], VersioningService);
1965
- //#endregion
1966
- //#region src/router/route-registry.ts
1967
- const CONCRETE_HTTP_METHODS = [
1968
- "get",
1969
- "post",
1970
- "put",
1971
- "delete",
1972
- "patch",
1973
- "head",
1974
- "options",
1975
- "trace"
1976
- ];
1977
- let RouteRegistry = class RouteRegistry {
1978
- versioningService;
1979
- localePathService;
1980
- routes = [];
1981
- namedRoutes = /* @__PURE__ */ new Map();
1982
- _sortedCache = null;
1983
- _routeToNameCache = null;
1984
- constructor(versioningService, localePathService) {
1985
- this.versioningService = versioningService;
1986
- this.localePathService = localePathService;
1987
- }
1988
- /**
1989
- * Register a route. Expands via VersioningService + LocalePathService.
1990
- * Named routes must have unique names.
1991
- *
1992
- * @returns Array of expanded RegisteredRoute entries (primary + locale variants)
1993
- * @throws DuplicateRouteNameError if a named route with the same name already exists
1994
- */
1995
- register(input) {
1996
- const domainParamNames = input.domainParamNames ?? (input.domain ? extractDomainParamNames(input.domain) : []);
1997
- const versionedPaths = this.versioningService.resolve(input.basePath, input.version);
1998
- const expandedRoutes = [];
1999
- const localeEnabled = this.localePathService.enabled;
2000
- for (const versionedPath of versionedPaths) {
2001
- const resolvedPaths = this.localePathService.resolve(versionedPath);
2002
- let localeVariantPaths;
2003
- if (localeEnabled) {
2004
- const variants = resolvedPaths.filter((p) => p.isLocaleVariant);
2005
- localeVariantPaths = variants.length > 0 ? variants.map((p) => p.path) : void 0;
2006
- }
2007
- for (const resolved of resolvedPaths) {
2008
- const route = {
2009
- name: resolved.isLocaleVariant ? void 0 : input.name,
2010
- method: input.method,
2011
- path: resolved.path,
2012
- localePaths: resolved.isLocaleVariant ? void 0 : localeVariantPaths,
2013
- paramNames: extractParamNames(resolved.path),
2014
- domain: input.domain,
2015
- domainParamNames,
2016
- controller: input.controller,
2017
- action: input.action,
2018
- hidden: input.hidden,
2019
- middleware: input.middleware,
2020
- isLocaleVariant: resolved.isLocaleVariant || void 0
2021
- };
2022
- if (route.name) {
2023
- if (this.namedRoutes.has(route.name)) {
2024
- const existing = this.namedRoutes.get(route.name);
2025
- throw new DuplicateRouteNameError(route.name, `${existing.controller}.${existing.action}`, `${route.controller}.${route.action}`);
2026
- }
2027
- this.namedRoutes.set(route.name, route);
2028
- }
2029
- this.routes.push(route);
2030
- expandedRoutes.push(route);
2031
- }
2032
- }
2033
- this._sortedCache = null;
2034
- this._routeToNameCache = null;
2035
- return expandedRoutes;
2036
- }
2037
- /** Get a named route by name */
2038
- get(name) {
2039
- return this.namedRoutes.get(name);
2040
- }
2041
- /** Check if a named route exists */
2042
- has(name) {
2043
- return this.namedRoutes.has(name);
2044
- }
2045
- /**
2046
- * Resolve a Hono-style route path pattern (e.g. as exposed by `c.req.routePath`)
2047
- * back to its registered name, scoped to the request's HTTP method. Locale variant
2048
- * paths resolve to the canonical primary route name. Method matching is
2049
- * case-insensitive; routes registered with `'all'` resolve under any verb.
2050
- */
2051
- findNameByRoute(method, path) {
2052
- this._routeToNameCache ??= this.buildRouteToNameCache();
2053
- return this._routeToNameCache.get(`${method.toLowerCase()}:${path}`);
2054
- }
2055
- buildRouteToNameCache() {
2056
- const cache = /* @__PURE__ */ new Map();
2057
- for (const route of this.namedRoutes.values()) {
2058
- const methods = route.method === "all" ? CONCRETE_HTTP_METHODS : [route.method];
2059
- const paths = route.localePaths ? [route.path, ...route.localePaths] : [route.path];
2060
- for (const m of methods) for (const p of paths) cache.set(`${m}:${p}`, route.name);
2061
- }
2062
- return cache;
2063
- }
2064
- /** Get all routes sorted by specificity (static > param > wildcard, locale variant before its primary) */
2065
- all() {
2066
- this._sortedCache ??= sortRoutesBySpecificity(this.routes);
2067
- return this._sortedCache;
2068
- }
2069
- /** Get only named routes */
2070
- named() {
2071
- return [...this.namedRoutes.values()];
2072
- }
2073
- };
2074
- RouteRegistry = __decorate([
2075
- Transient(),
2076
- __decorateParam(0, inject(ROUTER_TOKENS.VersioningService)),
2077
- __decorateParam(1, inject(ROUTER_TOKENS.LocalePathService)),
2078
- __decorateMetadata("design:paramtypes", [Object, Object])
2079
- ], RouteRegistry);
2080
- //#endregion
2081
- //#region src/router/uri.ts
2082
- /**
2083
- * Encode a value for use as a path parameter.
2084
- *
2085
- * Splits on `/` and encodes each segment with `encodeURIComponent`, so callers
2086
- * can pass slash-containing values for catch-all params (e.g. `:slug{.+}`) and
2087
- * still get a usable URL — `'auth/login'` becomes `'auth/login'`, not
2088
- * `'auth%2Flogin'`. Single segments behave exactly like `encodeURIComponent`.
2089
- */
2090
- function encodePathParam(value) {
2091
- return value.split("/").map(encodeURIComponent).join("/");
2092
- }
2093
- /**
2094
- * Build a URL from a registered route, filling path/domain params and appending extras as query string.
2095
- *
2096
- * Pure function — no request context needed. Used by both the `Uri` class and the standalone `route()` function.
2097
- *
2098
- * @param route - The registered route to build a URL for
2099
- * @param name - Route name (used in error messages)
2100
- * @param params - Path params, domain params, and extra query params
2101
- * @returns Relative URL string (or absolute with domain prefix if route has a domain pattern)
2102
- *
2103
- * @throws MissingRouteParamError if a required path or domain param is missing
2104
- */
2105
- function buildRouteUrl(route, name, params) {
2106
- const allParams = { ...params };
2107
- const consumedKeys = /* @__PURE__ */ new Set();
2108
- let url = route.path;
2109
- if (allParams.locale && route.localePaths?.length) {
2110
- url = `/${allParams.locale}${url === "/" ? "" : url}`;
2111
- consumedKeys.add("locale");
2112
- }
2113
- for (const paramName of route.paramNames) {
2114
- const value = allParams[paramName];
2115
- if (value === void 0) throw new MissingRouteParamError(paramName, name, route.path);
2116
- url = url.replace(new RegExp(`:${paramName}(\\{[^}]*\\})?`), encodePathParam(value));
2117
- consumedKeys.add(paramName);
2118
- }
2119
- let domain;
2120
- if (route.domain) {
2121
- domain = route.domain;
2122
- for (const domainParam of route.domainParamNames) {
2123
- const value = allParams[domainParam];
2124
- if (value === void 0) throw new MissingRouteParamError(domainParam, name, route.domain);
2125
- domain = domain.replace(`{${domainParam}}`, encodeURIComponent(value));
2126
- consumedKeys.add(domainParam);
2127
- }
2128
- }
2129
- const queryEntries = Object.entries(allParams).filter(([key]) => !consumedKeys.has(key));
2130
- if (queryEntries.length > 0) {
2131
- const queryString = queryEntries.filter(([, value]) => Boolean(value)).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");
2132
- url = `${url}${queryString.length ? `?${queryString}` : ""}`;
2133
- }
2134
- if (domain) url = `https://${domain}${url}`;
2135
- return url;
2136
- }
2137
- let Uri = class Uri {
2138
- registry;
2139
- routerContext;
2140
- _defaults = {};
2141
- trailingSlash;
2142
- constructor(registry, routerContext, application) {
2143
- this.registry = registry;
2144
- this.routerContext = routerContext;
2145
- this.trailingSlash = application.config.trailingSlash ?? "ignore";
2146
- }
2147
- /**
2148
- * Set default URL parameters for this request.
2149
- * Applied to all subsequent `route()` calls — explicit params override defaults.
2150
- *
2151
- * @param params - Default parameters (e.g., `{ locale: 'en' }`)
2152
- */
2153
- defaults(params) {
2154
- this._defaults = {
2155
- ...this._defaults,
2156
- ...params
2157
- };
2158
- }
2159
- /**
2160
- * Read the currently configured default URL parameters.
2161
- *
2162
- * Used by frameworks that need to share these with the client (e.g. the
2163
- * Inertia adapter ships them as a shared prop so `route()` calls in the
2164
- * browser auto-fill the same sticky params as the server).
2165
- */
2166
- getDefaults() {
2167
- return { ...this._defaults };
2168
- }
2169
- /**
2170
- * Generate a URL from a named route.
2171
- *
2172
- * Keys matching `:param` placeholders fill the path.
2173
- * Domain params (`{tenant}`) are consumed from the same object.
2174
- * Extra keys become query string parameters.
2175
- * Default params (from `defaults()`) are merged — explicit params override.
2176
- *
2177
- * @param name - Named route identifier
2178
- * @param params - Route params + domain params + extra query params
2179
- * @param options - URL generation options
2180
- * @returns Generated URL string
2181
- *
2182
- * @throws RouteNameNotFoundError if route name not found
2183
- * @throws MissingRouteParamError if required params missing
2184
- */
2185
- route(name, params, options) {
2186
- const registeredRoute = this.registry.get(name);
2187
- if (!registeredRoute) throw new RouteNameNotFoundError(name);
2188
- let url = applyTrailingSlash(buildRouteUrl(registeredRoute, name, {
2189
- ...this._defaults,
2190
- ...params
2191
- }), this.trailingSlash);
2192
- if (options?.absolute && !url.startsWith("http")) url = `${new URL(this.routerContext.c.req.url).origin}${url}`;
2193
- return url;
2194
- }
2195
- /**
2196
- * Generate a signed URL from a named route.
2197
- *
2198
- * @param name - Named route identifier
2199
- * @param params - Route params + domain params + extra query params
2200
- * @param options - Signing options (e.g., expiresIn) and URL options
2201
- * @returns Signed URL string with signature query param
2202
- *
2203
- * @throws Error if APP_SECRET environment variable is not set
2204
- */
2205
- async signedRoute(name, params, options) {
2206
- return signUrl(this.route(name, params, options), this.getAppSecret(), options);
2207
- }
2208
- /**
2209
- * Generate a temporary signed URL from a named route.
2210
- *
2211
- * @param name - Named route identifier
2212
- * @param expiresIn - Time-to-live in seconds
2213
- * @param params - Route params + domain params + extra query params
2214
- * @param options - URL generation options
2215
- * @returns Signed URL string with signature and expires query params
2216
- *
2217
- * @throws Error if APP_SECRET environment variable is not set
2218
- */
2219
- async temporarySignedRoute(name, expiresIn, params, options) {
2220
- return this.signedRoute(name, params, {
2221
- ...options,
2222
- expiresIn
2223
- });
2224
- }
2225
- /**
2226
- * Check if the current request has a valid signature.
2227
- *
2228
- * @returns true if the URL signature is valid and not expired
2229
- */
2230
- async hasValidSignature() {
2231
- const secret = this.routerContext.c.env.APP_SECRET;
2232
- if (!secret) return false;
2233
- return verifySignedUrl(this.routerContext.c.req.url, secret);
2234
- }
2235
- /**
2236
- * Get the current request URL pathname (without query string).
2237
- */
2238
- current() {
2239
- return applyTrailingSlash(new URL(this.routerContext.c.req.url).pathname, this.trailingSlash);
2240
- }
2241
- /**
2242
- * Get the current request URL with query string (pathname + search).
2243
- */
2244
- full() {
2245
- const parsed = new URL(this.routerContext.c.req.url);
2246
- return applyTrailingSlash(`${parsed.pathname}${parsed.search}`, this.trailingSlash);
2247
- }
2248
- /**
2249
- * Get the previous request URL from the Referer header.
2250
- *
2251
- * @param fallback - URL to return if no Referer header (default: '/')
2252
- */
2253
- previous(fallback = "/") {
2254
- return this.routerContext.c.req.header("referer") ?? fallback;
2255
- }
2256
- /**
2257
- * Get the previous request URL pathname (no query string or host) from the Referer header.
2258
- *
2259
- * @param fallback - Path to return if no Referer header (default: '/')
2260
- */
2261
- previousPath(fallback = "/") {
2262
- const referer = this.routerContext.c.req.header("referer");
2263
- if (!referer) return fallback;
2264
- try {
2265
- return new URL(referer).pathname;
2266
- } catch {
2267
- return referer;
2268
- }
2269
- }
2270
- /**
2271
- * Build a URL to a raw path (not a named route) with optional query params.
2272
- *
2273
- * @param path - URL path (e.g., '/users')
2274
- * @param queryParams - Query parameters to append
2275
- * @param options - URL generation options
2276
- */
2277
- to(path, queryParams, options) {
2278
- let url = applyTrailingSlash(path, this.trailingSlash);
2279
- if (queryParams) {
2280
- const entries = Object.entries(queryParams);
2281
- if (entries.length > 0) {
2282
- const queryString = entries.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");
2283
- url = url.includes("?") ? `${url}&${queryString}` : `${url}?${queryString}`;
2284
- }
2285
- }
2286
- if (options?.absolute && !url.startsWith("http")) url = `${new URL(this.routerContext.c.req.url).origin}${url}`;
2287
- return url;
2288
- }
2289
- /**
2290
- * Build a URL with query string parameters. Merges with existing query params in path.
2291
- *
2292
- * @param path - URL path, may already contain query params
2293
- * @param queryParams - Query parameters to merge (new values override existing)
2294
- */
2295
- query(path, queryParams) {
2296
- const parsed = new URL(path, "https://placeholder.local");
2297
- for (const [key, value] of Object.entries(queryParams)) parsed.searchParams.set(key, value);
2298
- return applyTrailingSlash(`${parsed.pathname}${parsed.search}`, this.trailingSlash);
2299
- }
2300
- getAppSecret() {
2301
- const secret = this.routerContext.c.env.APP_SECRET;
2302
- if (!secret) throw new Error("APP_SECRET environment variable is required for signed URLs");
2303
- return secret;
2304
- }
2305
- };
2306
- Uri = __decorate([
2307
- Transient(),
2308
- __decorateParam(0, inject(ROUTER_TOKENS.RouteRegistry)),
2309
- __decorateParam(1, inject(ROUTER_TOKENS.RouterContext)),
2310
- __decorateParam(2, inject(DI_TOKENS.Application)),
2311
- __decorateMetadata("design:paramtypes", [
2312
- Object,
2313
- Object,
2314
- Object
2315
- ])
2316
- ], Uri);
2317
- //#endregion
2318
- //#region src/router/route-url.ts
2319
- /**
2320
- * Generate a URL from a named route.
2321
- *
2322
- * Keys in `params` matching `:param` placeholders fill the path.
2323
- * Domain params (`{tenant}`) are also consumed from `params`.
2324
- * Extra keys become query string parameters.
2325
- *
2326
- * Resolves RouteRegistry from the application container via AsyncLocalStorage.
2327
- * Available after `Application.initialize()` has been called.
2328
- *
2329
- * @param name - Named route identifier
2330
- * @param params - Route params + domain params + extra query params
2331
- * @returns Generated URL string
2332
- *
2333
- * @example
2334
- * ```typescript
2335
- * // In a controller (preferred):
2336
- * ctx.route('users.show', { id: '1' })
2337
- *
2338
- * // Outside controllers (standalone function):
2339
- * import { route } from 'stratal/router'
2340
- *
2341
- * route('users.show', { id: '1' })
2342
- * ```
2343
- */
2344
- function route(name, params) {
2345
- const container = getContainer();
2346
- const registry = container.resolve(ROUTER_TOKENS.RouteRegistry);
2347
- const application = container.resolve(DI_TOKENS.Application);
2348
- const registeredRoute = registry.get(name);
2349
- if (!registeredRoute) throw new RouteNameNotFoundError(name);
2350
- return applyTrailingSlash(buildRouteUrl(registeredRoute, name, params), application.config.trailingSlash ?? "ignore");
2351
- }
2352
- //#endregion
2353
- //#region src/router/middleware/verify-signature.middleware.ts
2354
- /**
2355
- * Middleware that verifies signed URLs.
2356
- *
2357
- * Checks the `signature` (and optionally `expires`) query params against the
2358
- * request URL using HMAC-SHA256 via `crypto.subtle.verify()`.
2359
- *
2360
- * Requires `APP_SECRET` in the Cloudflare Workers environment bindings.
2361
- *
2362
- * @throws InvalidSignatureError (403) if signature is missing, invalid, or expired
2363
- *
2364
- * @example
2365
- * ```typescript
2366
- * @Module({ controllers: [UnsubscribeController], providers: [VerifySignatureMiddleware] })
2367
- * export class EmailModule implements RouteConfigurable {
2368
- * configureRoutes(router: Router): void {
2369
- * router.middleware(VerifySignatureMiddleware)
2370
- * }
2371
- * }
2372
- * ```
2373
- */
2374
- var VerifySignatureMiddleware = class {
2375
- async handle(ctx, next) {
2376
- const url = ctx.c.req.url;
2377
- const secret = ctx.c.env.APP_SECRET;
2378
- if (!secret) throw new MissingEnvironmentVariableError("APP_SECRET");
2379
- if (!await verifySignedUrl(url, secret)) throw new InvalidSignatureError();
2380
- await next();
2381
- }
2382
- };
2383
- //#endregion
2384
- //#region src/i18n/services/i18n.service.ts
2385
- /**
2386
- * I18n Service
2387
- *
2388
- * Request-scoped service for translations.
2389
- * Injects RouterContext to access request-specific locale.
2390
- * Uses pre-built CoreContext from MessageLoaderService (singleton) for zero-cost lookups.
2391
- */
2392
- let I18nService = class I18nService {
2393
- loader;
2394
- routerContext;
2395
- constructor(loader, routerContext) {
2396
- this.loader = loader;
2397
- this.routerContext = routerContext;
2398
- }
2399
- /**
2400
- * Translate a message key
2401
- *
2402
- * @param key - Message key (e.g., 'common.actions.save')
2403
- * @param params - Optional parameters for interpolation
2404
- * @returns Translated string
2405
- */
2406
- t(key, params) {
2407
- const context = this.loader.getCoreContext(this.getLocale());
2408
- const result = params !== void 0 ? translate(context, key, params) : translate(context, key);
2409
- return typeof result === "string" ? result : key;
2410
- }
2411
- /**
2412
- * Get current locale
2413
- *
2414
- * @returns Current locale code from RouterContext or default locale
2415
- */
2416
- getLocale() {
2417
- return this.routerContext?.getLocale() ?? "en";
2418
- }
2419
- };
2420
- I18nService = __decorate([
2421
- Transient(I18N_TOKENS.I18nService),
2422
- __decorateParam(0, inject(I18N_TOKENS.MessageLoader)),
2423
- __decorateParam(1, inject(ROUTER_TOKENS.RouterContext, { isOptional: true })),
2424
- __decorateMetadata("design:paramtypes", [Object, Object])
2425
- ], I18nService);
2426
- //#endregion
2427
- //#region src/i18n/i18n.module.ts
2428
- /**
2429
- * I18n Module
2430
- *
2431
- * Core infrastructure module for internationalization.
2432
- * Provides message translation and locale handling.
2433
- *
2434
- * - `forRoot()` configures locale settings (call once in root module)
2435
- * - `registerMessages()` adds translations (call from any module, as many times as needed)
2436
- *
2437
- * @example
2438
- * ```typescript
2439
- * @Module({
2440
- * imports: [
2441
- * I18nModule.forRoot({ defaultLocale: 'en', locales: ['en', 'fr'] }),
2442
- * I18nModule.registerMessages(appMessages),
2443
- * ],
2444
- * })
2445
- * export class AppModule {}
2446
- * ```
2447
- *
2448
- * @example Package contributing messages
2449
- * ```typescript
2450
- * @Module({
2451
- * imports: [
2452
- * I18nModule.registerMessages(tenancyMessages),
2453
- * ],
2454
- * })
2455
- * export class TenancyModule {}
2456
- * ```
2457
- */
2458
- var _I18nModule;
2459
- setupI18nCompiler();
2460
- z.config({ customError: backendErrorMap });
2461
- let I18nModule = _I18nModule = class I18nModule {
2462
- /**
2463
- * Configure I18n locale settings
2464
- *
2465
- * Call once in the root module. Does not accept messages —
2466
- * use `registerMessages()` to add translations.
2467
- *
2468
- * @param options - Locale configuration (defaultLocale, fallbackLocale, locales)
2469
- */
2470
- static forRoot(options = {}) {
2471
- return {
2472
- module: _I18nModule,
2473
- providers: [{
2474
- provide: I18N_TOKENS.Options,
2475
- useValue: options
2476
- }]
2477
- };
2478
- }
2479
- /**
2480
- * Register i18n messages
2481
- *
2482
- * Can be called from any module, as many times as needed.
2483
- * Messages are deep-merged in registration order — later calls override earlier ones at leaf level.
2484
- *
2485
- * @param messages - Messages keyed by locale code
2486
- *
2487
- * @example App-level messages
2488
- * ```typescript
2489
- * I18nModule.registerMessages({
2490
- * en: { common: { hello: 'Hello' }, errors: { notFound: 'Not found' } },
2491
- * fr: { common: { hello: 'Bonjour' }, errors: { notFound: 'Introuvable' } },
2492
- * })
2493
- * ```
2494
- *
2495
- * @example Package-level messages
2496
- * ```typescript
2497
- * I18nModule.registerMessages({
2498
- * en: { tenancy: { tenantNotFound: 'Tenant not found' } },
2499
- * })
2500
- * ```
2501
- */
2502
- static registerMessages(messages) {
2503
- MessageRegistry.addMessages(messages);
2504
- return {
2505
- module: _I18nModule,
2506
- providers: []
2507
- };
2508
- }
2509
- configureRoutes(router) {
2510
- router.use(I18nContextMiddleware);
2511
- }
2512
- };
2513
- I18nModule = _I18nModule = __decorate([Module({ providers: [
2514
- {
2515
- provide: I18N_TOKENS.MessageRegistry,
2516
- useClass: MessageRegistry,
2517
- scope: Scope.Singleton
2518
- },
2519
- {
2520
- provide: I18N_TOKENS.MessageLoader,
2521
- useClass: MessageLoaderService,
2522
- scope: Scope.Singleton
2523
- },
2524
- {
2525
- provide: I18N_TOKENS.I18nService,
2526
- useClass: I18nService
2527
- }
2528
- ] })], I18nModule);
2529
- //#endregion
2530
- export { OpenAPIModule as A, LocaleNotSupportedError as B, validationErrorResponseSchema as C, createMiddlewareChain as D, getRouteMetadata as E, getMessages as F, I18nContextMiddleware as H, messages as I, buildDetectorOptions as L, MessageRegistry as M, MessageLoaderService as N, createDomainMiddleware as O, getLocales as P, resolveI18nOptions as R, uuidParamSchema as S, getRouteDecoratedMethods as T, OpenAPIConfigService as V, commonErrorSchemas as _, buildRouteUrl as a, paginationQuerySchema as b, LocalePathService as c, extractDomainParamNames as d, extractParamNames as f, toOpenAPIPath as g, sortRoutesBySpecificity as h, Uri as i, OpenAPIService as j, parseDomainPattern as k, HonoApp as l, getPathSpecificityScore as m, VerifySignatureMiddleware as n, RouteRegistry as o, generateConventionRouteName as p, route as r, VersioningService as s, I18nModule as t, RouteRegistrationService as u, errorResponseSchema as v, Route as w, successMessageSchema as x, paginatedResponseSchema as y, TranslationMissingError as z };
2531
-
2532
- //# sourceMappingURL=i18n.module-CzXLW9Hy.mjs.map