stratal 0.0.27 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +912 -0
- package/README.md +96 -14
- package/dist/bin/cloudflare-workers-loader.mjs +18 -3
- package/dist/bin/cloudflare-workers-loader.mjs.map +1 -1
- package/dist/bin/quarry.mjs +73 -12
- package/dist/bin/quarry.mjs.map +1 -1
- package/dist/cache/index.d.mts +8 -9
- package/dist/cache/index.d.mts.map +1 -1
- package/dist/cache/index.mjs +56 -17
- package/dist/cache/index.mjs.map +1 -1
- package/dist/{cache.service-uElmBtdS.d.mts → cache.service-Dg0Hj0gp.d.mts} +27 -12
- package/dist/cache.service-Dg0Hj0gp.d.mts.map +1 -0
- package/dist/{command-DoBD2Cwl.d.mts → command-BXMccV_9.d.mts} +6 -3
- package/dist/command-BXMccV_9.d.mts.map +1 -0
- package/dist/{command-BvmUAPPQ.mjs → command-Bb820B6W.mjs} +36 -13
- package/dist/command-Bb820B6W.mjs.map +1 -0
- package/dist/common.schemas-BigQCwYB.mjs +89 -0
- package/dist/common.schemas-BigQCwYB.mjs.map +1 -0
- package/dist/config/index.d.mts +15 -15
- package/dist/config/index.d.mts.map +1 -1
- package/dist/config/index.mjs +4 -6
- package/dist/config/index.mjs.map +1 -1
- package/dist/{consumer-registry-D3iMTSdy.d.mts → consumer-registry-i9oCdarv.d.mts} +2 -3
- package/dist/consumer-registry-i9oCdarv.d.mts.map +1 -0
- package/dist/{container-storage-BmOJ4_Na.mjs → container-storage-DfF7SXdV.mjs} +11 -1
- package/dist/container-storage-DfF7SXdV.mjs.map +1 -0
- package/dist/{controller.decorator-YSTPQntu.mjs → controller.decorator-BB0JcwMg.mjs} +18 -9
- package/dist/controller.decorator-BB0JcwMg.mjs.map +1 -0
- package/dist/cron/index.d.mts +6 -7
- package/dist/cron/index.d.mts.map +1 -1
- package/dist/cron/index.mjs +1 -1
- package/dist/{cron-job-NesZRk8F.d.mts → cron-job-DAgNw4Bn.d.mts} +1 -2
- package/dist/cron-job-DAgNw4Bn.d.mts.map +1 -0
- package/dist/{cron.module-C81HTzR7.mjs → cron.module-Dnyl9sFk.mjs} +6 -7
- package/dist/{cron.module-C81HTzR7.mjs.map → cron.module-Dnyl9sFk.mjs.map} +1 -1
- package/dist/{decorate-CuAoSZvs.mjs → decorate-C79yt3Wt.mjs} +2 -2
- package/dist/di/index.d.mts +1 -1
- package/dist/di/index.mjs +2 -3
- package/dist/{di-D7qmrAir.mjs → di-DQ3xbjgf.mjs} +48 -6
- package/dist/di-DQ3xbjgf.mjs.map +1 -0
- package/dist/email/index.d.mts +158 -133
- package/dist/email/index.d.mts.map +1 -1
- package/dist/email/index.mjs +66 -78
- package/dist/email/index.mjs.map +1 -1
- package/dist/{en-CDZBMcc1.mjs → en-CCL84z4k.mjs} +2 -2
- package/dist/{en-CDZBMcc1.mjs.map → en-CCL84z4k.mjs.map} +1 -1
- package/dist/env-ug22bJj7.d.mts.map +1 -1
- package/dist/errors/index.d.mts +1 -1
- package/dist/errors/index.mjs +3 -3
- package/dist/{errors-C01O2T-n.mjs → errors-CrOXI_y5.mjs} +6 -8
- package/dist/errors-CrOXI_y5.mjs.map +1 -0
- package/dist/events/index.d.mts +8 -9
- package/dist/events/index.d.mts.map +1 -1
- package/dist/events/index.mjs +1 -1
- package/dist/{events-BhEQuT1X.mjs → events-GtwGEHyT.mjs} +5 -6
- package/dist/events-GtwGEHyT.mjs.map +1 -0
- package/dist/{exception-context-D-kvney-.mjs → exception-context-CdnGTQNF.mjs} +13 -7
- package/dist/exception-context-CdnGTQNF.mjs.map +1 -0
- package/dist/{gateway-context-m7kEzRa2.mjs → gateway-context-Cmc1NRS0.mjs} +7 -7
- package/dist/{gateway-context-m7kEzRa2.mjs.map → gateway-context-Cmc1NRS0.mjs.map} +1 -1
- package/dist/gateway-mode-0rUaeW3g.mjs +53 -0
- package/dist/gateway-mode-0rUaeW3g.mjs.map +1 -0
- package/dist/guards/index.d.mts +38 -20
- package/dist/guards/index.d.mts.map +1 -1
- package/dist/guards/index.mjs +2 -2
- package/dist/{guards-DALPXy3_.mjs → guards-DOGly9IF.mjs} +34 -11
- package/dist/guards-DOGly9IF.mjs.map +1 -0
- package/dist/hono-app-IhdqL0Fk.mjs +399 -0
- package/dist/hono-app-IhdqL0Fk.mjs.map +1 -0
- package/dist/{http-method.decorator-BljM8BDj.mjs → http-method.decorator-BDi9LTWj.mjs} +9 -8
- package/dist/http-method.decorator-BDi9LTWj.mjs.map +1 -0
- package/dist/i18n/index.d.mts +26 -137
- package/dist/i18n/index.d.mts.map +1 -1
- package/dist/i18n/index.mjs +4 -5
- package/dist/i18n/index.mjs.map +1 -1
- package/dist/i18n/messages/en/index.d.mts +1 -1
- package/dist/i18n/messages/en/index.mjs +1 -1
- package/dist/i18n/utils/index.d.mts +1 -2
- package/dist/i18n/utils/index.d.mts.map +1 -1
- package/dist/i18n/validation/index.d.mts +80 -5
- package/dist/i18n/validation/index.d.mts.map +1 -0
- package/dist/i18n/validation/index.mjs +4 -5
- package/dist/{i18n.module-B2DvWUPa.mjs → i18n.module-Bm3xlViA.mjs} +28 -13
- package/dist/i18n.module-Bm3xlViA.mjs.map +1 -0
- package/dist/index-0ItCjaqw.d.mts.map +1 -1
- package/dist/{index-CNuFQSNj.d.mts → index-3HJfhLlE.d.mts} +9 -11
- package/dist/index-3HJfhLlE.d.mts.map +1 -0
- package/dist/{index-uybm0bhQ.d.mts → index-B2WtEagR.d.mts} +1187 -234
- package/dist/index-B2WtEagR.d.mts.map +1 -0
- package/dist/index-BUt92sAE.d.mts.map +1 -1
- package/dist/index-CAHNCpvr.d.mts +203 -0
- package/dist/index-CAHNCpvr.d.mts.map +1 -0
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +2 -2
- package/dist/{is-command-CEPO9n8c.mjs → is-command-CSVv14wh.mjs} +2 -2
- package/dist/{is-command-CEPO9n8c.mjs.map → is-command-CSVv14wh.mjs.map} +1 -1
- package/dist/{is-seeder-Gvh_AM71.mjs → is-seeder-gLgXYbqC.mjs} +2 -2
- package/dist/is-seeder-gLgXYbqC.mjs.map +1 -0
- package/dist/{lazy-module-loader-M6YKudNL.d.mts → lazy-module-loader-C3OHoUnR.d.mts} +4 -5
- package/dist/lazy-module-loader-C3OHoUnR.d.mts.map +1 -0
- package/dist/limit-BE0HNAF6.mjs +116 -0
- package/dist/limit-BE0HNAF6.mjs.map +1 -0
- package/dist/locale-path.service-BqzDIrgy.mjs +213 -0
- package/dist/locale-path.service-BqzDIrgy.mjs.map +1 -0
- package/dist/locale-url-nZrZxqJP.mjs.map +1 -1
- package/dist/{locale-url.service-6bgia24_.mjs → locale-url.service-H8PH6GGh.mjs} +15 -5
- package/dist/{locale-url.service-6bgia24_.mjs.map → locale-url.service-H8PH6GGh.mjs.map} +1 -1
- package/dist/logger/index.mjs +3 -5
- package/dist/logger/index.mjs.map +1 -1
- package/dist/macroable-cvDTFZ_A.mjs.map +1 -1
- package/dist/metadata-Ba8Fxs1F.mjs +48 -0
- package/dist/metadata-Ba8Fxs1F.mjs.map +1 -0
- package/dist/module/index.d.mts +8 -9
- package/dist/module/index.d.mts.map +1 -1
- package/dist/module/index.mjs +3 -4
- package/dist/module/index.mjs.map +1 -1
- package/dist/{module-registry-NxX5O0Qk.mjs → module-registry-lkQPDuQe.mjs} +17 -15
- package/dist/module-registry-lkQPDuQe.mjs.map +1 -0
- package/dist/{module.decorator-CYHY6pG5.mjs → module.decorator-CYbrOlX9.mjs} +2 -2
- package/dist/{module.decorator-CYHY6pG5.mjs.map → module.decorator-CYbrOlX9.mjs.map} +1 -1
- package/dist/openapi/index.d.mts +19 -16
- package/dist/openapi/index.d.mts.map +1 -1
- package/dist/openapi/index.mjs +2 -2
- package/dist/{openapi-CMwuCp31.mjs → openapi-DBfBa5Qh.mjs} +41 -65
- package/dist/openapi-DBfBa5Qh.mjs.map +1 -0
- package/dist/openapi-generator-CCwiu8AU.mjs +134 -0
- package/dist/openapi-generator-CCwiu8AU.mjs.map +1 -0
- package/dist/{openapi-tools.service-BC5EC3R3.mjs → openapi-tools.service-VKZJlTIX.mjs} +12 -4
- package/dist/openapi-tools.service-VKZJlTIX.mjs.map +1 -0
- package/dist/openapi.service-Ds-R1tih.d.mts +43 -0
- package/dist/openapi.service-Ds-R1tih.d.mts.map +1 -0
- package/dist/quarry/index.d.mts +10 -11
- package/dist/quarry/index.d.mts.map +1 -1
- package/dist/quarry/index.mjs +4 -4
- package/dist/quarry/runner.d.mts +19 -20
- package/dist/quarry/runner.d.mts.map +1 -1
- package/dist/quarry/runner.mjs +16 -13
- package/dist/quarry/runner.mjs.map +1 -1
- package/dist/{quarry-registry-DRnV-DDa.d.mts → quarry-registry-Ci4k58FZ.d.mts} +3 -4
- package/dist/quarry-registry-Ci4k58FZ.d.mts.map +1 -0
- package/dist/{quarry.module-CcGxU2dJ.mjs → quarry.module-BssRR8f0.mjs} +20 -37
- package/dist/quarry.module-BssRR8f0.mjs.map +1 -0
- package/dist/queue/index.d.mts +2 -2
- package/dist/queue/index.mjs +3 -3
- package/dist/queue/index.mjs.map +1 -1
- package/dist/{queue.module-CEs4_kEM.mjs → queue.module-DnAdSbR-.mjs} +40 -41
- package/dist/queue.module-DnAdSbR-.mjs.map +1 -0
- package/dist/{r2-storage.provider-BoZmR6Ut.mjs → r2-storage.provider-wU_6api4.mjs} +45 -6
- package/dist/r2-storage.provider-wU_6api4.mjs.map +1 -0
- package/dist/{rate-limit.decorator-Djs4oYDB.mjs → rate-limit.decorator-PYQ9EYqs.mjs} +10 -5
- package/dist/{rate-limit.decorator-Djs4oYDB.mjs.map → rate-limit.decorator-PYQ9EYqs.mjs.map} +1 -1
- package/dist/rate-limiter/index.d.mts +64 -19
- package/dist/rate-limiter/index.d.mts.map +1 -1
- package/dist/rate-limiter/index.mjs +67 -83
- package/dist/rate-limiter/index.mjs.map +1 -1
- package/dist/response-cache/index.d.mts +326 -0
- package/dist/response-cache/index.d.mts.map +1 -0
- package/dist/response-cache/index.mjs +511 -0
- package/dist/response-cache/index.mjs.map +1 -0
- package/dist/response-cache.error-B4D0zy9W.mjs +24 -0
- package/dist/response-cache.error-B4D0zy9W.mjs.map +1 -0
- package/dist/response-headers-BqA57S_X.mjs +158 -0
- package/dist/response-headers-BqA57S_X.mjs.map +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/dist/route-cache-binding-CrLh8tB6.mjs +390 -0
- package/dist/route-cache-binding-CrLh8tB6.mjs.map +1 -0
- package/dist/route-metadata-QQhbed1i.mjs +17 -0
- package/dist/route-metadata-QQhbed1i.mjs.map +1 -0
- package/dist/{route-name-DGoBOfPg.mjs → route-name-BslSbmMP.mjs} +15 -11
- package/dist/route-name-BslSbmMP.mjs.map +1 -0
- package/dist/route-registration.service-COKBZTsn.mjs +1265 -0
- package/dist/route-registration.service-COKBZTsn.mjs.map +1 -0
- package/dist/{route-registry-BvLJisvK.mjs → route-registry-DFz9_-N6.mjs} +6 -6
- package/dist/{route-registry-BvLJisvK.mjs.map → route-registry-DFz9_-N6.mjs.map} +1 -1
- package/dist/router/index.d.mts +2 -2
- package/dist/router/index.mjs +16 -15
- package/dist/{router-resolver-sUV_jTrU.mjs → router-resolver-B-dMSMIG.mjs} +7 -3
- package/dist/router-resolver-B-dMSMIG.mjs.map +1 -0
- package/dist/{router-DwyqEXgf.mjs → router-wlsVkGkM.mjs} +5 -14
- package/dist/{router-DwyqEXgf.mjs.map → router-wlsVkGkM.mjs.map} +1 -1
- package/dist/seeder/index.d.mts +12 -13
- package/dist/seeder/index.d.mts.map +1 -1
- package/dist/seeder/index.mjs +4 -4
- package/dist/{seeder-BPGY5rUb.mjs → seeder-BrAk7XIj.mjs} +5 -6
- package/dist/{seeder-BPGY5rUb.mjs.map → seeder-BrAk7XIj.mjs.map} +1 -1
- package/dist/{seeder-registry-DEvCycsT.mjs → seeder-registry-Bo5BgLbC.mjs} +5 -6
- package/dist/{seeder-registry-DEvCycsT.mjs.map → seeder-registry-Bo5BgLbC.mjs.map} +1 -1
- package/dist/{seeder.module-CIwQbdN4.mjs → seeder.module-DOoVgTsK.mjs} +5 -5
- package/dist/{seeder.module-CIwQbdN4.mjs.map → seeder.module-DOoVgTsK.mjs.map} +1 -1
- package/dist/signed-url-DIU0sK_6.mjs.map +1 -1
- package/dist/storage/index.d.mts +63 -13
- package/dist/storage/index.d.mts.map +1 -1
- package/dist/storage/index.mjs +2 -2
- package/dist/storage/providers/index.d.mts +18 -16
- package/dist/storage/providers/index.d.mts.map +1 -1
- package/dist/storage/providers/index.mjs +1 -1
- package/dist/{storage-C30X81CS.mjs → storage-DBWVYMnn.mjs} +167 -49
- package/dist/storage-DBWVYMnn.mjs.map +1 -0
- package/dist/storage-provider.interface-CinHpjl1.d.mts +315 -0
- package/dist/storage-provider.interface-CinHpjl1.d.mts.map +1 -0
- package/dist/storage.error-Ao38Qdjx.mjs +7 -0
- package/dist/storage.error-Ao38Qdjx.mjs.map +1 -0
- package/dist/{stratal-D5j_I14G.d.mts → stratal-Bs1_E8GQ.d.mts} +13 -3
- package/dist/stratal-Bs1_E8GQ.d.mts.map +1 -0
- package/dist/{stratal-BL6FKUM_.mjs → stratal-CiwNpWy2.mjs} +56 -23
- package/dist/stratal-CiwNpWy2.mjs.map +1 -0
- package/dist/{tiered-cache.service-Dv3BhxxE.d.mts → tiered-cache.service-Bz8bbvyb.d.mts} +16 -3
- package/dist/tiered-cache.service-Bz8bbvyb.d.mts.map +1 -0
- package/dist/trailing-slash-2SctvePW.mjs.map +1 -1
- package/dist/types-CmV_9xBD.d.mts.map +1 -1
- package/dist/{uri-iwofWJ_T.mjs → uri-BrOq148R.mjs} +13 -9
- package/dist/{uri-iwofWJ_T.mjs.map → uri-BrOq148R.mjs.map} +1 -1
- package/dist/{usage-generator-DAWYasuP.mjs → usage-generator-BW7EcO-C.mjs} +3 -3
- package/dist/{usage-generator-DAWYasuP.mjs.map → usage-generator-BW7EcO-C.mjs.map} +1 -1
- package/dist/{zod-eKqqhZ5_.mjs → validation-CZ_B0aXY.mjs} +38 -17
- package/dist/validation-CZ_B0aXY.mjs.map +1 -0
- package/dist/{validation.context-CRvmrhq7.mjs → validation.context-CdbwQWIr.mjs} +2 -2
- package/dist/{validation.context-CRvmrhq7.mjs.map → validation.context-CdbwQWIr.mjs.map} +1 -1
- package/dist/{versioning.service-CCa2oYMJ.mjs → versioning.service-DnF_4Lnd.mjs} +5 -5
- package/dist/{versioning.service-CCa2oYMJ.mjs.map → versioning.service-DnF_4Lnd.mjs.map} +1 -1
- package/dist/websocket/index.d.mts +12 -13
- package/dist/websocket/index.d.mts.map +1 -1
- package/dist/websocket/index.mjs +1 -1
- package/dist/workers/index.d.mts +70 -7
- package/dist/workers/index.d.mts.map +1 -1
- package/dist/workers/index.mjs +72 -3
- package/dist/workers/index.mjs.map +1 -1
- package/dist/{zod-wecrEVAs.d.mts → zod-BGisqLs3.d.mts} +9 -16
- package/dist/zod-BGisqLs3.d.mts.map +1 -0
- package/package.json +33 -25
- package/dist/cache.service-uElmBtdS.d.mts.map +0 -1
- package/dist/chunk-BBjsoOtd.mjs +0 -27
- package/dist/command-BvmUAPPQ.mjs.map +0 -1
- package/dist/command-DoBD2Cwl.d.mts.map +0 -1
- package/dist/consumer-registry-D3iMTSdy.d.mts.map +0 -1
- package/dist/container-storage-BmOJ4_Na.mjs.map +0 -1
- package/dist/controller.decorator-YSTPQntu.mjs.map +0 -1
- package/dist/cron-job-NesZRk8F.d.mts.map +0 -1
- package/dist/di-D7qmrAir.mjs.map +0 -1
- package/dist/errors-C01O2T-n.mjs.map +0 -1
- package/dist/events-BhEQuT1X.mjs.map +0 -1
- package/dist/exception-context-D-kvney-.mjs.map +0 -1
- package/dist/guards-DALPXy3_.mjs.map +0 -1
- package/dist/hono-app-COAgmutc.mjs +0 -168
- package/dist/hono-app-COAgmutc.mjs.map +0 -1
- package/dist/http-method.decorator-BljM8BDj.mjs.map +0 -1
- package/dist/i18n.module-B2DvWUPa.mjs.map +0 -1
- package/dist/index-CNuFQSNj.d.mts.map +0 -1
- package/dist/index-DtBNIFuP.d.mts +0 -42
- package/dist/index-DtBNIFuP.d.mts.map +0 -1
- package/dist/index-HgOLNruQ.d.mts +0 -203
- package/dist/index-HgOLNruQ.d.mts.map +0 -1
- package/dist/index-uybm0bhQ.d.mts.map +0 -1
- package/dist/is-seeder-Gvh_AM71.mjs.map +0 -1
- package/dist/lazy-module-loader-M6YKudNL.d.mts.map +0 -1
- package/dist/locale-path.service-CH0CaxwH.mjs +0 -165
- package/dist/locale-path.service-CH0CaxwH.mjs.map +0 -1
- package/dist/metadata-DzzprcID.mjs +0 -39
- package/dist/metadata-DzzprcID.mjs.map +0 -1
- package/dist/module-registry-NxX5O0Qk.mjs.map +0 -1
- package/dist/openapi-CMwuCp31.mjs.map +0 -1
- package/dist/openapi-tools.service-BC5EC3R3.mjs.map +0 -1
- package/dist/openapi.service-2rvJBCEg.d.mts +0 -50
- package/dist/openapi.service-2rvJBCEg.d.mts.map +0 -1
- package/dist/quarry-registry-DRnV-DDa.d.mts.map +0 -1
- package/dist/quarry.module-CcGxU2dJ.mjs.map +0 -1
- package/dist/queue.module-CEs4_kEM.mjs.map +0 -1
- package/dist/r2-storage.provider-BoZmR6Ut.mjs.map +0 -1
- package/dist/route-name-DGoBOfPg.mjs.map +0 -1
- package/dist/route-registration.service-CDPQKpm4.mjs +0 -918
- package/dist/route-registration.service-CDPQKpm4.mjs.map +0 -1
- package/dist/router-resolver-sUV_jTrU.mjs.map +0 -1
- package/dist/storage-C30X81CS.mjs.map +0 -1
- package/dist/storage-provider.interface-ClUwxz4S.d.mts +0 -201
- package/dist/storage-provider.interface-ClUwxz4S.d.mts.map +0 -1
- package/dist/storage.error-BStXPmO4.mjs +0 -8
- package/dist/storage.error-BStXPmO4.mjs.map +0 -1
- package/dist/stratal-BL6FKUM_.mjs.map +0 -1
- package/dist/stratal-D5j_I14G.d.mts.map +0 -1
- package/dist/tiered-cache.service-Dv3BhxxE.d.mts.map +0 -1
- package/dist/validation-CpOjviyT.mjs +0 -49
- package/dist/validation-CpOjviyT.mjs.map +0 -1
- package/dist/zod-eKqqhZ5_.mjs.map +0 -1
- package/dist/zod-wecrEVAs.d.mts.map +0 -1
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
+
import { C as ROUTER_TOKENS, l as Singleton, n as CONTAINER_TOKEN, p as inject, r as DI_TOKENS } from "./di-DQ3xbjgf.mjs";
|
|
3
|
+
import { r as runWithContainer } from "./container-storage-DfF7SXdV.mjs";
|
|
4
|
+
import { n as __decorateParam, t as __decorate } from "./decorate-C79yt3Wt.mjs";
|
|
5
|
+
import { LOGGER_TOKENS } from "./logger/index.mjs";
|
|
6
|
+
import { d as HttpException } from "./errors-CrOXI_y5.mjs";
|
|
7
|
+
import { a as RouterContext, l as ROUTER_CONTEXT_KEYS, r as createHttpExceptionContext } from "./exception-context-CdnGTQNF.mjs";
|
|
8
|
+
import { o as RouterError } from "./module-registry-lkQPDuQe.mjs";
|
|
9
|
+
import { t as isGatewayMode } from "./gateway-mode-0rUaeW3g.mjs";
|
|
10
|
+
import { n as OPENAPI_TOKENS } from "./openapi-tools.service-VKZJlTIX.mjs";
|
|
11
|
+
import { o as createMiddlewareChain, t as RouteRegistrationService } from "./route-registration.service-COKBZTsn.mjs";
|
|
12
|
+
import { i as representationOf, n as setResponseHeaders, o as shouldLoopback, r as REPRESENTATION_PROP, t as isSharedCacheable } from "./response-headers-BqA57S_X.mjs";
|
|
13
|
+
import { d as resolveCachedEntrypoint, l as RESPONSE_CACHE_TOKENS } from "./route-cache-binding-CrLh8tB6.mjs";
|
|
14
|
+
import { n as PER_CALLER_RATE_LIMIT_HEADERS } from "./limit-BE0HNAF6.mjs";
|
|
15
|
+
import { r as resolveTrailingSlash, t as applyTrailingSlash } from "./trailing-slash-2SctvePW.mjs";
|
|
16
|
+
import { Hono } from "hono";
|
|
17
|
+
import { matchedRoutes } from "hono/route";
|
|
18
|
+
//#region src/router/errors/route-not-found.error.ts
|
|
19
|
+
var RouteNotFoundError = class extends HttpException {
|
|
20
|
+
path;
|
|
21
|
+
method;
|
|
22
|
+
constructor(path, method) {
|
|
23
|
+
super(404, `Route not found: ${method} ${path}`);
|
|
24
|
+
this.path = path;
|
|
25
|
+
this.method = method;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/router/middleware/logger.middleware.ts
|
|
30
|
+
/**
|
|
31
|
+
* Create a Hono middleware that logs HTTP requests using our Logger service
|
|
32
|
+
*
|
|
33
|
+
* Logs request method, path, status code, and duration in milliseconds.
|
|
34
|
+
* Format: [HTTP] METHOD /path -> STATUS (duration ms)
|
|
35
|
+
*
|
|
36
|
+
* @param logger - Logger service instance
|
|
37
|
+
* @returns Hono middleware handler
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* const logger = container.resolve<LoggerService>(LOGGER_TOKENS.LoggerService)
|
|
42
|
+
* app.use('*', createLoggerMiddleware(logger))
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
function createLoggerMiddleware(logger) {
|
|
46
|
+
return async (c, next) => {
|
|
47
|
+
const start = Date.now();
|
|
48
|
+
const method = c.req.method;
|
|
49
|
+
const path = c.req.path;
|
|
50
|
+
await next();
|
|
51
|
+
const duration = Date.now() - start;
|
|
52
|
+
const status = c.res.status;
|
|
53
|
+
logger.info(`[HTTP] ${method} ${path} -> ${status}`, {
|
|
54
|
+
method,
|
|
55
|
+
path,
|
|
56
|
+
status,
|
|
57
|
+
duration
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/router/nested-dispatch.ts
|
|
63
|
+
/**
|
|
64
|
+
* Requests the application dispatched into its own router while another
|
|
65
|
+
* request was already running.
|
|
66
|
+
*
|
|
67
|
+
* A nested dispatch is not an eyeball request, and the difference is not
|
|
68
|
+
* cosmetic: it exists to render something the caller asked for *indirectly*,
|
|
69
|
+
* so a route is entitled to answer it differently — that is the whole point of
|
|
70
|
+
* one, and the reason a route can recognise it at all. Its response is
|
|
71
|
+
* therefore not the response the same URL would give a client, and anything
|
|
72
|
+
* that stores responses by URL must never see it.
|
|
73
|
+
*
|
|
74
|
+
* The mark lives in a module-private `WeakSet` keyed on the `Request` object,
|
|
75
|
+
* for the reasons `markGatewayMode` uses one for the `ExecutionContext`:
|
|
76
|
+
* nothing reachable from a request can add to it, so it cannot be forged the
|
|
77
|
+
* way a header or a query parameter can. Keyed on the request rather than on
|
|
78
|
+
* the context because a nested dispatch shares the outer request's execution
|
|
79
|
+
* context — it needs the same `waitUntil` and the same bindings — and one
|
|
80
|
+
* `Request` is one dispatch.
|
|
81
|
+
*/
|
|
82
|
+
const NESTED_DISPATCHES = /* @__PURE__ */ new WeakSet();
|
|
83
|
+
/**
|
|
84
|
+
* Mark a request as dispatched into this app from inside another request.
|
|
85
|
+
*
|
|
86
|
+
* Returns the same object it was given, so it can be used inline.
|
|
87
|
+
*/
|
|
88
|
+
function markNestedDispatch(request) {
|
|
89
|
+
NESTED_DISPATCHES.add(request);
|
|
90
|
+
return request;
|
|
91
|
+
}
|
|
92
|
+
/** Whether this request was marked by {@link markNestedDispatch}. */
|
|
93
|
+
function isNestedDispatch(request) {
|
|
94
|
+
return NESTED_DISPATCHES.has(request);
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region src/router/middleware/gateway-dispatch.middleware.ts
|
|
98
|
+
/**
|
|
99
|
+
* `c.executionCtx` is a Hono getter that throws when the context was built
|
|
100
|
+
* without one (a bare `app.fetch(request)`), so it can only be read through a
|
|
101
|
+
* `try`/`catch`.
|
|
102
|
+
*/
|
|
103
|
+
function executionContextOf(c) {
|
|
104
|
+
try {
|
|
105
|
+
return c.executionCtx;
|
|
106
|
+
} catch {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Find the binding for the route that will actually handle this request.
|
|
112
|
+
*
|
|
113
|
+
* `routePath(c)` inside a `use('*')` middleware returns `'*'` — the path *this
|
|
114
|
+
* handler* was registered under, which Hono's own docstring shows — so it
|
|
115
|
+
* cannot be used directly.
|
|
116
|
+
*
|
|
117
|
+
* `matchedRoutes(c)` returns **every** pattern that matched, in registration
|
|
118
|
+
* order, which is more than the one that will run. Scanning it backward is
|
|
119
|
+
* wrong, and dangerously so:
|
|
120
|
+
*
|
|
121
|
+
* ```ts
|
|
122
|
+
* @Get('/summary') @Cacheable({ partitionBy: ['user'] }) summary() {}
|
|
123
|
+
* @Get('/:id') @Cacheable({ partitionBy: ['tenant'] }) show() {}
|
|
124
|
+
* ```
|
|
125
|
+
*
|
|
126
|
+
* `GET /reports/summary` matches both, and static-before-parameterised is the
|
|
127
|
+
* ordinary registration order. A backward scan picks `/reports/:id` and
|
|
128
|
+
* forwards `{ tenant }`, while the cached entrypoint dispatches `summary()` —
|
|
129
|
+
* storing a per-user response under a tenant-only key, so every user in that
|
|
130
|
+
* tenant is served the first one's page.
|
|
131
|
+
*
|
|
132
|
+
* Scanning **forward from the handler after this middleware** picks the next
|
|
133
|
+
* route that will run, which is the one whose `@Cacheable` governs the
|
|
134
|
+
* response. `c.req.routeIndex` is this middleware's own position in the
|
|
135
|
+
* matched list, so `+ 1` starts at the first candidate.
|
|
136
|
+
*
|
|
137
|
+
* This is the primary defence. `RouteRegistrationService.partitionsResolved`
|
|
138
|
+
* is the second: inside the cached entrypoint it verifies that `ctx.props`
|
|
139
|
+
* actually covers the executing route's declared partitions, so even a
|
|
140
|
+
* mis-selected binding fails closed rather than caching under the wrong key.
|
|
141
|
+
*/
|
|
142
|
+
function lookupRoute(c, table) {
|
|
143
|
+
const routes = matchedRoutes(c);
|
|
144
|
+
const method = c.req.method;
|
|
145
|
+
for (let i = c.req.routeIndex + 1; i < routes.length; i++) {
|
|
146
|
+
const entry = table.lookup(method, routes[i].path);
|
|
147
|
+
if (entry) return entry;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Forward partitioned cacheable reads to the cached entrypoint.
|
|
152
|
+
*
|
|
153
|
+
* Both entrypoints run the *same* Hono app. This middleware is the only thing
|
|
154
|
+
* that differs between them, and it differs on one bit that no client can
|
|
155
|
+
* touch: whether `Stratal.fetch` marked this request's `ExecutionContext` as
|
|
156
|
+
* the gateway (see `markGatewayMode`). In the cached entrypoint the mark is
|
|
157
|
+
* absent, so this is a straight `next()` — which is what makes a loopback
|
|
158
|
+
* loop impossible.
|
|
159
|
+
*
|
|
160
|
+
* In gateway mode, for a `GET`/`HEAD` on a route whose effective `partitionBy`
|
|
161
|
+
* is non-empty, it primes the request container, resolves the declared
|
|
162
|
+
* partitions, and re-dispatches through `ctx.exports.<Cached>({ props })` —
|
|
163
|
+
* `ctx.props` being the part of the Workers Caching key that Cloudflare
|
|
164
|
+
* documents as impossible to bypass, and therefore what keeps one caller's
|
|
165
|
+
* cached response away from another.
|
|
166
|
+
*
|
|
167
|
+
* **Everything else runs inline**, which is always the safe direction: an
|
|
168
|
+
* inline response never enters the cached entrypoint at all, so it can never
|
|
169
|
+
* be stored. That covers a table miss, a non-`GET`/`HEAD` method, a request
|
|
170
|
+
* the app dispatched into itself (`markNestedDispatch`), a primer that
|
|
171
|
+
* short-circuited, and any partition resolver that returned `null`/`undefined`
|
|
172
|
+
* or threw.
|
|
173
|
+
*
|
|
174
|
+
* ### Placement
|
|
175
|
+
*
|
|
176
|
+
* Registered in `HonoApp`'s constructor, after the request-scope and logger
|
|
177
|
+
* middleware and before every application middleware:
|
|
178
|
+
*
|
|
179
|
+
* - **Inside** `createNoStoreFallbackMiddleware`, so a forwarded response that
|
|
180
|
+
* somehow carried no `Cache-Control` still leaves with an explicit decision.
|
|
181
|
+
* - **Inside** the trailing-slash redirect, so a non-canonical URL is
|
|
182
|
+
* redirected once, by the gateway, rather than forwarded and cached under a
|
|
183
|
+
* path that would then redirect again.
|
|
184
|
+
* - **Inside** the request-scope middleware, because primers and partition
|
|
185
|
+
* resolvers resolve out of the request-scoped container (`ctx.user()` is a
|
|
186
|
+
* macro over the request-scoped `AuthContext`).
|
|
187
|
+
* - **Inside** the logger middleware, so the gateway logs the requests it
|
|
188
|
+
* forwards — on a cache hit the app never runs, so this is the only place
|
|
189
|
+
* that observes them at all.
|
|
190
|
+
* - **Outside** every application middleware (global `Router.use()`,
|
|
191
|
+
* group/route middleware, guards, validators), because on a cache hit none
|
|
192
|
+
* of those run. Running them in the gateway would double their side effects
|
|
193
|
+
* on a miss and produce behaviour a hit could never reproduce.
|
|
194
|
+
*/
|
|
195
|
+
function createGatewayDispatchMiddleware(container) {
|
|
196
|
+
let table;
|
|
197
|
+
let looked = false;
|
|
198
|
+
let bootChecked = false;
|
|
199
|
+
let bootFailure;
|
|
200
|
+
return async (c, next) => {
|
|
201
|
+
if (!looked) {
|
|
202
|
+
looked = true;
|
|
203
|
+
table = container.tryResolve(RESPONSE_CACHE_TOKENS.GatewayRouteTable);
|
|
204
|
+
}
|
|
205
|
+
const routeTable = table;
|
|
206
|
+
const entrypoint = routeTable?.entrypoint;
|
|
207
|
+
if (!routeTable || !entrypoint) return next();
|
|
208
|
+
const ctx = executionContextOf(c);
|
|
209
|
+
if (!isGatewayMode(ctx)) return next();
|
|
210
|
+
if (isNestedDispatch(c.req.raw)) return next();
|
|
211
|
+
if (!bootChecked) {
|
|
212
|
+
bootChecked = true;
|
|
213
|
+
try {
|
|
214
|
+
resolveCachedEntrypoint(ctx, entrypoint);
|
|
215
|
+
} catch (error) {
|
|
216
|
+
bootFailure = error;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (bootFailure) throw bootFailure;
|
|
220
|
+
if (routeTable.isEmpty) return next();
|
|
221
|
+
const entry = lookupRoute(c, routeTable);
|
|
222
|
+
if (!entry) return next();
|
|
223
|
+
if (!shouldLoopback(c.req.method, entry)) return next();
|
|
224
|
+
const routerContext = new RouterContext(c);
|
|
225
|
+
const primers = container.tryResolve(RESPONSE_CACHE_TOKENS.GatewayPrimerService);
|
|
226
|
+
if (primers && !await primers.prime(routerContext)) return next();
|
|
227
|
+
const resolver = container.tryResolve(RESPONSE_CACHE_TOKENS.PartitionResolverService);
|
|
228
|
+
if (!resolver) return next();
|
|
229
|
+
const { props, resolved } = await resolver.resolve(routerContext, entry.partitionBy);
|
|
230
|
+
if (!resolved) return next();
|
|
231
|
+
const representation = representationOf(c.req.raw.headers, routeTable.keyBy);
|
|
232
|
+
if (representation !== void 0) props[REPRESENTATION_PROP] = representation;
|
|
233
|
+
return resolveCachedEntrypoint(ctx, entrypoint)({ props }).fetch(c.req.raw);
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
//#endregion
|
|
237
|
+
//#region src/router/middleware/no-store-fallback.middleware.ts
|
|
238
|
+
const NO_STORE = "private, no-store";
|
|
239
|
+
/** `{ 'X-RateLimit-Limit': null, … }` — the delete form `setResponseHeaders` takes. */
|
|
240
|
+
const DROP_PER_CALLER_HEADERS = Object.fromEntries(PER_CALLER_RATE_LIMIT_HEADERS.map((name) => [name, null]));
|
|
241
|
+
/**
|
|
242
|
+
* The single outermost safety net behind Stratal's caching-decision
|
|
243
|
+
* guarantee: every response leaving a Stratal app carries an explicit
|
|
244
|
+
* `Cache-Control` header. Workers Caching applies RFC 9111 heuristic
|
|
245
|
+
* freshness to anything that doesn't — a header-less `200` is cached for two
|
|
246
|
+
* hours (§4.2.1), and `404`/`405`/`410`/`501` are heuristically cacheable too
|
|
247
|
+
* (§4.2.2), same for a redirect like the trailing-slash `308`.
|
|
248
|
+
*
|
|
249
|
+
* `RouteRegistrationService.applyCacheDecision` already stamps a decision for
|
|
250
|
+
* every ordinary controller response — `@Cacheable` routes get their computed
|
|
251
|
+
* headers, everything else is left alone for this middleware to catch. This
|
|
252
|
+
* is the backstop for everything that path never sees: thrown errors (404s,
|
|
253
|
+
* 500s rendered by the exception handler), guard/scoped-middleware
|
|
254
|
+
* short-circuits (`wrapHandlerWithChain` returning a middleware's `Response`
|
|
255
|
+
* directly), the trailing-slash redirect, wildcard/`@All` controller routes,
|
|
256
|
+
* and any future response path that forgets to call it.
|
|
257
|
+
*
|
|
258
|
+
* Registered as the very first global middleware, so it wraps everything
|
|
259
|
+
* else. Hono's `compose()` fully resolves a thrown error or a middleware
|
|
260
|
+
* short-circuit into `context.res` at the layer where it happens, then
|
|
261
|
+
* returns *normally* to every middleware further out — the same guarantee
|
|
262
|
+
* `createLoggerMiddleware` already depends on to log the right status for a
|
|
263
|
+
* 404. By the time `next()` resolves here, `c.res` holds the final response,
|
|
264
|
+
* whichever code path produced it.
|
|
265
|
+
*/
|
|
266
|
+
function createNoStoreFallbackMiddleware() {
|
|
267
|
+
return async (c, next) => {
|
|
268
|
+
await next();
|
|
269
|
+
if (!c.res.headers.has("Cache-Control")) {
|
|
270
|
+
const stamped = setResponseHeaders(c.res, { "Cache-Control": NO_STORE });
|
|
271
|
+
if (stamped !== c.res) c.res = stamped;
|
|
272
|
+
}
|
|
273
|
+
if (isSharedCacheable(c.res)) {
|
|
274
|
+
const stripped = setResponseHeaders(c.res, DROP_PER_CALLER_HEADERS);
|
|
275
|
+
if (stripped !== c.res) c.res = stripped;
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
//#endregion
|
|
280
|
+
//#region src/router/middleware/trailing-slash-redirect.ts
|
|
281
|
+
const REDIRECT_STATUS = 308;
|
|
282
|
+
/**
|
|
283
|
+
* Create a Hono middleware that canonicalises trailing slashes via 308 redirects.
|
|
284
|
+
*
|
|
285
|
+
* - `'ignore'` — returns `null`; routes match both `/foo` and `/foo/` natively
|
|
286
|
+
* (Hono handles this when constructed with `strict: false`).
|
|
287
|
+
* - `'always'` — non-trailing requests redirect to the trailing-slash form.
|
|
288
|
+
* Paths whose last segment contains `.` (e.g. `/api/openapi.json`) are skipped.
|
|
289
|
+
* - `'never'` — trailing requests redirect to the non-trailing form.
|
|
290
|
+
*
|
|
291
|
+
* Paths in the config's `exclude` list are never redirected — both forms
|
|
292
|
+
* are served as requested (routes match either, `strict: false`). When
|
|
293
|
+
* `getLocales` is provided, exclusions also match locale-prefixed request
|
|
294
|
+
* paths (`/fr/callback` for exclude `'/callback'`). It is a thunk because
|
|
295
|
+
* the middleware is created before the i18n module resolves its locales.
|
|
296
|
+
*
|
|
297
|
+
* Root (`/`) is always passed through unchanged.
|
|
298
|
+
*
|
|
299
|
+
* 308 is used so that POST/PUT/PATCH bodies survive the redirect.
|
|
300
|
+
*
|
|
301
|
+
* Location headers are emitted as path-relative URIs so the user agent
|
|
302
|
+
* resolves them against the effective request URI — sidestepping scheme
|
|
303
|
+
* mismatches behind HTTPS-terminating proxies that proxy HTTPS pages to an
|
|
304
|
+
* HTTP-speaking backend (which would otherwise produce a mixed-content block).
|
|
305
|
+
*/
|
|
306
|
+
function createTrailingSlashRedirect(config, getLocales) {
|
|
307
|
+
const options = resolveTrailingSlash(config);
|
|
308
|
+
if (options.mode === "ignore") return null;
|
|
309
|
+
return async (c, next) => {
|
|
310
|
+
const url = new URL(c.req.url);
|
|
311
|
+
const canonicalPath = applyTrailingSlash(url.pathname, options, getLocales?.());
|
|
312
|
+
if (canonicalPath === url.pathname) return next();
|
|
313
|
+
return c.redirect(`${canonicalPath}${url.search}`, REDIRECT_STATUS);
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
//#endregion
|
|
317
|
+
//#region src/router/hono-app.ts
|
|
318
|
+
var hono_app_exports = /* @__PURE__ */ __exportAll({ HonoApp: () => HonoApp });
|
|
319
|
+
const isMiddlewareClass = (arg) => typeof arg === "function" && arg.prototype && "handle" in arg.prototype;
|
|
320
|
+
let HonoApp = class HonoApp extends Hono {
|
|
321
|
+
configured = false;
|
|
322
|
+
_container;
|
|
323
|
+
_logger;
|
|
324
|
+
/**
|
|
325
|
+
* Reference to the original Hono `use` implementation.
|
|
326
|
+
* Captured in constructor after super() sets it as an instance property.
|
|
327
|
+
* Used by private methods to register middleware without going through the override.
|
|
328
|
+
*/
|
|
329
|
+
nativeUse;
|
|
330
|
+
constructor(container, logger, application) {
|
|
331
|
+
const trailingSlash = application.config.trailingSlash ?? "ignore";
|
|
332
|
+
super({ strict: false });
|
|
333
|
+
this._container = container;
|
|
334
|
+
this._logger = logger;
|
|
335
|
+
this.nativeUse = this.use;
|
|
336
|
+
this.use = ((...args) => {
|
|
337
|
+
if (isMiddlewareClass(args[0])) {
|
|
338
|
+
this.nativeUse("*", createMiddlewareChain(args));
|
|
339
|
+
return this;
|
|
340
|
+
}
|
|
341
|
+
if (typeof args[0] === "string" && args.length > 1 && isMiddlewareClass(args[1])) {
|
|
342
|
+
this.nativeUse(args[0], createMiddlewareChain(args.slice(1)));
|
|
343
|
+
return this;
|
|
344
|
+
}
|
|
345
|
+
return this.nativeUse(...args);
|
|
346
|
+
});
|
|
347
|
+
this.nativeUse("*", createNoStoreFallbackMiddleware());
|
|
348
|
+
const trailingSlashRedirect = createTrailingSlashRedirect(trailingSlash, () => this._container.tryResolve(ROUTER_TOKENS.LocalePathService)?.localePathConfig?.allLocales);
|
|
349
|
+
if (trailingSlashRedirect) this.nativeUse("*", trailingSlashRedirect);
|
|
350
|
+
this.setupRequestScope();
|
|
351
|
+
this.applyGlobalMiddleware();
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Apply global middleware (logger + error handler).
|
|
355
|
+
* Called by Application after locale middleware is applied by LocalePathService.
|
|
356
|
+
*/
|
|
357
|
+
applyGlobalMiddleware() {
|
|
358
|
+
this.nativeUse("*", createLoggerMiddleware(this._logger));
|
|
359
|
+
this.nativeUse("*", createGatewayDispatchMiddleware(this._container));
|
|
360
|
+
this.onError((err, c) => this.handleException(c, err));
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Configure OpenAPI endpoints, controller routes, and 404 handler.
|
|
364
|
+
* Called once by Application.initialize().
|
|
365
|
+
*/
|
|
366
|
+
async configure() {
|
|
367
|
+
if (this.configured) throw new RouterError("HonoApp has already been configured");
|
|
368
|
+
this._container.resolve(OPENAPI_TOKENS.OpenAPIService).setupEndpoints(this, this._container);
|
|
369
|
+
await this._container.resolve(RouteRegistrationService).configure();
|
|
370
|
+
this.notFound((c) => {
|
|
371
|
+
throw new RouteNotFoundError(c.req.path, c.req.method);
|
|
372
|
+
});
|
|
373
|
+
this.configured = true;
|
|
374
|
+
}
|
|
375
|
+
setupRequestScope() {
|
|
376
|
+
this.nativeUse("*", async (c, next) => {
|
|
377
|
+
const routerContext = new RouterContext(c);
|
|
378
|
+
const requestContainer = this._container.createRequestScope(routerContext);
|
|
379
|
+
c.set(ROUTER_CONTEXT_KEYS.REQUEST_CONTAINER, requestContainer);
|
|
380
|
+
await runWithContainer(requestContainer, next);
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
handleException(c, err) {
|
|
384
|
+
const requestContainer = c.get(ROUTER_CONTEXT_KEYS.REQUEST_CONTAINER) ?? this._container;
|
|
385
|
+
const handler = requestContainer.resolve(DI_TOKENS.ExceptionHandler);
|
|
386
|
+
const ctx = createHttpExceptionContext(c);
|
|
387
|
+
return runWithContainer(requestContainer, () => handler.handle(err, ctx));
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
HonoApp = __decorate([
|
|
391
|
+
Singleton(),
|
|
392
|
+
__decorateParam(0, inject(CONTAINER_TOKEN)),
|
|
393
|
+
__decorateParam(1, inject(LOGGER_TOKENS.LoggerService)),
|
|
394
|
+
__decorateParam(2, inject(DI_TOKENS.Application))
|
|
395
|
+
], HonoApp);
|
|
396
|
+
//#endregion
|
|
397
|
+
export { RouteNotFoundError as a, markNestedDispatch as i, hono_app_exports as n, isNestedDispatch as r, HonoApp as t };
|
|
398
|
+
|
|
399
|
+
//# sourceMappingURL=hono-app-IhdqL0Fk.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hono-app-IhdqL0Fk.mjs","names":[],"sources":["../src/router/errors/route-not-found.error.ts","../src/router/middleware/logger.middleware.ts","../src/router/nested-dispatch.ts","../src/router/middleware/gateway-dispatch.middleware.ts","../src/router/middleware/no-store-fallback.middleware.ts","../src/router/middleware/trailing-slash-redirect.ts","../src/router/hono-app.ts"],"sourcesContent":["import { HttpException } from '../../errors'\n\nexport class RouteNotFoundError extends HttpException {\n public readonly path: string\n public readonly method: string\n\n constructor(path: string, method: string) {\n super(404, `Route not found: ${method} ${path}`)\n this.path = path\n this.method = method\n }\n}\n","import type { MiddlewareHandler } from 'hono'\nimport type { LoggerService } from '../../logger'\n\n/**\n * Create a Hono middleware that logs HTTP requests using our Logger service\n *\n * Logs request method, path, status code, and duration in milliseconds.\n * Format: [HTTP] METHOD /path -> STATUS (duration ms)\n *\n * @param logger - Logger service instance\n * @returns Hono middleware handler\n *\n * @example\n * ```typescript\n * const logger = container.resolve<LoggerService>(LOGGER_TOKENS.LoggerService)\n * app.use('*', createLoggerMiddleware(logger))\n * ```\n */\nexport function createLoggerMiddleware(logger: LoggerService): MiddlewareHandler {\n return async (c, next) => {\n const start = Date.now()\n const method = c.req.method\n const path = c.req.path\n\n await next()\n\n const duration = Date.now() - start\n const status = c.res.status\n\n logger.info(`[HTTP] ${method} ${path} -> ${status}`, {\n method,\n path,\n status,\n duration,\n })\n }\n}\n","/**\n * Requests the application dispatched into its own router while another\n * request was already running.\n *\n * A nested dispatch is not an eyeball request, and the difference is not\n * cosmetic: it exists to render something the caller asked for *indirectly*,\n * so a route is entitled to answer it differently — that is the whole point of\n * one, and the reason a route can recognise it at all. Its response is\n * therefore not the response the same URL would give a client, and anything\n * that stores responses by URL must never see it.\n *\n * The mark lives in a module-private `WeakSet` keyed on the `Request` object,\n * for the reasons `markGatewayMode` uses one for the `ExecutionContext`:\n * nothing reachable from a request can add to it, so it cannot be forged the\n * way a header or a query parameter can. Keyed on the request rather than on\n * the context because a nested dispatch shares the outer request's execution\n * context — it needs the same `waitUntil` and the same bindings — and one\n * `Request` is one dispatch.\n */\nconst NESTED_DISPATCHES = new WeakSet<Request>()\n\n/**\n * Mark a request as dispatched into this app from inside another request.\n *\n * Returns the same object it was given, so it can be used inline.\n */\nexport function markNestedDispatch(request: Request): Request {\n NESTED_DISPATCHES.add(request)\n return request\n}\n\n/** Whether this request was marked by {@link markNestedDispatch}. */\nexport function isNestedDispatch(request: Request): boolean {\n return NESTED_DISPATCHES.has(request)\n}\n","import type { Context, MiddlewareHandler } from 'hono'\nimport { matchedRoutes } from 'hono/route'\nimport type { Container } from '../../di/container'\nimport { shouldLoopback } from '../../response-cache/cached-entrypoint'\nimport { resolveCachedEntrypoint } from '../../response-cache/gateway-binding'\nimport { isGatewayMode } from '../../response-cache/gateway-mode'\nimport { REPRESENTATION_PROP, representationOf } from '../../response-cache/representation'\nimport { RESPONSE_CACHE_TOKENS } from '../../response-cache/response-cache.tokens'\nimport type { GatewayPrimerService } from '../../response-cache/services/gateway-primer.service'\nimport type { GatewayRouteEntry, GatewayRouteTable } from '../../response-cache/services/gateway-route-table'\nimport type { PartitionResolverService } from '../../response-cache/services/partition-resolver.service'\nimport { isNestedDispatch } from '../nested-dispatch'\nimport { RouterContext } from '../router-context'\nimport type { RouterEnv } from '../types'\n\n/**\n * `c.executionCtx` is a Hono getter that throws when the context was built\n * without one (a bare `app.fetch(request)`), so it can only be read through a\n * `try`/`catch`.\n */\nfunction executionContextOf(c: Context<RouterEnv>): unknown {\n try {\n return c.executionCtx\n } catch {\n return undefined\n }\n}\n\n/**\n * Find the binding for the route that will actually handle this request.\n *\n * `routePath(c)` inside a `use('*')` middleware returns `'*'` — the path *this\n * handler* was registered under, which Hono's own docstring shows — so it\n * cannot be used directly.\n *\n * `matchedRoutes(c)` returns **every** pattern that matched, in registration\n * order, which is more than the one that will run. Scanning it backward is\n * wrong, and dangerously so:\n *\n * ```ts\n * @Get('/summary') @Cacheable({ partitionBy: ['user'] }) summary() {}\n * @Get('/:id') @Cacheable({ partitionBy: ['tenant'] }) show() {}\n * ```\n *\n * `GET /reports/summary` matches both, and static-before-parameterised is the\n * ordinary registration order. A backward scan picks `/reports/:id` and\n * forwards `{ tenant }`, while the cached entrypoint dispatches `summary()` —\n * storing a per-user response under a tenant-only key, so every user in that\n * tenant is served the first one's page.\n *\n * Scanning **forward from the handler after this middleware** picks the next\n * route that will run, which is the one whose `@Cacheable` governs the\n * response. `c.req.routeIndex` is this middleware's own position in the\n * matched list, so `+ 1` starts at the first candidate.\n *\n * This is the primary defence. `RouteRegistrationService.partitionsResolved`\n * is the second: inside the cached entrypoint it verifies that `ctx.props`\n * actually covers the executing route's declared partitions, so even a\n * mis-selected binding fails closed rather than caching under the wrong key.\n */\nfunction lookupRoute(\n c: Context<RouterEnv>,\n table: GatewayRouteTable,\n): GatewayRouteEntry | undefined {\n const routes = matchedRoutes(c)\n const method = c.req.method\n\n for (let i = c.req.routeIndex + 1; i < routes.length; i++) {\n const entry = table.lookup(method, routes[i].path)\n if (entry) return entry\n }\n\n return undefined\n}\n\n/**\n * Forward partitioned cacheable reads to the cached entrypoint.\n *\n * Both entrypoints run the *same* Hono app. This middleware is the only thing\n * that differs between them, and it differs on one bit that no client can\n * touch: whether `Stratal.fetch` marked this request's `ExecutionContext` as\n * the gateway (see `markGatewayMode`). In the cached entrypoint the mark is\n * absent, so this is a straight `next()` — which is what makes a loopback\n * loop impossible.\n *\n * In gateway mode, for a `GET`/`HEAD` on a route whose effective `partitionBy`\n * is non-empty, it primes the request container, resolves the declared\n * partitions, and re-dispatches through `ctx.exports.<Cached>({ props })` —\n * `ctx.props` being the part of the Workers Caching key that Cloudflare\n * documents as impossible to bypass, and therefore what keeps one caller's\n * cached response away from another.\n *\n * **Everything else runs inline**, which is always the safe direction: an\n * inline response never enters the cached entrypoint at all, so it can never\n * be stored. That covers a table miss, a non-`GET`/`HEAD` method, a request\n * the app dispatched into itself (`markNestedDispatch`), a primer that\n * short-circuited, and any partition resolver that returned `null`/`undefined`\n * or threw.\n *\n * ### Placement\n *\n * Registered in `HonoApp`'s constructor, after the request-scope and logger\n * middleware and before every application middleware:\n *\n * - **Inside** `createNoStoreFallbackMiddleware`, so a forwarded response that\n * somehow carried no `Cache-Control` still leaves with an explicit decision.\n * - **Inside** the trailing-slash redirect, so a non-canonical URL is\n * redirected once, by the gateway, rather than forwarded and cached under a\n * path that would then redirect again.\n * - **Inside** the request-scope middleware, because primers and partition\n * resolvers resolve out of the request-scoped container (`ctx.user()` is a\n * macro over the request-scoped `AuthContext`).\n * - **Inside** the logger middleware, so the gateway logs the requests it\n * forwards — on a cache hit the app never runs, so this is the only place\n * that observes them at all.\n * - **Outside** every application middleware (global `Router.use()`,\n * group/route middleware, guards, validators), because on a cache hit none\n * of those run. Running them in the gateway would double their side effects\n * on a miss and produce behaviour a hit could never reproduce.\n */\nexport function createGatewayDispatchMiddleware(container: Container): MiddlewareHandler<RouterEnv> {\n // `ResponseCacheModule` is opt-in and registers after `HonoApp` is\n // constructed, so the table is looked up on first use, not here. The\n // separate `looked` flag matters: `??=` would reassign on every request for\n // apps *without* the module — exactly the apps that must pay nothing —\n // because `tryResolve` keeps returning `undefined`, so the memo never takes.\n let table: GatewayRouteTable | undefined\n let looked = false\n\n // Latches the *result* of the `ctx.exports` reachability check, not merely\n // that it ran. A typo'd entrypoint name cannot start resolving later in the\n // isolate's life, so failing exactly one arbitrary request and then silently\n // running every partitioned route inline forever is the outcome to avoid.\n let bootChecked = false\n let bootFailure: Error | undefined\n\n return async (c, next) => {\n // Deliberately before the `executionCtx` read. `c.executionCtx` is a getter\n // that *throws* when the context was built without one, and a caught throw\n // per request is far from free — the request/response benchmark moved 2-5%\n // when this ran first. Checking the memoised table costs a boolean and a\n // property read, and short-circuits every app that configures no gateway,\n // which is the population that must pay as close to nothing as possible.\n if (!looked) {\n looked = true\n table = container.tryResolve<GatewayRouteTable>(RESPONSE_CACHE_TOKENS.GatewayRouteTable)\n }\n\n const routeTable = table\n const entrypoint = routeTable?.entrypoint\n if (!routeTable || !entrypoint) return next()\n\n const ctx = executionContextOf(c)\n if (!isGatewayMode(ctx)) return next()\n\n // A request the app dispatched into itself renders inline, always. It can\n // be answered differently from the same URL asked for by a client — that\n // is what a route recognising one is for — so forwarding it would store\n // that answer under the client's key, and hand it to the next client the\n // key matches. It is also the only request whose answer depends on\n // reaching the same isolate: a route tells a nested dispatch apart from a\n // client by a value minted in this one, which nothing on the far side of\n // `ctx.exports` can be handed.\n if (isNestedDispatch(c.req.raw)) return next()\n\n if (!bootChecked) {\n bootChecked = true\n try {\n resolveCachedEntrypoint(ctx, entrypoint)\n } catch (error) {\n bootFailure = error as Error\n }\n }\n if (bootFailure) throw bootFailure\n\n // An app that configures a gateway but declares no partitioned route pays\n // exactly this check per request.\n if (routeTable.isEmpty) return next()\n\n const entry = lookupRoute(c, routeTable)\n if (!entry) return next()\n if (!shouldLoopback(c.req.method, entry)) return next()\n\n const routerContext = new RouterContext(c)\n\n const primers = container.tryResolve<GatewayPrimerService>(RESPONSE_CACHE_TOKENS.GatewayPrimerService)\n if (primers && !(await primers.prime(routerContext))) return next()\n\n const resolver = container.tryResolve<PartitionResolverService>(\n RESPONSE_CACHE_TOKENS.PartitionResolverService,\n )\n if (!resolver) return next()\n\n const { props, resolved } = await resolver.resolve(routerContext, entry.partitionBy)\n if (!resolved) return next()\n\n // The representation joins the partitions in the key. A route that answers\n // one URL with both a document and a JSON payload has two bodies to keep\n // apart, and `Vary` alone has been measured failing to keep them apart on a\n // URL with a query string — see `representation.ts`.\n const representation = representationOf(c.req.raw.headers, routeTable.keyBy)\n if (representation !== undefined) props[REPRESENTATION_PROP] = representation\n\n // The callable form of a loopback binding is what selects the callee's\n // `ctx.props`; `.fetch` on the binding itself would forward with the\n // gateway's own props and collapse every caller onto one cache entry.\n return resolveCachedEntrypoint(ctx, entrypoint)({ props }).fetch(c.req.raw)\n }\n}\n","import type { MiddlewareHandler } from 'hono'\nimport { PER_CALLER_RATE_LIMIT_HEADERS } from '../../rate-limiter/limit'\nimport { isSharedCacheable, setResponseHeaders } from '../../response-cache/response-headers'\nimport type { RouterEnv } from '../types'\n\nconst NO_STORE = 'private, no-store'\n\n/** `{ 'X-RateLimit-Limit': null, … }` — the delete form `setResponseHeaders` takes. */\nconst DROP_PER_CALLER_HEADERS = Object.fromEntries(\n PER_CALLER_RATE_LIMIT_HEADERS.map((name) => [name, null]),\n) as Record<string, string | null>\n\n/**\n * The single outermost safety net behind Stratal's caching-decision\n * guarantee: every response leaving a Stratal app carries an explicit\n * `Cache-Control` header. Workers Caching applies RFC 9111 heuristic\n * freshness to anything that doesn't — a header-less `200` is cached for two\n * hours (§4.2.1), and `404`/`405`/`410`/`501` are heuristically cacheable too\n * (§4.2.2), same for a redirect like the trailing-slash `308`.\n *\n * `RouteRegistrationService.applyCacheDecision` already stamps a decision for\n * every ordinary controller response — `@Cacheable` routes get their computed\n * headers, everything else is left alone for this middleware to catch. This\n * is the backstop for everything that path never sees: thrown errors (404s,\n * 500s rendered by the exception handler), guard/scoped-middleware\n * short-circuits (`wrapHandlerWithChain` returning a middleware's `Response`\n * directly), the trailing-slash redirect, wildcard/`@All` controller routes,\n * and any future response path that forgets to call it.\n *\n * Registered as the very first global middleware, so it wraps everything\n * else. Hono's `compose()` fully resolves a thrown error or a middleware\n * short-circuit into `context.res` at the layer where it happens, then\n * returns *normally* to every middleware further out — the same guarantee\n * `createLoggerMiddleware` already depends on to log the right status for a\n * 404. By the time `next()` resolves here, `c.res` holds the final response,\n * whichever code path produced it.\n */\nexport function createNoStoreFallbackMiddleware(): MiddlewareHandler<RouterEnv> {\n return async (c, next) => {\n await next()\n\n if (!c.res.headers.has('Cache-Control')) {\n const stamped = setResponseHeaders(c.res, { 'Cache-Control': NO_STORE })\n // Only reassign `c.res` when a new object was actually produced —\n // Hono's `res` setter merges the *previous* `c.res`'s headers onto\n // whatever is assigned, so reassigning the same (in-place-mutated)\n // object would immediately re-clone it for nothing.\n if (stamped !== c.res) c.res = stamped\n }\n\n // `X-RateLimit-*` describe one caller's remaining budget, so they must not survive on a\n // response a shared cache may store: the entry is replayed to every other caller, and a cache\n // hit never runs the throttle middleware, so the figure counts down for nobody.\n //\n // This is the only layer where the cache decision is already on the response — it is stamped\n // inside the route handler, and Hono's `res` setter merges headers as the chain unwinds, so a\n // middleware testing `Cache-Control` after its own `next()` races the header it depends on.\n //\n // The limit is still consumed and enforced; only the reporting is withheld.\n if (isSharedCacheable(c.res)) {\n const stripped = setResponseHeaders(c.res, DROP_PER_CALLER_HEADERS)\n if (stripped !== c.res) c.res = stripped\n }\n }\n}\n","import type { MiddlewareHandler } from 'hono'\nimport { applyTrailingSlash, resolveTrailingSlash } from '../trailing-slash'\nimport type { RouterEnv, TrailingSlashConfig } from '../types'\n\nconst REDIRECT_STATUS = 308\n\n/**\n * Create a Hono middleware that canonicalises trailing slashes via 308 redirects.\n *\n * - `'ignore'` — returns `null`; routes match both `/foo` and `/foo/` natively\n * (Hono handles this when constructed with `strict: false`).\n * - `'always'` — non-trailing requests redirect to the trailing-slash form.\n * Paths whose last segment contains `.` (e.g. `/api/openapi.json`) are skipped.\n * - `'never'` — trailing requests redirect to the non-trailing form.\n *\n * Paths in the config's `exclude` list are never redirected — both forms\n * are served as requested (routes match either, `strict: false`). When\n * `getLocales` is provided, exclusions also match locale-prefixed request\n * paths (`/fr/callback` for exclude `'/callback'`). It is a thunk because\n * the middleware is created before the i18n module resolves its locales.\n *\n * Root (`/`) is always passed through unchanged.\n *\n * 308 is used so that POST/PUT/PATCH bodies survive the redirect.\n *\n * Location headers are emitted as path-relative URIs so the user agent\n * resolves them against the effective request URI — sidestepping scheme\n * mismatches behind HTTPS-terminating proxies that proxy HTTPS pages to an\n * HTTP-speaking backend (which would otherwise produce a mixed-content block).\n */\nexport function createTrailingSlashRedirect(\n config: TrailingSlashConfig,\n getLocales?: () => readonly string[] | undefined,\n): MiddlewareHandler<RouterEnv> | null {\n const options = resolveTrailingSlash(config)\n if (options.mode === 'ignore') return null\n\n return async (c, next) => {\n const url = new URL(c.req.url)\n const canonicalPath = applyTrailingSlash(url.pathname, options, getLocales?.())\n if (canonicalPath === url.pathname) return next()\n return c.redirect(`${canonicalPath}${url.search}`, REDIRECT_STATUS)\n }\n}\n","import { Hono } from 'hono'\nimport type { Context, MiddlewareHandler } from 'hono'\nimport { inject } from '../di'\nimport type { Application } from '../application'\nimport type { Container } from '../di/container'\nimport { runWithContainer } from '../di/container-storage'\nimport { Singleton } from '../di/decorators'\nimport { CONTAINER_TOKEN, DI_TOKENS } from '../di/tokens'\nimport { createHttpExceptionContext } from '../errors/exception-context'\nimport type { ExceptionHandler } from '../errors/exception-handler'\nimport { LOGGER_TOKENS, type LoggerService } from '../logger'\nimport { OPENAPI_TOKENS, type OpenAPIService } from '../openapi'\nimport type { Constructor } from '../types'\nimport { ROUTER_CONTEXT_KEYS } from './constants'\nimport { RouteNotFoundError } from './errors'\nimport { RouterError } from './router.error'\nimport { createGatewayDispatchMiddleware, createLoggerMiddleware, createMiddlewareChain, createNoStoreFallbackMiddleware, createTrailingSlashRedirect } from './middleware'\nimport type { Middleware } from './middleware.interface'\nimport { RouterContext } from './router-context'\nimport { ROUTER_TOKENS } from './router.tokens'\nimport type { LocalePathService } from './services/locale-path.service'\nimport { RouteRegistrationService } from './services/route-registration.service'\nimport type { RouterEnv, TrailingSlashConfig } from './types'\n\nconst isMiddlewareClass = (arg: unknown): arg is Constructor<Middleware> =>\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n typeof arg === 'function' && arg.prototype && 'handle' in arg.prototype\n\n\n/**\n * HonoApp — extends plain Hono with Stratal-specific setup\n *\n * - Request scope middleware (child container per request)\n * - Global middleware (CORS, logging, error handling)\n * - `use()` overload for Stratal middleware classes\n * - `configure()` for OpenAPI, routes, and 404\n *\n * Request validation is attached per-route via `hono/validator` middleware (see\n * route-registration), not a base-class hook, so schema-less routes pull in no\n * validation/zod code.\n */\n@Singleton()\nexport class HonoApp extends Hono<RouterEnv> {\n private configured = false\n private readonly _container: Container\n private readonly _logger: LoggerService\n\n /**\n * Reference to the original Hono `use` implementation.\n * Captured in constructor after super() sets it as an instance property.\n * Used by private methods to register middleware without going through the override.\n */\n private nativeUse!: typeof this.use\n\n constructor(\n @inject(CONTAINER_TOKEN) container: Container,\n @inject(LOGGER_TOKENS.LoggerService) logger: LoggerService,\n @inject(DI_TOKENS.Application) application: Application,\n ) {\n const trailingSlash: TrailingSlashConfig = application.config.trailingSlash ?? 'ignore'\n\n super({\n // Always non-strict: a registered `/foo` route matches both `/foo` and `/foo/`.\n // For the redirect modes, the trailing-slash middleware runs first and\n // canonicalises via 308 before matching reaches the registered route.\n strict: false,\n })\n\n this._container = container\n this._logger = logger\n\n // Capture Hono's original `use` (set by super() as an instance property)\n this.nativeUse = this.use\n\n // Override `use` to support Stratal middleware classes alongside Hono-native handlers\n this.use = ((...args: unknown[]) => {\n if (isMiddlewareClass(args[0])) {\n this.nativeUse('*', createMiddlewareChain(args as Constructor<Middleware>[]))\n return this\n }\n\n if (typeof args[0] === 'string' && args.length > 1 && isMiddlewareClass(args[1])) {\n this.nativeUse(args[0], createMiddlewareChain(args.slice(1) as Constructor<Middleware>[]))\n return this\n }\n\n return (this.nativeUse as (...a: unknown[]) => unknown)(...args)\n }) as typeof this.use\n\n // Registered before anything else — including the trailing-slash\n // redirect — so it wraps every other middleware, every route, and every\n // error path. See its own docstring for why registration order matters\n // here: it relies on Hono's compose() resolving thrown errors and\n // short-circuits into `context.res` before unwinding back through\n // earlier middleware, so it only needs to run once, after `next()`.\n this.nativeUse('*', createNoStoreFallbackMiddleware())\n\n // Trailing-slash redirect runs first (of the request-handling middleware)\n // so redirected requests skip request-scope and logger overhead. Locales\n // are read lazily — the i18n module (and thus LocalePathService)\n // initialises after HonoApp is constructed.\n const trailingSlashRedirect = createTrailingSlashRedirect(trailingSlash, () =>\n this._container.tryResolve<LocalePathService>(ROUTER_TOKENS.LocalePathService)?.localePathConfig?.allLocales)\n if (trailingSlashRedirect) {\n this.nativeUse('*', trailingSlashRedirect)\n }\n\n // Internal setup — uses nativeUse to bypass the override\n this.setupRequestScope()\n this.applyGlobalMiddleware()\n }\n\n /**\n * Apply global middleware (logger + error handler).\n * Called by Application after locale middleware is applied by LocalePathService.\n */\n private applyGlobalMiddleware(): void {\n this.nativeUse('*', createLoggerMiddleware(this._logger) as MiddlewareHandler<RouterEnv>)\n\n // Last of the framework's own middleware, and therefore the innermost\n // layer that still sits *outside* every application middleware: on a cache\n // hit the app never runs, so the gateway must not run anything the hit\n // would skip. It is inside request-scope (partition resolvers need the\n // request container) and inside the logger (the gateway is the only place\n // that observes a request served from cache). See the factory's docstring.\n this.nativeUse('*', createGatewayDispatchMiddleware(this._container))\n\n this.onError((err, c) => this.handleException(c, err))\n }\n\n /**\n * Configure OpenAPI endpoints, controller routes, and 404 handler.\n * Called once by Application.initialize().\n */\n async configure(): Promise<void> {\n if (this.configured) throw new RouterError('HonoApp has already been configured')\n\n // OpenAPI endpoints\n const openAPIService = this._container.resolve<OpenAPIService>(OPENAPI_TOKENS.OpenAPIService)\n openAPIService.setupEndpoints(this, this._container)\n\n // Controller routes + global middleware\n const routeRegistrationService = this._container.resolve<RouteRegistrationService>(RouteRegistrationService)\n await routeRegistrationService.configure()\n\n // 404 handler (must be last)\n this.notFound((c) => { throw new RouteNotFoundError(c.req.path, c.req.method) })\n\n this.configured = true\n }\n\n private setupRequestScope(): void {\n this.nativeUse('*', async (c: Context<RouterEnv>, next: () => Promise<void>) => {\n const routerContext = new RouterContext(c)\n const requestContainer = this._container.createRequestScope(routerContext)\n c.set(ROUTER_CONTEXT_KEYS.REQUEST_CONTAINER, requestContainer)\n\n await runWithContainer(requestContainer, next)\n })\n }\n\n private handleException(c: Context<RouterEnv>, err: unknown) {\n // Fallback to global container if request scope setup failed before storing REQUEST_CONTAINER\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime guard: REQUEST_CONTAINER may be unset if request scope middleware throws\n const requestContainer = c.get(ROUTER_CONTEXT_KEYS.REQUEST_CONTAINER) ?? this._container\n const handler = requestContainer.resolve<ExceptionHandler>(DI_TOKENS.ExceptionHandler)\n const ctx = createHttpExceptionContext(c)\n // Run the handler within the request container's async context so standalone\n // helpers like `route()` (which read the ambient container via getContainer)\n // resolve correctly. Errors thrown before the request-scope middleware's\n // `runWithContainer` body — e.g. route-param validation failures — otherwise\n // reach here outside any container scope, breaking redirect-back rendering.\n return runWithContainer(requestContainer, () => handler.handle(err, ctx))\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAEA,IAAa,qBAAb,cAAwC,cAAc;CACpD;CACA;CAEA,YAAY,MAAc,QAAgB;EACxC,MAAM,KAAK,oBAAoB,OAAO,GAAG,MAAM;EAC/C,KAAK,OAAO;EACZ,KAAK,SAAS;CAChB;AACF;;;;;;;;;;;;;;;;;;ACOA,SAAgB,uBAAuB,QAA0C;CAC/E,OAAO,OAAO,GAAG,SAAS;EACxB,MAAM,QAAQ,KAAK,IAAI;EACvB,MAAM,SAAS,EAAE,IAAI;EACrB,MAAM,OAAO,EAAE,IAAI;EAEnB,MAAM,KAAK;EAEX,MAAM,WAAW,KAAK,IAAI,IAAI;EAC9B,MAAM,SAAS,EAAE,IAAI;EAErB,OAAO,KAAK,UAAU,OAAO,GAAG,KAAK,MAAM,UAAU;GACnD;GACA;GACA;GACA;EACF,CAAC;CACH;AACF;;;;;;;;;;;;;;;;;;;;;;ACjBA,MAAM,oCAAoB,IAAI,QAAiB;;;;;;AAO/C,SAAgB,mBAAmB,SAA2B;CAC5D,kBAAkB,IAAI,OAAO;CAC7B,OAAO;AACT;;AAGA,SAAgB,iBAAiB,SAA2B;CAC1D,OAAO,kBAAkB,IAAI,OAAO;AACtC;;;;;;;;ACdA,SAAS,mBAAmB,GAAgC;CAC1D,IAAI;EACF,OAAO,EAAE;CACX,QAAQ;EACN;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,SAAS,YACP,GACA,OAC+B;CAC/B,MAAM,SAAS,cAAc,CAAC;CAC9B,MAAM,SAAS,EAAE,IAAI;CAErB,KAAK,IAAI,IAAI,EAAE,IAAI,aAAa,GAAG,IAAI,OAAO,QAAQ,KAAK;EACzD,MAAM,QAAQ,MAAM,OAAO,QAAQ,OAAO,EAAE,CAAC,IAAI;EACjD,IAAI,OAAO,OAAO;CACpB;AAGF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+CA,SAAgB,gCAAgC,WAAoD;CAMlG,IAAI;CACJ,IAAI,SAAS;CAMb,IAAI,cAAc;CAClB,IAAI;CAEJ,OAAO,OAAO,GAAG,SAAS;EAOxB,IAAI,CAAC,QAAQ;GACX,SAAS;GACT,QAAQ,UAAU,WAA8B,sBAAsB,iBAAiB;EACzF;EAEA,MAAM,aAAa;EACnB,MAAM,aAAa,YAAY;EAC/B,IAAI,CAAC,cAAc,CAAC,YAAY,OAAO,KAAK;EAE5C,MAAM,MAAM,mBAAmB,CAAC;EAChC,IAAI,CAAC,cAAc,GAAG,GAAG,OAAO,KAAK;EAUrC,IAAI,iBAAiB,EAAE,IAAI,GAAG,GAAG,OAAO,KAAK;EAE7C,IAAI,CAAC,aAAa;GAChB,cAAc;GACd,IAAI;IACF,wBAAwB,KAAK,UAAU;GACzC,SAAS,OAAO;IACd,cAAc;GAChB;EACF;EACA,IAAI,aAAa,MAAM;EAIvB,IAAI,WAAW,SAAS,OAAO,KAAK;EAEpC,MAAM,QAAQ,YAAY,GAAG,UAAU;EACvC,IAAI,CAAC,OAAO,OAAO,KAAK;EACxB,IAAI,CAAC,eAAe,EAAE,IAAI,QAAQ,KAAK,GAAG,OAAO,KAAK;EAEtD,MAAM,gBAAgB,IAAI,cAAc,CAAC;EAEzC,MAAM,UAAU,UAAU,WAAiC,sBAAsB,oBAAoB;EACrG,IAAI,WAAW,CAAE,MAAM,QAAQ,MAAM,aAAa,GAAI,OAAO,KAAK;EAElE,MAAM,WAAW,UAAU,WACzB,sBAAsB,wBACxB;EACA,IAAI,CAAC,UAAU,OAAO,KAAK;EAE3B,MAAM,EAAE,OAAO,aAAa,MAAM,SAAS,QAAQ,eAAe,MAAM,WAAW;EACnF,IAAI,CAAC,UAAU,OAAO,KAAK;EAM3B,MAAM,iBAAiB,iBAAiB,EAAE,IAAI,IAAI,SAAS,WAAW,KAAK;EAC3E,IAAI,mBAAmB,KAAA,GAAW,MAAM,uBAAuB;EAK/D,OAAO,wBAAwB,KAAK,UAAU,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,GAAG;CAC5E;AACF;;;AC3MA,MAAM,WAAW;;AAGjB,MAAM,0BAA0B,OAAO,YACrC,8BAA8B,KAAK,SAAS,CAAC,MAAM,IAAI,CAAC,CAC1D;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,SAAgB,kCAAgE;CAC9E,OAAO,OAAO,GAAG,SAAS;EACxB,MAAM,KAAK;EAEX,IAAI,CAAC,EAAE,IAAI,QAAQ,IAAI,eAAe,GAAG;GACvC,MAAM,UAAU,mBAAmB,EAAE,KAAK,EAAE,iBAAiB,SAAS,CAAC;GAKvE,IAAI,YAAY,EAAE,KAAK,EAAE,MAAM;EACjC;EAWA,IAAI,kBAAkB,EAAE,GAAG,GAAG;GAC5B,MAAM,WAAW,mBAAmB,EAAE,KAAK,uBAAuB;GAClE,IAAI,aAAa,EAAE,KAAK,EAAE,MAAM;EAClC;CACF;AACF;;;AC5DA,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;AA0BxB,SAAgB,4BACd,QACA,YACqC;CACrC,MAAM,UAAU,qBAAqB,MAAM;CAC3C,IAAI,QAAQ,SAAS,UAAU,OAAO;CAEtC,OAAO,OAAO,GAAG,SAAS;EACxB,MAAM,MAAM,IAAI,IAAI,EAAE,IAAI,GAAG;EAC7B,MAAM,gBAAgB,mBAAmB,IAAI,UAAU,SAAS,aAAa,CAAC;EAC9E,IAAI,kBAAkB,IAAI,UAAU,OAAO,KAAK;EAChD,OAAO,EAAE,SAAS,GAAG,gBAAgB,IAAI,UAAU,eAAe;CACpE;AACF;;;;ACnBA,MAAM,qBAAqB,QAEzB,OAAO,QAAQ,cAAc,IAAI,aAAa,YAAY,IAAI;AAgBzD,IAAM,UAAN,MAAM,gBAAgB,KAAgB;CAC3C,aAAqB;CACrB;CACA;;;;;;CAOA;CAEA,YACE,WACA,QACA,aACA;EACA,MAAM,gBAAqC,YAAY,OAAO,iBAAiB;EAE/E,MAAM,EAIJ,QAAQ,MACV,CAAC;EAED,KAAK,aAAa;EAClB,KAAK,UAAU;EAGf,KAAK,YAAY,KAAK;EAGtB,KAAK,QAAQ,GAAG,SAAoB;GAClC,IAAI,kBAAkB,KAAK,EAAE,GAAG;IAC9B,KAAK,UAAU,KAAK,sBAAsB,IAAiC,CAAC;IAC5E,OAAO;GACT;GAEA,IAAI,OAAO,KAAK,OAAO,YAAY,KAAK,SAAS,KAAK,kBAAkB,KAAK,EAAE,GAAG;IAChF,KAAK,UAAU,KAAK,IAAI,sBAAsB,KAAK,MAAM,CAAC,CAA8B,CAAC;IACzF,OAAO;GACT;GAEA,OAAQ,KAAK,UAA2C,GAAG,IAAI;EACjE;EAQA,KAAK,UAAU,KAAK,gCAAgC,CAAC;EAMrD,MAAM,wBAAwB,4BAA4B,qBACxD,KAAK,WAAW,WAA8B,cAAc,iBAAiB,CAAC,EAAE,kBAAkB,UAAU;EAC9G,IAAI,uBACF,KAAK,UAAU,KAAK,qBAAqB;EAI3C,KAAK,kBAAkB;EACvB,KAAK,sBAAsB;CAC7B;;;;;CAMA,wBAAsC;EACpC,KAAK,UAAU,KAAK,uBAAuB,KAAK,OAAO,CAAiC;EAQxF,KAAK,UAAU,KAAK,gCAAgC,KAAK,UAAU,CAAC;EAEpE,KAAK,SAAS,KAAK,MAAM,KAAK,gBAAgB,GAAG,GAAG,CAAC;CACvD;;;;;CAMA,MAAM,YAA2B;EAC/B,IAAI,KAAK,YAAY,MAAM,IAAI,YAAY,qCAAqC;EAIhF,KAD4B,WAAW,QAAwB,eAAe,cACjE,CAAC,CAAC,eAAe,MAAM,KAAK,UAAU;EAInD,MADiC,KAAK,WAAW,QAAkC,wBACtD,CAAC,CAAC,UAAU;EAGzC,KAAK,UAAU,MAAM;GAAE,MAAM,IAAI,mBAAmB,EAAE,IAAI,MAAM,EAAE,IAAI,MAAM;EAAE,CAAC;EAE/E,KAAK,aAAa;CACpB;CAEA,oBAAkC;EAChC,KAAK,UAAU,KAAK,OAAO,GAAuB,SAA8B;GAC9E,MAAM,gBAAgB,IAAI,cAAc,CAAC;GACzC,MAAM,mBAAmB,KAAK,WAAW,mBAAmB,aAAa;GACzE,EAAE,IAAI,oBAAoB,mBAAmB,gBAAgB;GAE7D,MAAM,iBAAiB,kBAAkB,IAAI;EAC/C,CAAC;CACH;CAEA,gBAAwB,GAAuB,KAAc;EAG3D,MAAM,mBAAmB,EAAE,IAAI,oBAAoB,iBAAiB,KAAK,KAAK;EAC9E,MAAM,UAAU,iBAAiB,QAA0B,UAAU,gBAAgB;EACrF,MAAM,MAAM,2BAA2B,CAAC;EAMxC,OAAO,iBAAiB,wBAAwB,QAAQ,OAAO,KAAK,GAAG,CAAC;CAC1E;AACF;;CArIC,UAAU;CAcN,gBAAA,GAAA,OAAO,eAAe,CAAA;CACtB,gBAAA,GAAA,OAAO,cAAc,aAAa,CAAA;CAClC,gBAAA,GAAA,OAAO,UAAU,WAAW,CAAA"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { u as ROUTE_METADATA_KEYS } from "./exception-context-
|
|
3
|
-
import { r as z } from "./zod-eKqqhZ5_.mjs";
|
|
1
|
+
import { _ as defineMetadata, v as getMetadata } from "./di-DQ3xbjgf.mjs";
|
|
2
|
+
import { u as ROUTE_METADATA_KEYS } from "./exception-context-CdnGTQNF.mjs";
|
|
4
3
|
//#region src/router/decorators/http-method.decorator.ts
|
|
5
4
|
/**
|
|
6
5
|
* Creates an HTTP method decorator factory for the given HTTP method.
|
|
@@ -16,7 +15,7 @@ function createHttpMethodDecorator(method) {
|
|
|
16
15
|
type: "explicit",
|
|
17
16
|
method,
|
|
18
17
|
path,
|
|
19
|
-
config: config ?? {
|
|
18
|
+
config: config ?? {}
|
|
20
19
|
};
|
|
21
20
|
defineMetadata(ROUTE_METADATA_KEYS.ROUTE_CONFIG, metadata, target, propertyKey);
|
|
22
21
|
const existing = getMetadata(ROUTE_METADATA_KEYS.DECORATED_METHODS, target) ?? [];
|
|
@@ -34,12 +33,14 @@ function createHttpMethodDecorator(method) {
|
|
|
34
33
|
*
|
|
35
34
|
* @example
|
|
36
35
|
* ```typescript
|
|
37
|
-
*
|
|
36
|
+
* import { array, object, uuid } from 'zod/mini'
|
|
37
|
+
*
|
|
38
|
+
* @Controller('/users', { version: '1' })
|
|
38
39
|
* class UsersController {
|
|
39
|
-
* @Get('/', { response:
|
|
40
|
+
* @Get('/', { response: array(userSchema), summary: 'List users' })
|
|
40
41
|
* async list(ctx: RouterContext) { ... }
|
|
41
42
|
*
|
|
42
|
-
* @Get('/:id', { params:
|
|
43
|
+
* @Get('/:id', { params: object({ id: uuid() }), response: userSchema })
|
|
43
44
|
* async getUser(ctx: RouterContext) { ... }
|
|
44
45
|
* }
|
|
45
46
|
* ```
|
|
@@ -94,4 +95,4 @@ const All = createHttpMethodDecorator("all");
|
|
|
94
95
|
//#endregion
|
|
95
96
|
export { Post as a, Patch as i, Delete as n, Put as o, Get as r, All as t };
|
|
96
97
|
|
|
97
|
-
//# sourceMappingURL=http-method.decorator-
|
|
98
|
+
//# sourceMappingURL=http-method.decorator-BDi9LTWj.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-method.decorator-BDi9LTWj.mjs","names":[],"sources":["../src/router/decorators/http-method.decorator.ts"],"sourcesContent":["import { defineMetadata, getMetadata } from '../../di/metadata'\nimport { ROUTE_METADATA_KEYS } from '../constants'\nimport type { ExplicitRouteMetadata, HttpMethod, RouteConfig } from '../types'\n\n/**\n * Creates an HTTP method decorator factory for the given HTTP method.\n *\n * The returned decorator stores {@link ExplicitRouteMetadata} on the method and\n * tracks the method name under {@link ROUTE_METADATA_KEYS.DECORATED_METHODS}\n * on the controller prototype so they can be discovered at registration time.\n */\nfunction createHttpMethodDecorator(method: HttpMethod) {\n return function (path: string, config?: RouteConfig) {\n return function (\n target: object,\n propertyKey: string,\n descriptor: PropertyDescriptor\n ) {\n const metadata: ExplicitRouteMetadata = {\n type: 'explicit',\n method,\n path,\n config: config ?? {},\n }\n\n defineMetadata(\n ROUTE_METADATA_KEYS.ROUTE_CONFIG,\n metadata,\n target,\n propertyKey\n )\n\n // Track this method as decorated on the prototype\n const existing: string[] =\n getMetadata<string[]>(ROUTE_METADATA_KEYS.DECORATED_METHODS, target) ?? []\n existing.push(propertyKey)\n defineMetadata(ROUTE_METADATA_KEYS.DECORATED_METHODS, existing, target)\n\n return descriptor\n }\n }\n}\n\n/**\n * Registers a GET route on the controller method.\n *\n * @param path - Route path relative to the controller base path\n * @param config - Optional route configuration (response schema, body, params, etc.)\n *\n * @example\n * ```typescript\n * import { array, object, uuid } from 'zod/mini'\n *\n * @Controller('/users', { version: '1' })\n * class UsersController {\n * @Get('/', { response: array(userSchema), summary: 'List users' })\n * async list(ctx: RouterContext) { ... }\n *\n * @Get('/:id', { params: object({ id: uuid() }), response: userSchema })\n * async getUser(ctx: RouterContext) { ... }\n * }\n * ```\n */\nexport const Get = createHttpMethodDecorator('get')\n\n/**\n * Registers a POST route on the controller method.\n *\n * @param path - Route path relative to the controller base path\n * @param config - Optional route configuration (response schema, body, params, etc.)\n *\n * @example\n * ```typescript\n * @Controller('/api/v1/users')\n * class UsersController {\n * @Post('/', { body: createUserSchema, response: userSchema, statusCode: 201 })\n * async createUser(ctx: RouterContext) { ... }\n * }\n * ```\n */\nexport const Post = createHttpMethodDecorator('post')\n\n/**\n * Registers a PUT route on the controller method.\n *\n * @param path - Route path relative to the controller base path\n * @param config - Optional route configuration\n */\nexport const Put = createHttpMethodDecorator('put')\n\n/**\n * Registers a PATCH route on the controller method.\n *\n * @param path - Route path relative to the controller base path\n * @param config - Optional route configuration\n */\nexport const Patch = createHttpMethodDecorator('patch')\n\n/**\n * Registers a DELETE route on the controller method.\n *\n * @param path - Route path relative to the controller base path\n * @param config - Optional route configuration\n */\nexport const Delete = createHttpMethodDecorator('delete')\n\n/**\n * Registers an ALL (any HTTP method) route on the controller method.\n * Routes using @All are registered without OpenAPI validation\n * since OpenAPI does not support a catch-all HTTP method.\n *\n * @param path - Route path relative to the controller base path\n * @param config - Optional route configuration\n */\nexport const All = createHttpMethodDecorator('all')\n"],"mappings":";;;;;;;;;;AAWA,SAAS,0BAA0B,QAAoB;CACrD,OAAO,SAAU,MAAc,QAAsB;EACnD,OAAO,SACL,QACA,aACA,YACA;GACA,MAAM,WAAkC;IACtC,MAAM;IACN;IACA;IACA,QAAQ,UAAU,CAAC;GACrB;GAEA,eACE,oBAAoB,cACpB,UACA,QACA,WACF;GAGA,MAAM,WACJ,YAAsB,oBAAoB,mBAAmB,MAAM,KAAK,CAAC;GAC3E,SAAS,KAAK,WAAW;GACzB,eAAe,oBAAoB,mBAAmB,UAAU,MAAM;GAEtE,OAAO;EACT;CACF;AACF;;;;;;;;;;;;;;;;;;;;;AAsBA,MAAa,MAAM,0BAA0B,KAAK;;;;;;;;;;;;;;;;AAiBlD,MAAa,OAAO,0BAA0B,MAAM;;;;;;;AAQpD,MAAa,MAAM,0BAA0B,KAAK;;;;;;;AAQlD,MAAa,QAAQ,0BAA0B,OAAO;;;;;;;AAQtD,MAAa,SAAS,0BAA0B,QAAQ;;;;;;;;;AAUxD,MAAa,MAAM,0BAA0B,KAAK"}
|