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
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import { c as Request, l as Singleton, p as inject, r as DI_TOKENS } from "./di-
|
|
2
|
-
import { n as __decorateParam, t as __decorate } from "./decorate-
|
|
3
|
-
import { d as HttpException } from "./errors-
|
|
4
|
-
import { n as Module } from "./module.decorator-
|
|
5
|
-
import "./
|
|
6
|
-
import { t as
|
|
7
|
-
import { r as
|
|
8
|
-
import { t as withZodI18n } from "./validation-
|
|
9
|
-
import {
|
|
10
|
-
import { n as Delete, o as Put, r as Get } from "./http-method.decorator-BljM8BDj.mjs";
|
|
1
|
+
import { c as Request, l as Singleton, p as inject, r as DI_TOKENS } from "./di-DQ3xbjgf.mjs";
|
|
2
|
+
import { n as __decorateParam, t as __decorate } from "./decorate-C79yt3Wt.mjs";
|
|
3
|
+
import { d as HttpException } from "./errors-CrOXI_y5.mjs";
|
|
4
|
+
import { n as Module } from "./module.decorator-CYbrOlX9.mjs";
|
|
5
|
+
import { t as StorageError } from "./storage.error-Ao38Qdjx.mjs";
|
|
6
|
+
import { t as Controller } from "./controller.decorator-BB0JcwMg.mjs";
|
|
7
|
+
import { n as Delete, o as Put, r as Get } from "./http-method.decorator-BDi9LTWj.mjs";
|
|
8
|
+
import { t as withZodI18n } from "./validation-CZ_B0aXY.mjs";
|
|
9
|
+
import { _default, date, enum as enum$1, int, maximum, minLength, minimum, number, object, optional, string, url } from "zod/mini";
|
|
11
10
|
//#region src/storage/storage.tokens.ts
|
|
12
11
|
/**
|
|
13
12
|
* Dependency injection tokens for the Storage module
|
|
@@ -68,7 +67,7 @@ let StorageManagerService = class StorageManagerService {
|
|
|
68
67
|
* @returns Storage provider instance
|
|
69
68
|
*/
|
|
70
69
|
async createProvider(config) {
|
|
71
|
-
const { R2StorageProvider } = await import("./r2-storage.provider-
|
|
70
|
+
const { R2StorageProvider } = await import("./r2-storage.provider-wU_6api4.mjs").then((n) => n.n);
|
|
72
71
|
const bucket = this.env[config.binding];
|
|
73
72
|
if (!bucket) throw new StorageError(`R2 binding "${config.binding}" was not found in the environment`);
|
|
74
73
|
return new R2StorageProvider(config, bucket, this.env, this.options.route);
|
|
@@ -163,6 +162,73 @@ let StorageService = class StorageService {
|
|
|
163
162
|
return provider.exists(fullPath);
|
|
164
163
|
}
|
|
165
164
|
/**
|
|
165
|
+
* Delete many files in one call
|
|
166
|
+
*
|
|
167
|
+
* Providers delete in bulk — R2 takes 1000 keys per round trip — so this costs a fraction of
|
|
168
|
+
* the subrequests that looping over `delete` would. Paths that do not exist are ignored.
|
|
169
|
+
*
|
|
170
|
+
* @param relativePaths - Relative paths within the disk
|
|
171
|
+
* @param disk - Optional disk name (uses default if not provided)
|
|
172
|
+
*/
|
|
173
|
+
async deleteMany(relativePaths, disk) {
|
|
174
|
+
if (relativePaths.length === 0) return;
|
|
175
|
+
const diskName = this.resolveDisk(disk);
|
|
176
|
+
await (await this.storageManager.getProvider(diskName)).deleteMany(relativePaths.map((path) => this.buildFullPath(path, diskName)));
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Read an object's metadata without transferring its body
|
|
180
|
+
*
|
|
181
|
+
* Use this to learn a file's size or content type when the contents are not needed — asking
|
|
182
|
+
* `head` for a hundred objects costs a hundred metadata reads, where `download` would move a
|
|
183
|
+
* hundred bodies.
|
|
184
|
+
*
|
|
185
|
+
* @param relativePath - Relative path within the disk
|
|
186
|
+
* @param disk - Optional disk name (uses default if not provided)
|
|
187
|
+
* @returns Metadata, or null when nothing exists at that path
|
|
188
|
+
*/
|
|
189
|
+
async head(relativePath, disk) {
|
|
190
|
+
const diskName = this.resolveDisk(disk);
|
|
191
|
+
const provider = await this.storageManager.getProvider(diskName);
|
|
192
|
+
const fullPath = this.buildFullPath(relativePath, diskName);
|
|
193
|
+
const result = await provider.head(fullPath);
|
|
194
|
+
if (result === null) return null;
|
|
195
|
+
return {
|
|
196
|
+
...result,
|
|
197
|
+
path: this.toRelativePath(result.path, diskName)
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* List objects on a disk, one page at a time
|
|
202
|
+
*
|
|
203
|
+
* **The result is a page, not the whole set.** Providers cap a single listing (R2 at 1000
|
|
204
|
+
* objects), so anything that aggregates across a prefix — summing sizes, counting files,
|
|
205
|
+
* copying a tree — must loop while `truncated` is true, passing `cursor` back in. Reading only
|
|
206
|
+
* the first page yields an answer that is quietly too small, with nothing having failed.
|
|
207
|
+
*
|
|
208
|
+
* Returned paths are relative to the disk, so they can be passed straight to `download`,
|
|
209
|
+
* `head` or `delete`.
|
|
210
|
+
*
|
|
211
|
+
* @param options - Prefix (relative to the disk), cursor and page limit
|
|
212
|
+
* @param disk - Optional disk name (uses default if not provided)
|
|
213
|
+
* @returns One page of matching objects
|
|
214
|
+
*/
|
|
215
|
+
async list(options = {}, disk) {
|
|
216
|
+
const diskName = this.resolveDisk(disk);
|
|
217
|
+
const provider = await this.storageManager.getProvider(diskName);
|
|
218
|
+
const fullPrefix = this.buildFullPath(options.prefix ?? "", diskName);
|
|
219
|
+
const page = await provider.list({
|
|
220
|
+
...options,
|
|
221
|
+
prefix: fullPrefix
|
|
222
|
+
});
|
|
223
|
+
return {
|
|
224
|
+
...page,
|
|
225
|
+
objects: page.objects.map((object) => ({
|
|
226
|
+
...object,
|
|
227
|
+
path: this.toRelativePath(object.path, diskName)
|
|
228
|
+
}))
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
166
232
|
* Generate a presigned download URL
|
|
167
233
|
* @param relativePath - Relative path within the disk
|
|
168
234
|
* @param expiresIn - Optional expiry time in seconds (uses default if not provided)
|
|
@@ -229,6 +295,23 @@ let StorageService = class StorageService {
|
|
|
229
295
|
return `${root}/${relativePath}`.replace(/\/+/g, "/").replace(/^\//, "");
|
|
230
296
|
}
|
|
231
297
|
/**
|
|
298
|
+
* Strip the disk root from a provider path
|
|
299
|
+
*
|
|
300
|
+
* The inverse of `buildFullPath`, so what `head`/`list` hand back is what `download`/`delete`
|
|
301
|
+
* accept. A path that does not start with the root is returned unchanged rather than mangled —
|
|
302
|
+
* that can only happen if a provider returned something outside the disk, and silently trimming
|
|
303
|
+
* a prefix off it would disguise the fault as a valid path.
|
|
304
|
+
*
|
|
305
|
+
* @param fullPath - Path as the provider reported it
|
|
306
|
+
* @param diskName - Name of the disk
|
|
307
|
+
* @returns Path relative to the disk root
|
|
308
|
+
*/
|
|
309
|
+
toRelativePath(fullPath, diskName) {
|
|
310
|
+
const root = this.buildFullPath("", diskName);
|
|
311
|
+
if (root === "" || !fullPath.startsWith(root)) return fullPath;
|
|
312
|
+
return fullPath.slice(root.length).replace(/^\//, "");
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
232
315
|
* Substitute template variables in path
|
|
233
316
|
* Override this method in subclasses to add custom substitutions
|
|
234
317
|
*
|
|
@@ -364,7 +447,38 @@ var FileNotFoundError = class extends HttpException {
|
|
|
364
447
|
};
|
|
365
448
|
//#endregion
|
|
366
449
|
//#region src/storage/controllers/storage.controller.ts
|
|
367
|
-
const diskParam =
|
|
450
|
+
const diskParam = object({ disk: string() });
|
|
451
|
+
/**
|
|
452
|
+
* Content types rendered inline by {@link StorageController.download}.
|
|
453
|
+
*
|
|
454
|
+
* Stored objects are served from the same origin as the application, so any response the browser
|
|
455
|
+
* treats as a document runs in that origin's security context. Echoing an object's stored content
|
|
456
|
+
* type back with `Content-Disposition: inline` therefore turns a bucket into a script-injection
|
|
457
|
+
* vector: an object stored as `text/html` — or `image/svg+xml`, which is scriptable and is
|
|
458
|
+
* deliberately absent below — executes against whatever session fetched it.
|
|
459
|
+
*
|
|
460
|
+
* The allowlist is deliberately the safe set rather than a blocklist of dangerous types, so a
|
|
461
|
+
* format nobody anticipated downloads instead of rendering.
|
|
462
|
+
*/
|
|
463
|
+
const DEFAULT_INLINE_CONTENT_TYPES = /* @__PURE__ */ new Set([
|
|
464
|
+
"application/pdf",
|
|
465
|
+
"image/png",
|
|
466
|
+
"image/jpeg",
|
|
467
|
+
"image/gif",
|
|
468
|
+
"image/webp"
|
|
469
|
+
]);
|
|
470
|
+
/**
|
|
471
|
+
* Strips characters that cannot legally appear in the quoted `filename="…"` parameter.
|
|
472
|
+
*
|
|
473
|
+
* Beyond the quote and backslash that would break out of the quoting, RFC 7230 §3.2 limits a header
|
|
474
|
+
* field value to visible ASCII, space, horizontal tab and obs-text — so every C0 control and DEL is
|
|
475
|
+
* removed too, not just CR and LF. A key holding one of those would otherwise emit a malformed
|
|
476
|
+
* header that a strict parser or intermediary may reject or interpret differently. Bytes 0x80–0xFF
|
|
477
|
+
* are obs-text and stay, so non-ASCII filenames survive intact.
|
|
478
|
+
*/
|
|
479
|
+
function contentDispositionFilename(path) {
|
|
480
|
+
return (path.split("/").pop() ?? "download").replace(/["\\\x00-\x1f\x7f]/g, "") || "download";
|
|
481
|
+
}
|
|
368
482
|
let StorageController = class StorageController {
|
|
369
483
|
storage;
|
|
370
484
|
constructor(storage) {
|
|
@@ -376,10 +490,13 @@ let StorageController = class StorageController {
|
|
|
376
490
|
const result = await this.storage.download(path, disk);
|
|
377
491
|
const stream = result.toStream();
|
|
378
492
|
if (!stream) throw new FileNotFoundError(path);
|
|
493
|
+
const renderInline = DEFAULT_INLINE_CONTENT_TYPES.has(result.contentType.split(";")[0].trim().toLowerCase());
|
|
379
494
|
return new Response(stream, { headers: {
|
|
380
|
-
"Content-Type": result.contentType,
|
|
495
|
+
"Content-Type": renderInline ? result.contentType : "application/octet-stream",
|
|
381
496
|
"Content-Length": String(result.size),
|
|
382
|
-
"Content-Disposition": "inline"
|
|
497
|
+
"Content-Disposition": renderInline ? "inline" : `attachment; filename="${contentDispositionFilename(path)}"`,
|
|
498
|
+
"X-Content-Type-Options": "nosniff",
|
|
499
|
+
"Content-Security-Policy": "sandbox; default-src 'none'"
|
|
383
500
|
} });
|
|
384
501
|
}
|
|
385
502
|
async upload(ctx) {
|
|
@@ -422,53 +539,54 @@ StorageController = __decorate([Controller("/storage", { hideFromDocs: true }),
|
|
|
422
539
|
* Hono stores wildcard params under the key matching the path pattern.
|
|
423
540
|
*/
|
|
424
541
|
function extractWildcardPath(ctx) {
|
|
425
|
-
|
|
542
|
+
const encoded = new URL(ctx.c.req.url).pathname.split("/").slice(3).join("/");
|
|
543
|
+
try {
|
|
544
|
+
return decodeURIComponent(encoded);
|
|
545
|
+
} catch {
|
|
546
|
+
return encoded;
|
|
547
|
+
}
|
|
426
548
|
}
|
|
427
549
|
//#endregion
|
|
428
550
|
//#region src/storage/contracts/delete-file.input.ts
|
|
429
|
-
const deleteFileInputSchema =
|
|
430
|
-
path:
|
|
431
|
-
disk:
|
|
551
|
+
const deleteFileInputSchema = object({
|
|
552
|
+
path: string().check(minLength(1, withZodI18n("zodI18n.errors.custom.filePathRequired"))),
|
|
553
|
+
disk: optional(string())
|
|
432
554
|
});
|
|
433
555
|
//#endregion
|
|
434
556
|
//#region src/storage/contracts/file-exists.input.ts
|
|
435
|
-
const fileExistsInputSchema =
|
|
436
|
-
path:
|
|
437
|
-
disk:
|
|
557
|
+
const fileExistsInputSchema = object({
|
|
558
|
+
path: string().check(minLength(1, withZodI18n("zodI18n.errors.custom.filePathRequired"))),
|
|
559
|
+
disk: optional(string())
|
|
438
560
|
});
|
|
439
561
|
//#endregion
|
|
440
562
|
//#region src/storage/contracts/get-presigned-url.input.ts
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
563
|
+
const httpMethod = enum$1([
|
|
564
|
+
"GET",
|
|
565
|
+
"PUT",
|
|
566
|
+
"DELETE",
|
|
567
|
+
"HEAD"
|
|
568
|
+
]);
|
|
569
|
+
const getPresignedUrlInputSchema = object({
|
|
570
|
+
path: string().check(minLength(1, withZodI18n("zodI18n.errors.custom.filePathRequired"))),
|
|
571
|
+
method: _default(httpMethod, "GET"),
|
|
572
|
+
expiresIn: optional(number().check(int(), minimum(1), maximum(604800))),
|
|
573
|
+
disk: optional(string())
|
|
451
574
|
});
|
|
452
|
-
const presignedUrlResultSchema =
|
|
453
|
-
url:
|
|
454
|
-
expiresIn:
|
|
455
|
-
expiresAt:
|
|
456
|
-
method:
|
|
457
|
-
"GET",
|
|
458
|
-
"PUT",
|
|
459
|
-
"DELETE",
|
|
460
|
-
"HEAD"
|
|
461
|
-
])
|
|
575
|
+
const presignedUrlResultSchema = object({
|
|
576
|
+
url: url(),
|
|
577
|
+
expiresIn: number(),
|
|
578
|
+
expiresAt: date(),
|
|
579
|
+
method: httpMethod
|
|
462
580
|
});
|
|
463
581
|
//#endregion
|
|
464
582
|
//#region src/storage/contracts/upload-file.input.ts
|
|
465
|
-
const uploadResultSchema =
|
|
466
|
-
path:
|
|
467
|
-
disk:
|
|
468
|
-
fullPath:
|
|
469
|
-
size:
|
|
470
|
-
mimeType:
|
|
471
|
-
uploadedAt:
|
|
583
|
+
const uploadResultSchema = object({
|
|
584
|
+
path: string(),
|
|
585
|
+
disk: string(),
|
|
586
|
+
fullPath: string(),
|
|
587
|
+
size: number(),
|
|
588
|
+
mimeType: string(),
|
|
589
|
+
uploadedAt: date()
|
|
472
590
|
});
|
|
473
591
|
//#endregion
|
|
474
592
|
//#region src/storage/errors/file-too-large.error.ts
|
|
@@ -493,4 +611,4 @@ var InvalidFileTypeError = class extends HttpException {
|
|
|
493
611
|
//#endregion
|
|
494
612
|
export { presignedUrlResultSchema as a, StorageController as c, StorageService as d, StorageManagerService as f, getPresignedUrlInputSchema as i, FileNotFoundError as l, FileTooLargeError as n, fileExistsInputSchema as o, STORAGE_TOKENS as p, uploadResultSchema as r, deleteFileInputSchema as s, InvalidFileTypeError as t, StorageModule as u };
|
|
495
613
|
|
|
496
|
-
//# sourceMappingURL=storage-
|
|
614
|
+
//# sourceMappingURL=storage-DBWVYMnn.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-DBWVYMnn.mjs","names":["enum_"],"sources":["../src/storage/storage.tokens.ts","../src/storage/services/storage-manager.service.ts","../src/storage/services/storage.service.ts","../src/storage/storage.module.ts","../src/storage/errors/file-not-found.error.ts","../src/storage/controllers/storage.controller.ts","../src/storage/contracts/delete-file.input.ts","../src/storage/contracts/file-exists.input.ts","../src/storage/contracts/get-presigned-url.input.ts","../src/storage/contracts/upload-file.input.ts","../src/storage/errors/file-too-large.error.ts","../src/storage/errors/invalid-file-type.error.ts"],"sourcesContent":["/**\n * Dependency injection tokens for the Storage module\n * Using Symbol-based tokens to avoid magic strings\n */\nexport const STORAGE_TOKENS = {\n Options: Symbol.for('stratal:storage:options'),\n StorageService: Symbol.for('stratal:storage:service'),\n StorageManager: Symbol.for('stratal:storage:manager'),\n} as const\n","import { inject } from '../../di'\nimport { Singleton } from '../../di/decorators'\nimport { DI_TOKENS } from '../../di/tokens'\nimport { type StratalEnv } from '../../env'\nimport { StorageError } from '../storage.error'\nimport type { IStorageProvider } from '../providers/storage-provider.interface'\nimport { STORAGE_TOKENS } from '../storage.tokens'\nimport type { StorageConfig, StorageEntry } from '../types'\n\n/**\n * Storage Manager Service\n * Manages multiple storage providers (one per disk)\n * Handles lazy initialization and caching of R2 providers\n */\n@Singleton(STORAGE_TOKENS.StorageManager)\nexport class StorageManagerService {\n private readonly providers = new Map<string, IStorageProvider>()\n private readonly creationPromises = new Map<string, Promise<IStorageProvider>>()\n private readonly diskConfigs = new Map<string, StorageEntry>()\n\n constructor(\n @inject(STORAGE_TOKENS.Options)\n private readonly options: StorageConfig,\n @inject(DI_TOKENS.CloudflareEnv)\n private readonly env: StratalEnv\n ) {\n this.initializeDiskConfigs()\n }\n\n /**\n * Initialize disk configurations from options\n */\n private initializeDiskConfigs(): void {\n for (const entry of this.options.storage) {\n this.diskConfigs.set(entry.disk, entry)\n }\n }\n\n /**\n * Get provider for a specific disk\n * Lazily initializes provider on first access\n * @param diskName - Name of the disk\n * @returns Storage provider instance\n */\n async getProvider(diskName: string): Promise<IStorageProvider> {\n // Return cached provider if exists\n const cached = this.providers.get(diskName)\n if (cached) {\n return cached\n }\n\n // Return in-flight creation promise to deduplicate concurrent calls\n const inflight = this.creationPromises.get(diskName)\n if (inflight) {\n return inflight\n }\n\n // Get disk configuration\n const diskConfig = this.diskConfigs.get(diskName)\n if (!diskConfig) {\n throw new StorageError(`Disk \"${diskName}\" is not configured`)\n }\n\n // Create provider and deduplicate concurrent calls\n const promise = this.createProvider(diskConfig).then((provider) => {\n this.providers.set(diskName, provider)\n this.creationPromises.delete(diskName)\n return provider\n }).catch((error: unknown) => {\n this.creationPromises.delete(diskName)\n throw error\n })\n\n this.creationPromises.set(diskName, promise)\n\n return promise\n }\n\n /**\n * Create an R2 provider instance\n * Dynamically imports R2StorageProvider to support code splitting\n * @param config - Storage entry configuration\n * @returns Storage provider instance\n */\n private async createProvider(config: StorageEntry): Promise<IStorageProvider> {\n const { R2StorageProvider } = await import('../providers/r2-storage.provider')\n const bucket = this.env[config.binding as keyof StratalEnv] as unknown as R2Bucket | undefined\n if (!bucket) {\n throw new StorageError(`R2 binding \"${config.binding}\" was not found in the environment`)\n }\n return new R2StorageProvider(config, bucket, this.env, this.options.route)\n }\n\n /**\n * Get disk configuration\n * @param diskName - Name of the disk\n * @returns Storage entry configuration\n */\n getDiskConfig(diskName: string): StorageEntry {\n const config = this.diskConfigs.get(diskName)\n if (!config) {\n throw new StorageError(`Disk \"${diskName}\" is not configured`)\n }\n return config\n }\n\n /**\n * Check if a disk exists\n * @param diskName - Name of the disk\n * @returns True if disk exists, false otherwise\n */\n hasDisk(diskName: string): boolean {\n return this.diskConfigs.has(diskName)\n }\n\n /**\n * Get all available disk names\n * @returns Array of disk names\n */\n getAvailableDisks(): string[] {\n return Array.from(this.diskConfigs.keys())\n }\n}\n","import { inject } from '../../di'\nimport { Request } from '../../di/decorators'\nimport type {\n DownloadResult,\n HeadResult,\n ListOptions,\n ListResult,\n PresignedUrlResult,\n UploadOptions,\n UploadResult,\n} from '../contracts'\nimport { StorageError } from '../storage.error'\nimport type { StreamingBlobPayloadInputTypes } from '../providers/storage-provider.interface'\nimport { STORAGE_TOKENS } from '../storage.tokens'\nimport type { StorageConfig } from '../types'\nimport { type StorageManagerService } from './storage-manager.service'\n\n/**\n * Storage Service\n *\n * Main facade for storage operations.\n * Request-scoped for proper isolation.\n *\n * @example\n * ```typescript\n * @inject(STORAGE_TOKENS.StorageService)\n * private readonly storage: StorageService\n *\n * await this.storage.upload(file, 'documents/report.pdf')\n * ```\n */\n@Request(STORAGE_TOKENS.StorageService)\nexport class StorageService {\n constructor(\n @inject(STORAGE_TOKENS.StorageManager)\n protected readonly storageManager: StorageManagerService,\n @inject(STORAGE_TOKENS.Options)\n protected readonly options: StorageConfig\n ) { }\n\n /**\n * Upload content to storage\n * @param body - Content to upload (stream, buffer, or string)\n * @param relativePath - Relative path within the disk\n * @param options - Upload options including size and mime type\n * @param disk - Optional disk name (uses default if not provided)\n * @returns Upload result with metadata\n */\n async upload(\n body: StreamingBlobPayloadInputTypes,\n relativePath: string,\n options: UploadOptions,\n disk?: string\n ): Promise<UploadResult> {\n const diskName = this.resolveDisk(disk)\n const provider = await this.storageManager.getProvider(diskName)\n const fullPath = this.buildFullPath(relativePath, diskName)\n\n return provider.upload(body, fullPath, options)\n }\n\n /**\n * Download a file from storage\n * @param relativePath - Relative path within the disk\n * @param disk - Optional disk name (uses default if not provided)\n * @returns Download result with stream and metadata\n */\n async download(relativePath: string, disk?: string): Promise<DownloadResult> {\n const diskName = this.resolveDisk(disk)\n const provider = await this.storageManager.getProvider(diskName)\n const fullPath = this.buildFullPath(relativePath, diskName)\n\n return provider.download(fullPath)\n }\n\n /**\n * Delete a file from storage\n * @param relativePath - Relative path within the disk\n * @param disk - Optional disk name (uses default if not provided)\n */\n async delete(relativePath: string, disk?: string): Promise<void> {\n const diskName = this.resolveDisk(disk)\n const provider = await this.storageManager.getProvider(diskName)\n const fullPath = this.buildFullPath(relativePath, diskName)\n\n await provider.delete(fullPath)\n }\n\n /**\n * Check if a file exists in storage\n * @param relativePath - Relative path within the disk\n * @param disk - Optional disk name (uses default if not provided)\n * @returns True if file exists, false otherwise\n */\n async exists(relativePath: string, disk?: string): Promise<boolean> {\n const diskName = this.resolveDisk(disk)\n const provider = await this.storageManager.getProvider(diskName)\n const fullPath = this.buildFullPath(relativePath, diskName)\n\n return provider.exists(fullPath)\n }\n\n /**\n * Delete many files in one call\n *\n * Providers delete in bulk — R2 takes 1000 keys per round trip — so this costs a fraction of\n * the subrequests that looping over `delete` would. Paths that do not exist are ignored.\n *\n * @param relativePaths - Relative paths within the disk\n * @param disk - Optional disk name (uses default if not provided)\n */\n async deleteMany(relativePaths: string[], disk?: string): Promise<void> {\n if (relativePaths.length === 0) {\n return\n }\n\n const diskName = this.resolveDisk(disk)\n const provider = await this.storageManager.getProvider(diskName)\n\n await provider.deleteMany(relativePaths.map((path) => this.buildFullPath(path, diskName)))\n }\n\n /**\n * Read an object's metadata without transferring its body\n *\n * Use this to learn a file's size or content type when the contents are not needed — asking\n * `head` for a hundred objects costs a hundred metadata reads, where `download` would move a\n * hundred bodies.\n *\n * @param relativePath - Relative path within the disk\n * @param disk - Optional disk name (uses default if not provided)\n * @returns Metadata, or null when nothing exists at that path\n */\n async head(relativePath: string, disk?: string): Promise<HeadResult | null> {\n const diskName = this.resolveDisk(disk)\n const provider = await this.storageManager.getProvider(diskName)\n const fullPath = this.buildFullPath(relativePath, diskName)\n\n const result = await provider.head(fullPath)\n if (result === null) {\n return null\n }\n\n return { ...result, path: this.toRelativePath(result.path, diskName) }\n }\n\n /**\n * List objects on a disk, one page at a time\n *\n * **The result is a page, not the whole set.** Providers cap a single listing (R2 at 1000\n * objects), so anything that aggregates across a prefix — summing sizes, counting files,\n * copying a tree — must loop while `truncated` is true, passing `cursor` back in. Reading only\n * the first page yields an answer that is quietly too small, with nothing having failed.\n *\n * Returned paths are relative to the disk, so they can be passed straight to `download`,\n * `head` or `delete`.\n *\n * @param options - Prefix (relative to the disk), cursor and page limit\n * @param disk - Optional disk name (uses default if not provided)\n * @returns One page of matching objects\n */\n async list(options: ListOptions = {}, disk?: string): Promise<ListResult> {\n const diskName = this.resolveDisk(disk)\n const provider = await this.storageManager.getProvider(diskName)\n // An empty relative prefix still has to become the disk root, or a listing would escape the\n // disk and return every other disk's objects sharing the bucket.\n const fullPrefix = this.buildFullPath(options.prefix ?? '', diskName)\n\n const page = await provider.list({ ...options, prefix: fullPrefix })\n\n return {\n ...page,\n objects: page.objects.map((object) => ({\n ...object,\n path: this.toRelativePath(object.path, diskName),\n })),\n }\n }\n\n /**\n * Generate a presigned download URL\n * @param relativePath - Relative path within the disk\n * @param expiresIn - Optional expiry time in seconds (uses default if not provided)\n * @param disk - Optional disk name (uses default if not provided)\n * @returns Presigned URL result\n */\n async getPresignedDownloadUrl(\n relativePath: string,\n expiresIn?: number,\n disk?: string\n ): Promise<PresignedUrlResult> {\n return this.getPresignedUrl(relativePath, 'GET', expiresIn, disk)\n }\n\n /**\n * Generate a presigned upload URL\n * @param relativePath - Relative path within the disk\n * @param expiresIn - Optional expiry time in seconds (uses default if not provided)\n * @param disk - Optional disk name (uses default if not provided)\n * @returns Presigned URL result\n */\n async getPresignedUploadUrl(\n relativePath: string,\n expiresIn?: number,\n disk?: string\n ): Promise<PresignedUrlResult> {\n return this.getPresignedUrl(relativePath, 'PUT', expiresIn, disk)\n }\n\n /**\n * Generate a presigned delete URL\n * @param relativePath - Relative path within the disk\n * @param expiresIn - Optional expiry time in seconds (uses default if not provided)\n * @param disk - Optional disk name (uses default if not provided)\n * @returns Presigned URL result\n */\n async getPresignedDeleteUrl(\n relativePath: string,\n expiresIn?: number,\n disk?: string\n ): Promise<PresignedUrlResult> {\n return this.getPresignedUrl(relativePath, 'DELETE', expiresIn, disk)\n }\n\n /**\n * Generate a presigned URL for any method\n * @param relativePath - Relative path within the disk\n * @param method - HTTP method (GET, PUT, DELETE, HEAD)\n * @param expiresIn - Optional expiry time in seconds (uses default if not provided)\n * @param disk - Optional disk name (uses default if not provided)\n * @returns Presigned URL result\n */\n protected async getPresignedUrl(\n relativePath: string,\n method: 'GET' | 'PUT' | 'DELETE' | 'HEAD',\n expiresIn?: number,\n disk?: string\n ): Promise<PresignedUrlResult> {\n const diskName = this.resolveDisk(disk)\n const provider = await this.storageManager.getProvider(diskName)\n const fullPath = this.buildFullPath(relativePath, diskName)\n const validatedExpiresIn = this.validateExpiresIn(expiresIn)\n\n return provider.getPresignedUrl(fullPath, method, validatedExpiresIn)\n }\n\n /**\n * Resolve disk name (use default if not provided)\n * @param disk - Optional disk name\n * @returns Resolved disk name\n */\n protected resolveDisk(disk?: string): string {\n const diskName = disk ?? this.options.defaultStorageDisk\n\n if (!this.storageManager.hasDisk(diskName)) {\n throw new StorageError(`Disk \"${diskName}\" is not configured`)\n }\n\n return diskName\n }\n\n /**\n * Build full path with disk root and path template substitution\n * @param relativePath - Relative path within the disk\n * @param diskName - Name of the disk\n * @returns Full path including disk root\n */\n protected buildFullPath(relativePath: string, diskName: string): string {\n const diskConfig = this.storageManager.getDiskConfig(diskName)\n let root = diskConfig.root || ''\n\n // Substitute template variables\n root = this.substituteTemplateVariables(root)\n\n // Combine root and relative path\n const fullPath = `${root}/${relativePath}`.replace(/\\/+/g, '/').replace(/^\\//, '')\n\n return fullPath\n }\n\n /**\n * Strip the disk root from a provider path\n *\n * The inverse of `buildFullPath`, so what `head`/`list` hand back is what `download`/`delete`\n * accept. A path that does not start with the root is returned unchanged rather than mangled —\n * that can only happen if a provider returned something outside the disk, and silently trimming\n * a prefix off it would disguise the fault as a valid path.\n *\n * @param fullPath - Path as the provider reported it\n * @param diskName - Name of the disk\n * @returns Path relative to the disk root\n */\n protected toRelativePath(fullPath: string, diskName: string): string {\n const root = this.buildFullPath('', diskName)\n if (root === '' || !fullPath.startsWith(root)) {\n return fullPath\n }\n\n return fullPath.slice(root.length).replace(/^\\//, '')\n }\n\n /**\n * Substitute template variables in path\n * Override this method in subclasses to add custom substitutions\n *\n * @param path - Path with template variables\n * @returns Path with substituted variables\n */\n protected substituteTemplateVariables(path: string): string {\n let result = path\n\n // Substitute {date}, {year}, {month}\n const now = new Date()\n result = result.replace(/{date}/g, now.toISOString().split('T')[0])\n result = result.replace(/{year}/g, now.getFullYear().toString())\n result = result.replace(/{month}/g, (now.getMonth() + 1).toString().padStart(2, '0'))\n\n return result\n }\n\n /**\n * Validate expiry time for presigned URLs\n * @param expiresIn - Optional expiry time in seconds\n * @returns Validated expiry time\n */\n protected validateExpiresIn(expiresIn?: number): number {\n const presignedUrlConfig = this.options.presignedUrl\n const validatedExpiresIn = expiresIn ?? presignedUrlConfig.defaultExpiry\n\n const minExpiry = 1\n const maxExpiry = presignedUrlConfig.maxExpiry\n\n if (validatedExpiresIn < minExpiry || validatedExpiresIn > maxExpiry) {\n throw new StorageError(`Presigned URL expiry ${validatedExpiresIn}s is out of range (${minExpiry}–${maxExpiry}s)`)\n }\n\n return validatedExpiresIn\n }\n\n /**\n * Get all available disk names\n * @returns Array of disk names\n */\n getAvailableDisks(): string[] {\n return this.storageManager.getAvailableDisks()\n }\n\n /**\n * Chunked upload for streaming data without known size\n * Uses multipart upload under the hood - handles retries and large files\n *\n * Use this method when:\n * - Content-Length is unknown or unreliable\n * - Uploading from streams that can't be rewound\n * - Need automatic retry handling for transient failures\n *\n * @param body - Content to upload (stream or buffer)\n * @param relativePath - Relative path within the disk\n * @param options - Upload options (mimeType required, size optional)\n * @param disk - Optional disk name (uses default if not provided)\n * @returns Upload result with metadata\n */\n async chunkedUpload(\n body: StreamingBlobPayloadInputTypes,\n relativePath: string,\n options: Omit<UploadOptions, 'size'> & { size?: number },\n disk?: string\n ): Promise<UploadResult> {\n const diskName = this.resolveDisk(disk)\n const provider = await this.storageManager.getProvider(diskName)\n const fullPath = this.buildFullPath(relativePath, diskName)\n\n return provider.chunkedUpload(body, fullPath, options)\n }\n}\n","/**\n * Storage Module\n * Provides file storage capabilities using Cloudflare R2\n * Supports multiple disk configurations with dynamic path templates\n */\n\nimport { Module } from '../module'\nimport type { AsyncModuleOptions, DynamicModule } from '../module/types'\nimport { StorageManagerService } from './services/storage-manager.service'\nimport { StorageService } from './services/storage.service'\nimport { STORAGE_TOKENS } from './storage.tokens'\nimport type { StorageConfig } from './types'\n\n/**\n * Storage module options\n * Same as StorageConfig from types.ts\n */\nexport type StorageModuleOptions = StorageConfig\n\n@Module({\n providers: [\n { provide: STORAGE_TOKENS.StorageManager, useClass: StorageManagerService },\n { provide: STORAGE_TOKENS.StorageService, useClass: StorageService },\n ],\n})\nexport class StorageModule {\n /**\n * Configure StorageModule with static options\n *\n * @example\n * ```typescript\n * StorageModule.forRoot({\n * storage: [{ disk: 'uploads', binding: 'MY_BUCKET', root: 'uploads' }],\n * defaultStorageDisk: 'uploads',\n * presignedUrl: { defaultExpiry: 3600, maxExpiry: 86400 }\n * })\n * ```\n */\n static forRoot(options: StorageModuleOptions): DynamicModule {\n return {\n module: StorageModule,\n providers: [\n { provide: STORAGE_TOKENS.Options, useValue: options },\n ],\n }\n }\n\n /**\n * Configure StorageModule with async factory\n *\n * Use when configuration depends on other services.\n *\n * @example\n * ```typescript\n * StorageModule.forRootAsync({\n * inject: [storageConfig.KEY],\n * useFactory: (storage) => ({\n * storage: storage.storage,\n * defaultStorageDisk: storage.defaultStorageDisk,\n * presignedUrl: storage.presignedUrl\n * })\n * })\n * ```\n */\n static forRootAsync(options: AsyncModuleOptions<StorageModuleOptions>): DynamicModule {\n return {\n module: StorageModule,\n providers: [\n {\n provide: STORAGE_TOKENS.Options,\n useFactory: options.useFactory,\n inject: options.inject,\n },\n ],\n }\n }\n}\n","import { HttpException } from '../../errors'\n\nexport class FileNotFoundError extends HttpException {\n constructor(path?: string) {\n super(404, path ? `File not found: \"${path}\"` : 'File not found')\n }\n}\n","import { object, string } from 'zod/mini'\nimport { inject } from '../../di'\nimport { Controller } from '../../router/decorators/controller.decorator'\nimport { Delete, Get, Put } from '../../router/decorators/http-method.decorator'\nimport { type RouterContext } from '../../router/router-context'\nimport { FileNotFoundError } from '../errors/file-not-found.error'\nimport type { StorageService } from '../services/storage.service'\nimport { STORAGE_TOKENS } from '../storage.tokens'\n\nconst diskParam = object({\n disk: string(),\n})\n\n/**\n * Content types rendered inline by {@link StorageController.download}.\n *\n * Stored objects are served from the same origin as the application, so any response the browser\n * treats as a document runs in that origin's security context. Echoing an object's stored content\n * type back with `Content-Disposition: inline` therefore turns a bucket into a script-injection\n * vector: an object stored as `text/html` — or `image/svg+xml`, which is scriptable and is\n * deliberately absent below — executes against whatever session fetched it.\n *\n * The allowlist is deliberately the safe set rather than a blocklist of dangerous types, so a\n * format nobody anticipated downloads instead of rendering.\n */\nconst DEFAULT_INLINE_CONTENT_TYPES: ReadonlySet<string> = new Set([\n 'application/pdf',\n 'image/png',\n 'image/jpeg',\n 'image/gif',\n 'image/webp',\n])\n\n/**\n * Strips characters that cannot legally appear in the quoted `filename=\"…\"` parameter.\n *\n * Beyond the quote and backslash that would break out of the quoting, RFC 7230 §3.2 limits a header\n * field value to visible ASCII, space, horizontal tab and obs-text — so every C0 control and DEL is\n * removed too, not just CR and LF. A key holding one of those would otherwise emit a malformed\n * header that a strict parser or intermediary may reject or interpret differently. Bytes 0x80–0xFF\n * are obs-text and stay, so non-ASCII filenames survive intact.\n */\nfunction contentDispositionFilename(path: string): string {\n const base = path.split('/').pop() ?? 'download'\n // eslint-disable-next-line no-control-regex -- the control range is the point: these bytes are illegal in a header value.\n return base.replace(/[\"\\\\\\x00-\\x1f\\x7f]/g, '') || 'download'\n}\n\n/**\n * Storage Controller\n *\n * Auto-registered controller that proxies R2 operations behind signed URLs.\n * Signature verification is applied via VerifySignatureMiddleware on the module's\n * configureRoutes() method.\n *\n * Routes:\n * - GET /storage/:disk/* → download file\n * - PUT /storage/:disk/* → upload file\n * - DELETE /storage/:disk/* → delete file\n */\n@Controller('/storage', { hideFromDocs: true })\nexport class StorageController {\n constructor(\n @inject(STORAGE_TOKENS.StorageService)\n private readonly storage: StorageService\n ) {}\n\n @Get('/:disk/*', { hideFromDocs: true, params: diskParam })\n async download(ctx: RouterContext): Promise<Response> {\n const disk = ctx.param('disk')\n const path = extractWildcardPath(ctx)\n const result = await this.storage.download(path, disk)\n\n const stream = result.toStream()\n if (!stream) {\n throw new FileNotFoundError(path)\n }\n\n const renderInline = DEFAULT_INLINE_CONTENT_TYPES.has(\n result.contentType.split(';')[0].trim().toLowerCase()\n )\n\n return new Response(stream, {\n headers: {\n // A type outside the allowlist is handed back as an opaque blob, so the browser has no\n // reason to parse it as markup even when the stored content type claims otherwise.\n 'Content-Type': renderInline ? result.contentType : 'application/octet-stream',\n 'Content-Length': String(result.size),\n 'Content-Disposition': renderInline\n ? 'inline'\n : `attachment; filename=\"${contentDispositionFilename(path)}\"`,\n // Prevents the browser sniffing past the content type above and rendering a disguised\n // payload anyway.\n 'X-Content-Type-Options': 'nosniff',\n // Defence in depth for the inline case: a viewer or decoder handling a malformed file gets\n // an opaque origin with no scripting, so it cannot reach the session it was served to.\n 'Content-Security-Policy': \"sandbox; default-src 'none'\",\n },\n })\n }\n\n @Put('/:disk/*', { hideFromDocs: true, params: diskParam })\n async upload(ctx: RouterContext): Promise<Response> {\n const disk = ctx.param('disk')\n const path = extractWildcardPath(ctx)\n\n const body = ctx.c.req.raw.body\n const contentType = ctx.header('content-type') ?? 'application/octet-stream'\n const contentLength = ctx.header('content-length')\n\n await this.storage.upload(body, path, {\n mimeType: contentType,\n size: contentLength ? parseInt(contentLength, 10) : 0,\n }, disk)\n\n return ctx.json({ path, disk }, 200)\n }\n\n @Delete('/:disk/*', { hideFromDocs: true, params: diskParam })\n async destroy(ctx: RouterContext): Promise<Response> {\n const disk = ctx.param('disk')\n const path = extractWildcardPath(ctx)\n\n await this.storage.delete(path, disk)\n\n return ctx.c.body(null, 204)\n }\n}\n\n/**\n * Extract the wildcard path from the Hono context.\n * Hono stores wildcard params under the key matching the path pattern.\n */\nfunction extractWildcardPath(ctx: RouterContext): string {\n // Hono exposes wildcard capture as the raw path after the matched prefix\n const url = new URL(ctx.c.req.url)\n const fullPath = url.pathname\n // Remove /storage/:disk/ prefix to get the file path\n const parts = fullPath.split('/')\n // ['', 'storage', 'disk', ...rest]\n const encoded = parts.slice(3).join('/')\n\n // `url.pathname` is percent-encoded, but object keys are stored raw — so a key holding a space,\n // a non-ASCII character, `#` or `?` would be looked up as `%20`/`%C3%A9`/… and miss. Keys are\n // opaque strings that may themselves contain `/`, so the remainder is decoded whole rather than\n // per segment. A malformed escape cannot be decoded and is left as-is; the lookup then fails as\n // a normal missing object instead of throwing a URIError out of the handler.\n try {\n return decodeURIComponent(encoded)\n } catch {\n return encoded\n }\n}\n","import { minLength, object, optional, string } from 'zod/mini'\nimport type { infer as Infer } from 'zod/mini'\nimport { withZodI18n } from '../../i18n/validation'\n\nexport const deleteFileInputSchema = object({\n path: string().check(minLength(1, withZodI18n('zodI18n.errors.custom.filePathRequired'))),\n disk: optional(string()),\n})\n\nexport type DeleteFileInput = Infer<typeof deleteFileInputSchema>\n","import { minLength, object, optional, string } from 'zod/mini'\nimport type { infer as Infer } from 'zod/mini'\nimport { withZodI18n } from '../../i18n/validation'\n\nexport const fileExistsInputSchema = object({\n path: string().check(minLength(1, withZodI18n('zodI18n.errors.custom.filePathRequired'))),\n disk: optional(string()),\n})\n\nexport type FileExistsInput = Infer<typeof fileExistsInputSchema>\n","import { date, enum as enum_, int, maximum, minimum, minLength, number, object, optional, string, url, _default } from 'zod/mini'\nimport type { infer as Infer } from 'zod/mini'\nimport { withZodI18n } from '../../i18n/validation'\n\nconst httpMethod = enum_(['GET', 'PUT', 'DELETE', 'HEAD'])\n\nexport const getPresignedUrlInputSchema = object({\n path: string().check(minLength(1, withZodI18n('zodI18n.errors.custom.filePathRequired'))),\n method: _default(httpMethod, 'GET'),\n expiresIn: optional(number().check(int(), minimum(1), maximum(604800))),\n disk: optional(string()),\n})\n\nexport type GetPresignedUrlInput = Infer<typeof getPresignedUrlInputSchema>\n\nexport const presignedUrlResultSchema = object({\n url: url(),\n expiresIn: number(),\n expiresAt: date(),\n method: httpMethod,\n})\n\nexport type PresignedUrlResult = Infer<typeof presignedUrlResultSchema>\n","import { date, number, object, string } from 'zod/mini'\nimport type { infer as Infer } from 'zod/mini'\n\n/**\n * Upload options for streaming uploads\n */\nexport interface UploadOptions {\n /**\n * Size of the content in bytes\n */\n size: number\n /**\n * MIME type of the content\n */\n mimeType?: string\n /**\n * Custom metadata to store with the object (S3-specific)\n * Stored as S3 object metadata headers\n */\n metadata?: Record<string, string>\n /**\n * Object tagging for lifecycle policies (S3-specific)\n * Format: key=value (e.g., \"Tus-Completed=true\")\n */\n tagging?: string\n}\n\nexport const uploadResultSchema = object({\n path: string(),\n disk: string(),\n fullPath: string(),\n size: number(),\n mimeType: string(),\n uploadedAt: date(),\n})\n\nexport type UploadResult = Infer<typeof uploadResultSchema>\n","import { HttpException } from '../../errors'\n\nexport class FileTooLargeError extends HttpException {\n constructor(public readonly size?: number, public readonly maxSize?: number) {\n super(413, 'File too large')\n }\n}\n","import { HttpException } from '../../errors'\n\nexport class InvalidFileTypeError extends HttpException {\n constructor(public readonly mimeType?: string) {\n super(422, 'Invalid file type')\n }\n}\n"],"mappings":"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"}
|