stratal 0.0.22 → 0.0.24

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 (270) hide show
  1. package/README.md +1 -1
  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 +41 -54
  5. package/dist/bin/quarry.mjs.map +1 -1
  6. package/dist/cache/index.d.mts +5 -3
  7. package/dist/cache/index.d.mts.map +1 -1
  8. package/dist/cache/index.mjs +123 -39
  9. package/dist/cache/index.mjs.map +1 -1
  10. package/dist/{cache.service-e34gV6tz.d.mts → cache.service-uElmBtdS.d.mts} +24 -34
  11. package/dist/cache.service-uElmBtdS.d.mts.map +1 -0
  12. package/dist/{command-BU4ApTo5.mjs → command-BvmUAPPQ.mjs} +15 -3
  13. package/dist/command-BvmUAPPQ.mjs.map +1 -0
  14. package/dist/{command-wXfvHbBZ.d.mts → command-CPhFHjG3.d.mts} +2 -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 +3 -3
  19. package/dist/config/index.d.mts.map +1 -1
  20. package/dist/config/index.mjs +7 -6
  21. package/dist/config/index.mjs.map +1 -1
  22. package/dist/{consumer-registry-DHQtypr1.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-GpNNz79X.mjs → container-storage-BmOJ4_Na.mjs} +1 -1
  25. package/dist/{container-storage-GpNNz79X.mjs.map → container-storage-BmOJ4_Na.mjs.map} +1 -1
  26. package/dist/{controller.decorator-DIUazNU7.mjs → controller.decorator-C5UVeJS3.mjs} +4 -4
  27. package/dist/{controller.decorator-DIUazNU7.mjs.map → controller.decorator-C5UVeJS3.mjs.map} +1 -1
  28. package/dist/cron/index.d.mts +79 -4
  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-9bpN9bu4.mjs → cron.module-Bgzq5hiT.mjs} +17 -7
  34. package/dist/cron.module-Bgzq5hiT.mjs.map +1 -0
  35. package/dist/{decorate-HgTKAYK8.mjs → decorate-CuAoSZvs.mjs} +2 -2
  36. package/dist/{deep-merge-C8NgcXw4.mjs → deep-merge-ByiAOZ3r.mjs} +1 -1
  37. package/dist/{deep-merge-C8NgcXw4.mjs.map → deep-merge-ByiAOZ3r.mjs.map} +1 -1
  38. package/dist/di/index.d.mts +2 -2
  39. package/dist/di/index.mjs +3 -3
  40. package/dist/{di-BO1QIb5H.mjs → di-DseMn-z9.mjs} +244 -135
  41. package/dist/di-DseMn-z9.mjs.map +1 -0
  42. package/dist/email/index.d.mts +33 -40
  43. package/dist/email/index.d.mts.map +1 -1
  44. package/dist/email/index.mjs +456 -41
  45. package/dist/email/index.mjs.map +1 -1
  46. package/dist/{en-BPP6h6y5.mjs → en-CDZBMcc1.mjs} +2 -2
  47. package/dist/{en-BPP6h6y5.mjs.map → en-CDZBMcc1.mjs.map} +1 -1
  48. package/dist/{env-DKSbuBi5.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 +1 -1
  51. package/dist/errors/index.mjs +3 -3
  52. package/dist/{errors-BBZTnjdq.mjs → errors-mXYxG0XB.mjs} +5 -5
  53. package/dist/{errors-BBZTnjdq.mjs.map → errors-mXYxG0XB.mjs.map} +1 -1
  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-D1KdDaiP.mjs → events-BXJGZjpG.mjs} +16 -6
  58. package/dist/events-BXJGZjpG.mjs.map +1 -0
  59. package/dist/{exception-context-B4kM-M53.mjs → exception-context-kEoMFwze.mjs} +3 -3
  60. package/dist/{exception-context-B4kM-M53.mjs.map → exception-context-kEoMFwze.mjs.map} +1 -1
  61. package/dist/{gateway-context-CFe6a9gz.mjs → gateway-context-TMu_AlJt.mjs} +25 -6
  62. package/dist/{gateway-context-CFe6a9gz.mjs.map → gateway-context-TMu_AlJt.mjs.map} +1 -1
  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-Ced-uNIF.mjs → guards-DALPXy3_.mjs} +2 -2
  67. package/dist/{guards-Ced-uNIF.mjs.map → guards-DALPXy3_.mjs.map} +1 -1
  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-CdjKFJZZ.mjs → http-method.decorator-ByWZb9DO.mjs} +4 -4
  71. package/dist/{http-method.decorator-CdjKFJZZ.mjs.map → http-method.decorator-ByWZb9DO.mjs.map} +1 -1
  72. package/dist/i18n/index.d.mts +4 -4
  73. package/dist/i18n/index.d.mts.map +1 -1
  74. package/dist/i18n/index.mjs +5 -5
  75. package/dist/i18n/index.mjs.map +1 -1
  76. package/dist/i18n/messages/en/index.d.mts +1 -1
  77. package/dist/i18n/messages/en/index.mjs +1 -1
  78. package/dist/i18n/utils/index.mjs +1 -1
  79. package/dist/i18n/validation/index.d.mts +3 -3
  80. package/dist/i18n/validation/index.mjs +3 -3
  81. package/dist/{i18n.module-BlXrtAlV.mjs → i18n.module-DRQAZoSZ.mjs} +14 -11
  82. package/dist/{i18n.module-BlXrtAlV.mjs.map → i18n.module-DRQAZoSZ.mjs.map} +1 -1
  83. package/dist/{i18n.tokens-hwRpmjRq.mjs → i18n.tokens-CZ_v8oyS.mjs} +1 -1
  84. package/dist/{i18n.tokens-hwRpmjRq.mjs.map → i18n.tokens-CZ_v8oyS.mjs.map} +1 -1
  85. package/dist/{index-B4UBK-2T.d.mts → index-0ItCjaqw.d.mts} +1 -1
  86. package/dist/index-0ItCjaqw.d.mts.map +1 -0
  87. package/dist/{index-CW1YHSft.d.mts → index-B5JBRcWD.d.mts} +249 -103
  88. package/dist/index-B5JBRcWD.d.mts.map +1 -0
  89. package/dist/{index-BtlE9RuO.d.mts → index-BUt92sAE.d.mts} +1 -1
  90. package/dist/index-BUt92sAE.d.mts.map +1 -0
  91. package/dist/{index-DEncMcC6.d.mts → index-B_JoEl3V.d.mts} +221 -16
  92. package/dist/index-B_JoEl3V.d.mts.map +1 -0
  93. package/dist/{index-Dj5IMwtr.d.mts → index-DtBNIFuP.d.mts} +4 -6
  94. package/dist/index-DtBNIFuP.d.mts.map +1 -0
  95. package/dist/{index-KMgSCSM7.d.mts → index-HgOLNruQ.d.mts} +1 -1
  96. package/dist/{index-KMgSCSM7.d.mts.map → index-HgOLNruQ.d.mts.map} +1 -1
  97. package/dist/index.d.mts +6 -5
  98. package/dist/index.mjs +3 -2
  99. package/dist/{is-command-CX5rAfZW.mjs → is-command-CEPO9n8c.mjs} +2 -2
  100. package/dist/{is-command-CX5rAfZW.mjs.map → is-command-CEPO9n8c.mjs.map} +1 -1
  101. package/dist/{is-seeder-CYCtELlm.mjs → is-seeder-Gvh_AM71.mjs} +1 -1
  102. package/dist/{is-seeder-CYCtELlm.mjs.map → is-seeder-Gvh_AM71.mjs.map} +1 -1
  103. package/dist/lazy-module-loader-Ib383jH_.d.mts +60 -0
  104. package/dist/lazy-module-loader-Ib383jH_.d.mts.map +1 -0
  105. package/dist/locale-path.service-D-dHiIPc.mjs +165 -0
  106. package/dist/locale-path.service-D-dHiIPc.mjs.map +1 -0
  107. package/dist/locale-url-nZrZxqJP.mjs +44 -0
  108. package/dist/locale-url-nZrZxqJP.mjs.map +1 -0
  109. package/dist/locale-url.service-C2EWmGdq.mjs +41 -0
  110. package/dist/locale-url.service-C2EWmGdq.mjs.map +1 -0
  111. package/dist/logger/index.d.mts +1 -1
  112. package/dist/logger/index.mjs +2 -2
  113. package/dist/logger/index.mjs.map +1 -1
  114. package/dist/macroable/index.d.mts +2 -2
  115. package/dist/macroable/index.mjs +1 -1
  116. package/dist/{macroable-DzlfzT50.mjs → macroable-cvDTFZ_A.mjs} +1 -1
  117. package/dist/{macroable-DzlfzT50.mjs.map → macroable-cvDTFZ_A.mjs.map} +1 -1
  118. package/dist/{metadata-BVkc4aUu.mjs → metadata-DzzprcID.mjs} +1 -1
  119. package/dist/{metadata-BVkc4aUu.mjs.map → metadata-DzzprcID.mjs.map} +1 -1
  120. package/dist/module/index.d.mts +4 -3
  121. package/dist/module/index.d.mts.map +1 -1
  122. package/dist/module/index.mjs +10 -2
  123. package/dist/module/index.mjs.map +1 -0
  124. package/dist/{module-xYoHba6B.mjs → module-registry-Dm-pqHd3.mjs} +189 -57
  125. package/dist/module-registry-Dm-pqHd3.mjs.map +1 -0
  126. package/dist/module.decorator-CYHY6pG5.mjs +19 -0
  127. package/dist/module.decorator-CYHY6pG5.mjs.map +1 -0
  128. package/dist/openapi/index.d.mts +44 -8
  129. package/dist/openapi/index.d.mts.map +1 -1
  130. package/dist/openapi/index.mjs +3 -2
  131. package/dist/{openapi-C6lm0RmV.mjs → openapi-CstuTM8S.mjs} +55 -229
  132. package/dist/openapi-CstuTM8S.mjs.map +1 -0
  133. package/dist/openapi-tools.service-BC5EC3R3.mjs +206 -0
  134. package/dist/openapi-tools.service-BC5EC3R3.mjs.map +1 -0
  135. package/dist/{openapi.service-CrLlsXAd.d.mts → openapi.service-YhTiJ1bO.d.mts} +3 -3
  136. package/dist/{openapi.service-CrLlsXAd.d.mts.map → openapi.service-YhTiJ1bO.d.mts.map} +1 -1
  137. package/dist/quarry/index.d.mts +14 -5
  138. package/dist/quarry/index.d.mts.map +1 -1
  139. package/dist/quarry/index.mjs +6 -5
  140. package/dist/quarry/runner.d.mts +11 -11
  141. package/dist/quarry/runner.d.mts.map +1 -1
  142. package/dist/quarry/runner.mjs +192 -22
  143. package/dist/quarry/runner.mjs.map +1 -1
  144. package/dist/{quarry-registry-D4hIGScf.d.mts → quarry-registry-CXg0RFXq.d.mts} +4 -4
  145. package/dist/quarry-registry-CXg0RFXq.d.mts.map +1 -0
  146. package/dist/{quarry-registry-DkraZNwn.mjs → quarry.module-BuRPGMDm.mjs} +22 -21
  147. package/dist/quarry.module-BuRPGMDm.mjs.map +1 -0
  148. package/dist/queue/index.d.mts +3 -3
  149. package/dist/queue/index.mjs +42 -31
  150. package/dist/queue/index.mjs.map +1 -1
  151. package/dist/queue.module-nddvxzCB.mjs +613 -0
  152. package/dist/queue.module-nddvxzCB.mjs.map +1 -0
  153. package/dist/queue.tokens-DjHnFmre.mjs +11 -0
  154. package/dist/queue.tokens-DjHnFmre.mjs.map +1 -0
  155. package/dist/{r2-storage.provider-Hfm6LdZQ.mjs → r2-storage.provider-DCxQt9dD.mjs} +4 -4
  156. package/dist/{r2-storage.provider-Hfm6LdZQ.mjs.map → r2-storage.provider-DCxQt9dD.mjs.map} +1 -1
  157. package/dist/{rate-limit.decorator-D69zdZbp.mjs → rate-limit.decorator-BPAie_p3.mjs} +3 -3
  158. package/dist/{rate-limit.decorator-D69zdZbp.mjs.map → rate-limit.decorator-BPAie_p3.mjs.map} +1 -1
  159. package/dist/rate-limiter/index.d.mts +5 -5
  160. package/dist/rate-limiter/index.d.mts.map +1 -1
  161. package/dist/rate-limiter/index.mjs +26 -21
  162. package/dist/rate-limiter/index.mjs.map +1 -1
  163. package/dist/route-name-DGoBOfPg.mjs +171 -0
  164. package/dist/route-name-DGoBOfPg.mjs.map +1 -0
  165. package/dist/route-registration.service-D6vSwiKP.mjs +918 -0
  166. package/dist/route-registration.service-D6vSwiKP.mjs.map +1 -0
  167. package/dist/route-registry-CYqLp2Nj.mjs +123 -0
  168. package/dist/route-registry-CYqLp2Nj.mjs.map +1 -0
  169. package/dist/router/index.d.mts +2 -2
  170. package/dist/router/index.mjs +18 -8
  171. package/dist/router-CWGBD-Bg.mjs +78 -0
  172. package/dist/router-CWGBD-Bg.mjs.map +1 -0
  173. package/dist/router-resolver-D4YlPNlm.mjs +88 -0
  174. package/dist/router-resolver-D4YlPNlm.mjs.map +1 -0
  175. package/dist/seeder/index.d.mts +14 -4
  176. package/dist/seeder/index.d.mts.map +1 -1
  177. package/dist/seeder/index.mjs +5 -3
  178. package/dist/{seeder-BADTig4n.mjs → seeder-7ubkms-Y.mjs} +7 -56
  179. package/dist/seeder-7ubkms-Y.mjs.map +1 -0
  180. package/dist/seeder-registry-CyUmKsJq.mjs +57 -0
  181. package/dist/seeder-registry-CyUmKsJq.mjs.map +1 -0
  182. package/dist/seeder.module-CYYwk3Qk.mjs +15 -0
  183. package/dist/seeder.module-CYYwk3Qk.mjs.map +1 -0
  184. package/dist/{signed-url-BqUqt5dF.mjs → signed-url-DIU0sK_6.mjs} +1 -1
  185. package/dist/{signed-url-BqUqt5dF.mjs.map → signed-url-DIU0sK_6.mjs.map} +1 -1
  186. package/dist/storage/index.d.mts +3 -3
  187. package/dist/storage/index.d.mts.map +1 -1
  188. package/dist/storage/index.mjs +2 -2
  189. package/dist/storage/providers/index.d.mts +2 -2
  190. package/dist/storage/providers/index.d.mts.map +1 -1
  191. package/dist/storage/providers/index.mjs +1 -1
  192. package/dist/{storage-BA3ppVYM.mjs → storage-MDZypIE9.mjs} +12 -11
  193. package/dist/{storage-BA3ppVYM.mjs.map → storage-MDZypIE9.mjs.map} +1 -1
  194. package/dist/{storage-provider.interface-DQMtT42e.d.mts → storage-provider.interface-ClUwxz4S.d.mts} +2 -2
  195. package/dist/storage-provider.interface-ClUwxz4S.d.mts.map +1 -0
  196. package/dist/storage.error-Dnib4VHc.mjs +8 -0
  197. package/dist/{storage.error-C6FY037a.mjs.map → storage.error-Dnib4VHc.mjs.map} +1 -1
  198. package/dist/{stratal-Bdq4IdB3.mjs → stratal-DL9M38_s.mjs} +142 -140
  199. package/dist/stratal-DL9M38_s.mjs.map +1 -0
  200. package/dist/{stratal-BsKmvP6J.d.mts → stratal-DwDJPY9N.d.mts} +3 -3
  201. package/dist/{stratal-BsKmvP6J.d.mts.map → stratal-DwDJPY9N.d.mts.map} +1 -1
  202. package/dist/tiered-cache.service-Dv3BhxxE.d.mts +79 -0
  203. package/dist/tiered-cache.service-Dv3BhxxE.d.mts.map +1 -0
  204. package/dist/trailing-slash-CFyw8nYu.mjs +34 -0
  205. package/dist/trailing-slash-CFyw8nYu.mjs.map +1 -0
  206. package/dist/{types-BaeHi67f.d.mts → types-CmV_9xBD.d.mts} +1 -1
  207. package/dist/types-CmV_9xBD.d.mts.map +1 -0
  208. package/dist/uri-h7Q8Jug9.mjs +251 -0
  209. package/dist/uri-h7Q8Jug9.mjs.map +1 -0
  210. package/dist/{usage-generator-DTqaUMR9.mjs → usage-generator-DAWYasuP.mjs} +4 -4
  211. package/dist/usage-generator-DAWYasuP.mjs.map +1 -0
  212. package/dist/{validation-DUzcjb8Q.mjs → validation-CpOjviyT.mjs} +6 -6
  213. package/dist/{validation-DUzcjb8Q.mjs.map → validation-CpOjviyT.mjs.map} +1 -1
  214. package/dist/{validation.context-XTysWJ3b.mjs → validation.context-CRvmrhq7.mjs} +3 -3
  215. package/dist/{validation.context-XTysWJ3b.mjs.map → validation.context-CRvmrhq7.mjs.map} +1 -1
  216. package/dist/versioning.service-C6aHky8-.mjs +36 -0
  217. package/dist/versioning.service-C6aHky8-.mjs.map +1 -0
  218. package/dist/websocket/index.d.mts +11 -2
  219. package/dist/websocket/index.d.mts.map +1 -1
  220. package/dist/websocket/index.mjs +1 -1
  221. package/dist/workers/index.d.mts +2 -2
  222. package/dist/workers/index.d.mts.map +1 -1
  223. package/dist/workers/index.mjs +3 -3
  224. package/dist/workers/index.mjs.map +1 -1
  225. package/dist/{zod-hMa3rSHV.mjs → zod-eKqqhZ5_.mjs} +2 -2
  226. package/dist/{zod-hMa3rSHV.mjs.map → zod-eKqqhZ5_.mjs.map} +1 -1
  227. package/dist/{zod-DvWTfRpI.d.mts → zod-wecrEVAs.d.mts} +8 -3
  228. package/dist/zod-wecrEVAs.d.mts.map +1 -0
  229. package/package.json +19 -30
  230. package/dist/base-email.provider-BWZHIjt8.mjs +0 -42
  231. package/dist/base-email.provider-BWZHIjt8.mjs.map +0 -1
  232. package/dist/cache.service-e34gV6tz.d.mts.map +0 -1
  233. package/dist/cache.tokens-ovi_c52J.mjs +0 -6
  234. package/dist/cache.tokens-ovi_c52J.mjs.map +0 -1
  235. package/dist/colors-axmupKdp.mjs +0 -16
  236. package/dist/colors-axmupKdp.mjs.map +0 -1
  237. package/dist/command-BU4ApTo5.mjs.map +0 -1
  238. package/dist/command-wXfvHbBZ.d.mts.map +0 -1
  239. package/dist/consumer-registry-DHQtypr1.d.mts.map +0 -1
  240. package/dist/cron-manager-9bpN9bu4.mjs.map +0 -1
  241. package/dist/cron-manager-CSTIBPcM.d.mts +0 -124
  242. package/dist/cron-manager-CSTIBPcM.d.mts.map +0 -1
  243. package/dist/di-BO1QIb5H.mjs.map +0 -1
  244. package/dist/env-DKSbuBi5.d.mts.map +0 -1
  245. package/dist/events-D1KdDaiP.mjs.map +0 -1
  246. package/dist/index-B4UBK-2T.d.mts.map +0 -1
  247. package/dist/index-BtlE9RuO.d.mts.map +0 -1
  248. package/dist/index-CW1YHSft.d.mts.map +0 -1
  249. package/dist/index-DEncMcC6.d.mts.map +0 -1
  250. package/dist/index-Dj5IMwtr.d.mts.map +0 -1
  251. package/dist/module-xYoHba6B.mjs.map +0 -1
  252. package/dist/openapi-C6lm0RmV.mjs.map +0 -1
  253. package/dist/quarry-registry-D4hIGScf.d.mts.map +0 -1
  254. package/dist/quarry-registry-DkraZNwn.mjs.map +0 -1
  255. package/dist/queue.module-DeWJ0tQM.mjs +0 -355
  256. package/dist/queue.module-DeWJ0tQM.mjs.map +0 -1
  257. package/dist/resend.provider-Ur6tU7fK.mjs +0 -68
  258. package/dist/resend.provider-Ur6tU7fK.mjs.map +0 -1
  259. package/dist/router-Cy6DjkvP.mjs +0 -1852
  260. package/dist/router-Cy6DjkvP.mjs.map +0 -1
  261. package/dist/seeder-BADTig4n.mjs.map +0 -1
  262. package/dist/smtp.provider-C129sNBT.mjs +0 -76
  263. package/dist/smtp.provider-C129sNBT.mjs.map +0 -1
  264. package/dist/storage-provider.interface-DQMtT42e.d.mts.map +0 -1
  265. package/dist/storage.error-C6FY037a.mjs +0 -8
  266. package/dist/stratal-Bdq4IdB3.mjs.map +0 -1
  267. package/dist/types-BaeHi67f.d.mts.map +0 -1
  268. package/dist/usage-generator-DTqaUMR9.mjs.map +0 -1
  269. package/dist/zod-DvWTfRpI.d.mts.map +0 -1
  270. /package/dist/{chunk-D1SwGrFN.mjs → chunk-BBjsoOtd.mjs} +0 -0
@@ -1,1852 +0,0 @@
1
- import { n as getContainer, r as runWithContainer } from "./container-storage-GpNNz79X.mjs";
2
- import { _ as getMethodInjections, c as DI_TOKENS, l as Request, m as inject, o as ROUTER_TOKENS, s as CONTAINER_TOKEN, u as Singleton } from "./di-BO1QIb5H.mjs";
3
- import { n as getMetadata, t as defineMetadata } from "./metadata-BVkc4aUu.mjs";
4
- import { n as __decorateParam, t as __decorate } from "./decorate-HgTKAYK8.mjs";
5
- import { LOGGER_TOKENS } from "./logger/index.mjs";
6
- import { d as abort, u as HttpException } from "./errors-BBZTnjdq.mjs";
7
- import { a as RouterContext, c as METHOD_STATUS_CODES, d as SECURITY_SCHEMES, f as VERSION_NEUTRAL, l as ROUTER_CONTEXT_KEYS, o as DEFAULT_CONTENT_TYPE, r as createHttpExceptionContext, s as HTTP_METHODS, u as ROUTE_METADATA_KEYS } from "./exception-context-B4kM-M53.mjs";
8
- import { o as RouterError, s as createThrottleMiddleware } from "./module-xYoHba6B.mjs";
9
- import { t as I18N_TOKENS } from "./i18n.tokens-hwRpmjRq.mjs";
10
- import { i as zod_exports, r as z, t as OpenAPIHono } from "./zod-hMa3rSHV.mjs";
11
- import { a as OPENAPI_TOKENS } from "./openapi-C6lm0RmV.mjs";
12
- import { i as getMethodGuards, r as getControllerGuards, t as GuardExecutionService } from "./guards-Ced-uNIF.mjs";
13
- import { n as getRateLimits } from "./rate-limit.decorator-D69zdZbp.mjs";
14
- import { n as getControllerOptions, r as getControllerRoute } from "./controller.decorator-DIUazNU7.mjs";
15
- import { a as getWsOnCloseMethod, o as getWsOnErrorMethod, s as getWsOnMessageMethod, t as GatewayContext, u as isGateway } from "./gateway-context-CFe6a9gz.mjs";
16
- import "./http-method.decorator-CdjKFJZZ.mjs";
17
- import { n as verifySignedUrl, t as signUrl } from "./signed-url-BqUqt5dF.mjs";
18
- import { languageDetector } from "hono/language";
19
- //#region src/router/errors/route-not-found.error.ts
20
- var RouteNotFoundError = class extends HttpException {
21
- path;
22
- method;
23
- constructor(path, method) {
24
- super(404, `Route not found: ${method} ${path}`);
25
- this.path = path;
26
- this.method = method;
27
- }
28
- };
29
- //#endregion
30
- //#region src/router/errors/schema-validation.error.ts
31
- var SchemaValidationError = class extends HttpException {
32
- issues;
33
- constructor(zodError) {
34
- super(400, "Schema validation failed");
35
- this.issues = zodError.issues.map((err) => ({
36
- path: err.path.join("."),
37
- message: err.message,
38
- code: err.code
39
- }));
40
- }
41
- };
42
- //#endregion
43
- //#region src/router/errors/index.ts
44
- /**
45
- * Error thrown when a signed URL has an invalid or expired signature.
46
- *
47
- * HTTP Status: 403 Forbidden
48
- */
49
- var InvalidSignatureError = class extends HttpException {
50
- constructor() {
51
- super(403, "Invalid or expired signature");
52
- }
53
- };
54
- /**
55
- * ResponseValidationError
56
- *
57
- * Thrown when a controller's response body does not match the declared Zod response schema.
58
- * Indicates a server-side schema mismatch — the controller is returning data that
59
- * violates its own API contract.
60
- */
61
- var ResponseValidationError = class extends HttpException {
62
- issues;
63
- constructor(zodError) {
64
- super(500, "Response validation failed");
65
- this.issues = zodError.issues.map((err) => ({
66
- path: err.path.join("."),
67
- message: err.message,
68
- code: err.code
69
- }));
70
- }
71
- };
72
- //#endregion
73
- //#region src/router/middleware/logger.middleware.ts
74
- /**
75
- * Create a Hono middleware that logs HTTP requests using our Logger service
76
- *
77
- * Logs request method, path, status code, and duration in milliseconds.
78
- * Format: [HTTP] METHOD /path -> STATUS (duration ms)
79
- *
80
- * @param logger - Logger service instance
81
- * @returns Hono middleware handler
82
- *
83
- * @example
84
- * ```typescript
85
- * const logger = container.resolve<LoggerService>(LOGGER_TOKENS.LoggerService)
86
- * app.use('*', createLoggerMiddleware(logger))
87
- * ```
88
- */
89
- function createLoggerMiddleware(logger) {
90
- return async (c, next) => {
91
- const start = Date.now();
92
- const method = c.req.method;
93
- const path = c.req.path;
94
- await next();
95
- const duration = Date.now() - start;
96
- const status = c.res.status;
97
- logger.info(`[HTTP] ${method} ${path} -> ${status}`, {
98
- method,
99
- path,
100
- status,
101
- duration
102
- });
103
- };
104
- }
105
- //#endregion
106
- //#region src/router/middleware/domain.middleware.ts
107
- /**
108
- * Parse a domain pattern into a regex and extract parameter names.
109
- *
110
- * @example
111
- * parseDomainPattern('{tenant}.example.com')
112
- * // => { regex: /^([^.]+)\.example\.com$/, paramNames: ['tenant'] }
113
- *
114
- * parseDomainPattern('{region}.{tenant}.example.com')
115
- * // => { regex: /^([^.]+)\.([^.]+)\.example\.com$/, paramNames: ['region', 'tenant'] }
116
- */
117
- function parseDomainPattern(pattern) {
118
- const paramNames = [];
119
- const escaped = pattern.replace(/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g, (_match, paramName) => {
120
- paramNames.push(paramName);
121
- return "([^.]+)";
122
- }).replace(/\./g, "\\.");
123
- return {
124
- regex: new RegExp(`^${escaped}$`),
125
- paramNames
126
- };
127
- }
128
- /**
129
- * Strip port number from a host header value.
130
- * 'example.com:8787' => 'example.com'
131
- */
132
- function stripPort(host) {
133
- const colonIdx = host.lastIndexOf(":");
134
- if (colonIdx === -1) return host;
135
- const afterColon = host.slice(colonIdx + 1);
136
- return /^\d+$/.test(afterColon) ? host.slice(0, colonIdx) : host;
137
- }
138
- /**
139
- * Create a Hono middleware that matches the request host against a domain pattern.
140
- *
141
- * When the host matches, domain parameters are extracted and stored in context
142
- * variables accessible via `ctx.domain(key)`.
143
- *
144
- * When the host does NOT match, aborts with 404.
145
- *
146
- * @param pattern - Domain pattern with `{param}` placeholders (e.g., '{tenant}.myapp.com')
147
- *
148
- * @example
149
- * ```typescript
150
- * // Applied automatically by RouteRegistrationService for controllers with domain config
151
- * @Controller('/dashboard', { domain: '{tenant}.myapp.com' })
152
- * export class DashboardController {
153
- * async index(ctx: RouterContext) {
154
- * const tenant = ctx.domain('tenant')
155
- * }
156
- * }
157
- * ```
158
- */
159
- function createDomainMiddleware(pattern) {
160
- const { regex, paramNames } = parseDomainPattern(pattern);
161
- return async (c, next) => {
162
- const host = stripPort(c.req.header("host") ?? "");
163
- const match = regex.exec(host);
164
- if (!match) abort(404, "Domain mismatch");
165
- for (let i = 0; i < paramNames.length; i++) c.set(`domain:${paramNames[i]}`, match[i + 1]);
166
- await next();
167
- };
168
- }
169
- //#endregion
170
- //#region src/router/middleware/middleware-chain.ts
171
- /**
172
- * Create a Hono middleware handler that executes a chain of Stratal middleware classes.
173
- *
174
- * Each middleware is resolved from the request-scoped container per request,
175
- * then executed in order (first registered = outermost in the chain).
176
- *
177
- * @param classes - Middleware classes to chain
178
- * @returns Hono middleware handler
179
- */
180
- function createMiddlewareChain(classes) {
181
- return async (c, next) => {
182
- const requestContainer = c.get(ROUTER_CONTEXT_KEYS.REQUEST_CONTAINER);
183
- const ctx = new RouterContext(c);
184
- let current = next;
185
- for (let i = classes.length - 1; i >= 0; i--) {
186
- const prevNext = current;
187
- const middlewareClass = classes[i];
188
- current = () => {
189
- const middleware = requestContainer.resolve(middlewareClass);
190
- let called = false;
191
- const guardedNext = () => {
192
- if (called) return Promise.reject(new RouterError(`Middleware "${middlewareClass.name ?? "anonymous"}" called next() multiple times`));
193
- called = true;
194
- return prevNext();
195
- };
196
- return middleware.handle(ctx, guardedNext);
197
- };
198
- }
199
- const result = await current();
200
- if (result instanceof Response) return result;
201
- };
202
- }
203
- //#endregion
204
- //#region src/router/trailing-slash.ts
205
- /**
206
- * Apply a trailing-slash mode to a URL or path.
207
- *
208
- * - `'ignore'` — return as-is.
209
- * - `'always'` — append `/` to the pathname unless it already has one.
210
- * Skipped when the last segment contains `.` (file-like paths) and for the
211
- * root `/` path.
212
- * - `'never'` — strip a trailing `/` from the pathname. Skipped for root.
213
- *
214
- * Preserves query string and hash. Handles both relative paths
215
- * (`/foo?x=1`) and absolute URLs (`https://host/foo?x=1`).
216
- *
217
- * Used by URL-generation helpers and the redirect middleware so canonical
218
- * form is computed in one place.
219
- */
220
- function applyTrailingSlash(url, mode) {
221
- if (mode === "ignore") return url;
222
- const isAbsolute = /^https?:\/\//i.test(url);
223
- const parsed = isAbsolute ? new URL(url) : new URL(url, "http://placeholder.local");
224
- const path = parsed.pathname;
225
- if (path === "/") return url;
226
- const hasTrailing = path.endsWith("/");
227
- if (mode === "always" && !hasTrailing) {
228
- if (path.slice(path.lastIndexOf("/") + 1).includes(".")) return url;
229
- parsed.pathname = `${path}/`;
230
- } else if (mode === "never" && hasTrailing) parsed.pathname = path.slice(0, -1);
231
- else return url;
232
- return isAbsolute ? parsed.toString() : `${parsed.pathname}${parsed.search}${parsed.hash}`;
233
- }
234
- //#endregion
235
- //#region src/router/middleware/trailing-slash-redirect.ts
236
- const REDIRECT_STATUS = 308;
237
- /**
238
- * Create a Hono middleware that canonicalises trailing slashes via 308 redirects.
239
- *
240
- * - `'ignore'` — returns `null`; routes match both `/foo` and `/foo/` natively
241
- * (Hono handles this when constructed with `strict: false`).
242
- * - `'always'` — non-trailing requests redirect to the trailing-slash form.
243
- * Paths whose last segment contains `.` (e.g. `/api/openapi.json`) are skipped.
244
- * - `'never'` — trailing requests redirect to the non-trailing form.
245
- *
246
- * Root (`/`) is always passed through unchanged.
247
- *
248
- * 308 is used so that POST/PUT/PATCH bodies survive the redirect.
249
- *
250
- * Location headers are emitted as path-relative URIs so the user agent
251
- * resolves them against the effective request URI — sidestepping scheme
252
- * mismatches behind HTTPS-terminating proxies that proxy HTTPS pages to an
253
- * HTTP-speaking backend (which would otherwise produce a mixed-content block).
254
- */
255
- function createTrailingSlashRedirect(mode) {
256
- if (mode === "ignore") return null;
257
- return async (c, next) => {
258
- const url = new URL(c.req.url);
259
- const canonicalPath = applyTrailingSlash(url.pathname, mode);
260
- if (canonicalPath === url.pathname) return next();
261
- return c.redirect(`${canonicalPath}${url.search}`, REDIRECT_STATUS);
262
- };
263
- }
264
- //#endregion
265
- //#region src/router/decorators/route.decorator.ts
266
- /**
267
- * Decorator to add OpenAPI metadata to a controller method using convention-based routing.
268
- *
269
- * **Cannot be mixed with HTTP method decorators** (`@Get`, `@Post`, `@Put`, `@Patch`,
270
- * `@Delete`, `@All`) in the same controller. Use one pattern or the other.
271
- *
272
- * Stores route configuration (schemas, response, tags, security) in metadata.
273
- * HTTP method, path, and success status code are auto-derived from the method name:
274
- * - index() → GET /base-path → 200
275
- * - show() → GET /base-path/:id → 200
276
- * - create() → POST /base-path → 201
277
- * - update() → PUT /base-path/:id → 200
278
- * - patch() → PATCH /base-path/:id → 200
279
- * - destroy() → DELETE /base-path/:id → 200
280
- *
281
- * @param config - Route configuration (schemas, response, tags, security)
282
- *
283
- * @example
284
- * ```typescript
285
- * @Controller('/api/v1/notes', {
286
- * tags: ['Notes'],
287
- * security: ['bearerAuth']
288
- * })
289
- * export class NotesController implements Controller {
290
- * @Route({
291
- * body: createNoteSchema,
292
- * response: noteSchema, // 201 auto-derived from 'create' method
293
- * tags: ['Mutations'],
294
- * description: 'Create a new note'
295
- * })
296
- * async create(ctx: RouterContext): Promise<Response> {
297
- * // POST /api/v1/notes (auto-derived from method name)
298
- * // Body schema: createNoteSchema (auto-validated)
299
- * // Response: 201 → noteSchema (status auto-derived)
300
- * // Tags: ['Notes', 'Mutations'] (merged with controller)
301
- * // Security: ['bearerAuth'] (inherited from controller)
302
- * const body = ctx.body()
303
- * const note = await this.notesService.create(body)
304
- * return ctx.json(note, 201)
305
- * }
306
- *
307
- * @Route({
308
- * query: paginationSchema,
309
- * response: z.array(noteSchema) // 200 auto-derived from 'index' method
310
- * })
311
- * async index(ctx: RouterContext): Promise<Response> {
312
- * // GET /api/v1/notes (auto-derived)
313
- * // Query params auto-validated
314
- * const notes = await this.notesService.list()
315
- * return ctx.json(notes)
316
- * }
317
- *
318
- * @Route({
319
- * params: z.object({ id: z.string().uuid() }),
320
- * response: {
321
- * schema: noteSchema,
322
- * description: 'Note details'
323
- * },
324
- * security: [] // Override to make public
325
- * })
326
- * async show(ctx: RouterContext): Promise<Response> {
327
- * // GET /api/v1/notes/:id (auto-derived)
328
- * // URL params auto-validated
329
- * // Response: 200 → noteSchema (status auto-derived)
330
- * // Security: [] (public route, override controller security)
331
- * const id = ctx.param('id')
332
- * const note = await this.notesService.findById(id)
333
- * return ctx.json(note)
334
- * }
335
- * }
336
- * ```
337
- */
338
- function Route(config) {
339
- return function(target, propertyKey, descriptor) {
340
- const metadata = {
341
- type: "convention",
342
- config
343
- };
344
- defineMetadata(ROUTE_METADATA_KEYS.ROUTE_CONFIG, metadata, target, propertyKey);
345
- const existing = getMetadata(ROUTE_METADATA_KEYS.DECORATED_METHODS, target) ?? [];
346
- existing.push(propertyKey);
347
- defineMetadata(ROUTE_METADATA_KEYS.DECORATED_METHODS, existing, target);
348
- return descriptor;
349
- };
350
- }
351
- /**
352
- * Get the route metadata from a controller method
353
- *
354
- * @param target - Controller instance or prototype
355
- * @param methodName - Name of the method
356
- * @returns Route metadata or undefined if not decorated
357
- */
358
- function getRouteMetadata(target, methodName) {
359
- return getMetadata(ROUTE_METADATA_KEYS.ROUTE_CONFIG, target, methodName);
360
- }
361
- /**
362
- * Get all methods with route decorators (@Route, @Get, @Post, etc.) from a controller
363
- *
364
- * @param ControllerClass - Controller class
365
- * @returns Array of method names that have route metadata
366
- */
367
- function getRouteDecoratedMethods(ControllerClass) {
368
- const methods = /* @__PURE__ */ new Set();
369
- let proto = ControllerClass.prototype;
370
- while (proto && proto !== Object.prototype) {
371
- const own = getMetadata(ROUTE_METADATA_KEYS.DECORATED_METHODS, proto);
372
- if (own) for (const m of own) methods.add(m);
373
- proto = Object.getPrototypeOf(proto);
374
- }
375
- return [...methods];
376
- }
377
- //#endregion
378
- //#region src/router/schemas/common.schemas.ts
379
- /**
380
- * Common OpenAPI Schemas
381
- *
382
- * Reusable schema definitions for common API patterns:
383
- * - Error responses
384
- * - Pagination
385
- * - Common parameters
386
- */
387
- /**
388
- * Generic error response schema
389
- * Used for all error responses (4xx, 5xx)
390
- * Matches the ErrorResponse shape produced by ExceptionHandler
391
- */
392
- const errorResponseSchema = z.object({
393
- message: z.string().describe("Human-readable error message"),
394
- timestamp: z.string().datetime().describe("ISO timestamp when error occurred"),
395
- stack: z.string().optional().describe("Stack trace (development only)")
396
- }).openapi("ErrorResponse");
397
- /**
398
- * Validation error response schema
399
- * Used for 400 Bad Request with validation failures
400
- * Matches the ErrorResponse shape produced by ExceptionHandler
401
- */
402
- const validationErrorResponseSchema = errorResponseSchema.openapi("ValidationErrorResponse");
403
- /**
404
- * Pagination query parameters schema
405
- * Used for list endpoints
406
- */
407
- const paginationQuerySchema = z.object({
408
- page: z.coerce.number().int().positive().default(1).describe("Page number (1-indexed)"),
409
- limit: z.coerce.number().int().positive().max(100).default(20).describe("Items per page (max 100)")
410
- }).openapi("PaginationQuery");
411
- /**
412
- * Paginated response wrapper schema
413
- * Generic wrapper for paginated list responses
414
- */
415
- const paginatedResponseSchema = (itemSchema) => z.object({
416
- data: z.array(itemSchema).describe("Array of items for current page"),
417
- pagination: z.object({
418
- page: z.number().int().positive().describe("Current page number"),
419
- limit: z.number().int().positive().describe("Items per page"),
420
- total: z.number().int().nonnegative().describe("Total number of items"),
421
- totalPages: z.number().int().nonnegative().describe("Total number of pages")
422
- })
423
- });
424
- /**
425
- * UUID parameter schema
426
- * Used for :id parameters in RESTful routes
427
- */
428
- const uuidParamSchema = z.object({ id: z.string().uuid().describe("Resource UUID") }).openapi("UUIDParam");
429
- /**
430
- * Success message response schema
431
- * Used for operations that don't return data (e.g., DELETE)
432
- */
433
- const successMessageSchema = z.object({
434
- message: z.string().describe("Success message"),
435
- data: z.record(z.string(), z.unknown()).optional().describe("Optional additional data")
436
- }).openapi("SuccessMessage");
437
- /**
438
- * Common HTTP status error schemas
439
- * Pre-configured for standard error responses
440
- */
441
- const commonErrorSchemas = {
442
- 400: {
443
- schema: validationErrorResponseSchema,
444
- description: "Validation error"
445
- },
446
- 401: {
447
- schema: errorResponseSchema,
448
- description: "Unauthorized"
449
- },
450
- 403: {
451
- schema: errorResponseSchema,
452
- description: "Forbidden"
453
- },
454
- 404: {
455
- schema: errorResponseSchema,
456
- description: "Not found"
457
- },
458
- 409: {
459
- schema: errorResponseSchema,
460
- description: "Conflict"
461
- },
462
- 500: {
463
- schema: errorResponseSchema,
464
- description: "Internal server error"
465
- }
466
- };
467
- //#endregion
468
- //#region src/router/utils/path.ts
469
- /**
470
- * Path normalization and route ordering utilities.
471
- *
472
- * Users always write Hono-style `:param` paths (`:companyId`, `:id`).
473
- * OpenAPI requires `{param}` style — conversion happens only at registration time.
474
- */
475
- /**
476
- * Convert Hono-style `:param` path segments to OpenAPI-style `{param}`.
477
- * Strips regex constraints (e.g., `:locale{sw}` → `{locale}`).
478
- *
479
- * @example
480
- * toOpenAPIPath('/users/:id') // '/users/{id}'
481
- * toOpenAPIPath('/:companyId/users/:userId') // '/{companyId}/users/{userId}'
482
- * toOpenAPIPath('/users/:id/posts') // '/users/{id}/posts'
483
- * toOpenAPIPath('/:locale{en|fr}/users') // '/{locale}/users'
484
- */
485
- function toOpenAPIPath(path) {
486
- return path.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)(\{[^}]*\})?/g, "{$1}");
487
- }
488
- /**
489
- * Convert Hono-style `:param` path segments to OpenAPI-style `{param}`,
490
- * preserving regex constraints.
491
- *
492
- * Used for Hono route registration via `app.openapi()`. The non-greedy
493
- * regex in `@hono/zod-openapi` (`\/{(.+?)}/g`) converts `{param}` back
494
- * to `:param` while leaving the constraint suffix intact.
495
- *
496
- * @example
497
- * toRoutingOpenAPIPath('/:locale{sw}/users/:id') // '/{locale}{sw}/users/{id}'
498
- * toRoutingOpenAPIPath('/users/:id') // '/users/{id}'
499
- */
500
- function toRoutingOpenAPIPath(path) {
501
- return path.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)(\{[^}]*\})?/g, (_, name, constraint) => constraint ? `{${name}}${constraint}` : `{${name}}`);
502
- }
503
- /**
504
- * Compute a packed specificity key for route ordering.
505
- * Encodes both score and segment count into a single number to avoid object allocation.
506
- *
507
- * Lower score = higher priority (registered first in Hono).
508
- * Scoring: static = 0, `:param{constraint}` = 5, `:param` = 10, wildcard `{.+}` / `{.*}` = 100.
509
- *
510
- * Packed as: score * 10000 - segmentCount (negative segment count so more segments = lower key = higher priority)
511
- *
512
- * Locale variants score against the path with the leading `/:locale{…}` segment
513
- * stripped — the variant's score therefore matches its primary, but its larger
514
- * segment count makes it sort just before the primary. Without this, a primary
515
- * catch-all (e.g. `/:slug{.+}`) gobbles locale-prefixed URLs because Hono picks
516
- * whichever matching route was registered first.
517
- */
518
- function getPathSpecificityKey(route) {
519
- const segmentCount = countSegments(route.path);
520
- const scoringPath = route.isLocaleVariant ? route.path.replace(/^\/:locale\{[^}]*\}/, "") || "/" : route.path;
521
- let score = 0;
522
- let i = 0;
523
- while (i < scoringPath.length) {
524
- if (scoringPath.charCodeAt(i) === 47) {
525
- i++;
526
- continue;
527
- }
528
- let end = scoringPath.indexOf("/", i);
529
- if (end === -1) end = scoringPath.length;
530
- const segment = scoringPath.substring(i, end);
531
- if (segment.includes("{.+}") || segment.includes("{.*}")) score += 100;
532
- else if (segment.charCodeAt(0) === 58) score += segment.includes("{") ? 5 : 10;
533
- i = end;
534
- }
535
- return score * 1e4 - segmentCount;
536
- }
537
- function countSegments(path) {
538
- let count = 0;
539
- let i = 0;
540
- while (i < path.length) {
541
- if (path.charCodeAt(i) === 47) {
542
- i++;
543
- continue;
544
- }
545
- let end = path.indexOf("/", i);
546
- if (end === -1) end = path.length;
547
- count++;
548
- i = end;
549
- }
550
- return count;
551
- }
552
- /**
553
- * Compute a specificity score for route ordering.
554
- * Lower score = higher priority (registered first in Hono).
555
- *
556
- * Scoring: static = 0, `:param{constraint}` = 5, `:param` = 10, wildcard `{.+}` / `{.*}` = 100.
557
- */
558
- function getPathSpecificityScore(path) {
559
- const segments = path.split("/").filter(Boolean);
560
- let score = 0;
561
- for (const segment of segments) if (segment.includes("{.+}") || segment.includes("{.*}")) score += 100;
562
- else if (segment.startsWith(":") && segment.includes("{")) score += 5;
563
- else if (segment.startsWith(":")) score += 10;
564
- return score;
565
- }
566
- /**
567
- * Sort routes by specificity so Hono registers them in the correct order.
568
- *
569
- * 1. Static paths before parameterized before wildcards
570
- * 2. More segments = more specific (tie-breaker)
571
- * 3. Locale-prefixed variants before their primary (so a locale-prefixed
572
- * request matches the variant first; a primary catch-all would otherwise
573
- * swallow the locale prefix into its param)
574
- */
575
- function sortRoutesBySpecificity(routes) {
576
- const keys = /* @__PURE__ */ new Map();
577
- for (const route of routes) keys.set(route, getPathSpecificityKey(route));
578
- const copy = routes.slice();
579
- copy.sort((a, b) => keys.get(a) - keys.get(b));
580
- return copy;
581
- }
582
- //#endregion
583
- //#region src/router/utils/route-name.ts
584
- /**
585
- * Route naming utilities.
586
- *
587
- * Extracts parameter names from paths and domains,
588
- * and generates convention-based route names.
589
- */
590
- /**
591
- * Extract parameter names from a Hono-style path.
592
- *
593
- * @example
594
- * extractParamNames('/users/:id') // ['id']
595
- * extractParamNames('/:companyId/users/:userId') // ['companyId', 'userId']
596
- * extractParamNames('/users') // []
597
- */
598
- function extractParamNames(path) {
599
- if (!path.includes(":")) return [];
600
- return [...path.matchAll(/:([a-zA-Z_][a-zA-Z0-9_]*)/g)].map((m) => m[1]);
601
- }
602
- /**
603
- * Extract parameter names from a domain pattern.
604
- *
605
- * @example
606
- * extractDomainParamNames('{tenant}.example.com') // ['tenant']
607
- * extractDomainParamNames('{region}.{tenant}.example.com') // ['region', 'tenant']
608
- * extractDomainParamNames('example.com') // []
609
- */
610
- function extractDomainParamNames(domain) {
611
- if (!domain.includes("{")) return [];
612
- return [...domain.matchAll(/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g)].map((m) => m[1]);
613
- }
614
- /**
615
- * Auto-generate a route name for convention-based `@Route` methods.
616
- *
617
- * Strips common prefixes (`/api/`, `/v{N}/`) and parameter segments,
618
- * then joins remaining static segments with dots and appends the method name.
619
- *
620
- * @example
621
- * generateConventionRouteName('/users', 'index') // 'users.index'
622
- * generateConventionRouteName('/users', 'show') // 'users.show'
623
- * generateConventionRouteName('/api/v1/users', 'create') // 'users.create'
624
- * generateConventionRouteName('/api/v1/users/:userId/notes', 'index') // 'users.notes.index'
625
- * generateConventionRouteName('/:companyId/users', 'index') // 'users.index'
626
- * generateConventionRouteName('/users/:userId/notes/:noteId/tags', 'index') // 'users.notes.tags.index'
627
- */
628
- function generateConventionRouteName(basePath, methodName) {
629
- const parts = basePath.split("/");
630
- const segments = [];
631
- for (const s of parts) if (s && s !== "api" && !s.startsWith(":") && !/^v\d+$/.test(s)) segments.push(s);
632
- if (segments.length === 0) return methodName;
633
- return `${segments.join(".")}.${methodName}`;
634
- }
635
- //#endregion
636
- //#region src/router/services/route-registration.service.ts
637
- const invokeHandler = (instance, method, ...args) => {
638
- try {
639
- return Promise.resolve(instance[method](...args));
640
- } catch (err) {
641
- return Promise.reject(err);
642
- }
643
- };
644
- let RouteRegistrationService = class RouteRegistrationService {
645
- logger;
646
- registry;
647
- routerResolver;
648
- localePathService;
649
- app;
650
- moduleRegistry;
651
- controllerClasses = /* @__PURE__ */ new Map();
652
- upgradeWebSocketFn = null;
653
- constructor(logger, registry, routerResolver, localePathService, app, moduleRegistry) {
654
- this.logger = logger;
655
- this.registry = registry;
656
- this.routerResolver = routerResolver;
657
- this.localePathService = localePathService;
658
- this.app = app;
659
- this.moduleRegistry = moduleRegistry;
660
- }
661
- /**
662
- * Configure router with controllers and global middleware.
663
- * Resolves controllers from ModuleRegistry and global middleware from RouterResolver.
664
- */
665
- async configure() {
666
- const controllers = this.moduleRegistry.getAllControllers();
667
- const globalMiddleware = this.routerResolver?.getGlobalMiddleware() ?? [];
668
- this.logger.info("Registering controllers", { controllerCount: controllers.length });
669
- if (globalMiddleware.length > 0) this.app.use("*", createMiddlewareChain(globalMiddleware));
670
- if (controllers.some(isGateway)) {
671
- const { upgradeWebSocket } = await import("hono/cloudflare-workers");
672
- this.upgradeWebSocketFn = upgradeWebSocket;
673
- }
674
- const actions = /* @__PURE__ */ new WeakMap();
675
- for (const ControllerClass of controllers) this.collectRoutes(ControllerClass, actions);
676
- for (const route of this.registry.all()) actions.get(route)?.();
677
- this.logger.info("Controller registration complete");
678
- }
679
- /**
680
- * Pass 1: Collect routes from a controller into RouteRegistry and store Hono actions.
681
- * Versioning and locale expansion are handled by RouteRegistry.register().
682
- */
683
- collectRoutes(ControllerClass, actions) {
684
- const isWsGateway = isGateway(ControllerClass);
685
- const controllerRoute = getControllerRoute(ControllerClass);
686
- if (!controllerRoute) throw new RouterError(`Controller "${ControllerClass.name}" registration failed: ${isWsGateway ? "Missing @Gateway decorator or route metadata" : "Missing @Controller decorator or route metadata"}`);
687
- const controllerOpts = getControllerOptions(ControllerClass);
688
- const controllerGuards = getControllerGuards(ControllerClass)?.guards ?? [];
689
- const routerConfig = this.routerResolver?.resolveForController(ControllerClass) ?? { middleware: [] };
690
- const classThrottleMiddleware = Array.from(new Set(getRateLimits(ControllerClass).map(createThrottleMiddleware)));
691
- const basePath = routerConfig.prefix ? this.joinPaths(routerConfig.prefix, controllerRoute) : controllerRoute;
692
- const effectiveVersion = controllerOpts?.version ?? routerConfig.version;
693
- const effectiveDomain = controllerOpts?.domain ?? routerConfig.domain;
694
- if (isWsGateway) {
695
- const wsMiddleware = [...routerConfig.middleware, ...classThrottleMiddleware];
696
- const expandedRoutes = this.registry.register({
697
- method: "ws",
698
- basePath,
699
- version: effectiveVersion,
700
- domain: effectiveDomain,
701
- controller: ControllerClass.name,
702
- action: "ws",
703
- hidden: routerConfig.hideFromDocs ?? false,
704
- middleware: wsMiddleware.map((m) => m.name)
705
- });
706
- for (const route of expandedRoutes) actions.set(route, () => {
707
- if (wsMiddleware.length > 0) this.app.use(route.path, createMiddlewareChain(wsMiddleware));
708
- if (effectiveDomain) {
709
- const domainHandler = createDomainMiddleware(effectiveDomain);
710
- this.app.use(route.path, domainHandler);
711
- this.app.use(`${route.path}/*`, domainHandler);
712
- }
713
- this.registerGatewayForPath(ControllerClass, route.path, controllerGuards);
714
- });
715
- return;
716
- }
717
- const className = ControllerClass.name;
718
- this.controllerClasses.set(className, ControllerClass);
719
- const prototype = ControllerClass.prototype;
720
- if (prototype.handle) {
721
- const wildcardMiddleware = [...routerConfig.middleware, ...classThrottleMiddleware];
722
- const expandedRoutes = this.registry.register({
723
- method: "all",
724
- basePath,
725
- version: effectiveVersion,
726
- domain: effectiveDomain,
727
- controller: className,
728
- action: "handle",
729
- hidden: routerConfig.hideFromDocs ?? false,
730
- middleware: wildcardMiddleware.map((m) => m.name)
731
- });
732
- for (const route of expandedRoutes) actions.set(route, () => {
733
- if (wildcardMiddleware.length > 0) this.app.use(route.path, createMiddlewareChain(wildcardMiddleware));
734
- this.registerWildcardRoute(ControllerClass, route.path);
735
- });
736
- return;
737
- }
738
- const decoratedMethods = getRouteDecoratedMethods(ControllerClass);
739
- if (decoratedMethods.length === 0) throw new RouterError(`Controller "${ControllerClass.name}" registration failed: No route decorators found. Use @Route() or HTTP method decorators (@Get, @Post, etc.) on controller methods.`);
740
- const methodMetadata = [];
741
- let hasConvention = false;
742
- let hasExplicit = false;
743
- for (const m of decoratedMethods) {
744
- const meta = getRouteMetadata(prototype, m);
745
- if (!meta) continue;
746
- methodMetadata.push({
747
- method: m,
748
- meta
749
- });
750
- if (meta.type === "convention") hasConvention = true;
751
- else if (meta.type === "explicit") hasExplicit = true;
752
- }
753
- if (hasConvention && hasExplicit) throw new RouterError(`Controller "${ControllerClass.name}" registration failed: Cannot mix @Route() with HTTP method decorators (@Get, @Post, etc.) in the same controller. Use one pattern or the other.`);
754
- const routerHidden = routerConfig.hideFromDocs;
755
- const controllerHidden = controllerOpts?.hideFromDocs ?? false;
756
- const routerName = routerConfig.name;
757
- const controllerName = controllerOpts?.name;
758
- const effectiveNamePrefix = routerName && controllerName ? `${routerName}${controllerName}` : routerName ?? controllerName;
759
- for (const { method: methodName, meta } of methodMetadata) {
760
- const resolved = this.resolveMethodAndPath(meta, methodName, basePath, className);
761
- if (!resolved) continue;
762
- const methodThrottleMiddleware = getRateLimits(prototype, methodName).map(createThrottleMiddleware);
763
- const effectiveMiddleware = Array.from(new Set([
764
- ...routerConfig.middleware,
765
- ...classThrottleMiddleware,
766
- ...methodThrottleMiddleware
767
- ]));
768
- const middlewareNames = effectiveMiddleware.map((m) => m.name);
769
- const { httpMethod, fullPath, routeConfig: rawRouteConfig, statusCodeOverride } = resolved;
770
- let mergedParams = rawRouteConfig.params;
771
- if (routerConfig.params) {
772
- const prefixShape = routerConfig.params.shape;
773
- mergedParams = mergedParams ? mergedParams.extend(prefixShape) : routerConfig.params.extend({});
774
- }
775
- const routeConfig = mergedParams === rawRouteConfig.params ? rawRouteConfig : {
776
- ...rawRouteConfig,
777
- params: mergedParams
778
- };
779
- const hideFromDocs = routeConfig.hideFromDocs ?? routerHidden ?? controllerHidden;
780
- let routeName;
781
- if (routeConfig.name) routeName = effectiveNamePrefix ? `${effectiveNamePrefix}${routeConfig.name}` : routeConfig.name;
782
- else if (meta.type === "convention") {
783
- const autoName = generateConventionRouteName(basePath, methodName);
784
- routeName = effectiveNamePrefix ? `${effectiveNamePrefix}${autoName}` : autoName;
785
- }
786
- const expandedRoutes = this.registry.register({
787
- name: routeName,
788
- method: httpMethod,
789
- basePath: fullPath,
790
- version: effectiveVersion,
791
- domain: effectiveDomain,
792
- controller: className,
793
- action: methodName,
794
- hidden: hideFromDocs,
795
- middleware: middlewareNames
796
- });
797
- const methodGuards = getMethodGuards(prototype, methodName)?.guards ?? [];
798
- const allGuards = methodGuards.length > 0 ? [...controllerGuards, ...methodGuards] : controllerGuards;
799
- const responseSchema = httpMethod !== "all" ? this.extractResponseSchema(routeConfig) : null;
800
- const handler = this.createControllerHandler(ControllerClass, methodName, responseSchema);
801
- for (const route of expandedRoutes) actions.set(route, () => {
802
- if (effectiveDomain) {
803
- const domainHandler = createDomainMiddleware(effectiveDomain);
804
- this.app.use(route.path, domainHandler);
805
- this.app.use(`${route.path}/*`, domainHandler);
806
- }
807
- if (allGuards.length > 0) this.logger.info(`Route guards`, {
808
- controller: className,
809
- method: httpMethod.toUpperCase(),
810
- path: route.path,
811
- methodName,
812
- guardCount: allGuards.length
813
- });
814
- if (httpMethod === "all") {
815
- this.logger.info(`Registering @All route`, {
816
- controller: className,
817
- path: route.path,
818
- methodName
819
- });
820
- if (effectiveMiddleware.length > 0) this.app.use(route.path, createMiddlewareChain(effectiveMiddleware));
821
- if (allGuards.length > 0) this.app.use(route.path, this.createGuardMiddleware(allGuards));
822
- this.app.all(route.path, handler);
823
- return;
824
- }
825
- const metadata = this.mergeMetadata(controllerOpts, routeConfig, ControllerClass, methodName);
826
- const openApiRoute = this.buildOpenAPIRoute(httpMethod, route.path, routeConfig, metadata, meta.type === "convention" ? methodName : void 0, statusCodeOverride, route.isLocaleVariant ?? false);
827
- this.logger.info(`Registering route`, {
828
- controller: className,
829
- method: httpMethod.toUpperCase(),
830
- path: route.path,
831
- methodName,
832
- tags: metadata.tags,
833
- hidden: route.hidden
834
- });
835
- const wrappedHandler = this.wrapHandlerWithChain(handler, effectiveMiddleware, allGuards);
836
- this.app.openapi(openApiRoute, wrappedHandler);
837
- if (!route.hidden) {
838
- const { hide: _, ...specRoute } = openApiRoute;
839
- this.app.openAPIRegistry.registerPath({
840
- ...specRoute,
841
- path: toOpenAPIPath(route.path)
842
- });
843
- }
844
- });
845
- }
846
- }
847
- /**
848
- * Register a single WebSocket gateway route
849
- */
850
- registerGatewayForPath(GatewayClass, fullPath, guards) {
851
- const onMsgMethod = getWsOnMessageMethod(GatewayClass);
852
- const onCloseMethod = getWsOnCloseMethod(GatewayClass);
853
- const onErrMethod = getWsOnErrorMethod(GatewayClass);
854
- const wsHandler = this.upgradeWebSocketFn((c) => {
855
- const gateway = new RouterContext(c).getContainer().resolve(GatewayClass);
856
- const events = {};
857
- const bindWsHandler = (method, onCatch) => {
858
- return (evt, ws) => {
859
- invokeHandler(gateway, method, evt, new GatewayContext(c, ws)).catch((err) => {
860
- this.logger.error(`WebSocket ${method} handler error`, err, { gateway: GatewayClass.name });
861
- onCatch?.(err, ws);
862
- });
863
- };
864
- };
865
- if (onMsgMethod) events.onMessage = bindWsHandler(onMsgMethod, (_err, ws) => ws.close(1011, "Internal Error"));
866
- if (onCloseMethod) events.onClose = bindWsHandler(onCloseMethod);
867
- else events.onClose = (_evt, ws) => {
868
- ws.close();
869
- };
870
- if (onErrMethod) events.onError = bindWsHandler(onErrMethod);
871
- return events;
872
- });
873
- this.nameHandler(wsHandler, GatewayClass.name, onMsgMethod ?? "[anonymous]", "ws");
874
- this.logger.info("Registering WebSocket gateway", {
875
- gateway: GatewayClass.name,
876
- path: fullPath
877
- });
878
- const handlers = [];
879
- if (guards.length > 0) {
880
- this.logger.info("Gateway guards", {
881
- gateway: GatewayClass.name,
882
- path: fullPath,
883
- guardCount: guards.length
884
- });
885
- handlers.push(this.createGuardMiddleware(guards));
886
- }
887
- handlers.push(wsHandler);
888
- this.app.get(fullPath, ...handlers);
889
- }
890
- /**
891
- * Create a guard execution middleware
892
- *
893
- * This middleware executes all guards for a route before the handler.
894
- * Guards are executed in order; all must pass for the request to proceed.
895
- *
896
- * @param guards - Array of guards to execute
897
- * @returns Hono middleware function
898
- */
899
- createGuardMiddleware(guards) {
900
- const guardService = new GuardExecutionService(this.logger);
901
- return async (c, next) => {
902
- const ctx = new RouterContext(c);
903
- const container = ctx.getContainer();
904
- await guardService.executeGuards(guards, ctx, container);
905
- await next();
906
- };
907
- }
908
- /**
909
- * Wrap a controller handler with a `scopedMiddleware → guards → handler`
910
- * chain that runs *inside* the Hono route handler — after request
911
- * validators have populated `c.req.valid(...)`. This is the only place
912
- * we can run user middleware after `@hono/zod-openapi`'s validators in
913
- * the same pipeline.
914
- *
915
- * Returns a Hono handler with the same signature as the original so
916
- * `app.openapi(route, wrapped)` works transparently.
917
- */
918
- wrapHandlerWithChain(handler, scopedMiddleware, guards) {
919
- if (scopedMiddleware.length === 0 && guards.length === 0) return handler;
920
- const scopedChain = scopedMiddleware.length > 0 ? createMiddlewareChain(scopedMiddleware) : null;
921
- const guardChain = guards.length > 0 ? this.createGuardMiddleware(guards) : null;
922
- return async (c) => {
923
- let captured;
924
- const runHandler = async () => {
925
- captured = await handler(c);
926
- };
927
- const runGuards = guardChain ? () => guardChain(c, runHandler) : runHandler;
928
- const result = await (scopedChain ? () => scopedChain(c, runGuards) : runGuards)();
929
- if (result instanceof Response) return result;
930
- return captured;
931
- };
932
- }
933
- /**
934
- * Register wildcard route for non-RESTful controllers
935
- */
936
- registerWildcardRoute(ControllerClass, route) {
937
- this.logger.info(`Registering wildcard route`, {
938
- controller: ControllerClass.name,
939
- route: `${route}/:path{.+}`,
940
- method: "ALL"
941
- });
942
- const handler = this.createControllerHandler(ControllerClass, "handle");
943
- this.app.all(route, handler);
944
- this.app.all(`${route}/:path{.+}`, handler);
945
- }
946
- /**
947
- * Resolve HTTP method, path, route config, and status code from route metadata.
948
- */
949
- resolveMethodAndPath(meta, methodName, basePath, className) {
950
- if (meta.type === "convention") {
951
- const derived = this.deriveHttpMethodAndPath(methodName, basePath);
952
- if (!derived) throw new RouterError(`Cannot derive HTTP method/path for convention-based route "${className}.${methodName}". Ensure the method name follows the naming convention (e.g., index, create, show).`);
953
- return {
954
- httpMethod: derived.method,
955
- fullPath: derived.path,
956
- routeConfig: meta.config,
957
- statusCodeOverride: meta.config.statusCode
958
- };
959
- }
960
- return {
961
- httpMethod: meta.method,
962
- fullPath: this.joinPaths(basePath, meta.path),
963
- routeConfig: meta.config,
964
- statusCodeOverride: meta.config.statusCode
965
- };
966
- }
967
- /**
968
- * Join a base path and a route path, normalizing slashes
969
- */
970
- joinPaths(basePath, routePath) {
971
- if (basePath.endsWith("/")) basePath = basePath.slice(0, -1);
972
- if (routePath === "/" || routePath === "") return basePath || "/";
973
- if (!routePath.startsWith("/")) routePath = "/" + routePath;
974
- return basePath + routePath;
975
- }
976
- /**
977
- * Auto-derive HTTP method and path from controller method name
978
- * Uses HTTP_METHODS constant for RESTful convention mapping
979
- */
980
- deriveHttpMethodAndPath(methodName, basePath) {
981
- if (!(methodName in HTTP_METHODS)) return null;
982
- const mapping = HTTP_METHODS[methodName];
983
- return {
984
- method: mapping.method,
985
- path: basePath + mapping.path
986
- };
987
- }
988
- /**
989
- * Merge controller-level and route-level metadata
990
- * Tags are merged (appended), security is merged (union)
991
- * Guards automatically add sessionCookie security if present
992
- */
993
- mergeMetadata(controllerOpts, routeConfig, ControllerClass, methodName) {
994
- const tags = [...controllerOpts?.tags ?? [], ...routeConfig.tags ?? []];
995
- const prototype = ControllerClass.prototype;
996
- const hasMethodGuards = (getMethodGuards(prototype, methodName)?.guards.length ?? 0) > 0;
997
- const hasControllerGuards = (getControllerGuards(ControllerClass)?.guards.length ?? 0) > 0;
998
- const requiresAuth = hasMethodGuards || hasControllerGuards;
999
- let security = [];
1000
- if (routeConfig.security !== void 0) security = [...controllerOpts?.security ?? [], ...routeConfig.security];
1001
- else if (controllerOpts?.security) security = controllerOpts.security;
1002
- if (requiresAuth && !security.includes(SECURITY_SCHEMES.SESSION_COOKIE)) security.push(SECURITY_SCHEMES.SESSION_COOKIE);
1003
- return {
1004
- tags,
1005
- security: security.length > 0 ? security.map((scheme) => ({ [scheme]: [] })) : []
1006
- };
1007
- }
1008
- /**
1009
- * Build OpenAPI route configuration from metadata
1010
- * Creates a route definition compatible with @hono/zod-openapi.
1011
- *
1012
- * Scoped middleware and guards are NOT attached to `route.middleware`
1013
- * here — they're composed into a wrapped handler in `collectRoutes` so
1014
- * they run after Hono's request validators. See `wrapHandlerWithChain`.
1015
- */
1016
- buildOpenAPIRoute(method, path, routeConfig, metadata, methodName, statusCodeOverride, hasLocaleParam = false) {
1017
- try {
1018
- const route = {
1019
- method,
1020
- path: toRoutingOpenAPIPath(path),
1021
- request: {},
1022
- responses: {},
1023
- hide: true
1024
- };
1025
- if (routeConfig.body) {
1026
- const bodySchema = this.isRouteBodyObject(routeConfig.body) ? routeConfig.body.schema : routeConfig.body;
1027
- const bodyContentType = this.isRouteBodyObject(routeConfig.body) ? routeConfig.body.contentType ?? "application/json" : DEFAULT_CONTENT_TYPE;
1028
- route.request = {
1029
- ...route.request,
1030
- body: { content: { [bodyContentType]: { schema: bodySchema } } }
1031
- };
1032
- }
1033
- if (routeConfig.query) route.request = {
1034
- ...route.request,
1035
- query: routeConfig.query
1036
- };
1037
- if (routeConfig.params) route.request = {
1038
- ...route.request,
1039
- params: routeConfig.params
1040
- };
1041
- const localeConfig = this.localePathService.localePathConfig;
1042
- if (hasLocaleParam && localeConfig) {
1043
- const localeParam = z.object({ locale: z.enum(localeConfig.prefixedLocales).openapi({ param: {
1044
- name: "locale",
1045
- in: "path"
1046
- } }).optional() });
1047
- route.request = {
1048
- ...route.request,
1049
- params: route.request.params ? route.request.params.extend(localeParam.shape) : localeParam
1050
- };
1051
- }
1052
- const successStatus = statusCodeOverride ?? (methodName && METHOD_STATUS_CODES[methodName]) ?? 200;
1053
- const responses = {};
1054
- const responseDef = routeConfig.response;
1055
- if (responseDef) if (typeof responseDef === "object" && "schema" in responseDef) responses[successStatus] = {
1056
- content: { [responseDef.contentType ?? "application/json"]: { schema: responseDef.schema } },
1057
- description: responseDef.description ?? `Response ${successStatus}`
1058
- };
1059
- else responses[successStatus] = {
1060
- content: { [DEFAULT_CONTENT_TYPE]: { schema: responseDef } },
1061
- description: `Response ${successStatus}`
1062
- };
1063
- for (const [statusStr, schema] of Object.entries(commonErrorSchemas)) {
1064
- const status = parseInt(statusStr);
1065
- responses[status] ??= schema;
1066
- }
1067
- route.responses = responses;
1068
- if (metadata.tags.length > 0) route.tags = metadata.tags;
1069
- if (metadata.security.length > 0) route.security = metadata.security;
1070
- if (routeConfig.description) route.description = routeConfig.description;
1071
- if (routeConfig.summary) route.summary = routeConfig.summary;
1072
- return (0, zod_exports.createRoute)(route);
1073
- } catch (error) {
1074
- throw new RouterError(`OpenAPI route registration failed for "${path}": ${error instanceof Error ? error.message : String(error)}`);
1075
- }
1076
- }
1077
- /**
1078
- * Check if a body definition is a RouteBodyObject (has schema key) vs bare ZodType
1079
- */
1080
- isRouteBodyObject(body) {
1081
- return typeof body === "object" && "schema" in body;
1082
- }
1083
- /**
1084
- * Resolve method parameter injections from the container
1085
- *
1086
- * @param prototype - Controller prototype
1087
- * @param methodName - Method name to get injections for
1088
- * @param container - Request-scoped container
1089
- * @returns Array of resolved dependencies in parameter order
1090
- */
1091
- resolveMethodInjections(prototype, methodName, container) {
1092
- const injections = getMethodInjections(prototype, methodName);
1093
- if (!injections.length) return [];
1094
- return injections.map((inj) => container.resolve(inj.token));
1095
- }
1096
- /**
1097
- * Name a handler function so Hono's inspectRoutes() can identify it.
1098
- * Format: `{type}:{Controller}.{method}` (e.g. `http:UsersController.create`)
1099
- */
1100
- nameHandler(fn, controller, method, type = "http") {
1101
- Object.defineProperty(fn, "name", { value: `${type}:${controller}.${method}` });
1102
- }
1103
- /**
1104
- * Create controller handler that resolves controller from request-scoped container
1105
- * This ensures each request gets a fresh controller with request-scoped context
1106
- */
1107
- createControllerHandler(ControllerClass, methodName, responseSchema = null) {
1108
- const handler = async (c) => {
1109
- const ctx = new RouterContext(c);
1110
- const requestContainer = ctx.getContainer();
1111
- const controller = requestContainer.resolve(ControllerClass);
1112
- const method = controller[methodName];
1113
- if (typeof method === "function") {
1114
- const injectedArgs = this.resolveMethodInjections(ControllerClass.prototype, methodName, requestContainer);
1115
- const response = await method.apply(controller, [ctx, ...injectedArgs]);
1116
- if (responseSchema && c.env.ENVIRONMENT !== "production") return this.validateResponse(response, responseSchema);
1117
- return response;
1118
- }
1119
- throw new RouterError(`Method "${methodName}" not found on controller "${ControllerClass.name}"`);
1120
- };
1121
- this.nameHandler(handler, ControllerClass.name, methodName);
1122
- return handler;
1123
- }
1124
- /**
1125
- * Extract the Zod schema from a RouteResponse definition.
1126
- * Returns null for non-JSON content types or when no response is defined.
1127
- */
1128
- extractResponseSchema(routeConfig) {
1129
- const responseDef = routeConfig.response;
1130
- if (!responseDef) return null;
1131
- if (this.isRouteResponseObject(responseDef)) {
1132
- if (!(responseDef.contentType ?? "application/json").includes("application/json")) return null;
1133
- return responseDef.schema;
1134
- }
1135
- return responseDef;
1136
- }
1137
- /**
1138
- * Check if a response definition is a RouteResponseObject (has schema key) vs bare ZodType
1139
- */
1140
- isRouteResponseObject(response) {
1141
- return typeof response === "object" && "schema" in response;
1142
- }
1143
- /**
1144
- * Validate a Response body against its declared Zod schema.
1145
- *
1146
- * Skips validation for:
1147
- * - Non-JSON content types
1148
- * - Empty bodies (204 No Content, 304 Not Modified)
1149
- *
1150
- * Clones the response to read the body without consuming the original stream.
1151
- */
1152
- async validateResponse(response, schema) {
1153
- const contentType = response.headers.get("content-type");
1154
- if (!contentType || !contentType.includes("application/json")) return response;
1155
- if (response.status === 204 || response.status === 304) return response;
1156
- const cloned = response.clone();
1157
- let body;
1158
- try {
1159
- body = await cloned.json();
1160
- } catch {
1161
- return response;
1162
- }
1163
- const result = schema.safeParse(body);
1164
- if (!result.success) throw new ResponseValidationError(result.error);
1165
- return response;
1166
- }
1167
- };
1168
- RouteRegistrationService = __decorate([
1169
- Singleton(),
1170
- __decorateParam(0, inject(LOGGER_TOKENS.LoggerService)),
1171
- __decorateParam(1, inject(ROUTER_TOKENS.RouteRegistry)),
1172
- __decorateParam(2, inject(ROUTER_TOKENS.RouterResolver, { isOptional: true })),
1173
- __decorateParam(3, inject(ROUTER_TOKENS.LocalePathService)),
1174
- __decorateParam(4, inject(ROUTER_TOKENS.HonoApp)),
1175
- __decorateParam(5, inject(DI_TOKENS.ModuleRegistry))
1176
- ], RouteRegistrationService);
1177
- //#endregion
1178
- //#region src/router/hono-app.ts
1179
- const isMiddlewareClass = (arg) => typeof arg === "function" && arg.prototype && "handle" in arg.prototype;
1180
- let HonoApp = class HonoApp extends OpenAPIHono {
1181
- configured = false;
1182
- _container;
1183
- _logger;
1184
- /**
1185
- * Reference to the original Hono `use` implementation.
1186
- * Captured in constructor after super() sets it as an instance property.
1187
- * Used by private methods to register middleware without going through the override.
1188
- */
1189
- nativeUse;
1190
- constructor(container, logger, application) {
1191
- const trailingSlash = application.config.trailingSlash ?? "ignore";
1192
- super({
1193
- strict: false,
1194
- defaultHook: (result, c) => {
1195
- if (!result.success) throw new SchemaValidationError(result.error);
1196
- const override = c.get("validationSuccessResponse");
1197
- if (override) return override;
1198
- }
1199
- });
1200
- this._container = container;
1201
- this._logger = logger;
1202
- this.nativeUse = this.use;
1203
- this.use = ((...args) => {
1204
- if (isMiddlewareClass(args[0])) {
1205
- this.nativeUse("*", createMiddlewareChain(args));
1206
- return this;
1207
- }
1208
- if (typeof args[0] === "string" && args.length > 1 && isMiddlewareClass(args[1])) {
1209
- this.nativeUse(args[0], createMiddlewareChain(args.slice(1)));
1210
- return this;
1211
- }
1212
- return this.nativeUse(...args);
1213
- });
1214
- const trailingSlashRedirect = createTrailingSlashRedirect(trailingSlash);
1215
- if (trailingSlashRedirect) this.nativeUse("*", trailingSlashRedirect);
1216
- this.setupRequestScope();
1217
- this.applyGlobalMiddleware();
1218
- }
1219
- /**
1220
- * Apply global middleware (logger + error handler).
1221
- * Called by Application after locale middleware is applied by LocalePathService.
1222
- */
1223
- applyGlobalMiddleware() {
1224
- this.nativeUse("*", createLoggerMiddleware(this._logger));
1225
- this.onError((err, c) => this.handleException(c, err));
1226
- }
1227
- /**
1228
- * Configure OpenAPI endpoints, controller routes, and 404 handler.
1229
- * Called once by Application.initialize().
1230
- */
1231
- async configure() {
1232
- if (this.configured) throw new RouterError("HonoApp has already been configured");
1233
- this._container.resolve(OPENAPI_TOKENS.OpenAPIService).setupEndpoints(this, this._container);
1234
- await this._container.resolve(RouteRegistrationService).configure();
1235
- this.notFound((c) => {
1236
- throw new RouteNotFoundError(c.req.path, c.req.method);
1237
- });
1238
- this.configured = true;
1239
- }
1240
- setupRequestScope() {
1241
- this.nativeUse("*", async (c, next) => {
1242
- const routerContext = new RouterContext(c);
1243
- const requestContainer = this._container.createRequestScope(routerContext);
1244
- c.set(ROUTER_CONTEXT_KEYS.REQUEST_CONTAINER, requestContainer);
1245
- await runWithContainer(requestContainer, next);
1246
- });
1247
- }
1248
- handleException(c, err) {
1249
- const handler = (c.get(ROUTER_CONTEXT_KEYS.REQUEST_CONTAINER) ?? this._container).resolve(DI_TOKENS.ExceptionHandler);
1250
- const ctx = createHttpExceptionContext(c);
1251
- return handler.handle(err, ctx);
1252
- }
1253
- };
1254
- HonoApp = __decorate([
1255
- Singleton(),
1256
- __decorateParam(0, inject(CONTAINER_TOKEN)),
1257
- __decorateParam(1, inject(LOGGER_TOKENS.LoggerService)),
1258
- __decorateParam(2, inject(DI_TOKENS.Application))
1259
- ], HonoApp);
1260
- //#endregion
1261
- //#region src/i18n/i18n.options.ts
1262
- /**
1263
- * Resolve I18n options with defaults
1264
- */
1265
- function resolveI18nOptions(options) {
1266
- const detection = options?.detection;
1267
- const enabled = detection ? detection.enabled !== false : true;
1268
- const strategy = detection && "strategy" in detection ? detection.strategy ?? "cookie" : "cookie";
1269
- const prefixDefaultLocale = detection && "prefixDefaultLocale" in detection && detection.prefixDefaultLocale !== void 0 ? detection.prefixDefaultLocale : false;
1270
- return {
1271
- defaultLocale: options?.defaultLocale ?? "en",
1272
- fallbackLocale: options?.fallbackLocale ?? "en",
1273
- locales: options?.locales ?? ["en"],
1274
- detection: {
1275
- enabled,
1276
- strategy,
1277
- prefixDefaultLocale
1278
- }
1279
- };
1280
- }
1281
- /**
1282
- * Build Hono languageDetector options from I18n module options
1283
- */
1284
- function buildDetectorOptions(options) {
1285
- const resolved = resolveI18nOptions(options);
1286
- const strategy = resolved.detection.strategy;
1287
- const detectorOptions = {
1288
- order: [strategy],
1289
- fallbackLanguage: resolved.defaultLocale,
1290
- supportedLanguages: resolved.locales,
1291
- lookupCookie: "locale",
1292
- lookupQueryString: "locale",
1293
- lookupFromPathIndex: 0,
1294
- ignoreCase: true
1295
- };
1296
- if (strategy === "cookie") {
1297
- detectorOptions.caches = ["cookie"];
1298
- if (options?.detection && "cookieOptions" in options.detection && options.detection.cookieOptions) detectorOptions.cookieOptions = options.detection.cookieOptions;
1299
- } else detectorOptions.caches = false;
1300
- return detectorOptions;
1301
- }
1302
- //#endregion
1303
- //#region src/router/services/locale-path.service.ts
1304
- let LocalePathService = class LocalePathService {
1305
- honoApp;
1306
- _config;
1307
- _pathDetectionEnabled;
1308
- _prefixDefaultLocale;
1309
- constructor(container, honoApp) {
1310
- this.honoApp = honoApp;
1311
- const i18nOptions = container.isRegistered(I18N_TOKENS.Options) ? container.resolve(I18N_TOKENS.Options) : void 0;
1312
- const detection = i18nOptions?.detection;
1313
- const detectionEnabled = detection ? detection.enabled !== false : true;
1314
- const strategy = (detection && "strategy" in detection && detection.strategy) ?? "cookie";
1315
- this._pathDetectionEnabled = detectionEnabled && strategy === "path";
1316
- this._prefixDefaultLocale = detection && "prefixDefaultLocale" in detection && detection.prefixDefaultLocale !== void 0 ? detection.prefixDefaultLocale : false;
1317
- if (this._pathDetectionEnabled) {
1318
- const allLocales = i18nOptions?.locales ?? ["en"];
1319
- const defaultLocale = i18nOptions?.defaultLocale ?? "en";
1320
- this._config = this._prefixDefaultLocale === true ? {
1321
- allLocales,
1322
- prefixedLocales: allLocales,
1323
- defaultLocale: null
1324
- } : {
1325
- allLocales,
1326
- prefixedLocales: allLocales.filter((l) => l !== defaultLocale),
1327
- defaultLocale
1328
- };
1329
- } else this._config = null;
1330
- if (detectionEnabled) this.setupLanguageDetection(i18nOptions);
1331
- if (this._config?.defaultLocale && this._prefixDefaultLocale === "redirect") this.setupDefaultLocaleRedirect(this._config.defaultLocale);
1332
- }
1333
- /** Whether path-based locale detection is enabled */
1334
- get enabled() {
1335
- return this._pathDetectionEnabled;
1336
- }
1337
- /** The computed locale path config, or null if path detection is disabled */
1338
- get localePathConfig() {
1339
- return this._config;
1340
- }
1341
- /** The prefixDefaultLocale setting (false, true, or 'redirect') */
1342
- get prefixDefaultLocale() {
1343
- return this._prefixDefaultLocale;
1344
- }
1345
- /**
1346
- * Expand a path into primary + locale-prefixed variants.
1347
- *
1348
- * @param path - The base path to expand
1349
- * @returns Array of resolved paths with locale metadata
1350
- */
1351
- resolve(path) {
1352
- if (!this._config) return [{
1353
- path,
1354
- isLocaleVariant: false
1355
- }];
1356
- const constraint = this.buildLocaleConstraint();
1357
- const suffix = path === "/" ? "" : path;
1358
- if (this._config.defaultLocale === null) return [{
1359
- path: `/:locale${constraint}${suffix}`,
1360
- isLocaleVariant: true
1361
- }];
1362
- const result = [{
1363
- path,
1364
- isLocaleVariant: false
1365
- }];
1366
- if (this._config.prefixedLocales.length > 0) result.push({
1367
- path: `/:locale${constraint}${suffix}`,
1368
- isLocaleVariant: true
1369
- });
1370
- return result;
1371
- }
1372
- /**
1373
- * Build a Hono regex constraint from prefixed locales.
1374
- * e.g., `{en|de|fr}` — restricts `:locale` to only match known values.
1375
- */
1376
- buildLocaleConstraint() {
1377
- return `{${(this._config.defaultLocale === null ? this._config.allLocales : this._config.prefixedLocales).join("|")}}`;
1378
- }
1379
- /**
1380
- * Apply Hono's languageDetector middleware and bridge the detected language
1381
- * to Stratal's LOCALE context variable.
1382
- */
1383
- setupLanguageDetection(i18nOptions) {
1384
- const detectorOptions = buildDetectorOptions(i18nOptions);
1385
- this.honoApp.use("*", languageDetector(detectorOptions));
1386
- this.honoApp.use("*", async (c, next) => {
1387
- const language = c.get("language");
1388
- if (language) c.set(ROUTER_CONTEXT_KEYS.LOCALE, language);
1389
- await next();
1390
- });
1391
- }
1392
- /**
1393
- * Redirect requests that include the default locale prefix to the unprefixed path.
1394
- * For example, `/en/users` → 301 redirect to `/users`.
1395
- *
1396
- * Only active when `prefixDefaultLocale` is `'redirect'`.
1397
- */
1398
- setupDefaultLocaleRedirect(defaultLocale) {
1399
- const prefix = `/${defaultLocale}`;
1400
- this.honoApp.use("*", async (c, next) => {
1401
- const path = new URL(c.req.url).pathname;
1402
- if (path === prefix || path.startsWith(`${prefix}/`)) {
1403
- const stripped = path.slice(prefix.length) || "/";
1404
- return c.redirect(stripped, 301);
1405
- }
1406
- await next();
1407
- });
1408
- }
1409
- };
1410
- LocalePathService = __decorate([
1411
- Singleton(),
1412
- __decorateParam(0, inject(CONTAINER_TOKEN)),
1413
- __decorateParam(1, inject(ROUTER_TOKENS.HonoApp))
1414
- ], LocalePathService);
1415
- //#endregion
1416
- //#region src/router/services/versioning.service.ts
1417
- let VersioningService = class VersioningService {
1418
- options;
1419
- constructor(app) {
1420
- this.options = app.config.versioning ?? null;
1421
- }
1422
- /** Whether versioning is enabled */
1423
- get enabled() {
1424
- return this.options !== null;
1425
- }
1426
- /**
1427
- * Resolve versioned paths for a base path.
1428
- *
1429
- * @param basePath - The base path (e.g., '/users')
1430
- * @param version - Explicit version from controller/router config
1431
- * @returns Array of versioned path strings (e.g., ['/v1/users', '/v2/users'])
1432
- */
1433
- resolve(basePath, version) {
1434
- if (!this.options) return [basePath];
1435
- if (version === VERSION_NEUTRAL) return [basePath];
1436
- const prefix = this.options.prefix ?? "v";
1437
- if (version !== void 0) return (Array.isArray(version) ? version : [version]).map((v) => `/${prefix}${v}${basePath}`);
1438
- if (this.options.defaultVersion !== void 0) return (Array.isArray(this.options.defaultVersion) ? this.options.defaultVersion : [this.options.defaultVersion]).map((v) => `/${prefix}${v}${basePath}`);
1439
- return [basePath];
1440
- }
1441
- };
1442
- VersioningService = __decorate([Singleton(), __decorateParam(0, inject(DI_TOKENS.Application))], VersioningService);
1443
- //#endregion
1444
- //#region src/router/route-registry.ts
1445
- const CONCRETE_HTTP_METHODS = [
1446
- "get",
1447
- "post",
1448
- "put",
1449
- "delete",
1450
- "patch",
1451
- "head",
1452
- "options",
1453
- "trace"
1454
- ];
1455
- let RouteRegistry = class RouteRegistry {
1456
- versioningService;
1457
- localePathService;
1458
- routes = [];
1459
- namedRoutes = /* @__PURE__ */ new Map();
1460
- _sortedCache = null;
1461
- _routeToNameCache = null;
1462
- constructor(versioningService, localePathService) {
1463
- this.versioningService = versioningService;
1464
- this.localePathService = localePathService;
1465
- }
1466
- /**
1467
- * Register a route. Expands via VersioningService + LocalePathService.
1468
- * Named routes must have unique names.
1469
- *
1470
- * @returns Array of expanded RegisteredRoute entries (primary + locale variants)
1471
- * @throws RouterError if a named route with the same name already exists
1472
- */
1473
- register(input) {
1474
- const domainParamNames = input.domainParamNames ?? (input.domain ? extractDomainParamNames(input.domain) : []);
1475
- const versionedPaths = this.versioningService.resolve(input.basePath, input.version);
1476
- const expandedRoutes = [];
1477
- const localeEnabled = this.localePathService.enabled;
1478
- for (const versionedPath of versionedPaths) {
1479
- const resolvedPaths = this.localePathService.resolve(versionedPath);
1480
- let localeVariantPaths;
1481
- if (localeEnabled) {
1482
- const variants = resolvedPaths.filter((p) => p.isLocaleVariant);
1483
- localeVariantPaths = variants.length > 0 ? variants.map((p) => p.path) : void 0;
1484
- }
1485
- for (const resolved of resolvedPaths) {
1486
- const route = {
1487
- name: resolved.isLocaleVariant ? void 0 : input.name,
1488
- method: input.method,
1489
- path: resolved.path,
1490
- localePaths: resolved.isLocaleVariant ? void 0 : localeVariantPaths,
1491
- paramNames: extractParamNames(resolved.path),
1492
- domain: input.domain,
1493
- domainParamNames,
1494
- controller: input.controller,
1495
- action: input.action,
1496
- hidden: input.hidden,
1497
- middleware: input.middleware,
1498
- isLocaleVariant: resolved.isLocaleVariant || void 0
1499
- };
1500
- if (route.name) {
1501
- if (this.namedRoutes.has(route.name)) {
1502
- const existing = this.namedRoutes.get(route.name);
1503
- throw new RouterError(`Duplicate route name "${route.name}": already registered by ${existing.controller}.${existing.action}, cannot register ${route.controller}.${route.action}`);
1504
- }
1505
- this.namedRoutes.set(route.name, route);
1506
- }
1507
- this.routes.push(route);
1508
- expandedRoutes.push(route);
1509
- }
1510
- }
1511
- this._sortedCache = null;
1512
- this._routeToNameCache = null;
1513
- return expandedRoutes;
1514
- }
1515
- /** Get a named route by name */
1516
- get(name) {
1517
- return this.namedRoutes.get(name);
1518
- }
1519
- /** Check if a named route exists */
1520
- has(name) {
1521
- return this.namedRoutes.has(name);
1522
- }
1523
- /**
1524
- * Resolve a Hono-style route path pattern (e.g. as exposed by `c.req.routePath`)
1525
- * back to its registered name, scoped to the request's HTTP method. Locale variant
1526
- * paths resolve to the canonical primary route name. Method matching is
1527
- * case-insensitive; routes registered with `'all'` resolve under any verb.
1528
- */
1529
- findNameByRoute(method, path) {
1530
- this._routeToNameCache ??= this.buildRouteToNameCache();
1531
- return this._routeToNameCache.get(`${method.toLowerCase()}:${path}`);
1532
- }
1533
- buildRouteToNameCache() {
1534
- const cache = /* @__PURE__ */ new Map();
1535
- for (const route of this.namedRoutes.values()) {
1536
- const methods = route.method === "all" ? CONCRETE_HTTP_METHODS : [route.method];
1537
- const paths = route.localePaths ? [route.path, ...route.localePaths] : [route.path];
1538
- for (const m of methods) for (const p of paths) cache.set(`${m}:${p}`, route.name);
1539
- }
1540
- return cache;
1541
- }
1542
- /** Get all routes sorted by specificity (static > param > wildcard, locale variant before its primary) */
1543
- all() {
1544
- this._sortedCache ??= sortRoutesBySpecificity(this.routes);
1545
- return this._sortedCache;
1546
- }
1547
- /** Get only named routes */
1548
- named() {
1549
- return [...this.namedRoutes.values()];
1550
- }
1551
- };
1552
- RouteRegistry = __decorate([
1553
- Singleton(),
1554
- __decorateParam(0, inject(ROUTER_TOKENS.VersioningService)),
1555
- __decorateParam(1, inject(ROUTER_TOKENS.LocalePathService))
1556
- ], RouteRegistry);
1557
- //#endregion
1558
- //#region src/router/route-url.ts
1559
- /**
1560
- * Generate a URL from a named route.
1561
- *
1562
- * Keys in `params` matching `:param` placeholders fill the path.
1563
- * Domain params (`{tenant}`) are also consumed from `params`.
1564
- * Extra keys become query string parameters.
1565
- *
1566
- * Resolves RouteRegistry from the application container via AsyncLocalStorage.
1567
- * Available after `Application.initialize()` has been called.
1568
- *
1569
- * @param name - Named route identifier
1570
- * @param params - Route params + domain params + extra query params
1571
- * @returns Generated URL string
1572
- *
1573
- * @example
1574
- * ```typescript
1575
- * // In a controller (preferred):
1576
- * ctx.route('users.show', { id: '1' })
1577
- *
1578
- * // Outside controllers (standalone function):
1579
- * import { route } from 'stratal/router'
1580
- *
1581
- * route('users.show', { id: '1' })
1582
- * ```
1583
- */
1584
- function route(name, params, options) {
1585
- return getContainer().resolve(ROUTER_TOKENS.Uri).route(name, params, options);
1586
- }
1587
- //#endregion
1588
- //#region src/router/uri.ts
1589
- /**
1590
- * Encode a value for use as a path parameter.
1591
- *
1592
- * Splits on `/` and encodes each segment with `encodeURIComponent`, so callers
1593
- * can pass slash-containing values for catch-all params (e.g. `:slug{.+}`) and
1594
- * still get a usable URL — `'auth/login'` becomes `'auth/login'`, not
1595
- * `'auth%2Flogin'`. Single segments behave exactly like `encodeURIComponent`.
1596
- */
1597
- function encodePathParam(value) {
1598
- return value.split("/").map(encodeURIComponent).join("/");
1599
- }
1600
- /**
1601
- * Build a URL from a registered route, filling path/domain params and appending extras as query string.
1602
- *
1603
- * Pure function — no request context needed. Used by both the `Uri` class and the standalone `route()` function.
1604
- *
1605
- * @param route - The registered route to build a URL for
1606
- * @param name - Route name (used in error messages)
1607
- * @param params - Path params, domain params, and extra query params
1608
- * @returns Relative URL string (or absolute with domain prefix if route has a domain pattern)
1609
- *
1610
- * @throws RouterError if a required path or domain param is missing
1611
- */
1612
- function buildRouteUrl(route, name, params) {
1613
- const allParams = { ...params };
1614
- const consumedKeys = /* @__PURE__ */ new Set();
1615
- let url = route.path;
1616
- if (allParams.locale && route.localePaths?.length) {
1617
- url = `/${allParams.locale}${url === "/" ? "" : url}`;
1618
- consumedKeys.add("locale");
1619
- }
1620
- for (const paramName of route.paramNames) {
1621
- const value = allParams[paramName];
1622
- if (value === void 0) throw new RouterError(`Missing required route parameter "${paramName}" for route "${name}" (path: ${route.path})`);
1623
- url = url.replace(new RegExp(`:${paramName}(\\{[^}]*\\})?`), encodePathParam(value));
1624
- consumedKeys.add(paramName);
1625
- }
1626
- let domain;
1627
- if (route.domain) {
1628
- domain = route.domain;
1629
- for (const domainParam of route.domainParamNames) {
1630
- const value = allParams[domainParam];
1631
- if (value === void 0) throw new RouterError(`Missing required domain parameter "${domainParam}" for route "${name}" (domain: ${route.domain})`);
1632
- domain = domain.replace(`{${domainParam}}`, encodeURIComponent(value));
1633
- consumedKeys.add(domainParam);
1634
- }
1635
- }
1636
- const queryEntries = Object.entries(allParams).filter(([key]) => !consumedKeys.has(key));
1637
- if (queryEntries.length > 0) {
1638
- const queryString = queryEntries.filter(([, value]) => Boolean(value)).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");
1639
- url = `${url}${queryString.length ? `?${queryString}` : ""}`;
1640
- }
1641
- if (domain) url = `https://${domain}${url}`;
1642
- return url;
1643
- }
1644
- let Uri = class Uri {
1645
- registry;
1646
- routerContext;
1647
- _defaults = {};
1648
- trailingSlash;
1649
- constructor(registry, routerContext, application) {
1650
- this.registry = registry;
1651
- this.routerContext = routerContext;
1652
- this.trailingSlash = application.config.trailingSlash ?? "ignore";
1653
- }
1654
- /**
1655
- * Set default URL parameters for this request.
1656
- * Applied to all subsequent `route()` calls — explicit params override defaults.
1657
- *
1658
- * @param params - Default parameters (e.g., `{ locale: 'en' }`)
1659
- */
1660
- defaults(params) {
1661
- this._defaults = {
1662
- ...this._defaults,
1663
- ...params
1664
- };
1665
- }
1666
- /**
1667
- * Read the currently configured default URL parameters.
1668
- *
1669
- * Used by frameworks that need to share these with the client (e.g. the
1670
- * Inertia adapter ships them as a shared prop so `route()` calls in the
1671
- * browser auto-fill the same sticky params as the server).
1672
- */
1673
- getDefaults() {
1674
- return { ...this._defaults };
1675
- }
1676
- /**
1677
- * Generate a URL from a named route.
1678
- *
1679
- * Keys matching `:param` placeholders fill the path.
1680
- * Domain params (`{tenant}`) are consumed from the same object.
1681
- * Extra keys become query string parameters.
1682
- * Default params (from `defaults()`) are merged — explicit params override.
1683
- *
1684
- * @param name - Named route identifier
1685
- * @param params - Route params + domain params + extra query params
1686
- * @param options - URL generation options
1687
- * @returns Generated URL string
1688
- *
1689
- * @throws RouterError if route name not found or required params missing
1690
- */
1691
- route(name, params, options) {
1692
- const registeredRoute = this.registry.get(name);
1693
- if (!registeredRoute) throw new RouterError(`Route name "${name}" was not found in the registry`);
1694
- let url = applyTrailingSlash(buildRouteUrl(registeredRoute, name, {
1695
- ...this._defaults,
1696
- ...params
1697
- }), this.trailingSlash);
1698
- if (options?.absolute && !url.startsWith("http")) url = `${new URL(this.routerContext.c.req.url).origin}${url}`;
1699
- return url;
1700
- }
1701
- /**
1702
- * Generate a signed URL from a named route.
1703
- *
1704
- * @param name - Named route identifier
1705
- * @param params - Route params + domain params + extra query params
1706
- * @param options - Signing options (e.g., expiresIn) and URL options
1707
- * @returns Signed URL string with signature query param
1708
- *
1709
- * @throws Error if APP_SECRET environment variable is not set
1710
- */
1711
- async signedRoute(name, params, options) {
1712
- return signUrl(this.route(name, params, options), this.getAppSecret(), options);
1713
- }
1714
- /**
1715
- * Generate a temporary signed URL from a named route.
1716
- *
1717
- * @param name - Named route identifier
1718
- * @param expiresIn - Time-to-live in seconds
1719
- * @param params - Route params + domain params + extra query params
1720
- * @param options - URL generation options
1721
- * @returns Signed URL string with signature and expires query params
1722
- *
1723
- * @throws Error if APP_SECRET environment variable is not set
1724
- */
1725
- async temporarySignedRoute(name, expiresIn, params, options) {
1726
- return this.signedRoute(name, params, {
1727
- ...options,
1728
- expiresIn
1729
- });
1730
- }
1731
- /**
1732
- * Check if the current request has a valid signature.
1733
- *
1734
- * @returns true if the URL signature is valid and not expired
1735
- */
1736
- async hasValidSignature() {
1737
- const secret = this.routerContext.c.env.APP_SECRET;
1738
- if (!secret) return false;
1739
- return verifySignedUrl(this.routerContext.c.req.url, secret);
1740
- }
1741
- /**
1742
- * Get the current request URL pathname (without query string).
1743
- */
1744
- current() {
1745
- return applyTrailingSlash(new URL(this.routerContext.c.req.url).pathname, this.trailingSlash);
1746
- }
1747
- /**
1748
- * Get the current request URL with query string (pathname + search).
1749
- */
1750
- full() {
1751
- const parsed = new URL(this.routerContext.c.req.url);
1752
- return applyTrailingSlash(`${parsed.pathname}${parsed.search}`, this.trailingSlash);
1753
- }
1754
- /**
1755
- * Get the previous request URL from the Referer header.
1756
- *
1757
- * @param fallback - URL to return if no Referer header (default: '/')
1758
- */
1759
- previous(fallback = "/") {
1760
- return this.routerContext.c.req.header("referer") ?? fallback;
1761
- }
1762
- /**
1763
- * Get the previous request URL pathname (no query string or host) from the Referer header.
1764
- *
1765
- * @param fallback - Path to return if no Referer header (default: '/')
1766
- */
1767
- previousPath(fallback = "/") {
1768
- const referer = this.routerContext.c.req.header("referer");
1769
- if (!referer) return fallback;
1770
- try {
1771
- return new URL(referer).pathname;
1772
- } catch {
1773
- return referer;
1774
- }
1775
- }
1776
- /**
1777
- * Build a URL to a raw path (not a named route) with optional query params.
1778
- *
1779
- * @param path - URL path (e.g., '/users')
1780
- * @param queryParams - Query parameters to append
1781
- * @param options - URL generation options
1782
- */
1783
- to(path, queryParams, options) {
1784
- let url = applyTrailingSlash(path, this.trailingSlash);
1785
- if (queryParams) {
1786
- const entries = Object.entries(queryParams);
1787
- if (entries.length > 0) {
1788
- const queryString = entries.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");
1789
- url = url.includes("?") ? `${url}&${queryString}` : `${url}?${queryString}`;
1790
- }
1791
- }
1792
- if (options?.absolute && !url.startsWith("http")) url = `${new URL(this.routerContext.c.req.url).origin}${url}`;
1793
- return url;
1794
- }
1795
- /**
1796
- * Build a URL with query string parameters. Merges with existing query params in path.
1797
- *
1798
- * @param path - URL path, may already contain query params
1799
- * @param queryParams - Query parameters to merge (new values override existing)
1800
- */
1801
- query(path, queryParams) {
1802
- const parsed = new URL(path, "https://placeholder.local");
1803
- for (const [key, value] of Object.entries(queryParams)) parsed.searchParams.set(key, value);
1804
- return applyTrailingSlash(`${parsed.pathname}${parsed.search}`, this.trailingSlash);
1805
- }
1806
- getAppSecret() {
1807
- const secret = this.routerContext.c.env.APP_SECRET;
1808
- if (!secret) throw new Error("APP_SECRET environment variable is required for signed URLs");
1809
- return secret;
1810
- }
1811
- };
1812
- Uri = __decorate([
1813
- Request(),
1814
- __decorateParam(0, inject(ROUTER_TOKENS.RouteRegistry)),
1815
- __decorateParam(1, inject(ROUTER_TOKENS.RouterContext)),
1816
- __decorateParam(2, inject(DI_TOKENS.Application))
1817
- ], Uri);
1818
- //#endregion
1819
- //#region src/router/middleware/verify-signature.middleware.ts
1820
- /**
1821
- * Middleware that verifies signed URLs.
1822
- *
1823
- * Checks the `signature` (and optionally `expires`) query params against the
1824
- * request URL using HMAC-SHA256 via `crypto.subtle.verify()`.
1825
- *
1826
- * Requires `APP_SECRET` in the Cloudflare Workers environment bindings.
1827
- *
1828
- * @throws InvalidSignatureError (403) if signature is missing, invalid, or expired
1829
- *
1830
- * @example
1831
- * ```typescript
1832
- * @Module({ controllers: [UnsubscribeController], providers: [VerifySignatureMiddleware] })
1833
- * export class EmailModule implements RouteConfigurable {
1834
- * configureRoutes(router: Router): void {
1835
- * router.middleware(VerifySignatureMiddleware)
1836
- * }
1837
- * }
1838
- * ```
1839
- */
1840
- var VerifySignatureMiddleware = class {
1841
- async handle(ctx, next) {
1842
- const url = ctx.c.req.url;
1843
- const secret = ctx.c.env.APP_SECRET;
1844
- if (!secret) throw new RouterError("Missing required environment variable \"APP_SECRET\"");
1845
- if (!await verifySignedUrl(url, secret)) throw new InvalidSignatureError();
1846
- await next();
1847
- }
1848
- };
1849
- //#endregion
1850
- export { parseDomainPattern as A, uuidParamSchema as C, getRouteMetadata as D, getRouteDecoratedMethods as E, ResponseValidationError as M, SchemaValidationError as N, createMiddlewareChain as O, RouteNotFoundError as P, successMessageSchema as S, Route as T, toOpenAPIPath as _, RouteRegistry as a, paginatedResponseSchema as b, buildDetectorOptions as c, RouteRegistrationService as d, extractDomainParamNames as f, sortRoutesBySpecificity as g, getPathSpecificityScore as h, route as i, InvalidSignatureError as j, createDomainMiddleware as k, resolveI18nOptions as l, generateConventionRouteName as m, Uri as n, VersioningService as o, extractParamNames as p, buildRouteUrl as r, LocalePathService as s, VerifySignatureMiddleware as t, HonoApp as u, commonErrorSchemas as v, validationErrorResponseSchema as w, paginationQuerySchema as x, errorResponseSchema as y };
1851
-
1852
- //# sourceMappingURL=router-Cy6DjkvP.mjs.map