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
package/dist/cache/index.mjs
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { l as Singleton, p as inject, r as DI_TOKENS } from "../di-
|
|
2
|
-
import { a as ApplicationError } from "../container-storage-
|
|
3
|
-
import { n as __decorateParam, t as __decorate } from "../decorate-
|
|
1
|
+
import { l as Singleton, p as inject, r as DI_TOKENS } from "../di-DQ3xbjgf.mjs";
|
|
2
|
+
import { a as ApplicationError } from "../container-storage-DfF7SXdV.mjs";
|
|
3
|
+
import { n as __decorateParam, t as __decorate } from "../decorate-C79yt3Wt.mjs";
|
|
4
4
|
import { LOGGER_TOKENS } from "../logger/index.mjs";
|
|
5
|
-
import "../
|
|
6
|
-
import {
|
|
7
|
-
import "../module/index.mjs";
|
|
5
|
+
import { n as Module } from "../module.decorator-CYbrOlX9.mjs";
|
|
6
|
+
import { waitUntil } from "cloudflare:workers";
|
|
8
7
|
//#region src/cache/cache.error.ts
|
|
9
8
|
var CacheError = class extends ApplicationError {};
|
|
10
9
|
//#endregion
|
|
@@ -80,26 +79,47 @@ let CacheService = _CacheService = class CacheService {
|
|
|
80
79
|
}
|
|
81
80
|
}
|
|
82
81
|
/**
|
|
83
|
-
* Store a value in cache
|
|
82
|
+
* Store a value in cache, **without blocking the request**.
|
|
83
|
+
*
|
|
84
|
+
* KV writes commit to KV's central store — reads are edge-cached, but writes
|
|
85
|
+
* are not, so a `put` can add hundreds of ms on the critical path. A cache is
|
|
86
|
+
* best-effort and eventually consistent (a `get` can serve a stale value for
|
|
87
|
+
* ~60s after a write regardless), so the write is scheduled via the Workers
|
|
88
|
+
* runtime's `waitUntil` and settles after the response is sent. The returned
|
|
89
|
+
* promise resolves immediately — the write is enqueued, not awaited — and
|
|
90
|
+
* failures are logged, never thrown (no caller is left to catch them).
|
|
91
|
+
*
|
|
92
|
+
* Do **not** route durability-critical writes (locks, idempotency claims,
|
|
93
|
+
* system-of-record state) through the cache; use the raw KV binding for
|
|
94
|
+
* those. Invalidations stay durable — see {@link delete}.
|
|
84
95
|
*
|
|
85
96
|
* @param key - Cache key
|
|
86
97
|
* @param value - Value to store (string, ArrayBuffer, ArrayBufferView, or ReadableStream)
|
|
87
98
|
* @param options - Put options (expiration, expirationTtl, metadata)
|
|
88
|
-
* @throws {CacheError} If operation fails
|
|
89
99
|
*
|
|
90
100
|
* @example
|
|
91
101
|
* ```typescript
|
|
92
|
-
*
|
|
93
|
-
* await cache.put('key', 'value')
|
|
94
|
-
*
|
|
95
|
-
* // With TTL
|
|
96
|
-
* await cache.put('key', 'value', { expirationTtl: 3600 })
|
|
97
|
-
*
|
|
98
|
-
* // With metadata
|
|
99
|
-
* await cache.put('key', 'value', { metadata: { created: Date.now() } })
|
|
102
|
+
* cache.put('key', 'value', { expirationTtl: 3600 })
|
|
100
103
|
* ```
|
|
101
104
|
*/
|
|
102
|
-
|
|
105
|
+
put(key, value, options) {
|
|
106
|
+
waitUntil(this.writeKv(key, value, options).catch(() => {}));
|
|
107
|
+
return Promise.resolve();
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Store a value and **await the durable write**, throwing on failure — the
|
|
111
|
+
* pre-deferral `put` semantics. Use for the rare durability-critical write
|
|
112
|
+
* that goes through the cache and must not be lost or silently fail: a queue
|
|
113
|
+
* idempotency claim that gates message redelivery, or a failed-job record.
|
|
114
|
+
* Everything best-effort should use the deferred {@link put}.
|
|
115
|
+
*
|
|
116
|
+
* @throws {CacheError} If the write fails.
|
|
117
|
+
*/
|
|
118
|
+
putDurable(key, value, options) {
|
|
119
|
+
return this.writeKv(key, value, options);
|
|
120
|
+
}
|
|
121
|
+
/** Single KV write path with unified error handling: logs, then throws. */
|
|
122
|
+
async writeKv(key, value, options) {
|
|
103
123
|
try {
|
|
104
124
|
await this.kv.put(key, value, options);
|
|
105
125
|
} catch (error) {
|
|
@@ -214,6 +234,25 @@ let TieredCacheService = _TieredCacheService = class TieredCacheService {
|
|
|
214
234
|
}
|
|
215
235
|
async put(key, value, options) {
|
|
216
236
|
await this.cache.put(key, value, options);
|
|
237
|
+
this.writeThroughL1(key, value, options);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Write-through put that **awaits the durable L2 write** (throwing on
|
|
241
|
+
* failure) while keeping the isolate-local L1 in lock-step. Use for set-once
|
|
242
|
+
* values whose loss would be a correctness bug — queue idempotency claims,
|
|
243
|
+
* failed-job records. The deferred {@link put} is for best-effort caching.
|
|
244
|
+
*
|
|
245
|
+
* @throws {CacheError} If the KV write fails.
|
|
246
|
+
*/
|
|
247
|
+
async putDurable(key, value, options) {
|
|
248
|
+
await this.cache.putDurable(key, value, options);
|
|
249
|
+
this.writeThroughL1(key, value, options);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Mirror a write into the isolate-local L1: cache string values; drop the
|
|
253
|
+
* entry for binary values, which L1 does not hold.
|
|
254
|
+
*/
|
|
255
|
+
writeThroughL1(key, value, options) {
|
|
217
256
|
if (typeof value === "string") this.l1Write(key, value, this.l1ExpiresAt(options));
|
|
218
257
|
else this.l1.delete(key);
|
|
219
258
|
}
|
package/dist/cache/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/cache/cache.error.ts","../../src/cache/cache.tokens.ts","../../src/cache/services/cache.service.ts","../../src/cache/services/tiered-cache.service.ts","../../src/cache/cache.module.ts"],"sourcesContent":["import { ApplicationError } from '../errors'\n\nexport class CacheError extends ApplicationError {}\n","export const CACHE_TOKENS = {\n CacheService: Symbol.for('stratal:cache:service'),\n TieredCacheService: Symbol.for('stratal:cache:tiered-service'),\n} as const\n\nexport type CacheToken = (typeof CACHE_TOKENS)[keyof typeof CACHE_TOKENS]\n","import { inject } from '../../di'\nimport { Singleton } from '../../di/decorators'\nimport { DI_TOKENS } from '../../di/tokens'\nimport { type StratalEnv } from '../../env'\nimport { LOGGER_TOKENS, type LoggerService } from '../../logger'\nimport { CACHE_TOKENS } from '../cache.tokens'\nimport { CacheError } from '../cache.error'\n\n/**\n * Cache Service\n *\n * Type-safe wrapper around Cloudflare KV namespaces for caching operations.\n *\n * Reads are eventually consistent — KV may serve an edge-cached value for up to\n * ~60s after a write. When you need isolate-local read-after-write coherence\n * (e.g. set-once markers like queue idempotency keys), opt into\n * {@link TieredCacheService}, which layers an isolate-local L1 over this\n * service. Do **not** use the L1 tier for read-modify-write counters that need\n * cross-edge freshness (e.g. rate limiting) — plain KV is the correct primitive\n * there.\n *\n * **Features:**\n * - Mirrors all KVNamespace methods with full type safety\n * - Supports multiple KV bindings via `withBinding()` / `binding(name)`\n * - Automatic error handling with logging\n * - Security: Raw errors are logged, not exposed to users\n *\n * **Usage:**\n * ```typescript\n * class MyService {\n * private readonly uploadsCache: CacheService\n *\n * constructor(\n * @inject(CACHE_TOKENS.CacheService) private readonly cache: CacheService,\n * ) {\n * // Initialize specialized caches in constructor\n * this.uploadsCache = this.cache.binding('UPLOADS_CACHE')\n * }\n *\n * async cacheData(key: string, value: string) {\n * await this.cache.put(key, value, { expirationTtl: 3600 })\n * await this.uploadsCache.put(`upload:${key}`, value)\n * }\n * }\n * ```\n *\n * @see https://developers.cloudflare.com/kv/api/\n */\n@Singleton(CACHE_TOKENS.CacheService)\nexport class CacheService {\n private kv: KVNamespace\n\n constructor(\n @inject(DI_TOKENS.CloudflareEnv) private readonly env: StratalEnv,\n @inject(LOGGER_TOKENS.LoggerService) private readonly logger: LoggerService\n ) {\n this.kv = env.CACHE\n }\n\n /** The KV namespace this instance is bound to. */\n get namespace(): KVNamespace {\n return this.kv\n }\n\n /**\n * Create a new CacheService instance bound to a different KV namespace.\n *\n * @param kv - KV namespace to use\n * @returns A new CacheService for the given binding\n */\n withBinding(kv: KVNamespace): CacheService {\n const instance = new CacheService(this.env, this.logger)\n instance.kv = kv\n return instance\n }\n\n /**\n * Create a new CacheService instance bound to a KV namespace by its binding\n * name, resolved from the environment.\n *\n * @param name - KV namespace binding name (e.g. `'UPLOADS_CACHE'`)\n * @returns A new CacheService for the given binding\n * @throws {CacheError} If no binding with that name exists in the environment\n */\n binding(name: string): CacheService {\n const kv = (this.env as unknown as Record<string, unknown>)[name] as KVNamespace | undefined\n if (!kv) {\n throw new CacheError(`KV binding \"${name}\" was not found in the environment`)\n }\n return this.withBinding(kv)\n }\n\n // ==================== GET METHODS ====================\n\n /**\n * Get a value from cache\n *\n * @param key - Cache key\n * @param typeOrOptions - Type string or options object (defaults to 'text')\n * @returns Value in specified type, or null if not found\n * @throws {CacheError} If operation fails\n */\n async get(key: string, typeOrOptions?: 'text' | KVNamespaceGetOptions<'text'>): Promise<string | null>\n async get<ExpectedValue = unknown>(key: string, typeOrOptions: 'json' | KVNamespaceGetOptions<'json'>): Promise<ExpectedValue | null>\n async get(key: string, typeOrOptions: 'arrayBuffer' | KVNamespaceGetOptions<'arrayBuffer'>): Promise<ArrayBuffer | null>\n async get(key: string, typeOrOptions: 'stream' | KVNamespaceGetOptions<'stream'>): Promise<ReadableStream | null>\n\n async get<ExpectedValue = unknown>(\n key: string,\n typeOrOptions?: string | KVNamespaceGetOptions<'text' | 'json' | 'arrayBuffer' | 'stream'>\n ): Promise<string | ExpectedValue | ArrayBuffer | ReadableStream | null> {\n try {\n if (typeof typeOrOptions === 'string') {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any -- bridging KV overloaded API\n return await this.kv.get(key, typeOrOptions as any)\n }\n\n if (typeOrOptions) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any -- bridging KV overloaded API\n return await this.kv.get(key, typeOrOptions as any)\n }\n\n return await this.kv.get(key)\n } catch (error) {\n this.logger.error('Cache get operation failed', { key, error })\n throw new CacheError(`Failed to get cache key \"${key}\"`)\n }\n }\n\n // ==================== GET WITH METADATA METHODS ====================\n\n /**\n * Get a value with metadata from cache\n *\n * @param key - Cache key\n * @param typeOrOptions - Type string or options object (defaults to 'text')\n * @returns Object with value, metadata, and cacheStatus\n * @throws {CacheError} If operation fails\n */\n async getWithMetadata<Metadata = unknown>(\n key: string,\n typeOrOptions?: 'text' | KVNamespaceGetOptions<'text'>\n ): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>\n async getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(\n key: string,\n typeOrOptions: 'json' | KVNamespaceGetOptions<'json'>\n ): Promise<KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>\n async getWithMetadata<Metadata = unknown>(\n key: string,\n typeOrOptions: 'arrayBuffer' | KVNamespaceGetOptions<'arrayBuffer'>\n ): Promise<KVNamespaceGetWithMetadataResult<ArrayBuffer, Metadata>>\n async getWithMetadata<Metadata = unknown>(\n key: string,\n typeOrOptions: 'stream' | KVNamespaceGetOptions<'stream'>\n ): Promise<KVNamespaceGetWithMetadataResult<ReadableStream, Metadata>>\n\n async getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(\n key: string,\n typeOrOptions?: string | KVNamespaceGetOptions<'text' | 'json' | 'arrayBuffer' | 'stream'>\n ): Promise<\n KVNamespaceGetWithMetadataResult<\n string | ExpectedValue | ArrayBuffer | ReadableStream,\n Metadata\n >\n > {\n try {\n if (typeof typeOrOptions === 'string') {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any -- bridging KV overloaded API\n return await this.kv.getWithMetadata(key, typeOrOptions as any)\n }\n\n if (typeOrOptions) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any -- bridging KV overloaded API\n return await this.kv.getWithMetadata(key, typeOrOptions as any)\n }\n\n return await this.kv.getWithMetadata(key)\n } catch (error) {\n this.logger.error('Cache getWithMetadata operation failed', { key, error })\n throw new CacheError(`Failed to get cache key \"${key}\"`)\n }\n }\n\n // ==================== PUT METHOD ====================\n\n /**\n * Store a value in cache\n *\n * @param key - Cache key\n * @param value - Value to store (string, ArrayBuffer, ArrayBufferView, or ReadableStream)\n * @param options - Put options (expiration, expirationTtl, metadata)\n * @throws {CacheError} If operation fails\n *\n * @example\n * ```typescript\n * // Simple put\n * await cache.put('key', 'value')\n *\n * // With TTL\n * await cache.put('key', 'value', { expirationTtl: 3600 })\n *\n * // With metadata\n * await cache.put('key', 'value', { metadata: { created: Date.now() } })\n * ```\n */\n async put(\n key: string,\n value: string | ArrayBuffer | ArrayBufferView | ReadableStream,\n options?: KVNamespacePutOptions\n ): Promise<void> {\n try {\n await this.kv.put(key, value as string, options)\n } catch (error) {\n this.logger.error('Cache put operation failed', { key, error })\n throw new CacheError(`Failed to store cache key \"${key}\"`)\n }\n }\n\n // ==================== DELETE METHODS ====================\n\n /**\n * Delete a value from cache\n *\n * @param key - Cache key to delete\n * @throws {CacheError} If operation fails\n */\n async delete(key: string): Promise<void> {\n try {\n await this.kv.delete(key)\n } catch (error) {\n this.logger.error('Cache delete operation failed', { key, error })\n throw new CacheError(`Failed to delete cache key \"${key}\"`)\n }\n }\n\n\n // ==================== LIST METHOD ====================\n\n /**\n * List keys in cache\n *\n * @param options - List options (limit, prefix, cursor)\n * @returns List result with keys and pagination info\n * @throws {CacheError} If operation fails\n *\n * @example\n * ```typescript\n * // List all keys\n * const result = await cache.list()\n *\n * // List with prefix\n * const result = await cache.list({ prefix: 'user:' })\n *\n * // Paginated list\n * const result = await cache.list({ limit: 100 })\n * if (!result.list_complete) {\n * const nextPage = await cache.list({ cursor: result.cursor })\n * }\n * ```\n */\n async list<Metadata = unknown>(\n options?: KVNamespaceListOptions\n ): Promise<KVNamespaceListResult<Metadata>> {\n try {\n return await this.kv.list<Metadata>(options)\n } catch (error) {\n this.logger.error('Cache list operation failed', { options, error })\n throw new CacheError('Failed to list cache keys')\n }\n }\n}\n","import { inject } from '../../di'\nimport { Singleton } from '../../di/decorators'\nimport { CACHE_TOKENS } from '../cache.tokens'\nimport type { CacheService } from './cache.service'\n\n/** A value held in the isolate-local L1 tier. */\ninterface L1Entry {\n /** The raw string value, exactly as it is (or would be) stored in KV. */\n value: string\n /** Absolute expiry in epoch milliseconds, or `null` for no expiry. */\n expiresAt: number | null\n}\n\n/**\n * Max entries kept in the isolate-local L1 before the oldest is evicted (FIFO).\n * Bounds memory per isolate; KV remains the unbounded source of truth.\n */\nconst L1_MAX_ENTRIES = 1000\n\n/**\n * Tiered Cache Service\n *\n * Layers an **isolate-local in-memory L1** over {@link CacheService} (KV, the\n * L2). Opt-in — inject `CACHE_TOKENS.TieredCacheService` instead of\n * `CACHE_TOKENS.CacheService` when you want it.\n *\n * **Why:** KV reads are eventually consistent (a `get` can return an\n * edge-cached value for up to ~60s after a `put`). The L1 makes writes made\n * through *this isolate* immediately and consistently visible to subsequent\n * reads on the same isolate — closing the read-after-write gap KV alone cannot.\n * This is what makes set-once patterns (e.g. queue idempotency markers) reliable\n * within an isolate, even inside KV's consistency window.\n *\n * **Use it for:** set-once / read-mostly values (idempotency claims, immutable\n * lookups). **Do not use it for** read-modify-write counters that need\n * cross-edge freshness (e.g. rate limiting): an isolate that wrote a key reads\n * its own value until the L1 entry expires, so concurrent increments from other\n * isolates are missed and overwritten. Use plain {@link CacheService} (KV) or a\n * Durable-Object store for those.\n *\n * **Semantics:**\n * - L1 caches string-backed values only (`text`/`json`). `arrayBuffer`/`stream`\n * reads and non-string writes bypass and invalidate L1.\n * - `put`/`delete` are write-through: KV and L1 update in lock-step, honoring\n * `expirationTtl`/`expiration` for the L1 entry's own expiry.\n * - `get` populates L1 from `text` reads (the only path that yields the raw\n * string). `json` reads are served from L1 when the string was cached by a\n * prior `put`/`text` read, otherwise they go straight to KV.\n * - `getWithMetadata` and `list` always read KV directly (metadata is not held\n * in L1).\n *\n * **Cross-isolate caveat:** the L1 only observes writes made through its own\n * isolate. A value mutated by another isolate is not seen until the local entry\n * expires or is invalidated by a local write/delete.\n *\n * **Bindings:** `binding(name)` returns a memoized tiered instance per binding,\n * so each KV namespace has its own stable, isolate-lifetime L1.\n */\n@Singleton(CACHE_TOKENS.TieredCacheService)\nexport class TieredCacheService {\n /** Isolate-local L1 tier. Per-instance, so each binding has its own. */\n private readonly l1 = new Map<string, L1Entry>()\n /** Memoized tiered instances per binding name (each with its own L1). */\n private readonly children = new Map<string, TieredCacheService>()\n\n constructor(\n @inject(CACHE_TOKENS.CacheService) private readonly cache: CacheService\n ) {}\n\n /**\n * Get the tiered cache bound to a KV namespace by its binding name. Memoized:\n * repeated calls with the same name return the same instance, preserving its\n * isolate-local L1 across requests/messages.\n *\n * @param name - KV namespace binding name (e.g. `'UPLOADS_CACHE'`)\n * @throws {CacheError} If no binding with that name exists in the environment\n */\n binding(name: string): TieredCacheService {\n let child = this.children.get(name)\n if (child === undefined) {\n child = new TieredCacheService(this.cache.binding(name))\n this.children.set(name, child)\n }\n return child\n }\n\n // ==================== GET ====================\n\n async get(key: string, typeOrOptions?: 'text' | KVNamespaceGetOptions<'text'>): Promise<string | null>\n async get<ExpectedValue = unknown>(key: string, typeOrOptions: 'json' | KVNamespaceGetOptions<'json'>): Promise<ExpectedValue | null>\n async get(key: string, typeOrOptions: 'arrayBuffer' | KVNamespaceGetOptions<'arrayBuffer'>): Promise<ArrayBuffer | null>\n async get(key: string, typeOrOptions: 'stream' | KVNamespaceGetOptions<'stream'>): Promise<ReadableStream | null>\n\n async get<ExpectedValue = unknown>(\n key: string,\n typeOrOptions?: string | KVNamespaceGetOptions<'text' | 'json' | 'arrayBuffer' | 'stream'>\n ): Promise<string | ExpectedValue | ArrayBuffer | ReadableStream | null> {\n const type = typeof typeOrOptions === 'string'\n ? typeOrOptions\n : typeOrOptions?.type ?? 'text'\n\n // L1 holds string-backed values only. Serve text/json from it when present.\n if (type === 'text' || type === 'json') {\n const cached = this.l1Read(key)\n if (cached !== null) {\n return type === 'json' ? (JSON.parse(cached) as ExpectedValue) : cached\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any -- bridging the typed CacheService.get overloads\n const value = await this.cache.get<ExpectedValue>(key, typeOrOptions as any)\n\n // Only `text` reads yield the raw string we can cache; back-populate L1.\n if (type === 'text' && typeof value === 'string') {\n this.l1Write(key, value, null)\n }\n\n return value\n }\n\n // ==================== GET WITH METADATA (KV-direct) ====================\n\n async getWithMetadata<Metadata = unknown>(\n key: string,\n typeOrOptions?: 'text' | KVNamespaceGetOptions<'text'>\n ): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>\n async getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(\n key: string,\n typeOrOptions: 'json' | KVNamespaceGetOptions<'json'>\n ): Promise<KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>\n async getWithMetadata<Metadata = unknown>(\n key: string,\n typeOrOptions: 'arrayBuffer' | KVNamespaceGetOptions<'arrayBuffer'>\n ): Promise<KVNamespaceGetWithMetadataResult<ArrayBuffer, Metadata>>\n async getWithMetadata<Metadata = unknown>(\n key: string,\n typeOrOptions: 'stream' | KVNamespaceGetOptions<'stream'>\n ): Promise<KVNamespaceGetWithMetadataResult<ReadableStream, Metadata>>\n\n async getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(\n key: string,\n typeOrOptions?: string | KVNamespaceGetOptions<'text' | 'json' | 'arrayBuffer' | 'stream'>\n ): Promise<\n KVNamespaceGetWithMetadataResult<\n string | ExpectedValue | ArrayBuffer | ReadableStream,\n Metadata\n >\n > {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any -- bridging the typed CacheService.getWithMetadata overloads\n return this.cache.getWithMetadata<ExpectedValue, Metadata>(key, typeOrOptions as any)\n }\n\n // ==================== PUT (write-through) ====================\n\n async put(\n key: string,\n value: string | ArrayBuffer | ArrayBufferView | ReadableStream,\n options?: KVNamespacePutOptions\n ): Promise<void> {\n await this.cache.put(key, value, options)\n // Write-through: cache strings in L1; invalidate for binary values, which\n // L1 does not hold.\n if (typeof value === 'string') {\n this.l1Write(key, value, this.l1ExpiresAt(options))\n } else {\n this.l1.delete(key)\n }\n }\n\n // ==================== DELETE (write-through) ====================\n\n async delete(key: string): Promise<void> {\n await this.cache.delete(key)\n this.l1.delete(key)\n }\n\n // ==================== LIST (KV-direct) ====================\n\n async list<Metadata = unknown>(\n options?: KVNamespaceListOptions\n ): Promise<KVNamespaceListResult<Metadata>> {\n return this.cache.list<Metadata>(options)\n }\n\n // ==================== L1 TIER ====================\n\n /** Read a fresh L1 entry, evicting it if expired. Returns `null` on miss. */\n private l1Read(key: string): string | null {\n const entry = this.l1.get(key)\n if (entry === undefined) return null\n if (entry.expiresAt !== null && Date.now() >= entry.expiresAt) {\n this.l1.delete(key)\n return null\n }\n return entry.value\n }\n\n /** Write an L1 entry, refreshing its recency and evicting the oldest at cap. */\n private l1Write(key: string, value: string, expiresAt: number | null): void {\n // Re-insert so the most recently written key is youngest in iteration order.\n this.l1.delete(key)\n if (this.l1.size >= L1_MAX_ENTRIES) {\n const oldest = this.l1.keys().next().value\n if (oldest !== undefined) this.l1.delete(oldest)\n }\n this.l1.set(key, { value, expiresAt })\n }\n\n /** Compute an absolute L1 expiry (epoch ms) from KV put options. */\n private l1ExpiresAt(options?: KVNamespacePutOptions): number | null {\n if (options?.expiration !== undefined) return options.expiration * 1000\n if (options?.expirationTtl !== undefined) return Date.now() + options.expirationTtl * 1000\n return null\n }\n}\n","/**\n * Cache Module\n *\n * Provides key-value caching capabilities using Cloudflare KV namespaces.\n *\n * **Features:**\n * - Type-safe KV wrapper with full method coverage\n * - Multiple KV binding support via `withBinding()`\n * - Automatic error handling with security-focused logging\n * - Singleton service for optimal performance\n */\n\nimport { Module } from '../module'\nimport { CACHE_TOKENS } from './cache.tokens'\nimport { CacheService, TieredCacheService } from './services'\n\n@Module({\n providers: [\n // Singleton - CacheService has no request dependencies\n { provide: CACHE_TOKENS.CacheService, useClass: CacheService },\n // Opt-in isolate-local L1 over CacheService (for set-once/read-mostly keys)\n { provide: CACHE_TOKENS.TieredCacheService, useClass: TieredCacheService },\n ],\n})\nexport class CacheModule {}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/cache/cache.error.ts","../../src/cache/cache.tokens.ts","../../src/cache/services/cache.service.ts","../../src/cache/services/tiered-cache.service.ts","../../src/cache/cache.module.ts"],"sourcesContent":["import { ApplicationError } from '../errors'\n\nexport class CacheError extends ApplicationError {}\n","export const CACHE_TOKENS = {\n CacheService: Symbol.for('stratal:cache:service'),\n TieredCacheService: Symbol.for('stratal:cache:tiered-service'),\n} as const\n\nexport type CacheToken = (typeof CACHE_TOKENS)[keyof typeof CACHE_TOKENS]\n","import { waitUntil } from 'cloudflare:workers'\nimport { inject } from '../../di'\nimport { Singleton } from '../../di/decorators'\nimport { DI_TOKENS } from '../../di/tokens'\nimport { type StratalEnv } from '../../env'\nimport { LOGGER_TOKENS, type LoggerService } from '../../logger'\nimport { CACHE_TOKENS } from '../cache.tokens'\nimport { CacheError } from '../cache.error'\n\n/**\n * Cache Service\n *\n * Type-safe wrapper around Cloudflare KV namespaces for caching operations.\n *\n * Reads are eventually consistent — KV may serve an edge-cached value for up to\n * ~60s after a write. When you need isolate-local read-after-write coherence\n * (e.g. set-once markers like queue idempotency keys), opt into\n * {@link TieredCacheService}, which layers an isolate-local L1 over this\n * service. Do **not** use the L1 tier for read-modify-write counters that need\n * cross-edge freshness (e.g. rate limiting) — plain KV is the correct primitive\n * there.\n *\n * **Features:**\n * - Mirrors all KVNamespace methods with full type safety\n * - Supports multiple KV bindings via `withBinding()` / `binding(name)`\n * - Automatic error handling with logging\n * - Security: Raw errors are logged, not exposed to users\n *\n * **Usage:**\n * ```typescript\n * class MyService {\n * private readonly uploadsCache: CacheService\n *\n * constructor(\n * @inject(CACHE_TOKENS.CacheService) private readonly cache: CacheService,\n * ) {\n * // Initialize specialized caches in constructor\n * this.uploadsCache = this.cache.binding('UPLOADS_CACHE')\n * }\n *\n * async cacheData(key: string, value: string) {\n * await this.cache.put(key, value, { expirationTtl: 3600 })\n * await this.uploadsCache.put(`upload:${key}`, value)\n * }\n * }\n * ```\n *\n * @see https://developers.cloudflare.com/kv/api/\n */\n@Singleton(CACHE_TOKENS.CacheService)\nexport class CacheService {\n private kv: KVNamespace\n\n constructor(\n @inject(DI_TOKENS.CloudflareEnv) private readonly env: StratalEnv,\n @inject(LOGGER_TOKENS.LoggerService) private readonly logger: LoggerService\n ) {\n this.kv = env.CACHE\n }\n\n /** The KV namespace this instance is bound to. */\n get namespace(): KVNamespace {\n return this.kv\n }\n\n /**\n * Create a new CacheService instance bound to a different KV namespace.\n *\n * @param kv - KV namespace to use\n * @returns A new CacheService for the given binding\n */\n withBinding(kv: KVNamespace): CacheService {\n const instance = new CacheService(this.env, this.logger)\n instance.kv = kv\n return instance\n }\n\n /**\n * Create a new CacheService instance bound to a KV namespace by its binding\n * name, resolved from the environment.\n *\n * @param name - KV namespace binding name (e.g. `'UPLOADS_CACHE'`)\n * @returns A new CacheService for the given binding\n * @throws {CacheError} If no binding with that name exists in the environment\n */\n binding(name: string): CacheService {\n const kv = (this.env as unknown as Record<string, unknown>)[name] as KVNamespace | undefined\n if (!kv) {\n throw new CacheError(`KV binding \"${name}\" was not found in the environment`)\n }\n return this.withBinding(kv)\n }\n\n // ==================== GET METHODS ====================\n\n /**\n * Get a value from cache\n *\n * @param key - Cache key\n * @param typeOrOptions - Type string or options object (defaults to 'text')\n * @returns Value in specified type, or null if not found\n * @throws {CacheError} If operation fails\n */\n async get(key: string, typeOrOptions?: 'text' | KVNamespaceGetOptions<'text'>): Promise<string | null>\n async get<ExpectedValue = unknown>(key: string, typeOrOptions: 'json' | KVNamespaceGetOptions<'json'>): Promise<ExpectedValue | null>\n async get(key: string, typeOrOptions: 'arrayBuffer' | KVNamespaceGetOptions<'arrayBuffer'>): Promise<ArrayBuffer | null>\n async get(key: string, typeOrOptions: 'stream' | KVNamespaceGetOptions<'stream'>): Promise<ReadableStream | null>\n\n async get<ExpectedValue = unknown>(\n key: string,\n typeOrOptions?: string | KVNamespaceGetOptions<'text' | 'json' | 'arrayBuffer' | 'stream'>\n ): Promise<string | ExpectedValue | ArrayBuffer | ReadableStream | null> {\n try {\n if (typeof typeOrOptions === 'string') {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any -- bridging KV overloaded API\n return await this.kv.get(key, typeOrOptions as any)\n }\n\n if (typeOrOptions) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any -- bridging KV overloaded API\n return await this.kv.get(key, typeOrOptions as any)\n }\n\n return await this.kv.get(key)\n } catch (error) {\n this.logger.error('Cache get operation failed', { key, error })\n throw new CacheError(`Failed to get cache key \"${key}\"`)\n }\n }\n\n // ==================== GET WITH METADATA METHODS ====================\n\n /**\n * Get a value with metadata from cache\n *\n * @param key - Cache key\n * @param typeOrOptions - Type string or options object (defaults to 'text')\n * @returns Object with value, metadata, and cacheStatus\n * @throws {CacheError} If operation fails\n */\n async getWithMetadata<Metadata = unknown>(\n key: string,\n typeOrOptions?: 'text' | KVNamespaceGetOptions<'text'>\n ): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>\n async getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(\n key: string,\n typeOrOptions: 'json' | KVNamespaceGetOptions<'json'>\n ): Promise<KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>\n async getWithMetadata<Metadata = unknown>(\n key: string,\n typeOrOptions: 'arrayBuffer' | KVNamespaceGetOptions<'arrayBuffer'>\n ): Promise<KVNamespaceGetWithMetadataResult<ArrayBuffer, Metadata>>\n async getWithMetadata<Metadata = unknown>(\n key: string,\n typeOrOptions: 'stream' | KVNamespaceGetOptions<'stream'>\n ): Promise<KVNamespaceGetWithMetadataResult<ReadableStream, Metadata>>\n\n async getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(\n key: string,\n typeOrOptions?: string | KVNamespaceGetOptions<'text' | 'json' | 'arrayBuffer' | 'stream'>\n ): Promise<\n KVNamespaceGetWithMetadataResult<\n string | ExpectedValue | ArrayBuffer | ReadableStream,\n Metadata\n >\n > {\n try {\n if (typeof typeOrOptions === 'string') {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any -- bridging KV overloaded API\n return await this.kv.getWithMetadata(key, typeOrOptions as any)\n }\n\n if (typeOrOptions) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any -- bridging KV overloaded API\n return await this.kv.getWithMetadata(key, typeOrOptions as any)\n }\n\n return await this.kv.getWithMetadata(key)\n } catch (error) {\n this.logger.error('Cache getWithMetadata operation failed', { key, error })\n throw new CacheError(`Failed to get cache key \"${key}\"`)\n }\n }\n\n // ==================== PUT METHOD ====================\n\n /**\n * Store a value in cache, **without blocking the request**.\n *\n * KV writes commit to KV's central store — reads are edge-cached, but writes\n * are not, so a `put` can add hundreds of ms on the critical path. A cache is\n * best-effort and eventually consistent (a `get` can serve a stale value for\n * ~60s after a write regardless), so the write is scheduled via the Workers\n * runtime's `waitUntil` and settles after the response is sent. The returned\n * promise resolves immediately — the write is enqueued, not awaited — and\n * failures are logged, never thrown (no caller is left to catch them).\n *\n * Do **not** route durability-critical writes (locks, idempotency claims,\n * system-of-record state) through the cache; use the raw KV binding for\n * those. Invalidations stay durable — see {@link delete}.\n *\n * @param key - Cache key\n * @param value - Value to store (string, ArrayBuffer, ArrayBufferView, or ReadableStream)\n * @param options - Put options (expiration, expirationTtl, metadata)\n *\n * @example\n * ```typescript\n * cache.put('key', 'value', { expirationTtl: 3600 })\n * ```\n */\n put(\n key: string,\n value: string | ArrayBuffer | ArrayBufferView | ReadableStream,\n options?: KVNamespacePutOptions\n ): Promise<void> {\n // Deferred: schedule the durable write off the request path. `writeKv` logs\n // on failure; the throw is swallowed here because nothing awaits this write.\n waitUntil(\n this.writeKv(key, value, options).catch(() => {\n /* failure already logged in writeKv */\n })\n )\n return Promise.resolve()\n }\n\n /**\n * Store a value and **await the durable write**, throwing on failure — the\n * pre-deferral `put` semantics. Use for the rare durability-critical write\n * that goes through the cache and must not be lost or silently fail: a queue\n * idempotency claim that gates message redelivery, or a failed-job record.\n * Everything best-effort should use the deferred {@link put}.\n *\n * @throws {CacheError} If the write fails.\n */\n putDurable(\n key: string,\n value: string | ArrayBuffer | ArrayBufferView | ReadableStream,\n options?: KVNamespacePutOptions\n ): Promise<void> {\n return this.writeKv(key, value, options)\n }\n\n /** Single KV write path with unified error handling: logs, then throws. */\n private async writeKv(\n key: string,\n value: string | ArrayBuffer | ArrayBufferView | ReadableStream,\n options?: KVNamespacePutOptions\n ): Promise<void> {\n try {\n await this.kv.put(key, value as string, options)\n } catch (error) {\n this.logger.error('Cache put operation failed', { key, error })\n throw new CacheError(`Failed to store cache key \"${key}\"`)\n }\n }\n\n // ==================== DELETE METHODS ====================\n\n /**\n * Delete a value from cache\n *\n * @param key - Cache key to delete\n * @throws {CacheError} If operation fails\n */\n async delete(key: string): Promise<void> {\n try {\n await this.kv.delete(key)\n } catch (error) {\n this.logger.error('Cache delete operation failed', { key, error })\n throw new CacheError(`Failed to delete cache key \"${key}\"`)\n }\n }\n\n\n // ==================== LIST METHOD ====================\n\n /**\n * List keys in cache\n *\n * @param options - List options (limit, prefix, cursor)\n * @returns List result with keys and pagination info\n * @throws {CacheError} If operation fails\n *\n * @example\n * ```typescript\n * // List all keys\n * const result = await cache.list()\n *\n * // List with prefix\n * const result = await cache.list({ prefix: 'user:' })\n *\n * // Paginated list\n * const result = await cache.list({ limit: 100 })\n * if (!result.list_complete) {\n * const nextPage = await cache.list({ cursor: result.cursor })\n * }\n * ```\n */\n async list<Metadata = unknown>(\n options?: KVNamespaceListOptions\n ): Promise<KVNamespaceListResult<Metadata>> {\n try {\n return await this.kv.list<Metadata>(options)\n } catch (error) {\n this.logger.error('Cache list operation failed', { options, error })\n throw new CacheError('Failed to list cache keys')\n }\n }\n}\n","import { inject } from '../../di'\nimport { Singleton } from '../../di/decorators'\nimport { CACHE_TOKENS } from '../cache.tokens'\nimport type { CacheService } from './cache.service'\n\n/** A value held in the isolate-local L1 tier. */\ninterface L1Entry {\n /** The raw string value, exactly as it is (or would be) stored in KV. */\n value: string\n /** Absolute expiry in epoch milliseconds, or `null` for no expiry. */\n expiresAt: number | null\n}\n\n/**\n * Max entries kept in the isolate-local L1 before the oldest is evicted (FIFO).\n * Bounds memory per isolate; KV remains the unbounded source of truth.\n */\nconst L1_MAX_ENTRIES = 1000\n\n/**\n * Tiered Cache Service\n *\n * Layers an **isolate-local in-memory L1** over {@link CacheService} (KV, the\n * L2). Opt-in — inject `CACHE_TOKENS.TieredCacheService` instead of\n * `CACHE_TOKENS.CacheService` when you want it.\n *\n * **Why:** KV reads are eventually consistent (a `get` can return an\n * edge-cached value for up to ~60s after a `put`). The L1 makes writes made\n * through *this isolate* immediately and consistently visible to subsequent\n * reads on the same isolate — closing the read-after-write gap KV alone cannot.\n * This is what makes set-once patterns (e.g. queue idempotency markers) reliable\n * within an isolate, even inside KV's consistency window.\n *\n * **Use it for:** set-once / read-mostly values (idempotency claims, immutable\n * lookups). **Do not use it for** read-modify-write counters that need\n * cross-edge freshness (e.g. rate limiting): an isolate that wrote a key reads\n * its own value until the L1 entry expires, so concurrent increments from other\n * isolates are missed and overwritten. Use plain {@link CacheService} (KV) or a\n * Durable-Object store for those.\n *\n * **Semantics:**\n * - L1 caches string-backed values only (`text`/`json`). `arrayBuffer`/`stream`\n * reads and non-string writes bypass and invalidate L1.\n * - `put`/`delete` are write-through: KV and L1 update in lock-step, honoring\n * `expirationTtl`/`expiration` for the L1 entry's own expiry.\n * - `get` populates L1 from `text` reads (the only path that yields the raw\n * string). `json` reads are served from L1 when the string was cached by a\n * prior `put`/`text` read, otherwise they go straight to KV.\n * - `getWithMetadata` and `list` always read KV directly (metadata is not held\n * in L1).\n *\n * **Cross-isolate caveat:** the L1 only observes writes made through its own\n * isolate. A value mutated by another isolate is not seen until the local entry\n * expires or is invalidated by a local write/delete.\n *\n * **Bindings:** `binding(name)` returns a memoized tiered instance per binding,\n * so each KV namespace has its own stable, isolate-lifetime L1.\n */\n@Singleton(CACHE_TOKENS.TieredCacheService)\nexport class TieredCacheService {\n /** Isolate-local L1 tier. Per-instance, so each binding has its own. */\n private readonly l1 = new Map<string, L1Entry>()\n /** Memoized tiered instances per binding name (each with its own L1). */\n private readonly children = new Map<string, TieredCacheService>()\n\n constructor(\n @inject(CACHE_TOKENS.CacheService) private readonly cache: CacheService\n ) {}\n\n /**\n * Get the tiered cache bound to a KV namespace by its binding name. Memoized:\n * repeated calls with the same name return the same instance, preserving its\n * isolate-local L1 across requests/messages.\n *\n * @param name - KV namespace binding name (e.g. `'UPLOADS_CACHE'`)\n * @throws {CacheError} If no binding with that name exists in the environment\n */\n binding(name: string): TieredCacheService {\n let child = this.children.get(name)\n if (child === undefined) {\n child = new TieredCacheService(this.cache.binding(name))\n this.children.set(name, child)\n }\n return child\n }\n\n // ==================== GET ====================\n\n async get(key: string, typeOrOptions?: 'text' | KVNamespaceGetOptions<'text'>): Promise<string | null>\n async get<ExpectedValue = unknown>(key: string, typeOrOptions: 'json' | KVNamespaceGetOptions<'json'>): Promise<ExpectedValue | null>\n async get(key: string, typeOrOptions: 'arrayBuffer' | KVNamespaceGetOptions<'arrayBuffer'>): Promise<ArrayBuffer | null>\n async get(key: string, typeOrOptions: 'stream' | KVNamespaceGetOptions<'stream'>): Promise<ReadableStream | null>\n\n async get<ExpectedValue = unknown>(\n key: string,\n typeOrOptions?: string | KVNamespaceGetOptions<'text' | 'json' | 'arrayBuffer' | 'stream'>\n ): Promise<string | ExpectedValue | ArrayBuffer | ReadableStream | null> {\n const type = typeof typeOrOptions === 'string'\n ? typeOrOptions\n : typeOrOptions?.type ?? 'text'\n\n // L1 holds string-backed values only. Serve text/json from it when present.\n if (type === 'text' || type === 'json') {\n const cached = this.l1Read(key)\n if (cached !== null) {\n return type === 'json' ? (JSON.parse(cached) as ExpectedValue) : cached\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any -- bridging the typed CacheService.get overloads\n const value = await this.cache.get<ExpectedValue>(key, typeOrOptions as any)\n\n // Only `text` reads yield the raw string we can cache; back-populate L1.\n if (type === 'text' && typeof value === 'string') {\n this.l1Write(key, value, null)\n }\n\n return value\n }\n\n // ==================== GET WITH METADATA (KV-direct) ====================\n\n async getWithMetadata<Metadata = unknown>(\n key: string,\n typeOrOptions?: 'text' | KVNamespaceGetOptions<'text'>\n ): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>\n async getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(\n key: string,\n typeOrOptions: 'json' | KVNamespaceGetOptions<'json'>\n ): Promise<KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>\n async getWithMetadata<Metadata = unknown>(\n key: string,\n typeOrOptions: 'arrayBuffer' | KVNamespaceGetOptions<'arrayBuffer'>\n ): Promise<KVNamespaceGetWithMetadataResult<ArrayBuffer, Metadata>>\n async getWithMetadata<Metadata = unknown>(\n key: string,\n typeOrOptions: 'stream' | KVNamespaceGetOptions<'stream'>\n ): Promise<KVNamespaceGetWithMetadataResult<ReadableStream, Metadata>>\n\n async getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(\n key: string,\n typeOrOptions?: string | KVNamespaceGetOptions<'text' | 'json' | 'arrayBuffer' | 'stream'>\n ): Promise<\n KVNamespaceGetWithMetadataResult<\n string | ExpectedValue | ArrayBuffer | ReadableStream,\n Metadata\n >\n > {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any -- bridging the typed CacheService.getWithMetadata overloads\n return this.cache.getWithMetadata<ExpectedValue, Metadata>(key, typeOrOptions as any)\n }\n\n // ==================== PUT (write-through) ====================\n\n async put(\n key: string,\n value: string | ArrayBuffer | ArrayBufferView | ReadableStream,\n options?: KVNamespacePutOptions\n ): Promise<void> {\n await this.cache.put(key, value, options)\n this.writeThroughL1(key, value, options)\n }\n\n /**\n * Write-through put that **awaits the durable L2 write** (throwing on\n * failure) while keeping the isolate-local L1 in lock-step. Use for set-once\n * values whose loss would be a correctness bug — queue idempotency claims,\n * failed-job records. The deferred {@link put} is for best-effort caching.\n *\n * @throws {CacheError} If the KV write fails.\n */\n async putDurable(\n key: string,\n value: string | ArrayBuffer | ArrayBufferView | ReadableStream,\n options?: KVNamespacePutOptions\n ): Promise<void> {\n await this.cache.putDurable(key, value, options)\n this.writeThroughL1(key, value, options)\n }\n\n /**\n * Mirror a write into the isolate-local L1: cache string values; drop the\n * entry for binary values, which L1 does not hold.\n */\n private writeThroughL1(\n key: string,\n value: string | ArrayBuffer | ArrayBufferView | ReadableStream,\n options?: KVNamespacePutOptions\n ): void {\n if (typeof value === 'string') {\n this.l1Write(key, value, this.l1ExpiresAt(options))\n } else {\n this.l1.delete(key)\n }\n }\n\n // ==================== DELETE (write-through) ====================\n\n async delete(key: string): Promise<void> {\n await this.cache.delete(key)\n this.l1.delete(key)\n }\n\n // ==================== LIST (KV-direct) ====================\n\n async list<Metadata = unknown>(\n options?: KVNamespaceListOptions\n ): Promise<KVNamespaceListResult<Metadata>> {\n return this.cache.list<Metadata>(options)\n }\n\n // ==================== L1 TIER ====================\n\n /** Read a fresh L1 entry, evicting it if expired. Returns `null` on miss. */\n private l1Read(key: string): string | null {\n const entry = this.l1.get(key)\n if (entry === undefined) return null\n if (entry.expiresAt !== null && Date.now() >= entry.expiresAt) {\n this.l1.delete(key)\n return null\n }\n return entry.value\n }\n\n /** Write an L1 entry, refreshing its recency and evicting the oldest at cap. */\n private l1Write(key: string, value: string, expiresAt: number | null): void {\n // Re-insert so the most recently written key is youngest in iteration order.\n this.l1.delete(key)\n if (this.l1.size >= L1_MAX_ENTRIES) {\n const oldest = this.l1.keys().next().value\n if (oldest !== undefined) this.l1.delete(oldest)\n }\n this.l1.set(key, { value, expiresAt })\n }\n\n /** Compute an absolute L1 expiry (epoch ms) from KV put options. */\n private l1ExpiresAt(options?: KVNamespacePutOptions): number | null {\n if (options?.expiration !== undefined) return options.expiration * 1000\n if (options?.expirationTtl !== undefined) return Date.now() + options.expirationTtl * 1000\n return null\n }\n}\n","/**\n * Cache Module\n *\n * Provides key-value caching capabilities using Cloudflare KV namespaces.\n *\n * **Features:**\n * - Type-safe KV wrapper with full method coverage\n * - Multiple KV binding support via `withBinding()`\n * - Automatic error handling with security-focused logging\n * - Singleton service for optimal performance\n */\n\nimport { Module } from '../module'\nimport { CACHE_TOKENS } from './cache.tokens'\nimport { CacheService, TieredCacheService } from './services'\n\n@Module({\n providers: [\n // Singleton - CacheService has no request dependencies\n { provide: CACHE_TOKENS.CacheService, useClass: CacheService },\n // Opt-in isolate-local L1 over CacheService (for set-once/read-mostly keys)\n { provide: CACHE_TOKENS.TieredCacheService, useClass: TieredCacheService },\n ],\n})\nexport class CacheModule {}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { t as StratalEnv } from "./env-ug22bJj7.mjs";
|
|
2
2
|
import { r as LoggerService } from "./index-BUt92sAE.mjs";
|
|
3
|
-
|
|
4
3
|
//#region src/cache/services/cache.service.d.ts
|
|
5
4
|
/**
|
|
6
5
|
* Cache Service
|
|
@@ -90,26 +89,42 @@ declare class CacheService {
|
|
|
90
89
|
getWithMetadata<Metadata = unknown>(key: string, typeOrOptions: 'arrayBuffer' | KVNamespaceGetOptions<'arrayBuffer'>): Promise<KVNamespaceGetWithMetadataResult<ArrayBuffer, Metadata>>;
|
|
91
90
|
getWithMetadata<Metadata = unknown>(key: string, typeOrOptions: 'stream' | KVNamespaceGetOptions<'stream'>): Promise<KVNamespaceGetWithMetadataResult<ReadableStream, Metadata>>;
|
|
92
91
|
/**
|
|
93
|
-
* Store a value in cache
|
|
92
|
+
* Store a value in cache, **without blocking the request**.
|
|
93
|
+
*
|
|
94
|
+
* KV writes commit to KV's central store — reads are edge-cached, but writes
|
|
95
|
+
* are not, so a `put` can add hundreds of ms on the critical path. A cache is
|
|
96
|
+
* best-effort and eventually consistent (a `get` can serve a stale value for
|
|
97
|
+
* ~60s after a write regardless), so the write is scheduled via the Workers
|
|
98
|
+
* runtime's `waitUntil` and settles after the response is sent. The returned
|
|
99
|
+
* promise resolves immediately — the write is enqueued, not awaited — and
|
|
100
|
+
* failures are logged, never thrown (no caller is left to catch them).
|
|
101
|
+
*
|
|
102
|
+
* Do **not** route durability-critical writes (locks, idempotency claims,
|
|
103
|
+
* system-of-record state) through the cache; use the raw KV binding for
|
|
104
|
+
* those. Invalidations stay durable — see {@link delete}.
|
|
94
105
|
*
|
|
95
106
|
* @param key - Cache key
|
|
96
107
|
* @param value - Value to store (string, ArrayBuffer, ArrayBufferView, or ReadableStream)
|
|
97
108
|
* @param options - Put options (expiration, expirationTtl, metadata)
|
|
98
|
-
* @throws {CacheError} If operation fails
|
|
99
109
|
*
|
|
100
110
|
* @example
|
|
101
111
|
* ```typescript
|
|
102
|
-
*
|
|
103
|
-
* await cache.put('key', 'value')
|
|
104
|
-
*
|
|
105
|
-
* // With TTL
|
|
106
|
-
* await cache.put('key', 'value', { expirationTtl: 3600 })
|
|
107
|
-
*
|
|
108
|
-
* // With metadata
|
|
109
|
-
* await cache.put('key', 'value', { metadata: { created: Date.now() } })
|
|
112
|
+
* cache.put('key', 'value', { expirationTtl: 3600 })
|
|
110
113
|
* ```
|
|
111
114
|
*/
|
|
112
115
|
put(key: string, value: string | ArrayBuffer | ArrayBufferView | ReadableStream, options?: KVNamespacePutOptions): Promise<void>;
|
|
116
|
+
/**
|
|
117
|
+
* Store a value and **await the durable write**, throwing on failure — the
|
|
118
|
+
* pre-deferral `put` semantics. Use for the rare durability-critical write
|
|
119
|
+
* that goes through the cache and must not be lost or silently fail: a queue
|
|
120
|
+
* idempotency claim that gates message redelivery, or a failed-job record.
|
|
121
|
+
* Everything best-effort should use the deferred {@link put}.
|
|
122
|
+
*
|
|
123
|
+
* @throws {CacheError} If the write fails.
|
|
124
|
+
*/
|
|
125
|
+
putDurable(key: string, value: string | ArrayBuffer | ArrayBufferView | ReadableStream, options?: KVNamespacePutOptions): Promise<void>;
|
|
126
|
+
/** Single KV write path with unified error handling: logs, then throws. */
|
|
127
|
+
private writeKv;
|
|
113
128
|
/**
|
|
114
129
|
* Delete a value from cache
|
|
115
130
|
*
|
|
@@ -143,4 +158,4 @@ declare class CacheService {
|
|
|
143
158
|
}
|
|
144
159
|
//#endregion
|
|
145
160
|
export { CacheService as t };
|
|
146
|
-
//# sourceMappingURL=cache.service-
|
|
161
|
+
//# sourceMappingURL=cache.service-Dg0Hj0gp.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.service-Dg0Hj0gp.d.mts","names":[],"sources":["../src/cache/services/cache.service.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAkDa;mBAIyC;mBACI;UAJhD;EAER,YACoD,KAAK,YACD,QAAQ;;MAM5D,aAAa;;;;;;;EAUjB,YAAY,IAAI,cAAc;;;;;;;;;EAc9B,QAAQ,eAAe;;;;;;;;;EAkBjB,IAAI,aAAa,yBAAyB,gCAAgC;EAC1E,IAAI,yBAAyB,aAAa,wBAAwB,gCAAgC,QAAQ;EAC1G,IAAI,aAAa,+BAA+B,uCAAuC,QAAQ;EAC/F,IAAI,aAAa,0BAA0B,kCAAkC,QAAQ;;;;;;;;;EAkCrF,gBAAgB,oBACpB,aACA,yBAAyB,gCACxB,QAAQ,yCAAyC;EAC9C,gBAAgB,yBAAyB,oBAC7C,aACA,wBAAwB,gCACvB,QAAQ,iCAAiC,eAAe;EACrD,gBAAgB,oBACpB,aACA,+BAA+B,uCAC9B,QAAQ,iCAAiC,aAAa;EACnD,gBAAgB,oBACpB,aACA,0BAA0B,kCACzB,QAAQ,iCAAiC,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;EAuD5D,IACE,aACA,gBAAgB,cAAc,kBAAkB,gBAChD,UAAU,wBACT;;;;;;;;;;EAoBH,WACE,aACA,gBAAgB,cAAc,kBAAkB,gBAChD,UAAU,wBACT;;UAKW;;;;;;;EAqBR,OAAO,cAAc;;;;;;;;;;;;;;;;;;;;;;;EAkCrB,KAAK,oBACT,UAAU,yBACT,QAAQ,sBAAsB"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { Hn as CommandInternals, Un as CommandResult, Vn as CommandInput } from "./index-B2WtEagR.mjs";
|
|
3
2
|
//#region src/quarry/constants.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* Symbol key for storing internal mutable state on Command instances.
|
|
@@ -92,6 +91,10 @@ declare abstract class Command {
|
|
|
92
91
|
* Get an array input. Throws CommandError if present but not an array.
|
|
93
92
|
*/
|
|
94
93
|
array(name: string): string[];
|
|
94
|
+
/** Record a line in the output buffer and stream it to stdout (if any). */
|
|
95
|
+
private emit;
|
|
96
|
+
/** Record a line in the errors buffer and stream it to stderr (if any). */
|
|
97
|
+
private emitError;
|
|
95
98
|
/** Write an informational message to output */
|
|
96
99
|
info(message: string): void;
|
|
97
100
|
/** Write a success message to output */
|
|
@@ -118,4 +121,4 @@ declare abstract class Command {
|
|
|
118
121
|
}
|
|
119
122
|
//#endregion
|
|
120
123
|
export { Command as t };
|
|
121
|
-
//# sourceMappingURL=command-
|
|
124
|
+
//# sourceMappingURL=command-BXMccV_9.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-BXMccV_9.d.mts","names":[],"sources":["../src/quarry/constants.ts","../src/quarry/command.ts"],"mappings":";;;;;;cAIa;;;;;;;;;;;;;;;;;;;;;;uBCqBS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;SAiCb;;SAEA;;SAEA;GAEN,oBAAoB;EAErB;;;;;WAeS,0BAA0B;;;;EAOnC,MAAM,GAAG,eAAe;;;;EAOxB,OAAO;;;;EAcP,QAAQ;;;;EAcR,OAAO;;;;EAeP,MAAM;;UAsBE;;UAMA;;EAMR,KAAK;;EAKL,QAAQ;;EAKR,KAAK;;EAKL,MAAM;;EAKN,KAAK;;EAKL;;EAKA,QAAQ;;EAKR,MAAM,mBAAmB;;EAiBzB,KAAK,iBAAiB;;;;;EAWhB,KAAK,cAAc,QAAQ,eAAe,QAAQ"}
|
|
@@ -33,6 +33,19 @@ var CommandError = class extends Error {
|
|
|
33
33
|
}
|
|
34
34
|
};
|
|
35
35
|
//#endregion
|
|
36
|
+
//#region src/quarry/output-stream.ts
|
|
37
|
+
function streamOf(name) {
|
|
38
|
+
return globalThis.process?.[name];
|
|
39
|
+
}
|
|
40
|
+
/** Write a line to stdout when a real stream exists; a no-op otherwise. */
|
|
41
|
+
function writeStdout(line) {
|
|
42
|
+
streamOf("stdout")?.write?.(line);
|
|
43
|
+
}
|
|
44
|
+
/** Write a line to stderr when a real stream exists; a no-op otherwise. */
|
|
45
|
+
function writeStderr(line) {
|
|
46
|
+
streamOf("stderr")?.write?.(line);
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
36
49
|
//#region src/quarry/command.ts
|
|
37
50
|
/**
|
|
38
51
|
* Abstract base class for Quarry commands.
|
|
@@ -144,33 +157,43 @@ var Command = class {
|
|
|
144
157
|
if (!Array.isArray(value)) throw new CommandError(`Input "${name}" expected an array, got ${typeof value}`);
|
|
145
158
|
return value;
|
|
146
159
|
}
|
|
160
|
+
/** Record a line in the output buffer and stream it to stdout (if any). */
|
|
161
|
+
emit(line) {
|
|
162
|
+
this[COMMAND_INTERNALS].output.push(line);
|
|
163
|
+
writeStdout(`${line}\n`);
|
|
164
|
+
}
|
|
165
|
+
/** Record a line in the errors buffer and stream it to stderr (if any). */
|
|
166
|
+
emitError(line) {
|
|
167
|
+
this[COMMAND_INTERNALS].errors.push(line);
|
|
168
|
+
writeStderr(`${line}\n`);
|
|
169
|
+
}
|
|
147
170
|
/** Write an informational message to output */
|
|
148
171
|
info(message) {
|
|
149
|
-
this
|
|
172
|
+
this.emit(cyan(message));
|
|
150
173
|
}
|
|
151
174
|
/** Write a success message to output */
|
|
152
175
|
success(message) {
|
|
153
|
-
this
|
|
176
|
+
this.emit(`${green(bold("✔"))} ${green(message)}`);
|
|
154
177
|
}
|
|
155
178
|
/** Write a warning message to output */
|
|
156
179
|
warn(message) {
|
|
157
|
-
this
|
|
180
|
+
this.emit(`${yellow(bold("⚠"))} ${yellow(message)}`);
|
|
158
181
|
}
|
|
159
182
|
/** Write an error message to errors */
|
|
160
183
|
error(message) {
|
|
161
|
-
this
|
|
184
|
+
this.emitError(red(message));
|
|
162
185
|
}
|
|
163
186
|
/** Write a plain line to output */
|
|
164
187
|
line(message) {
|
|
165
|
-
this
|
|
188
|
+
this.emit(message ?? "");
|
|
166
189
|
}
|
|
167
190
|
/** Write an empty line to output */
|
|
168
191
|
newLine() {
|
|
169
|
-
this
|
|
192
|
+
this.emit("");
|
|
170
193
|
}
|
|
171
194
|
/** Write a comment-style line to output */
|
|
172
195
|
comment(message) {
|
|
173
|
-
this
|
|
196
|
+
this.emit(dim(`// ${message}`));
|
|
174
197
|
}
|
|
175
198
|
/** Write a formatted table to output */
|
|
176
199
|
table(headers, rows) {
|
|
@@ -179,13 +202,13 @@ var Command = class {
|
|
|
179
202
|
return Math.max(h.length, maxRow);
|
|
180
203
|
});
|
|
181
204
|
const formatRow = (cells) => cells.map((cell, i) => cell.padEnd(colWidths[i])).join(" ");
|
|
182
|
-
this
|
|
183
|
-
this
|
|
184
|
-
for (const row of rows) this
|
|
205
|
+
this.emit(bold(formatRow(headers)));
|
|
206
|
+
this.emit(dim(colWidths.map((w) => "-".repeat(w)).join(" ")));
|
|
207
|
+
for (const row of rows) this.emit(formatRow(row));
|
|
185
208
|
}
|
|
186
209
|
/** Write an error message and set exit code */
|
|
187
210
|
fail(message, exitCode = 1) {
|
|
188
|
-
this
|
|
211
|
+
this.emitError(`${red(bold("✖"))} ${red(message)}`);
|
|
189
212
|
this[COMMAND_INTERNALS].exitCode = exitCode;
|
|
190
213
|
}
|
|
191
214
|
/**
|
|
@@ -202,6 +225,6 @@ var Command = class {
|
|
|
202
225
|
}
|
|
203
226
|
};
|
|
204
227
|
//#endregion
|
|
205
|
-
export {
|
|
228
|
+
export { bold as a, dimWhite as c, yellow as d, COMMAND_INTERNALS as i, green as l, writeStderr as n, cyan as o, CommandError as r, dim as s, Command as t, red as u };
|
|
206
229
|
|
|
207
|
-
//# sourceMappingURL=command-
|
|
230
|
+
//# sourceMappingURL=command-Bb820B6W.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-Bb820B6W.mjs","names":[],"sources":["../src/quarry/colors.ts","../src/quarry/constants.ts","../src/quarry/errors/command.error.ts","../src/quarry/output-stream.ts","../src/quarry/command.ts"],"sourcesContent":["/** Minimal ANSI color helpers that respect the `NO_COLOR` convention. */\nconst isEnabled = () => typeof process !== 'undefined' ? !process.env.NO_COLOR : true\n\n/** Create an ANSI formatter that wraps text with the given open/close SGR codes. */\nconst code = (open: number, close: number) => (s: string) =>\n isEnabled() ? `\\x1b[${open}m${s}\\x1b[${close}m` : s\n\nexport const bold = code(1, 22)\nexport const dim = code(2, 22)\nexport const cyan = code(36, 39)\nexport const green = code(32, 39)\nexport const red = code(31, 39)\nexport const yellow = code(33, 39)\nexport const dimWhite = (s: string) =>\n isEnabled() ? `\\x1b[2;37m${s}\\x1b[22;39m` : s\n","/**\n * Symbol key for storing internal mutable state on Command instances.\n * Keeps internal state hidden from user-facing autocomplete.\n */\nexport const COMMAND_INTERNALS = Symbol.for('stratal:command:internals')\n","/**\n * User-facing command error with a plain English message.\n *\n * Quarry catches this in `call()` and puts the message into `CommandResult.errors`.\n * Does NOT extend `ApplicationError` (which requires i18n keys + error codes).\n * Not routed through ExceptionHandler.\n */\nexport class CommandError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'CommandError'\n }\n}\n","// Live output for Quarry commands.\n//\n// The Quarry CLI runs in Node, where `process.stdout`/`process.stderr` are real\n// streams and command output should appear on the terminal immediately — long-\n// running commands (e.g. `inertia:dev`, which resolves only when the dev server\n// stops) must show progress live, not buffered until completion.\n//\n// The SAME command code can also run inside a worker via `quarry.call()` (e.g.\n// MCP-over-HTTP, an admin route, a scheduled command), where there is no\n// `process.stdout`. There, these are no-ops and the buffered output that\n// `CommandResult` carries back to the caller is the only channel. `process` and\n// its streams are resolved at call time (not module load) so the correct target\n// is used per runtime and test spies on `process.stdout.write` are honoured.\n\ninterface WritableLike { write?: (chunk: string) => void }\n\nfunction streamOf(name: 'stdout' | 'stderr'): WritableLike | undefined {\n return (globalThis as { process?: Record<'stdout' | 'stderr', WritableLike | undefined> }).process?.[name]\n}\n\n/** Write a line to stdout when a real stream exists; a no-op otherwise. */\nexport function writeStdout(line: string): void {\n streamOf('stdout')?.write?.(line)\n}\n\n/** Write a line to stderr when a real stream exists; a no-op otherwise. */\nexport function writeStderr(line: string): void {\n streamOf('stderr')?.write?.(line)\n}\n","import { bold, cyan, dim, green, red, yellow } from './colors'\nimport { COMMAND_INTERNALS } from './constants'\nimport { CommandError } from './errors/command.error'\nimport { writeStderr, writeStdout } from './output-stream'\nimport type { CommandInput, CommandInternals, CommandResult } from './types'\n\n/**\n * Abstract base class for Quarry commands.\n *\n * Subclasses define a static `command` signature string and implement `handle()`.\n *\n * @example\n * ```typescript\n * export class GreetCommand extends Command {\n * static command = 'greet {name : The name to greet} {--loud}'\n * static description = 'Greet someone'\n *\n * async handle(): Promise<void> {\n * const name = this.string('name')\n * const loud = this.boolean('loud')\n * this.info(loud ? `HELLO, ${name.toUpperCase()}!` : `Hello, ${name}!`)\n * }\n * }\n * ```\n */\nexport abstract class Command {\n /**\n * Laravel-style command signature string.\n *\n * **Command names:**\n * - `'greet'` — flat command (`quarry greet`)\n * - `'task add'` — subcommand hierarchy via spaces (`quarry task add`)\n * - `'task:add'` — namespaced flat command via colons (`quarry task:add`)\n *\n * **Arguments:**\n * - `{name}` — required argument\n * - `{name?}` — optional argument\n * - `{name=default}` — argument with default value\n * - `{name*}` — array/variadic argument\n * - `{name : description}` — argument with description\n *\n * **Options:**\n * - `{--flag}` — boolean flag\n * - `{--name=}` — option that accepts a value\n * - `{--name=default}` — option with default value\n * - `{--name=*}` — array option (multiple values)\n * - `{--A|name}` — option with single-char alias\n * - `{--name= : description}` — option with description\n *\n * @example\n * ```typescript\n * // Namespaced flat command: `quarry users:create ...`\n * static command = 'users:create {email : The user email} {--A|admin} {--R|role= : Assign a role}'\n *\n * // Subcommand hierarchy: `quarry users create ...`\n * static command = 'users create {email : The user email} {--A|admin} {--R|role= : Assign a role}'\n * ```\n */\n static command: string\n /** Human-readable description */\n static description?: string\n /** Alternative command names */\n static aliases?: string[];\n\n [COMMAND_INTERNALS]: CommandInternals\n\n constructor() {\n this[COMMAND_INTERNALS] = {\n inputs: {},\n output: [],\n errors: [],\n exitCode: 0,\n quarry: null,\n }\n }\n\n /**\n * Implement this method with the command's logic.\n * Return a number to set the exit code, or void for exit code 0.\n */\n // eslint-disable-next-line @typescript-eslint/no-invalid-void-type\n abstract handle(): number | void | Promise<number | void>\n\n // ── Input Accessors ──────────────────────────────────────────────\n\n /**\n * Get an input value with generic type.\n */\n input<T>(name: string): T {\n return this[COMMAND_INTERNALS].inputs[name] as T\n }\n\n /**\n * Get a string input. Throws CommandError if present but not a string.\n */\n string(name: string): string {\n const value = this[COMMAND_INTERNALS].inputs[name]\n if (value === undefined || value === null) {\n return ''\n }\n if (typeof value !== 'string') {\n throw new CommandError(`Input \"${name}\" expected a string, got ${typeof value}`)\n }\n return value\n }\n\n /**\n * Get a boolean input. Throws CommandError if present but not a boolean.\n */\n boolean(name: string): boolean {\n const value = this[COMMAND_INTERNALS].inputs[name]\n if (value === undefined || value === null) {\n return false\n }\n if (typeof value !== 'boolean') {\n throw new CommandError(`Input \"${name}\" expected a boolean, got ${typeof value}`)\n }\n return value\n }\n\n /**\n * Get a number input. Coerces strings to numbers. Throws CommandError on NaN.\n */\n number(name: string): number {\n const value = this[COMMAND_INTERNALS].inputs[name]\n if (value === undefined || value === null) {\n return 0\n }\n const num = typeof value === 'string' ? Number(value) : value\n if (typeof num !== 'number' || Number.isNaN(num)) {\n throw new CommandError(`Input \"${name}\" expected a number, got ${typeof value}`)\n }\n return num\n }\n\n /**\n * Get an array input. Throws CommandError if present but not an array.\n */\n array(name: string): string[] {\n const value = this[COMMAND_INTERNALS].inputs[name]\n if (value === undefined || value === null) {\n return []\n }\n if (!Array.isArray(value)) {\n throw new CommandError(`Input \"${name}\" expected an array, got ${typeof value}`)\n }\n return value as string[]\n }\n\n // ── Output Helpers ───────────────────────────────────────────────\n //\n // Output is written to the terminal immediately (streamed), not buffered and\n // flushed after handle() returns — long-running commands (e.g. inertia:dev,\n // which resolves only when the dev server stops) must show progress live. The\n // buffer is still recorded so getCommandResult()/tests and this.call() can\n // capture what was produced, and so commands run inside a worker (via\n // quarry.call(), where there is no process.stdout) still return their output.\n // writeStdout/writeStderr are no-ops when no real stream exists.\n\n /** Record a line in the output buffer and stream it to stdout (if any). */\n private emit(line: string): void {\n this[COMMAND_INTERNALS].output.push(line)\n writeStdout(`${line}\\n`)\n }\n\n /** Record a line in the errors buffer and stream it to stderr (if any). */\n private emitError(line: string): void {\n this[COMMAND_INTERNALS].errors.push(line)\n writeStderr(`${line}\\n`)\n }\n\n /** Write an informational message to output */\n info(message: string): void {\n this.emit(cyan(message))\n }\n\n /** Write a success message to output */\n success(message: string): void {\n this.emit(`${green(bold('✔'))} ${green(message)}`)\n }\n\n /** Write a warning message to output */\n warn(message: string): void {\n this.emit(`${yellow(bold('⚠'))} ${yellow(message)}`)\n }\n\n /** Write an error message to errors */\n error(message: string): void {\n this.emitError(red(message))\n }\n\n /** Write a plain line to output */\n line(message?: string): void {\n this.emit(message ?? '')\n }\n\n /** Write an empty line to output */\n newLine(): void {\n this.emit('')\n }\n\n /** Write a comment-style line to output */\n comment(message: string): void {\n this.emit(dim(`// ${message}`))\n }\n\n /** Write a formatted table to output */\n table(headers: string[], rows: string[][]): void {\n const colWidths = headers.map((h, i) => {\n const maxRow = rows.reduce((max, row) => Math.max(max, (row[i] ?? '').length), 0)\n return Math.max(h.length, maxRow)\n })\n\n const formatRow = (cells: string[]) =>\n cells.map((cell, i) => cell.padEnd(colWidths[i])).join(' ')\n\n this.emit(bold(formatRow(headers)))\n this.emit(dim(colWidths.map((w) => '-'.repeat(w)).join(' ')))\n for (const row of rows) {\n this.emit(formatRow(row))\n }\n }\n\n /** Write an error message and set exit code */\n fail(message: string, exitCode = 1): void {\n this.emitError(`${red(bold('✖'))} ${red(message)}`)\n this[COMMAND_INTERNALS].exitCode = exitCode\n }\n\n // ── Command Calling ──────────────────────────────────────────────\n\n /**\n * Call another command from within this command.\n * Delegates to Quarry.call() via internal reference.\n */\n async call(name: string, input?: CommandInput): Promise<CommandResult> {\n const internals = this[COMMAND_INTERNALS]\n if (!internals.quarry) {\n throw new CommandError('Cannot call commands: Quarry reference not set')\n }\n const result = await internals.quarry.call(name, input)\n\n // Forward child output/errors into parent (like Clipanion context switches)\n internals.output.push(...result.output)\n internals.errors.push(...result.errors)\n\n return result\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
+
import { n as named, t as describe } from "./metadata-Ba8Fxs1F.mjs";
|
|
3
|
+
import { _default, array, coerce, int, iso, maximum, nonnegative, number, object, optional, positive, record, string, unknown, uuid } from "zod/mini";
|
|
4
|
+
//#region src/router/schemas/common.schemas.ts
|
|
5
|
+
var common_schemas_exports = /* @__PURE__ */ __exportAll({
|
|
6
|
+
commonErrorSchemas: () => commonErrorSchemas,
|
|
7
|
+
errorResponseSchema: () => errorResponseSchema,
|
|
8
|
+
paginatedResponseSchema: () => paginatedResponseSchema,
|
|
9
|
+
paginationQuerySchema: () => paginationQuerySchema,
|
|
10
|
+
successMessageSchema: () => successMessageSchema,
|
|
11
|
+
uuidParamSchema: () => uuidParamSchema,
|
|
12
|
+
validationErrorResponseSchema: () => validationErrorResponseSchema
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* Common OpenAPI Schemas
|
|
16
|
+
*
|
|
17
|
+
* Reusable schema definitions for common API patterns (error responses,
|
|
18
|
+
* pagination, common parameters). Consumed by the lazy OpenAPI generator —
|
|
19
|
+
* never imported on the routing hot path — so these never reach a running
|
|
20
|
+
* worker unless the OpenAPI document is actually requested.
|
|
21
|
+
*
|
|
22
|
+
* Metadata (descriptions, component ids) is attached via the shared registry
|
|
23
|
+
* helpers; the generator reads `id` to emit shared `components.schemas` entries
|
|
24
|
+
* and `description` for field docs.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Generic error response schema. Matches the ErrorResponse shape produced by
|
|
28
|
+
* ExceptionHandler. Used for all error responses (4xx, 5xx).
|
|
29
|
+
*/
|
|
30
|
+
const errorResponseSchema = named(object({
|
|
31
|
+
message: describe(string(), "Human-readable error message"),
|
|
32
|
+
timestamp: describe(iso.datetime(), "ISO timestamp when error occurred"),
|
|
33
|
+
stack: describe(optional(string()), "Stack trace (development only)")
|
|
34
|
+
}), "ErrorResponse", "Error response");
|
|
35
|
+
/** Validation error response schema (400 Bad Request). Shares the ErrorResponse shape. */
|
|
36
|
+
const validationErrorResponseSchema = errorResponseSchema;
|
|
37
|
+
/** Pagination query parameters for list endpoints. */
|
|
38
|
+
const paginationQuerySchema = named(object({
|
|
39
|
+
page: describe(_default(coerce.number().check(int(), positive()), 1), "Page number (1-indexed)"),
|
|
40
|
+
limit: describe(_default(coerce.number().check(int(), positive(), maximum(100)), 20), "Items per page (max 100)")
|
|
41
|
+
}), "PaginationQuery", "Pagination query parameters");
|
|
42
|
+
/** Generic wrapper for paginated list responses. */
|
|
43
|
+
const paginatedResponseSchema = (itemSchema) => object({
|
|
44
|
+
data: describe(array(itemSchema), "Array of items for current page"),
|
|
45
|
+
pagination: object({
|
|
46
|
+
page: describe(number().check(int(), positive()), "Current page number"),
|
|
47
|
+
limit: describe(number().check(int(), positive()), "Items per page"),
|
|
48
|
+
total: describe(number().check(int(), nonnegative()), "Total number of items"),
|
|
49
|
+
totalPages: describe(number().check(int(), nonnegative()), "Total number of pages")
|
|
50
|
+
})
|
|
51
|
+
});
|
|
52
|
+
/** UUID parameter schema for `:id` parameters in RESTful routes. */
|
|
53
|
+
const uuidParamSchema = named(object({ id: describe(uuid(), "Resource UUID") }), "UUIDParam", "UUID path parameter");
|
|
54
|
+
/** Success message response for operations that don't return data (e.g. DELETE). */
|
|
55
|
+
const successMessageSchema = named(object({
|
|
56
|
+
message: describe(string(), "Success message"),
|
|
57
|
+
data: describe(optional(record(string(), unknown())), "Optional additional data")
|
|
58
|
+
}), "SuccessMessage", "Success message");
|
|
59
|
+
/** Pre-configured error response schemas keyed by standard HTTP status. */
|
|
60
|
+
const commonErrorSchemas = {
|
|
61
|
+
400: {
|
|
62
|
+
schema: validationErrorResponseSchema,
|
|
63
|
+
description: "Validation error"
|
|
64
|
+
},
|
|
65
|
+
401: {
|
|
66
|
+
schema: errorResponseSchema,
|
|
67
|
+
description: "Unauthorized"
|
|
68
|
+
},
|
|
69
|
+
403: {
|
|
70
|
+
schema: errorResponseSchema,
|
|
71
|
+
description: "Forbidden"
|
|
72
|
+
},
|
|
73
|
+
404: {
|
|
74
|
+
schema: errorResponseSchema,
|
|
75
|
+
description: "Not found"
|
|
76
|
+
},
|
|
77
|
+
409: {
|
|
78
|
+
schema: errorResponseSchema,
|
|
79
|
+
description: "Conflict"
|
|
80
|
+
},
|
|
81
|
+
500: {
|
|
82
|
+
schema: errorResponseSchema,
|
|
83
|
+
description: "Internal server error"
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
//#endregion
|
|
87
|
+
export { paginationQuerySchema as a, validationErrorResponseSchema as c, paginatedResponseSchema as i, common_schemas_exports as n, successMessageSchema as o, errorResponseSchema as r, uuidParamSchema as s, commonErrorSchemas as t };
|
|
88
|
+
|
|
89
|
+
//# sourceMappingURL=common.schemas-BigQCwYB.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"common.schemas-BigQCwYB.mjs","names":[],"sources":["../src/router/schemas/common.schemas.ts"],"sourcesContent":["import {\n array,\n coerce,\n int,\n iso,\n maximum,\n nonnegative,\n number,\n object,\n optional,\n positive,\n record,\n string,\n unknown,\n uuid,\n _default,\n} from 'zod/mini'\nimport { describe, named } from '../../i18n/validation/metadata'\nimport type { ZodType } from '../../i18n/validation/zod'\n\n/**\n * Common OpenAPI Schemas\n *\n * Reusable schema definitions for common API patterns (error responses,\n * pagination, common parameters). Consumed by the lazy OpenAPI generator —\n * never imported on the routing hot path — so these never reach a running\n * worker unless the OpenAPI document is actually requested.\n *\n * Metadata (descriptions, component ids) is attached via the shared registry\n * helpers; the generator reads `id` to emit shared `components.schemas` entries\n * and `description` for field docs.\n */\n\n/**\n * Generic error response schema. Matches the ErrorResponse shape produced by\n * ExceptionHandler. Used for all error responses (4xx, 5xx).\n */\nexport const errorResponseSchema = named(\n object({\n message: describe(string(), 'Human-readable error message'),\n timestamp: describe(iso.datetime(), 'ISO timestamp when error occurred'),\n stack: describe(optional(string()), 'Stack trace (development only)'),\n }),\n 'ErrorResponse',\n 'Error response',\n)\n\n/** Validation error response schema (400 Bad Request). Shares the ErrorResponse shape. */\nexport const validationErrorResponseSchema = errorResponseSchema\n\n/** Pagination query parameters for list endpoints. */\nexport const paginationQuerySchema = named(\n object({\n page: describe(_default(coerce.number().check(int(), positive()), 1), 'Page number (1-indexed)'),\n limit: describe(_default(coerce.number().check(int(), positive(), maximum(100)), 20), 'Items per page (max 100)'),\n }),\n 'PaginationQuery',\n 'Pagination query parameters',\n)\n\n/** Generic wrapper for paginated list responses. */\nexport const paginatedResponseSchema = <T extends ZodType>(itemSchema: T) =>\n object({\n data: describe(array(itemSchema), 'Array of items for current page'),\n pagination: object({\n page: describe(number().check(int(), positive()), 'Current page number'),\n limit: describe(number().check(int(), positive()), 'Items per page'),\n total: describe(number().check(int(), nonnegative()), 'Total number of items'),\n totalPages: describe(number().check(int(), nonnegative()), 'Total number of pages'),\n }),\n })\n\n/** UUID parameter schema for `:id` parameters in RESTful routes. */\nexport const uuidParamSchema = named(\n object({ id: describe(uuid(), 'Resource UUID') }),\n 'UUIDParam',\n 'UUID path parameter',\n)\n\n/** Success message response for operations that don't return data (e.g. DELETE). */\nexport const successMessageSchema = named(\n object({\n message: describe(string(), 'Success message'),\n data: describe(optional(record(string(), unknown())), 'Optional additional data'),\n }),\n 'SuccessMessage',\n 'Success message',\n)\n\n/** Pre-configured error response schemas keyed by standard HTTP status. */\nexport const commonErrorSchemas = {\n 400: { schema: validationErrorResponseSchema, description: 'Validation error' },\n 401: { schema: errorResponseSchema, description: 'Unauthorized' },\n 403: { schema: errorResponseSchema, description: 'Forbidden' },\n 404: { schema: errorResponseSchema, description: 'Not found' },\n 409: { schema: errorResponseSchema, description: 'Conflict' },\n 500: { schema: errorResponseSchema, description: 'Internal server error' },\n} as const\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,MAAa,sBAAsB,MACjC,OAAO;CACL,SAAS,SAAS,OAAO,GAAG,8BAA8B;CAC1D,WAAW,SAAS,IAAI,SAAS,GAAG,mCAAmC;CACvE,OAAO,SAAS,SAAS,OAAO,CAAC,GAAG,gCAAgC;AACtE,CAAC,GACD,iBACA,gBACF;;AAGA,MAAa,gCAAgC;;AAG7C,MAAa,wBAAwB,MACnC,OAAO;CACL,MAAM,SAAS,SAAS,OAAO,OAAO,CAAC,CAAC,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,yBAAyB;CAC/F,OAAO,SAAS,SAAS,OAAO,OAAO,CAAC,CAAC,MAAM,IAAI,GAAG,SAAS,GAAG,QAAQ,GAAG,CAAC,GAAG,EAAE,GAAG,0BAA0B;AAClH,CAAC,GACD,mBACA,6BACF;;AAGA,MAAa,2BAA8C,eACzD,OAAO;CACL,MAAM,SAAS,MAAM,UAAU,GAAG,iCAAiC;CACnE,YAAY,OAAO;EACjB,MAAM,SAAS,OAAO,CAAC,CAAC,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,qBAAqB;EACvE,OAAO,SAAS,OAAO,CAAC,CAAC,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,gBAAgB;EACnE,OAAO,SAAS,OAAO,CAAC,CAAC,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,uBAAuB;EAC7E,YAAY,SAAS,OAAO,CAAC,CAAC,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,uBAAuB;CACpF,CAAC;AACH,CAAC;;AAGH,MAAa,kBAAkB,MAC7B,OAAO,EAAE,IAAI,SAAS,KAAK,GAAG,eAAe,EAAE,CAAC,GAChD,aACA,qBACF;;AAGA,MAAa,uBAAuB,MAClC,OAAO;CACL,SAAS,SAAS,OAAO,GAAG,iBAAiB;CAC7C,MAAM,SAAS,SAAS,OAAO,OAAO,GAAG,QAAQ,CAAC,CAAC,GAAG,0BAA0B;AAClF,CAAC,GACD,kBACA,iBACF;;AAGA,MAAa,qBAAqB;CAChC,KAAK;EAAE,QAAQ;EAA+B,aAAa;CAAmB;CAC9E,KAAK;EAAE,QAAQ;EAAqB,aAAa;CAAe;CAChE,KAAK;EAAE,QAAQ;EAAqB,aAAa;CAAY;CAC7D,KAAK;EAAE,QAAQ;EAAqB,aAAa;CAAY;CAC7D,KAAK;EAAE,QAAQ;EAAqB,aAAa;CAAW;CAC5D,KAAK;EAAE,QAAQ;EAAqB,aAAa;CAAwB;AAC3E"}
|