stratal 0.0.26 → 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 +85 -14
- 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 @@
|
|
|
1
|
+
{"version":3,"file":"route-cache-binding-CrLh8tB6.mjs","names":["KEY"],"sources":["../src/response-cache/gateway-binding.ts","../src/response-cache/response-cache.tokens.ts","../src/response-cache/decorators/cacheable.decorator.ts","../src/response-cache/decorators/purges-cache.decorator.ts","../src/response-cache/boot-check.ts","../src/response-cache/resolve-cacheable.ts","../src/response-cache/services/route-cache-binding.ts"],"sourcesContent":["import type { PurgeSpec } from '../execution-context'\nimport { ResponseCacheConfigError } from './errors'\n\n/**\n * The two things the gateway asks of the cached entrypoint.\n *\n * `ctx.exports.<Name>` is typed as `Cloudflare.Exports[Name]`, which resolves\n * to `{}` unless the consumer has generated `Cloudflare.GlobalProps` with\n * `wrangler types`. The framework can't require that, and can't name the\n * consumer's export at compile time either — the name arrives as a string in\n * `gateway.entrypoint` — so the stub is described structurally here and the\n * lookup is checked at runtime by `resolveCachedEntrypoint`.\n */\nexport interface CachedEntrypointStub {\n /** The loopback `fetch`, routed through the cached entrypoint's cache. */\n fetch(request: Request): Promise<Response>\n /**\n * The RPC purge. Purges are scoped to the entrypoint that issues them, and\n * mutations run inline in the *gateway*, whose cache is disabled — so a\n * purge issued there would target an empty cache and silently do nothing.\n * See `cachedEntrypoint`.\n */\n purge(spec: PurgeSpec): Promise<{ success: boolean } | void>\n}\n\n/**\n * A loopback binding: callable to bind `ctx.props` for the invocation, and\n * directly usable when no props are needed.\n *\n * `LoopbackServiceStub<T>` in `@cloudflare/workers-types` is\n * `Fetcher<T> & ((opts: { props?: Props }) => Fetcher<T>)` — the callable form\n * is the documented way to choose the callee's `ctx.props`, and `ctx.props` is\n * what places a partition in the cache key.\n */\nexport type CachedEntrypointBinding = CachedEntrypointStub &\n ((options: { props?: Record<string, string> }) => CachedEntrypointStub)\n\n/**\n * Look up the configured cached entrypoint on `ctx.exports`, failing loudly if\n * it isn't there.\n *\n * A typo in `gateway: { entrypoint: 'Cachd' }`, a missing `export const\n * Cached = ...`, or a missing `enable_ctx_exports` compatibility flag would\n * otherwise all present identically: every partitioned route quietly runs\n * inline in the gateway forever, never caching, with no signal anywhere. The\n * whole point of `partitionBy` refusing to boot without a gateway is that a\n * declared partition is either honoured or it is an error — so this is an\n * error too.\n *\n * @throws {ResponseCacheConfigError} `ctx.exports` is unavailable, or carries\n * no export under `name`.\n */\nexport function resolveCachedEntrypoint(ctx: unknown, name: string): CachedEntrypointBinding {\n let exports: unknown\n try {\n exports = (ctx as { exports?: unknown } | null | undefined)?.exports\n } catch (error) {\n // Reading `ctx.exports` throws outright on a Worker without the\n // `enable_ctx_exports` compatibility flag.\n throw new ResponseCacheConfigError(\n `reading \\`ctx.exports\\` failed (${error instanceof Error ? error.message : String(error)}), so the ` +\n `cached entrypoint \"${name}\" cannot be reached. Add \"enable_ctx_exports\" to ` +\n '`compatibility_flags` in your Wrangler config.',\n )\n }\n\n if (typeof exports !== 'object' || exports === null) {\n throw new ResponseCacheConfigError(\n `\\`ctx.exports\\` is unavailable on this entrypoint, so the cached entrypoint \"${name}\" ` +\n 'configured via `ResponseCacheModule.forRoot({ gateway: { entrypoint } })` cannot be ' +\n 'reached. Add \"enable_ctx_exports\" to `compatibility_flags` in your Wrangler config, and ' +\n 'set `compatibility_date` to 2026-07-06 or later.',\n )\n }\n\n const binding = (exports as Record<string, unknown>)[name]\n\n if (binding === undefined || binding === null) {\n const available = Object.keys(exports)\n throw new ResponseCacheConfigError(\n `the cached entrypoint \"${name}\" is not exported by this Worker. ` +\n '`ResponseCacheModule.forRoot({ gateway: { entrypoint } })` must name a top-level export ' +\n `— \\`export const ${name} = cachedEntrypoint(stratal)\\` — that is also declared in your ` +\n 'Wrangler `exports` block with `cache: { enabled: true }`. Exports visible on ' +\n `\\`ctx.exports\\`: ${available.length > 0 ? available.join(', ') : '(none)'}.`,\n )\n }\n\n return binding as CachedEntrypointBinding\n}\n\n/**\n * A `WorkersCache` that purges through the cached entrypoint over RPC.\n *\n * Handed to `ResponseCacheService.purge()` in place of the gateway's own\n * `ctx.cache` whenever a gateway is configured and this request is running as\n * the gateway. See `cachedEntrypoint` for why that redirection is required\n * rather than merely tidy.\n */\nexport function createLoopbackPurgeTarget(ctx: unknown, name: string) {\n return {\n // `async` so an unreachable entrypoint surfaces as a rejection rather than\n // a synchronous throw from a function the `WorkersCache` contract says\n // returns a Promise.\n purge: async (spec: PurgeSpec) => resolveCachedEntrypoint(ctx, name).purge(spec),\n }\n}\n","export const RESPONSE_CACHE_TOKENS = {\n Options: Symbol.for('stratal:response-cache:options'),\n ResponseCacheService: Symbol.for('stratal:response-cache:service'),\n CacheabilityService: Symbol.for('stratal:response-cache:cacheability'),\n PartitionResolverService: Symbol.for('stratal:response-cache:partition-resolver'),\n GatewayPrimerService: Symbol.for('stratal:response-cache:gateway-primer'),\n GatewayPrimers: Symbol.for('stratal:response-cache:gateway-primers'),\n GatewayRouteTable: Symbol.for('stratal:response-cache:gateway-route-table'),\n} as const\n\nexport type ResponseCacheToken =\n (typeof RESPONSE_CACHE_TOKENS)[keyof typeof RESPONSE_CACHE_TOKENS]\n","import { defineMetadata, getMetadata } from '../../di/metadata'\nimport { ROUTE_METADATA_KEYS } from '../../router/constants'\nimport type { CacheableOptions } from '../types'\n\nconst KEY = ROUTE_METADATA_KEYS.CACHEABLE\n\n/**\n * Mark a route's response as cacheable by Cloudflare Workers Caching.\n *\n * Only `GET` and `HEAD` are ever cached. Omitted fields fall back to the\n * module's `defaults` block; an explicit value always wins.\n *\n * @example\n * ```typescript\n * @Get('/blog/:slug')\n * @Cacheable({ ttl: 300, swr: 60, tags: ['post:{param.slug}'] })\n * async show(ctx: RouterContext) { ... }\n * ```\n *\n * Module defaults fill in whatever the route leaves out:\n *\n * @example\n * ```typescript\n * ResponseCacheModule.forRoot({ defaults: { ttl: 300, swr: 60 } })\n *\n * @Get('/pricing')\n * @Cacheable() // ttl 300, swr 60\n * async pricing(ctx: RouterContext) { ... }\n * ```\n *\n * **A guarded route needs a `partitionBy`.** A guarded route's response differs\n * per caller, so caching it under one shared entry would serve one user's\n * response to another. `@Cacheable` on a route carrying `@UseGuards` is a boot\n * error unless it declares a non-empty `partitionBy` (or inherits one from\n * `defaults.partitionBy`) — and per-caller keying additionally requires\n * `ResponseCacheModule.forRoot({ gateway: { entrypoint } })`, without which\n * `partitionBy` itself is rejected at boot.\n *\n * @example\n * ```typescript\n * @Get('/dashboard')\n * @UseGuards(AuthGuard)\n * @Cacheable({ ttl: 60, partitionBy: ['user'] })\n * async dashboard(ctx: RouterContext) { ... }\n * ```\n */\nexport function Cacheable(options: CacheableOptions = {}): MethodDecorator {\n return (target: object, propertyKey: string | symbol) => {\n defineMetadata(KEY, options, target, propertyKey)\n }\n}\n\n/** Read the `@Cacheable` options for a method. Returns `undefined` when absent. */\nexport function getCacheable(target: object, methodName: string): CacheableOptions | undefined {\n return getMetadata<CacheableOptions>(KEY, target, methodName)\n}\n","import { defineMetadata, getMetadata } from '../../di/metadata'\nimport { ROUTE_METADATA_KEYS } from '../../router/constants'\nimport { ResponseCacheConfigError } from '../errors'\nimport type { PurgesCacheOptions } from '../types'\n\nconst KEY = ROUTE_METADATA_KEYS.PURGES_CACHE\n\n/**\n * Invalidate cached responses after this route mutates data.\n *\n * Runs after the handler returns a 2xx or 3xx. `pathPrefixes` matches the\n * request path only, so `['/blog']` also clears `/blog?page=2`.\n *\n * @example\n * ```typescript\n * @Post('/posts/:slug/publish')\n * @PurgesCache({ tags: ['post:{param.slug}', 'category:{data.post.categoryId}'] })\n * async publish(ctx: RouterContext) { ... }\n * ```\n */\nexport function PurgesCache(options: PurgesCacheOptions): MethodDecorator {\n const hasTargets = (options.tags?.length ?? 0) > 0 || (options.pathPrefixes?.length ?? 0) > 0\n\n if (options.purgeEverything && hasTargets) {\n throw new ResponseCacheConfigError(\n '@PurgesCache: `purgeEverything` is exclusive — remove `tags` and `pathPrefixes`.',\n )\n }\n\n if (!options.purgeEverything && !hasTargets) {\n throw new ResponseCacheConfigError(\n '@PurgesCache: specify at least one of `tags`, `pathPrefixes`, or `purgeEverything`.',\n )\n }\n\n return (target: object, propertyKey: string | symbol) => {\n defineMetadata(KEY, options, target, propertyKey)\n }\n}\n\n/** Read the `@PurgesCache` options for a method. Returns `undefined` when absent. */\nexport function getPurgesCache(target: object, methodName: string): PurgesCacheOptions | undefined {\n return getMetadata<PurgesCacheOptions>(KEY, target, methodName)\n}\n","import { ResponseCacheConfigError } from './errors'\nimport type { WorkersCache } from './services/response-cache.service'\nimport type { ResponseCacheModuleOptions } from './types'\n\n/**\n * Why this entrypoint cannot cache, or `undefined` when it can.\n *\n * A Worker cannot read its own Wrangler configuration, but the presence of\n * `ctx.cache` tells us whether Workers Caching is enabled for this entrypoint.\n *\n * Reported rather than thrown, and the distinction is the whole point. The\n * hazard is a route stamping `public, max-age=…` while nothing stores the\n * response: a claim the app makes and no layer honours. Refusing to make that\n * claim removes the hazard — the response falls through to\n * `createNoStoreFallbackMiddleware` and is stamped `private, no-store`, which\n * is both true and visible in the response itself. Failing the request\n * removes it too, but takes down every route in the isolate to do it,\n * including the ones that never asked to be cached.\n *\n * The answer belongs to an entrypoint rather than to the app, because a\n * gateway topology runs two of them — one with `cache.enabled` false by\n * design, one with it true — inside a single isolate.\n */\nexport function cachingUnavailableReason(\n cacheableRouteCount: number,\n cache: WorkersCache | undefined,\n): string | undefined {\n if (cacheableRouteCount === 0) return undefined\n if (cache) return undefined\n\n return (\n `${cacheableRouteCount} route(s) declare @Cacheable, but Workers Caching is not available on this entrypoint, ` +\n 'so their responses are served uncached and stamped `private, no-store`. ' +\n 'Set `\"cache\": { \"enabled\": true }` in your Wrangler config, use Wrangler >= 4.69.0, ' +\n 'and set `compatibility_date` to 2026-07-06 or later.'\n )\n}\n\n/**\n * The one export name a gateway can never forward to: its own.\n *\n * `ctx.exports.default` is the default export — the `Stratal` instance whose\n * `fetch` marks the context as the gateway and dispatches. Forwarding there\n * re-enters the gateway, which marks the *new* context and forwards again,\n * recursing until the runtime's subrequest limit kills the request. Rejecting\n * it at boot is what makes \"the cached entrypoint never re-dispatches, so no\n * loop is possible\" true rather than merely true-if-configured-correctly.\n */\nconst SELF_ENTRYPOINT = 'default'\n\n/**\n * Validate the configured entrypoint name itself.\n *\n * Split from the reachability check in `resolveCachedEntrypoint` because this\n * part needs no runtime: a name that is empty, or names the gateway itself, is\n * wrong on inspection and should never reach a first request.\n */\nexport function assertValidGatewayEntrypoint(options: ResponseCacheModuleOptions): void {\n const gateway = options.gateway\n if (!gateway) return\n\n const entrypoint = gateway.entrypoint\n\n if (typeof entrypoint !== 'string' || entrypoint.trim() === '') {\n throw new ResponseCacheConfigError(\n 'ResponseCacheModule: `gateway.entrypoint` must be a non-empty export name, e.g. ' +\n '`gateway: { entrypoint: \\'Cached\\' }` matching `export const Cached = ' +\n 'cachedEntrypoint(stratal)`.',\n )\n }\n\n if (entrypoint === SELF_ENTRYPOINT) {\n throw new ResponseCacheConfigError(\n 'ResponseCacheModule: `gateway.entrypoint` cannot be \"default\" — that is the gateway ' +\n 'itself. Forwarding to it would re-enter the gateway, which would forward again, ' +\n 'recursing until the Worker hits its subrequest limit. Export a separate cached ' +\n 'entrypoint (`export const Cached = cachedEntrypoint(stratal)`) and name that instead, ' +\n 'with `cache: { enabled: false }` on `default` and `true` on the named export.',\n )\n }\n}\n\n/**\n * Keep partition names out of the `$`-prefixed space the framework reserves.\n *\n * Partitions and the representation both become `ctx.props` entries, and props\n * is a flat object — a partition named `$representation` would overwrite the\n * representation or be overwritten by it, either way silently collapsing two\n * cache keys into one. The prefix is rejected wholesale rather than just the\n * one name in use today, so a later reserved key cannot break an app that had\n * already shipped a partition called it.\n */\nexport function assertPartitionNames(options: ResponseCacheModuleOptions): void {\n const reserved = Object.keys(options.partitions ?? {}).filter((name) => name.startsWith('$'))\n\n if (reserved.length > 0) {\n throw new ResponseCacheConfigError(\n `ResponseCacheModule: partition name(s) ${reserved.join(', ')} start with \"$\", which is ` +\n 'reserved for values the framework puts in `ctx.props` itself. Rename them.',\n )\n }\n}\n\n/**\n * Reject the two module options only the gateway entrypoint can act on, when\n * no gateway entrypoint is configured.\n *\n * `partitions` is looked up by `partitionBy`, which `bindRouteCache` rejects\n * under the same condition; `primers` is read only by `GatewayPrimerService`,\n * which the dispatch middleware is the sole caller of. Without\n * `gateway.entrypoint` neither is ever consulted, and accepting them silently\n * is the \"option a consumer sets that does nothing\" failure this feature is\n * otherwise careful to avoid — an author would reasonably read a configured\n * `partitions` block as proof that per-user keying is in effect. Fail at boot\n * instead, in the same breath as `partitionBy`.\n *\n * Validates the entrypoint name first, so `gateway: { entrypoint: '' }` is\n * reported as the malformed name it is rather than falling through to a\n * \"you need a gateway\" message about the wrong thing.\n */\nexport function assertNoGatewayOptions(options: ResponseCacheModuleOptions): void {\n assertValidGatewayEntrypoint(options)\n assertPartitionNames(options)\n\n if (options.gateway !== undefined) return\n\n if (Object.keys(options.partitions ?? {}).length > 0) {\n throw new ResponseCacheConfigError(\n 'ResponseCacheModule: `partitions` requires `gateway: { entrypoint }`. Partition resolvers ' +\n 'only run in the gateway, which forwards partitioned reads to a cached entrypoint over ' +\n '`ctx.exports` — without one, the resolvers configured here would never be called and ' +\n 'every `partitionBy` route would cache publicly. Add ' +\n '`gateway: { entrypoint: \\'Cached\\' }` and `export const Cached = cachedEntrypoint(stratal)`, ' +\n 'or remove `partitions` (and any `partitionBy`).',\n )\n }\n\n if ((options.primers?.length ?? 0) > 0) {\n throw new ResponseCacheConfigError(\n 'ResponseCacheModule: `primers` requires `gateway: { entrypoint }`. Primers exist to ' +\n 'populate the request container before partition resolution runs in the gateway — ' +\n 'without a cached entrypoint nothing would ever run them, including ' +\n '`AUTH_GATEWAY_PRIMERS`. Add `gateway: { entrypoint }`, or remove `primers`.',\n )\n }\n}\n","import { ResponseCacheConfigError } from './errors'\nimport type { CacheableOptions, ResolvedCacheable } from './types'\n\n/** Where a `@Cacheable` was declared, for error messages and the guard rule. */\nexport interface CacheableContext {\n controller: string\n method: string\n /** True when the route or its controller has guards attached. */\n guarded: boolean\n /**\n * The `:param` names this route's own path declares, in order — used to\n * validate `{param.*}` cache tags against a route that can never populate\n * them. `undefined` (rather than `[]`) means \"not known to the caller\",\n * which skips that validation entirely instead of treating every\n * `{param.*}` tag as unresolvable; only the real HTTP registration path\n * (`RouteRegistrationService`) can supply it, so unit tests exercising\n * `bindRouteCache` directly are free to omit it.\n */\n routeParams?: string[]\n /**\n * True when `ResponseCacheModule.forRoot({ gateway: { entrypoint } })` names\n * a cached entrypoint — the only configuration under which a partition can\n * actually be placed in the cache key. Governs both the `partitionBy` throw\n * in `bindRouteCache` and the guarded-route message below.\n */\n gatewayConfigured?: boolean\n}\n\n/**\n * Fold module defaults into a route's `@Cacheable` options.\n *\n * `partitionBy` replaces rather than merges: a route must be able to declare\n * itself public (`[]`) or re-partition without inheriting a stale default.\n * Reading the route alone must be enough to know how it is keyed.\n */\nexport function resolveCacheable(\n route: CacheableOptions,\n defaults: Omit<CacheableOptions, 'tags'>,\n context: CacheableContext,\n): ResolvedCacheable {\n const where = `${context.controller}.${context.method}`\n const ttl = route.ttl ?? defaults.ttl\n\n if (ttl === undefined) {\n throw new ResponseCacheConfigError(\n `${where}: @Cacheable needs a \\`ttl\\`, either on the route or via ResponseCacheModule.forRoot({ defaults: { ttl } }).`,\n )\n }\n\n if (ttl <= 0 || !Number.isFinite(ttl)) {\n throw new ResponseCacheConfigError(`${where}: @Cacheable \\`ttl\\` must be a positive number of seconds.`)\n }\n\n // Defaults to `ttl`: a response the shared cache may hold for an hour is\n // one a browser may hold for an hour too, and that equivalence is what a\n // route gets without saying anything. A route whose retraction must be\n // reliable narrows it — see `browserTtl` on `CacheableOptions`.\n const browserTtl = route.browserTtl ?? defaults.browserTtl ?? ttl\n\n // `0` is meaningful — it means the browser asks every time — so only\n // negative and non-finite values are rejected.\n if (browserTtl < 0 || !Number.isFinite(browserTtl)) {\n throw new ResponseCacheConfigError(\n `${where}: @Cacheable \\`browserTtl\\` must be a non-negative finite number of seconds.`,\n )\n }\n\n const swr = route.swr ?? defaults.swr\n\n // Unlike `ttl`, `0` is meaningful here — it means no stale window — so only\n // negative or non-finite values are rejected. Left unchecked, a negative,\n // `NaN`, or `Infinity` value emits a malformed `stale-while-revalidate`\n // directive; per RFC 9111 / RFC 5861 `delta-seconds`, Cloudflare silently\n // ignores it, disabling stale-while-revalidate with no error anywhere.\n if (swr !== undefined && (swr < 0 || !Number.isFinite(swr))) {\n throw new ResponseCacheConfigError(`${where}: @Cacheable \\`swr\\` must be a non-negative finite number of seconds.`)\n }\n\n const partitionBy = route.partitionBy ?? defaults.partitionBy ?? []\n\n // A guard means the response varies by caller, so it must be partitioned.\n // `partitionBy: []` on a guarded route is rejected rather than honoured:\n // declaring a guarded route public is exactly the mistake this rule exists\n // to catch. The advice differs by configuration — telling an app with no\n // gateway to \"declare a partitionBy\" would be a dead end, since\n // `bindRouteCache` rejects that too.\n if (context.guarded && partitionBy.length === 0) {\n throw new ResponseCacheConfigError(\n context.gatewayConfigured\n ? `${where}: @Cacheable on a guarded route needs a non-empty \\`partitionBy\\`. A guarded ` +\n 'route\\'s response differs per caller, so caching it under one shared entry would ' +\n 'serve one user\\'s response to another. Declare the partitions it varies by ' +\n '(`@Cacheable({ partitionBy: [\\'user\\'] })`), or set a module-level ' +\n '`defaults.partitionBy`. `partitionBy: []` is not accepted here even explicitly.'\n : `${where}: @Cacheable cannot be used on a guarded route without a gateway entrypoint. ` +\n 'A guarded route\\'s response differs per caller, so caching it would serve one ' +\n 'user\\'s response to another. Per-caller cache keying needs ' +\n '`ResponseCacheModule.forRoot({ gateway: { entrypoint } })` plus a `partitionBy` — ' +\n 'configure both, or remove @Cacheable from this route.',\n )\n }\n\n return {\n ttl,\n browserTtl,\n swr,\n tags: route.tags ?? [],\n partitionBy,\n vary: [...new Set([...(defaults.vary ?? []), ...(route.vary ?? [])])],\n }\n}\n","import { ResponseCacheConfigError } from '../errors'\nimport { resolveCacheable, type CacheableContext } from '../resolve-cacheable'\nimport type { CacheableOptions, PurgesCacheOptions, ResolvedCacheable } from '../types'\n\n/** What one route needs at request time, resolved once at registration. */\nexport interface RouteCacheBinding {\n cacheable?: ResolvedCacheable\n purges?: PurgesCacheOptions\n}\n\n/** Matches a `{body.*}` cache tag placeholder (any path under the `body` scope). */\nconst BODY_SCOPE_TAG = /\\{body\\.[^}]+\\}/\n\n/**\n * Reject any tag that reads the `{body.*}` scope: the HTTP router never\n * populates `TagScopes.body` (see `RouteRegistrationService.buildTagScopes`)\n * — the parsed request body isn't cheaply available at the point tags are\n * rendered, and re-parsing it on every request just to support a rarely used\n * tag scope was deliberately rejected. A `{body.*}` tag can therefore never\n * resolve.\n *\n * Left unchecked, this doesn't fail quietly: on a `@PurgesCache` route,\n * `buildPurgeSpec(...)` is evaluated as an *argument* to `purge(...)`, so\n * `renderTags` throwing `InvalidCacheTagError` happens before `purge`'s own\n * try/catch exists — and `applyCacheDecision` has no catch of its own either,\n * so the request 500s *after* its mutation already committed. Catching it\n * here, at registration, turns a request-time failure the author only\n * discovers in production into a boot-time error they see before deploying.\n */\nfunction assertNoBodyScopeTags(\n tags: string[] | undefined,\n decorator: '@Cacheable' | '@PurgesCache',\n context: CacheableContext,\n): void {\n const offending = tags?.find((tag) => BODY_SCOPE_TAG.test(tag))\n if (!offending) return\n\n throw new ResponseCacheConfigError(\n `${context.controller}.${context.method}: ${decorator} tag \"${offending}\" uses the ` +\n '`{body.*}` scope, which can never resolve — the parsed request body is not available ' +\n 'when cache tags are rendered. Use `{param.*}`, `{query.*}`, or `{data.*}` instead.',\n )\n}\n\n/** Matches a `{param.X}` cache tag placeholder, capturing the param name `X`. */\nconst PARAM_SCOPE_TAG = /\\{param\\.([^}.]+)/g\n\n/**\n * Reject a `{param.X}` tag whose `X` is not one of this route's own path\n * parameters.\n *\n * Unlike `{query.*}`/`{data.*}` — which depend on what a given *request*\n * happens to send, and so can only fail per-request — a route's path pattern\n * is fixed the moment it is registered. Whether `{param.id}` can ever resolve\n * is therefore knowable right now: if the route's path has no `:id` segment,\n * it never will, on any request. Left unchecked, this fails the exact same\n * way as `{body.*}` — `renderTags` throws mid-`purge(...)` call, after the\n * mutation already committed. Catching it here turns that into a boot-time\n * error instead.\n *\n * No-ops when `context.routeParams` is `undefined` — the caller didn't supply\n * the route's path, so there is nothing to validate against.\n */\nfunction assertParamTagsResolvable(\n tags: string[] | undefined,\n decorator: '@Cacheable' | '@PurgesCache',\n context: CacheableContext,\n): void {\n if (!tags?.length || context.routeParams === undefined) return\n\n const routeParams = context.routeParams\n\n for (const tag of tags) {\n for (const match of tag.matchAll(PARAM_SCOPE_TAG)) {\n const param = match[1]\n if (routeParams.includes(param)) continue\n\n const available = routeParams.length > 0 ? routeParams.join(', ') : '(none)'\n\n throw new ResponseCacheConfigError(\n `${context.controller}.${context.method}: ${decorator} tag \"${tag}\" references ` +\n `\\`{param.${param}}\\`, but this route's path has no \":${param}\" segment, so it can ` +\n `never resolve — available params: ${available}. Use one of those, or a ` +\n '`{query.*}`/`{data.*}` tag instead.',\n )\n }\n }\n}\n\n/** Matches a `{partition.X}` cache tag placeholder, capturing the partition name `X`. */\nconst PARTITION_SCOPE_TAG = /\\{partition\\.([^}.]+)/g\n\n/**\n * Reject a `{partition.X}` tag whose `X` is not one of this route's own\n * declared partitions.\n *\n * Knowable at boot for the same reason `{param.X}` is: `partitionBy` is fixed\n * when the route registers. A partition the route does not declare is not in\n * the cache key, so a tag naming it would describe an entry by something that\n * did not key it — and on `@PurgesCache` it throws mid-purge, after the\n * mutation committed.\n *\n * Only checked on `@Cacheable`, which is where `partitionBy` is declared. A\n * `@PurgesCache` route runs inline in the gateway, which resolves no\n * partitions of its own, so `{partition.*}` can never render there.\n */\nfunction assertPartitionTagsResolvable(\n tags: string[] | undefined,\n partitionBy: string[],\n decorator: '@Cacheable' | '@PurgesCache',\n context: CacheableContext,\n): void {\n if (!tags?.length) return\n\n for (const tag of tags) {\n for (const match of tag.matchAll(PARTITION_SCOPE_TAG)) {\n const partition = match[1]\n if (decorator === '@Cacheable' && partitionBy.includes(partition)) continue\n\n const available = partitionBy.length > 0 ? partitionBy.join(', ') : '(none)'\n\n throw new ResponseCacheConfigError(\n decorator === '@PurgesCache'\n ? `${context.controller}.${context.method}: @PurgesCache tag \"${tag}\" references ` +\n `\\`{partition.${partition}}\\`, but a mutation runs inline in the gateway, which ` +\n 'resolves no partitions — so it can never resolve. Purge by a `{param.*}`/' +\n '`{query.*}`/`{data.*}` tag the cached route also carries.'\n : `${context.controller}.${context.method}: @Cacheable tag \"${tag}\" references ` +\n `\\`{partition.${partition}}\\`, but this route does not declare that partition, so ` +\n `it is not in the cache key and can never resolve — declared: ${available}. Add it ` +\n 'to `partitionBy`, or use one of those.',\n )\n }\n }\n}\n\n/**\n * Reject a `pathPrefixes` entry that looks like it expects interpolation.\n *\n * `ResponseCacheService.buildPurgeSpec` copies `pathPrefixes` through\n * verbatim — only `tags` go through `renderTags`. So\n * `pathPrefixes: ['/blog/{param.slug}']` is sent to `ctx.cache.purge()` as\n * that literal string, which matches no request path: the purge \"succeeds\",\n * nothing is invalidated, and the author is never told. Same class of silent\n * miss as a `{body.*}` tag, so it gets the same boot-time treatment.\n */\nfunction assertNoTemplatedPathPrefixes(\n pathPrefixes: string[] | undefined,\n context: CacheableContext,\n): void {\n const offending = pathPrefixes?.find((prefix) => prefix.includes('{') || prefix.includes('}'))\n if (offending === undefined) return\n\n throw new ResponseCacheConfigError(\n `${context.controller}.${context.method}: @PurgesCache \\`pathPrefixes\\` entry \"${offending}\" ` +\n 'contains `{`/`}`, but path prefixes are not interpolated — only `tags` are. It would be ' +\n 'purged as that literal string and match nothing. Use a static prefix (`/blog`), or move ' +\n 'the dynamic part into a `{param.*}`/`{query.*}`/`{data.*}` cache tag.',\n )\n}\n\n/**\n * Fold a route's decorators and the module defaults into the config the request\n * path uses. Runs once per route at registration, never per request.\n *\n * Returns `undefined` when the route declares neither decorator, so the hot path\n * can skip all cache work with a single truthiness check.\n *\n * @throws {ResponseCacheConfigError} On a non-empty `partitionBy` when no\n * `gateway.entrypoint` is configured. Placing a partition in the cache key\n * requires the gateway to forward the read to a cached entrypoint over\n * `ctx.exports`. Accepting the option and ignoring it would give every\n * visitor a single shared entry for a route the author explicitly marked\n * per-user.\n * @throws {ResponseCacheConfigError} On a `{body.*}` tag in either decorator's\n * `tags` — see `assertNoBodyScopeTags`.\n * @throws {ResponseCacheConfigError} On a `{param.X}` tag whose `X` is not a\n * `:param` in this route's own path (when `context.routeParams` is\n * supplied) — see `assertParamTagsResolvable`.\n * @throws {ResponseCacheConfigError} On a `{`/`}` in `@PurgesCache`'s\n * `pathPrefixes` — see `assertNoTemplatedPathPrefixes`.\n */\nexport function bindRouteCache(\n cacheable: CacheableOptions | undefined,\n purges: PurgesCacheOptions | undefined,\n defaults: Omit<CacheableOptions, 'tags'>,\n context: CacheableContext,\n): RouteCacheBinding | undefined {\n if (!cacheable && !purges) return undefined\n\n assertNoBodyScopeTags(cacheable?.tags, '@Cacheable', context)\n assertNoBodyScopeTags(purges?.tags, '@PurgesCache', context)\n assertParamTagsResolvable(cacheable?.tags, '@Cacheable', context)\n assertParamTagsResolvable(purges?.tags, '@PurgesCache', context)\n assertPartitionTagsResolvable(purges?.tags, [], '@PurgesCache', context)\n assertNoTemplatedPathPrefixes(purges?.pathPrefixes, context)\n\n const binding: RouteCacheBinding = {}\n if (purges) binding.purges = purges\n\n if (cacheable) {\n const declared = cacheable.partitionBy ?? defaults.partitionBy ?? []\n\n assertPartitionTagsResolvable(cacheable.tags, declared, '@Cacheable', context)\n\n if (declared.length > 0 && !context.gatewayConfigured) {\n throw new ResponseCacheConfigError(\n `${context.controller}.${context.method}: \\`partitionBy\\` requires a gateway entrypoint. ` +\n 'Placing a partition in the cache key means forwarding the read to a cached entrypoint ' +\n 'over `ctx.exports`, so the option cannot be honored without one — and a route marked ' +\n 'per-caller that quietly shared a single cache entry would serve one visitor\\'s ' +\n 'response to everyone. Configure `ResponseCacheModule.forRoot({ gateway: { entrypoint: ' +\n '\\'Cached\\' }, partitions: { … } })` and `export const Cached = cachedEntrypoint(stratal)`, ' +\n 'or remove `partitionBy`.',\n )\n }\n\n binding.cacheable = resolveCacheable(cacheable, defaults, context)\n }\n\n return binding\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { C as ROUTER_TOKENS, l as Singleton } from "./di-DQ3xbjgf.mjs";
|
|
2
|
+
import { t as __decorate } from "./decorate-C79yt3Wt.mjs";
|
|
3
|
+
//#region src/router/route-metadata.ts
|
|
4
|
+
let RouteMetadataRegistry = class RouteMetadataRegistry {
|
|
5
|
+
entries = [];
|
|
6
|
+
add(entry) {
|
|
7
|
+
this.entries.push(entry);
|
|
8
|
+
}
|
|
9
|
+
all() {
|
|
10
|
+
return this.entries;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
RouteMetadataRegistry = __decorate([Singleton(ROUTER_TOKENS.RouteMetadataRegistry)], RouteMetadataRegistry);
|
|
14
|
+
//#endregion
|
|
15
|
+
export { RouteMetadataRegistry };
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=route-metadata-QQhbed1i.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-metadata-QQhbed1i.mjs","names":[],"sources":["../src/router/route-metadata.ts"],"sourcesContent":["import type { ZodType } from '../i18n/validation/zod'\nimport { Singleton } from '../di/decorators'\nimport { ROUTER_TOKENS } from './router.tokens'\nimport type { HttpMethod, SecuritySchemeRecord } from './types'\n\n/** A single response declaration for a route, keyed by status code. */\nexport interface RouteResponseMeta {\n status: number\n /** Absent for empty responses (e.g. 204); present for typed JSON responses. */\n schema?: ZodType\n contentType: string\n description: string\n}\n\n/**\n * Schema + documentation metadata for one registered route. Collected at\n * registration time and consumed lazily by the OpenAPI generator. Holds Zod\n * schema *instances* (never imports `zod` itself) so a route with no schemas\n * contributes none and pulls in no validation code.\n */\nexport interface RouteSchemaMeta {\n method: Exclude<HttpMethod, 'all'>\n /** Clean OpenAPI path with `{param}` placeholders and regex constraints stripped. */\n path: string\n /** Hidden routes are still registered (for access control) but excluded from the doc. */\n hidden: boolean\n tags: string[]\n security: SecuritySchemeRecord[]\n summary?: string\n description?: string\n request: {\n params?: ZodType\n query?: ZodType\n body?: { schema: ZodType; contentType: string }\n }\n responses: RouteResponseMeta[]\n /**\n * Localized variant: the `locale` path segment to document as an enum. The\n * value set is carried as plain strings so the generator emits the enum\n * without constructing a Zod schema on the hot path.\n */\n localeParam?: { name: string; values: string[] }\n /**\n * Visibility group labels for this route, resolved from the controller's and\n * route's `groups` options. Absent when none are declared.\n */\n groups?: string[]\n /** Extensible metadata bag for downstream packages. */\n meta?: Record<string, unknown>\n}\n\n/**\n * Stratal-owned registry of route schema metadata. Replaces writes to\n * `@hono/zod-openapi`'s internal `openAPIRegistry`: route registration pushes\n * entries here, and the OpenAPI generator reads them on demand.\n */\n@Singleton(ROUTER_TOKENS.RouteMetadataRegistry)\nexport class RouteMetadataRegistry {\n private readonly entries: RouteSchemaMeta[] = []\n\n add(entry: RouteSchemaMeta): void {\n this.entries.push(entry)\n }\n\n all(): readonly RouteSchemaMeta[] {\n return this.entries\n }\n}\n"],"mappings":";;;AAyDO,IAAM,wBAAN,MAAM,sBAAsB;CACjC,UAA8C,CAAC;CAE/C,IAAI,OAA8B;EAChC,KAAK,QAAQ,KAAK,KAAK;CACzB;CAEA,MAAkC;EAChC,OAAO,KAAK;CACd;AACF;AAXC,wBAAA,WAAA,CAAA,UAAU,cAAc,qBAAqB,CAAA,GAAA,qBAAA"}
|
|
@@ -19,19 +19,23 @@ function toOpenAPIPath(path) {
|
|
|
19
19
|
return path.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)(\{[^}]*\})?/g, "{$1}");
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
23
|
-
* preserving regex constraints.
|
|
22
|
+
* Extract the Hono-style `:param` names declared in a route path, in order.
|
|
24
23
|
*
|
|
25
|
-
* Used
|
|
26
|
-
*
|
|
27
|
-
*
|
|
24
|
+
* Used at registration time to validate `{param.*}` cache tags against the
|
|
25
|
+
* route's own path — see `bindRouteCache`'s `assertParamTagsResolvable`. The
|
|
26
|
+
* route's path is fixed once registered, so which `:param` names it can ever
|
|
27
|
+
* populate is knowable right now, before any request arrives.
|
|
28
28
|
*
|
|
29
29
|
* @example
|
|
30
|
-
*
|
|
31
|
-
*
|
|
30
|
+
* extractPathParams('/users/:id') // ['id']
|
|
31
|
+
* extractPathParams('/:companyId/users/:userId') // ['companyId', 'userId']
|
|
32
|
+
* extractPathParams('/:locale{en|fr}/users') // ['locale']
|
|
33
|
+
* extractPathParams('/health') // []
|
|
32
34
|
*/
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
+
function extractPathParams(path) {
|
|
36
|
+
const params = [];
|
|
37
|
+
for (const match of path.matchAll(/:([a-zA-Z_][a-zA-Z0-9_]*)(?:\{[^}]*\})?/g)) params.push(match[1]);
|
|
38
|
+
return params;
|
|
35
39
|
}
|
|
36
40
|
/**
|
|
37
41
|
* Compute a packed specificity key for route ordering.
|
|
@@ -166,6 +170,6 @@ function generateConventionRouteName(basePath, methodName) {
|
|
|
166
170
|
return `${segments.join(".")}.${methodName}`;
|
|
167
171
|
}
|
|
168
172
|
//#endregion
|
|
169
|
-
export {
|
|
173
|
+
export { getPathSpecificityScore as a, extractPathParams as i, extractParamNames as n, sortRoutesBySpecificity as o, generateConventionRouteName as r, toOpenAPIPath as s, extractDomainParamNames as t };
|
|
170
174
|
|
|
171
|
-
//# sourceMappingURL=route-name-
|
|
175
|
+
//# sourceMappingURL=route-name-BslSbmMP.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-name-BslSbmMP.mjs","names":[],"sources":["../src/router/utils/path.ts","../src/router/utils/route-name.ts"],"sourcesContent":["/**\n * Path normalization and route ordering utilities.\n *\n * Users always write Hono-style `:param` paths (`:companyId`, `:id`).\n * OpenAPI requires `{param}` style — conversion happens only at registration time.\n */\n\n/**\n * Convert Hono-style `:param` path segments to OpenAPI-style `{param}`.\n * Strips regex constraints (e.g., `:locale{sw}` → `{locale}`).\n *\n * @example\n * toOpenAPIPath('/users/:id') // '/users/{id}'\n * toOpenAPIPath('/:companyId/users/:userId') // '/{companyId}/users/{userId}'\n * toOpenAPIPath('/users/:id/posts') // '/users/{id}/posts'\n * toOpenAPIPath('/:locale{en|fr}/users') // '/{locale}/users'\n */\nexport function toOpenAPIPath(path: string): string {\n return path.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)(\\{[^}]*\\})?/g, '{$1}')\n}\n\n/**\n * Extract the Hono-style `:param` names declared in a route path, in order.\n *\n * Used at registration time to validate `{param.*}` cache tags against the\n * route's own path — see `bindRouteCache`'s `assertParamTagsResolvable`. The\n * route's path is fixed once registered, so which `:param` names it can ever\n * populate is knowable right now, before any request arrives.\n *\n * @example\n * extractPathParams('/users/:id') // ['id']\n * extractPathParams('/:companyId/users/:userId') // ['companyId', 'userId']\n * extractPathParams('/:locale{en|fr}/users') // ['locale']\n * extractPathParams('/health') // []\n */\nexport function extractPathParams(path: string): string[] {\n const params: string[] = []\n for (const match of path.matchAll(/:([a-zA-Z_][a-zA-Z0-9_]*)(?:\\{[^}]*\\})?/g)) {\n params.push(match[1])\n }\n return params\n}\n\n/**\n * Convert Hono-style `:param` path segments to OpenAPI-style `{param}`,\n * preserving regex constraints.\n *\n * Used for Hono route registration via `app.openapi()`. The non-greedy\n * regex in `@hono/zod-openapi` (`\\/{(.+?)}/g`) converts `{param}` back\n * to `:param` while leaving the constraint suffix intact.\n *\n * @example\n * toRoutingOpenAPIPath('/:locale{sw}/users/:id') // '/{locale}{sw}/users/{id}'\n * toRoutingOpenAPIPath('/users/:id') // '/users/{id}'\n */\nexport function toRoutingOpenAPIPath(path: string): string {\n return path.replace(\n /:([a-zA-Z_][a-zA-Z0-9_]*)(\\{[^}]*\\})?/g,\n (_, name: string, constraint?: string) => constraint ? `{${name}}${constraint}` : `{${name}}`,\n )\n}\n\n/**\n * Compute a packed specificity key for route ordering.\n * Encodes both score and segment count into a single number to avoid object allocation.\n *\n * Lower score = higher priority (registered first in Hono).\n * Scoring: static = 0, `:param{constraint}` = 5, `:param` = 10, wildcard `{.+}` / `{.*}` = 100.\n *\n * Packed as: score * 10000 - segmentCount (negative segment count so more segments = lower key = higher priority)\n *\n * Locale variants score against the path with the leading `/:locale{…}` segment\n * stripped — the variant's score therefore matches its primary, but its larger\n * segment count makes it sort just before the primary. Without this, a primary\n * catch-all (e.g. `/:slug{.+}`) gobbles locale-prefixed URLs because Hono picks\n * whichever matching route was registered first.\n */\nfunction getPathSpecificityKey(route: { path: string; isLocaleVariant?: boolean }): number {\n const segmentCount = countSegments(route.path)\n const scoringPath = route.isLocaleVariant\n ? (route.path.replace(/^\\/:locale\\{[^}]*\\}/, '') || '/')\n : route.path\n\n let score = 0\n let i = 0\n while (i < scoringPath.length) {\n if (scoringPath.charCodeAt(i) === 47 /* '/' */) { i++; continue }\n\n let end = scoringPath.indexOf('/', i)\n if (end === -1) end = scoringPath.length\n\n const segment = scoringPath.substring(i, end)\n\n if (segment.includes('{.+}') || segment.includes('{.*}')) {\n score += 100\n } else if (segment.charCodeAt(0) === 58 /* ':' */) {\n score += segment.includes('{') ? 5 : 10\n }\n\n i = end\n }\n\n return score * 10000 - segmentCount\n}\n\nfunction countSegments(path: string): number {\n let count = 0\n let i = 0\n while (i < path.length) {\n if (path.charCodeAt(i) === 47 /* '/' */) { i++; continue }\n let end = path.indexOf('/', i)\n if (end === -1) end = path.length\n count++\n i = end\n }\n return count\n}\n\n/**\n * Compute a specificity score for route ordering.\n * Lower score = higher priority (registered first in Hono).\n *\n * Scoring: static = 0, `:param{constraint}` = 5, `:param` = 10, wildcard `{.+}` / `{.*}` = 100.\n */\nexport function getPathSpecificityScore(path: string): number {\n const segments = path.split('/').filter(Boolean)\n let score = 0\n for (const segment of segments) {\n if (segment.includes('{.+}') || segment.includes('{.*}')) {\n score += 100\n } else if (segment.startsWith(':') && segment.includes('{')) {\n score += 5\n } else if (segment.startsWith(':')) {\n score += 10\n }\n }\n return score\n}\n\n/**\n * Sort routes by specificity so Hono registers them in the correct order.\n *\n * 1. Static paths before parameterized before wildcards\n * 2. More segments = more specific (tie-breaker)\n * 3. Locale-prefixed variants before their primary (so a locale-prefixed\n * request matches the variant first; a primary catch-all would otherwise\n * swallow the locale prefix into its param)\n */\nexport function sortRoutesBySpecificity<T extends { path: string; isLocaleVariant?: boolean }>(routes: T[]): T[] {\n // Pre-compute packed specificity keys (avoids object allocation per route)\n const keys = new Map<T, number>()\n for (const route of routes) {\n keys.set(route, getPathSpecificityKey(route))\n }\n\n const copy = routes.slice()\n copy.sort((a, b) => keys.get(a)! - keys.get(b)!)\n return copy\n}\n","/**\n * Route naming utilities.\n *\n * Extracts parameter names from paths and domains,\n * and generates convention-based route names.\n */\n\n/**\n * Extract parameter names from a Hono-style path.\n *\n * @example\n * extractParamNames('/users/:id') // ['id']\n * extractParamNames('/:companyId/users/:userId') // ['companyId', 'userId']\n * extractParamNames('/users') // []\n */\nexport function extractParamNames(path: string): string[] {\n if (!path.includes(':')) return []\n const matches = path.matchAll(/:([a-zA-Z_][a-zA-Z0-9_]*)/g)\n return [...matches].map(m => m[1])\n}\n\n/**\n * Extract parameter names from a domain pattern.\n *\n * @example\n * extractDomainParamNames('{tenant}.example.com') // ['tenant']\n * extractDomainParamNames('{region}.{tenant}.example.com') // ['region', 'tenant']\n * extractDomainParamNames('example.com') // []\n */\nexport function extractDomainParamNames(domain: string): string[] {\n if (!domain.includes('{')) return []\n const matches = domain.matchAll(/\\{([a-zA-Z_][a-zA-Z0-9_]*)\\}/g)\n return [...matches].map(m => m[1])\n}\n\n/**\n * Auto-generate a route name for convention-based `@Route` methods.\n *\n * Strips common prefixes (`/api/`, `/v{N}/`) and parameter segments,\n * then joins remaining static segments with dots and appends the method name.\n *\n * @example\n * generateConventionRouteName('/users', 'index') // 'users.index'\n * generateConventionRouteName('/users', 'show') // 'users.show'\n * generateConventionRouteName('/api/v1/users', 'create') // 'users.create'\n * generateConventionRouteName('/api/v1/users/:userId/notes', 'index') // 'users.notes.index'\n * generateConventionRouteName('/:companyId/users', 'index') // 'users.index'\n * generateConventionRouteName('/users/:userId/notes/:noteId/tags', 'index') // 'users.notes.tags.index'\n */\nexport function generateConventionRouteName(basePath: string, methodName: string): string {\n // Single-pass: split and filter in one loop (avoids 4 intermediate arrays)\n const parts = basePath.split('/')\n const segments: string[] = []\n for (const s of parts) {\n if (s && s !== 'api' && !s.startsWith(':') && !/^v\\d+$/.test(s)) {\n segments.push(s)\n }\n }\n\n if (segments.length === 0) {\n return methodName\n }\n\n return `${segments.join('.')}.${methodName}`\n}\n"],"mappings":"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"}
|