stratal 0.0.22 → 0.0.23
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/README.md +1 -1
- package/dist/bin/cloudflare-workers-loader.mjs +80 -7
- package/dist/bin/cloudflare-workers-loader.mjs.map +1 -1
- package/dist/bin/quarry.mjs +41 -54
- package/dist/bin/quarry.mjs.map +1 -1
- package/dist/cache/index.d.mts +5 -3
- package/dist/cache/index.d.mts.map +1 -1
- package/dist/cache/index.mjs +123 -39
- package/dist/cache/index.mjs.map +1 -1
- package/dist/{cache.service-e34gV6tz.d.mts → cache.service-uElmBtdS.d.mts} +24 -34
- package/dist/cache.service-uElmBtdS.d.mts.map +1 -0
- package/dist/{command-BU4ApTo5.mjs → command-BvmUAPPQ.mjs} +15 -3
- package/dist/command-BvmUAPPQ.mjs.map +1 -0
- package/dist/{command-wXfvHbBZ.d.mts → command-CPhFHjG3.d.mts} +2 -2
- package/dist/command-CPhFHjG3.d.mts.map +1 -0
- package/dist/command-not-found.error-ONAZ2Bpk.mjs +14 -0
- package/dist/command-not-found.error-ONAZ2Bpk.mjs.map +1 -0
- package/dist/config/index.d.mts +3 -3
- package/dist/config/index.d.mts.map +1 -1
- package/dist/config/index.mjs +7 -6
- package/dist/config/index.mjs.map +1 -1
- package/dist/{consumer-registry-DHQtypr1.d.mts → consumer-registry-D3iMTSdy.d.mts} +54 -22
- package/dist/consumer-registry-D3iMTSdy.d.mts.map +1 -0
- package/dist/{container-storage-GpNNz79X.mjs → container-storage-BmOJ4_Na.mjs} +1 -1
- package/dist/{container-storage-GpNNz79X.mjs.map → container-storage-BmOJ4_Na.mjs.map} +1 -1
- package/dist/{controller.decorator-DIUazNU7.mjs → controller.decorator-C5UVeJS3.mjs} +4 -4
- package/dist/{controller.decorator-DIUazNU7.mjs.map → controller.decorator-C5UVeJS3.mjs.map} +1 -1
- package/dist/cron/index.d.mts +79 -4
- package/dist/cron/index.d.mts.map +1 -1
- package/dist/cron/index.mjs +2 -2
- package/dist/cron-job-NesZRk8F.d.mts +58 -0
- package/dist/cron-job-NesZRk8F.d.mts.map +1 -0
- package/dist/{cron-manager-9bpN9bu4.mjs → cron.module-Bgzq5hiT.mjs} +17 -7
- package/dist/cron.module-Bgzq5hiT.mjs.map +1 -0
- package/dist/{decorate-HgTKAYK8.mjs → decorate-CuAoSZvs.mjs} +2 -2
- package/dist/{deep-merge-C8NgcXw4.mjs → deep-merge-ByiAOZ3r.mjs} +1 -1
- package/dist/{deep-merge-C8NgcXw4.mjs.map → deep-merge-ByiAOZ3r.mjs.map} +1 -1
- package/dist/di/index.d.mts +2 -2
- package/dist/di/index.mjs +3 -3
- package/dist/{di-BO1QIb5H.mjs → di-DseMn-z9.mjs} +244 -135
- package/dist/di-DseMn-z9.mjs.map +1 -0
- package/dist/email/index.d.mts +33 -40
- package/dist/email/index.d.mts.map +1 -1
- package/dist/email/index.mjs +456 -41
- package/dist/email/index.mjs.map +1 -1
- package/dist/{en-BPP6h6y5.mjs → en-CDZBMcc1.mjs} +2 -2
- package/dist/{en-BPP6h6y5.mjs.map → en-CDZBMcc1.mjs.map} +1 -1
- package/dist/{env-DKSbuBi5.d.mts → env-ug22bJj7.d.mts} +1 -1
- package/dist/env-ug22bJj7.d.mts.map +1 -0
- package/dist/errors/index.d.mts +1 -1
- package/dist/errors/index.mjs +3 -3
- package/dist/{errors-BBZTnjdq.mjs → errors-mXYxG0XB.mjs} +5 -5
- package/dist/{errors-BBZTnjdq.mjs.map → errors-mXYxG0XB.mjs.map} +1 -1
- package/dist/events/index.d.mts +14 -3
- package/dist/events/index.d.mts.map +1 -1
- package/dist/events/index.mjs +2 -2
- package/dist/{events-D1KdDaiP.mjs → events-BXJGZjpG.mjs} +16 -6
- package/dist/events-BXJGZjpG.mjs.map +1 -0
- package/dist/{exception-context-B4kM-M53.mjs → exception-context-kEoMFwze.mjs} +3 -3
- package/dist/{exception-context-B4kM-M53.mjs.map → exception-context-kEoMFwze.mjs.map} +1 -1
- package/dist/{gateway-context-CFe6a9gz.mjs → gateway-context-TMu_AlJt.mjs} +25 -6
- package/dist/{gateway-context-CFe6a9gz.mjs.map → gateway-context-TMu_AlJt.mjs.map} +1 -1
- package/dist/guards/index.d.mts +3 -3
- package/dist/guards/index.d.mts.map +1 -1
- package/dist/guards/index.mjs +1 -1
- package/dist/{guards-Ced-uNIF.mjs → guards-DALPXy3_.mjs} +2 -2
- package/dist/{guards-Ced-uNIF.mjs.map → guards-DALPXy3_.mjs.map} +1 -1
- package/dist/hono-app-CvV3hOfT.mjs +161 -0
- package/dist/hono-app-CvV3hOfT.mjs.map +1 -0
- package/dist/{http-method.decorator-CdjKFJZZ.mjs → http-method.decorator-ByWZb9DO.mjs} +4 -4
- package/dist/{http-method.decorator-CdjKFJZZ.mjs.map → http-method.decorator-ByWZb9DO.mjs.map} +1 -1
- package/dist/i18n/index.d.mts +4 -4
- package/dist/i18n/index.d.mts.map +1 -1
- package/dist/i18n/index.mjs +5 -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.mjs +1 -1
- package/dist/i18n/validation/index.d.mts +3 -3
- package/dist/i18n/validation/index.mjs +3 -3
- package/dist/{i18n.module-BlXrtAlV.mjs → i18n.module-DRQAZoSZ.mjs} +14 -11
- package/dist/{i18n.module-BlXrtAlV.mjs.map → i18n.module-DRQAZoSZ.mjs.map} +1 -1
- package/dist/{i18n.tokens-hwRpmjRq.mjs → i18n.tokens-CZ_v8oyS.mjs} +1 -1
- package/dist/{i18n.tokens-hwRpmjRq.mjs.map → i18n.tokens-CZ_v8oyS.mjs.map} +1 -1
- package/dist/{index-B4UBK-2T.d.mts → index-0ItCjaqw.d.mts} +1 -1
- package/dist/index-0ItCjaqw.d.mts.map +1 -0
- package/dist/{index-CW1YHSft.d.mts → index-B5JBRcWD.d.mts} +249 -103
- package/dist/index-B5JBRcWD.d.mts.map +1 -0
- package/dist/{index-BtlE9RuO.d.mts → index-BUt92sAE.d.mts} +1 -1
- package/dist/index-BUt92sAE.d.mts.map +1 -0
- package/dist/{index-DEncMcC6.d.mts → index-B_JoEl3V.d.mts} +221 -16
- package/dist/index-B_JoEl3V.d.mts.map +1 -0
- package/dist/{index-Dj5IMwtr.d.mts → index-DtBNIFuP.d.mts} +4 -6
- package/dist/index-DtBNIFuP.d.mts.map +1 -0
- package/dist/{index-KMgSCSM7.d.mts → index-HgOLNruQ.d.mts} +1 -1
- package/dist/{index-KMgSCSM7.d.mts.map → index-HgOLNruQ.d.mts.map} +1 -1
- package/dist/index.d.mts +6 -5
- package/dist/index.mjs +3 -2
- package/dist/{is-command-CX5rAfZW.mjs → is-command-CEPO9n8c.mjs} +2 -2
- package/dist/{is-command-CX5rAfZW.mjs.map → is-command-CEPO9n8c.mjs.map} +1 -1
- package/dist/{is-seeder-CYCtELlm.mjs → is-seeder-Gvh_AM71.mjs} +1 -1
- package/dist/{is-seeder-CYCtELlm.mjs.map → is-seeder-Gvh_AM71.mjs.map} +1 -1
- package/dist/lazy-module-loader-Ib383jH_.d.mts +60 -0
- package/dist/lazy-module-loader-Ib383jH_.d.mts.map +1 -0
- package/dist/locale-path.service-D-dHiIPc.mjs +165 -0
- package/dist/locale-path.service-D-dHiIPc.mjs.map +1 -0
- package/dist/locale-url-nZrZxqJP.mjs +44 -0
- package/dist/locale-url-nZrZxqJP.mjs.map +1 -0
- package/dist/locale-url.service-C2EWmGdq.mjs +41 -0
- package/dist/locale-url.service-C2EWmGdq.mjs.map +1 -0
- package/dist/logger/index.d.mts +1 -1
- package/dist/logger/index.mjs +2 -2
- package/dist/logger/index.mjs.map +1 -1
- package/dist/macroable/index.d.mts +2 -2
- package/dist/macroable/index.mjs +1 -1
- package/dist/{macroable-DzlfzT50.mjs → macroable-cvDTFZ_A.mjs} +1 -1
- package/dist/{macroable-DzlfzT50.mjs.map → macroable-cvDTFZ_A.mjs.map} +1 -1
- package/dist/{metadata-BVkc4aUu.mjs → metadata-DzzprcID.mjs} +1 -1
- package/dist/{metadata-BVkc4aUu.mjs.map → metadata-DzzprcID.mjs.map} +1 -1
- package/dist/module/index.d.mts +4 -3
- package/dist/module/index.d.mts.map +1 -1
- package/dist/module/index.mjs +10 -2
- package/dist/module/index.mjs.map +1 -0
- package/dist/{module-xYoHba6B.mjs → module-registry-Dm-pqHd3.mjs} +189 -57
- package/dist/module-registry-Dm-pqHd3.mjs.map +1 -0
- package/dist/module.decorator-CYHY6pG5.mjs +19 -0
- package/dist/module.decorator-CYHY6pG5.mjs.map +1 -0
- package/dist/openapi/index.d.mts +44 -8
- package/dist/openapi/index.d.mts.map +1 -1
- package/dist/openapi/index.mjs +3 -2
- package/dist/{openapi-C6lm0RmV.mjs → openapi-CstuTM8S.mjs} +55 -229
- package/dist/openapi-CstuTM8S.mjs.map +1 -0
- package/dist/openapi-tools.service-BC5EC3R3.mjs +206 -0
- package/dist/openapi-tools.service-BC5EC3R3.mjs.map +1 -0
- package/dist/{openapi.service-CrLlsXAd.d.mts → openapi.service-YhTiJ1bO.d.mts} +3 -3
- package/dist/{openapi.service-CrLlsXAd.d.mts.map → openapi.service-YhTiJ1bO.d.mts.map} +1 -1
- package/dist/quarry/index.d.mts +14 -5
- package/dist/quarry/index.d.mts.map +1 -1
- package/dist/quarry/index.mjs +6 -5
- package/dist/quarry/runner.d.mts +11 -11
- package/dist/quarry/runner.d.mts.map +1 -1
- package/dist/quarry/runner.mjs +192 -22
- package/dist/quarry/runner.mjs.map +1 -1
- package/dist/{quarry-registry-D4hIGScf.d.mts → quarry-registry-CXg0RFXq.d.mts} +4 -4
- package/dist/quarry-registry-CXg0RFXq.d.mts.map +1 -0
- package/dist/{quarry-registry-DkraZNwn.mjs → quarry.module-BuRPGMDm.mjs} +22 -21
- package/dist/quarry.module-BuRPGMDm.mjs.map +1 -0
- package/dist/queue/index.d.mts +3 -3
- package/dist/queue/index.mjs +42 -31
- package/dist/queue/index.mjs.map +1 -1
- package/dist/queue.module-nddvxzCB.mjs +613 -0
- package/dist/queue.module-nddvxzCB.mjs.map +1 -0
- package/dist/queue.tokens-DjHnFmre.mjs +11 -0
- package/dist/queue.tokens-DjHnFmre.mjs.map +1 -0
- package/dist/{r2-storage.provider-Hfm6LdZQ.mjs → r2-storage.provider-DCxQt9dD.mjs} +4 -4
- package/dist/{r2-storage.provider-Hfm6LdZQ.mjs.map → r2-storage.provider-DCxQt9dD.mjs.map} +1 -1
- package/dist/{rate-limit.decorator-D69zdZbp.mjs → rate-limit.decorator-BPAie_p3.mjs} +3 -3
- package/dist/{rate-limit.decorator-D69zdZbp.mjs.map → rate-limit.decorator-BPAie_p3.mjs.map} +1 -1
- package/dist/rate-limiter/index.d.mts +5 -5
- package/dist/rate-limiter/index.d.mts.map +1 -1
- package/dist/rate-limiter/index.mjs +26 -21
- package/dist/rate-limiter/index.mjs.map +1 -1
- package/dist/route-name-DGoBOfPg.mjs +171 -0
- package/dist/route-name-DGoBOfPg.mjs.map +1 -0
- package/dist/route-registration.service-D6vSwiKP.mjs +918 -0
- package/dist/route-registration.service-D6vSwiKP.mjs.map +1 -0
- package/dist/route-registry-CYqLp2Nj.mjs +123 -0
- package/dist/route-registry-CYqLp2Nj.mjs.map +1 -0
- package/dist/router/index.d.mts +2 -2
- package/dist/router/index.mjs +18 -8
- package/dist/router-CWGBD-Bg.mjs +78 -0
- package/dist/router-CWGBD-Bg.mjs.map +1 -0
- package/dist/router-resolver-D4YlPNlm.mjs +88 -0
- package/dist/router-resolver-D4YlPNlm.mjs.map +1 -0
- package/dist/seeder/index.d.mts +14 -4
- package/dist/seeder/index.d.mts.map +1 -1
- package/dist/seeder/index.mjs +5 -3
- package/dist/{seeder-BADTig4n.mjs → seeder-7ubkms-Y.mjs} +7 -56
- package/dist/seeder-7ubkms-Y.mjs.map +1 -0
- package/dist/seeder-registry-CyUmKsJq.mjs +57 -0
- package/dist/seeder-registry-CyUmKsJq.mjs.map +1 -0
- package/dist/seeder.module-CYYwk3Qk.mjs +15 -0
- package/dist/seeder.module-CYYwk3Qk.mjs.map +1 -0
- package/dist/{signed-url-BqUqt5dF.mjs → signed-url-DIU0sK_6.mjs} +1 -1
- package/dist/{signed-url-BqUqt5dF.mjs.map → signed-url-DIU0sK_6.mjs.map} +1 -1
- package/dist/storage/index.d.mts +3 -3
- package/dist/storage/index.d.mts.map +1 -1
- package/dist/storage/index.mjs +2 -2
- package/dist/storage/providers/index.d.mts +2 -2
- package/dist/storage/providers/index.d.mts.map +1 -1
- package/dist/storage/providers/index.mjs +1 -1
- package/dist/{storage-BA3ppVYM.mjs → storage-MDZypIE9.mjs} +12 -11
- package/dist/{storage-BA3ppVYM.mjs.map → storage-MDZypIE9.mjs.map} +1 -1
- package/dist/{storage-provider.interface-DQMtT42e.d.mts → storage-provider.interface-ClUwxz4S.d.mts} +2 -2
- package/dist/storage-provider.interface-ClUwxz4S.d.mts.map +1 -0
- package/dist/storage.error-Dnib4VHc.mjs +8 -0
- package/dist/{storage.error-C6FY037a.mjs.map → storage.error-Dnib4VHc.mjs.map} +1 -1
- package/dist/{stratal-Bdq4IdB3.mjs → stratal-DL9M38_s.mjs} +142 -140
- package/dist/stratal-DL9M38_s.mjs.map +1 -0
- package/dist/{stratal-BsKmvP6J.d.mts → stratal-DwDJPY9N.d.mts} +3 -3
- package/dist/{stratal-BsKmvP6J.d.mts.map → stratal-DwDJPY9N.d.mts.map} +1 -1
- package/dist/tiered-cache.service-Dv3BhxxE.d.mts +79 -0
- package/dist/tiered-cache.service-Dv3BhxxE.d.mts.map +1 -0
- package/dist/trailing-slash-CFyw8nYu.mjs +34 -0
- package/dist/trailing-slash-CFyw8nYu.mjs.map +1 -0
- package/dist/{types-BaeHi67f.d.mts → types-CmV_9xBD.d.mts} +1 -1
- package/dist/types-CmV_9xBD.d.mts.map +1 -0
- package/dist/uri-h7Q8Jug9.mjs +251 -0
- package/dist/uri-h7Q8Jug9.mjs.map +1 -0
- package/dist/{usage-generator-DTqaUMR9.mjs → usage-generator-DAWYasuP.mjs} +4 -4
- package/dist/usage-generator-DAWYasuP.mjs.map +1 -0
- package/dist/{validation-DUzcjb8Q.mjs → validation-CpOjviyT.mjs} +6 -6
- package/dist/{validation-DUzcjb8Q.mjs.map → validation-CpOjviyT.mjs.map} +1 -1
- package/dist/{validation.context-XTysWJ3b.mjs → validation.context-CRvmrhq7.mjs} +3 -3
- package/dist/{validation.context-XTysWJ3b.mjs.map → validation.context-CRvmrhq7.mjs.map} +1 -1
- package/dist/versioning.service-C6aHky8-.mjs +36 -0
- package/dist/versioning.service-C6aHky8-.mjs.map +1 -0
- package/dist/websocket/index.d.mts +11 -2
- package/dist/websocket/index.d.mts.map +1 -1
- package/dist/websocket/index.mjs +1 -1
- package/dist/workers/index.d.mts +2 -2
- package/dist/workers/index.d.mts.map +1 -1
- package/dist/workers/index.mjs +3 -3
- package/dist/workers/index.mjs.map +1 -1
- package/dist/{zod-hMa3rSHV.mjs → zod-eKqqhZ5_.mjs} +2 -2
- package/dist/{zod-hMa3rSHV.mjs.map → zod-eKqqhZ5_.mjs.map} +1 -1
- package/dist/{zod-DvWTfRpI.d.mts → zod-wecrEVAs.d.mts} +8 -3
- package/dist/zod-wecrEVAs.d.mts.map +1 -0
- package/package.json +19 -30
- package/dist/base-email.provider-BWZHIjt8.mjs +0 -42
- package/dist/base-email.provider-BWZHIjt8.mjs.map +0 -1
- package/dist/cache.service-e34gV6tz.d.mts.map +0 -1
- package/dist/cache.tokens-ovi_c52J.mjs +0 -6
- package/dist/cache.tokens-ovi_c52J.mjs.map +0 -1
- package/dist/colors-axmupKdp.mjs +0 -16
- package/dist/colors-axmupKdp.mjs.map +0 -1
- package/dist/command-BU4ApTo5.mjs.map +0 -1
- package/dist/command-wXfvHbBZ.d.mts.map +0 -1
- package/dist/consumer-registry-DHQtypr1.d.mts.map +0 -1
- package/dist/cron-manager-9bpN9bu4.mjs.map +0 -1
- package/dist/cron-manager-CSTIBPcM.d.mts +0 -124
- package/dist/cron-manager-CSTIBPcM.d.mts.map +0 -1
- package/dist/di-BO1QIb5H.mjs.map +0 -1
- package/dist/env-DKSbuBi5.d.mts.map +0 -1
- package/dist/events-D1KdDaiP.mjs.map +0 -1
- package/dist/index-B4UBK-2T.d.mts.map +0 -1
- package/dist/index-BtlE9RuO.d.mts.map +0 -1
- package/dist/index-CW1YHSft.d.mts.map +0 -1
- package/dist/index-DEncMcC6.d.mts.map +0 -1
- package/dist/index-Dj5IMwtr.d.mts.map +0 -1
- package/dist/module-xYoHba6B.mjs.map +0 -1
- package/dist/openapi-C6lm0RmV.mjs.map +0 -1
- package/dist/quarry-registry-D4hIGScf.d.mts.map +0 -1
- package/dist/quarry-registry-DkraZNwn.mjs.map +0 -1
- package/dist/queue.module-DeWJ0tQM.mjs +0 -355
- package/dist/queue.module-DeWJ0tQM.mjs.map +0 -1
- package/dist/resend.provider-Ur6tU7fK.mjs +0 -68
- package/dist/resend.provider-Ur6tU7fK.mjs.map +0 -1
- package/dist/router-Cy6DjkvP.mjs +0 -1852
- package/dist/router-Cy6DjkvP.mjs.map +0 -1
- package/dist/seeder-BADTig4n.mjs.map +0 -1
- package/dist/smtp.provider-C129sNBT.mjs +0 -76
- package/dist/smtp.provider-C129sNBT.mjs.map +0 -1
- package/dist/storage-provider.interface-DQMtT42e.d.mts.map +0 -1
- package/dist/storage.error-C6FY037a.mjs +0 -8
- package/dist/stratal-Bdq4IdB3.mjs.map +0 -1
- package/dist/types-BaeHi67f.d.mts.map +0 -1
- package/dist/usage-generator-DTqaUMR9.mjs.map +0 -1
- package/dist/zod-DvWTfRpI.d.mts.map +0 -1
- /package/dist/{chunk-D1SwGrFN.mjs → chunk-BBjsoOtd.mjs} +0 -0
package/dist/cache/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/cache/cache.error.ts","../../src/cache/services/cache.service.ts","../../src/cache/cache.module.ts"],"sourcesContent":["import { ApplicationError } from '../errors'\n\nexport class CacheError extends ApplicationError {}\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 * **Features:**\n * - Mirrors all KVNamespace methods with full type safety\n * - Supports multiple KV bindings via `withBinding()`\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 * @inject(DI_TOKENS.CloudflareEnv) private readonly env: Env\n * ) {\n * // Initialize specialized caches in constructor\n * this.uploadsCache = this.cache.withBinding(this.env.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 /**\n * Set the KV namespace binding\n *\n * Used internally by `withBinding()` to configure different KV instances.\n *\n * @param kv - KV namespace to use\n */\n setKV(kv: KVNamespace): void {\n this.kv = kv\n }\n\n /**\n * Create a new CacheService instance with a different KV binding\n *\n * **Pattern:** Returns a new instance (immutable)\n *\n * **Best Practice:** Initialize specialized caches as class properties in constructor\n *\n * @example\n * ```typescript\n * class MyService {\n * private readonly uploadsCache: CacheService\n * private readonly systemCache: CacheService\n *\n * constructor(\n * @inject(CACHE_TOKENS.CacheService) private readonly cache: CacheService,\n * @inject(DI_TOKENS.CloudflareEnv) private readonly env: Env\n * ) {\n * this.uploadsCache = this.cache.withBinding(this.env.UPLOADS_CACHE)\n * this.systemCache = this.cache.withBinding(this.env.SYSTEM_CONFIG_KV)\n * }\n * }\n * ```\n *\n * @param kv - KV namespace to use\n * @returns New CacheService instance with the specified binding\n */\n withBinding(kv: KVNamespace): CacheService {\n const instance = new CacheService(this.env, this.logger)\n instance.setKV(kv)\n return instance\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","/**\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 } from './services'\n\n@Module({\n providers: [\n // Singleton - CacheService has no request dependencies\n { provide: CACHE_TOKENS.CacheService, useClass: CacheService },\n ],\n})\nexport class CacheModule {}\n"],"mappings":";;;;;;;;AAEA,IAAa,aAAb,cAAgC,iBAAiB;;;;ACwC1C,IAAA,eAAA,gBAAA,MAAM,aAAa;CAI4B;CACI;CAJxD;CAEA,YACE,KACA,QACA;EAFkD,KAAA,MAAA;EACI,KAAA,SAAA;EAEtD,KAAK,KAAK,IAAI;;;;;;;;;CAUhB,MAAM,IAAuB;EAC3B,KAAK,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BZ,YAAY,IAA+B;EACzC,MAAM,WAAW,IAAA,cAAiB,KAAK,KAAK,KAAK,OAAO;EACxD,SAAS,MAAM,GAAG;EAClB,OAAO;;CAkBT,MAAM,IACJ,KACA,eACuE;EACvE,IAAI;GACF,IAAI,OAAO,kBAAkB,UAE3B,OAAO,MAAM,KAAK,GAAG,IAAI,KAAK,cAAqB;GAGrD,IAAI,eAEF,OAAO,MAAM,KAAK,GAAG,IAAI,KAAK,cAAqB;GAGrD,OAAO,MAAM,KAAK,GAAG,IAAI,IAAI;WACtB,OAAO;GACd,KAAK,OAAO,MAAM,8BAA8B;IAAE;IAAK;IAAO,CAAC;GAC/D,MAAM,IAAI,WAAW,4BAA4B,IAAI,GAAG;;;CA+B5D,MAAM,gBACJ,KACA,eAMA;EACA,IAAI;GACF,IAAI,OAAO,kBAAkB,UAE3B,OAAO,MAAM,KAAK,GAAG,gBAAgB,KAAK,cAAqB;GAGjE,IAAI,eAEF,OAAO,MAAM,KAAK,GAAG,gBAAgB,KAAK,cAAqB;GAGjE,OAAO,MAAM,KAAK,GAAG,gBAAgB,IAAI;WAClC,OAAO;GACd,KAAK,OAAO,MAAM,0CAA0C;IAAE;IAAK;IAAO,CAAC;GAC3E,MAAM,IAAI,WAAW,4BAA4B,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;CA0B5D,MAAM,IACJ,KACA,OACA,SACe;EACf,IAAI;GACF,MAAM,KAAK,GAAG,IAAI,KAAK,OAAiB,QAAQ;WACzC,OAAO;GACd,KAAK,OAAO,MAAM,8BAA8B;IAAE;IAAK;IAAO,CAAC;GAC/D,MAAM,IAAI,WAAW,8BAA8B,IAAI,GAAG;;;;;;;;;CAY9D,MAAM,OAAO,KAA4B;EACvC,IAAI;GACF,MAAM,KAAK,GAAG,OAAO,IAAI;WAClB,OAAO;GACd,KAAK,OAAO,MAAM,iCAAiC;IAAE;IAAK;IAAO,CAAC;GAClE,MAAM,IAAI,WAAW,+BAA+B,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;CA6B/D,MAAM,KACJ,SAC0C;EAC1C,IAAI;GACF,OAAO,MAAM,KAAK,GAAG,KAAe,QAAQ;WACrC,OAAO;GACd,KAAK,OAAO,MAAM,+BAA+B;IAAE;IAAS;IAAO,CAAC;GACpE,MAAM,IAAI,WAAW,4BAA4B;;;;;CArOtD,UAAU,aAAa,aAAa;oBAKhC,OAAO,UAAU,cAAc,CAAA;oBAC/B,OAAO,cAAc,cAAc,CAAA;;;;;;;;;;;;;;;ACzBjC,IAAA,cAAA,MAAM,YAAY;0BANxB,OAAO,EACN,WAAW,CAET;CAAE,SAAS,aAAa;CAAc,UAAU;CAAc,CAC/D,EACF,CAAC,CAAA,EAAA,YAAA"}
|
|
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,5 +1,5 @@
|
|
|
1
|
-
import { t as StratalEnv } from "./env-
|
|
2
|
-
import { r as LoggerService } from "./index-
|
|
1
|
+
import { t as StratalEnv } from "./env-ug22bJj7.mjs";
|
|
2
|
+
import { r as LoggerService } from "./index-BUt92sAE.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/cache/services/cache.service.d.ts
|
|
5
5
|
/**
|
|
@@ -7,9 +7,17 @@ import { r as LoggerService } from "./index-BtlE9RuO.mjs";
|
|
|
7
7
|
*
|
|
8
8
|
* Type-safe wrapper around Cloudflare KV namespaces for caching operations.
|
|
9
9
|
*
|
|
10
|
+
* Reads are eventually consistent — KV may serve an edge-cached value for up to
|
|
11
|
+
* ~60s after a write. When you need isolate-local read-after-write coherence
|
|
12
|
+
* (e.g. set-once markers like queue idempotency keys), opt into
|
|
13
|
+
* {@link TieredCacheService}, which layers an isolate-local L1 over this
|
|
14
|
+
* service. Do **not** use the L1 tier for read-modify-write counters that need
|
|
15
|
+
* cross-edge freshness (e.g. rate limiting) — plain KV is the correct primitive
|
|
16
|
+
* there.
|
|
17
|
+
*
|
|
10
18
|
* **Features:**
|
|
11
19
|
* - Mirrors all KVNamespace methods with full type safety
|
|
12
|
-
* - Supports multiple KV bindings via `withBinding()`
|
|
20
|
+
* - Supports multiple KV bindings via `withBinding()` / `binding(name)`
|
|
13
21
|
* - Automatic error handling with logging
|
|
14
22
|
* - Security: Raw errors are logged, not exposed to users
|
|
15
23
|
*
|
|
@@ -20,10 +28,9 @@ import { r as LoggerService } from "./index-BtlE9RuO.mjs";
|
|
|
20
28
|
*
|
|
21
29
|
* constructor(
|
|
22
30
|
* @inject(CACHE_TOKENS.CacheService) private readonly cache: CacheService,
|
|
23
|
-
* @inject(DI_TOKENS.CloudflareEnv) private readonly env: Env
|
|
24
31
|
* ) {
|
|
25
32
|
* // Initialize specialized caches in constructor
|
|
26
|
-
* this.uploadsCache = this.cache.
|
|
33
|
+
* this.uploadsCache = this.cache.binding('UPLOADS_CACHE')
|
|
27
34
|
* }
|
|
28
35
|
*
|
|
29
36
|
* async cacheData(key: string, value: string) {
|
|
@@ -40,41 +47,24 @@ declare class CacheService {
|
|
|
40
47
|
private readonly logger;
|
|
41
48
|
private kv;
|
|
42
49
|
constructor(env: StratalEnv, logger: LoggerService);
|
|
50
|
+
/** The KV namespace this instance is bound to. */
|
|
51
|
+
get namespace(): KVNamespace;
|
|
43
52
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* Used internally by `withBinding()` to configure different KV instances.
|
|
53
|
+
* Create a new CacheService instance bound to a different KV namespace.
|
|
47
54
|
*
|
|
48
55
|
* @param kv - KV namespace to use
|
|
56
|
+
* @returns A new CacheService for the given binding
|
|
49
57
|
*/
|
|
50
|
-
|
|
58
|
+
withBinding(kv: KVNamespace): CacheService;
|
|
51
59
|
/**
|
|
52
|
-
* Create a new CacheService instance
|
|
53
|
-
*
|
|
54
|
-
* **Pattern:** Returns a new instance (immutable)
|
|
60
|
+
* Create a new CacheService instance bound to a KV namespace by its binding
|
|
61
|
+
* name, resolved from the environment.
|
|
55
62
|
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* @
|
|
59
|
-
* ```typescript
|
|
60
|
-
* class MyService {
|
|
61
|
-
* private readonly uploadsCache: CacheService
|
|
62
|
-
* private readonly systemCache: CacheService
|
|
63
|
-
*
|
|
64
|
-
* constructor(
|
|
65
|
-
* @inject(CACHE_TOKENS.CacheService) private readonly cache: CacheService,
|
|
66
|
-
* @inject(DI_TOKENS.CloudflareEnv) private readonly env: Env
|
|
67
|
-
* ) {
|
|
68
|
-
* this.uploadsCache = this.cache.withBinding(this.env.UPLOADS_CACHE)
|
|
69
|
-
* this.systemCache = this.cache.withBinding(this.env.SYSTEM_CONFIG_KV)
|
|
70
|
-
* }
|
|
71
|
-
* }
|
|
72
|
-
* ```
|
|
73
|
-
*
|
|
74
|
-
* @param kv - KV namespace to use
|
|
75
|
-
* @returns New CacheService instance with the specified binding
|
|
63
|
+
* @param name - KV namespace binding name (e.g. `'UPLOADS_CACHE'`)
|
|
64
|
+
* @returns A new CacheService for the given binding
|
|
65
|
+
* @throws {CacheError} If no binding with that name exists in the environment
|
|
76
66
|
*/
|
|
77
|
-
|
|
67
|
+
binding(name: string): CacheService;
|
|
78
68
|
/**
|
|
79
69
|
* Get a value from cache
|
|
80
70
|
*
|
|
@@ -153,4 +143,4 @@ declare class CacheService {
|
|
|
153
143
|
}
|
|
154
144
|
//#endregion
|
|
155
145
|
export { CacheService as t };
|
|
156
|
-
//# sourceMappingURL=cache.service-
|
|
146
|
+
//# sourceMappingURL=cache.service-uElmBtdS.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.service-uElmBtdS.d.mts","names":[],"sources":["../src/cache/services/cache.service.ts"],"mappings":";;;;;;AAgDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cACa,YAAA;EAAA,iBAIyC,GAAA;EAAA,iBACI,MAAA;EAAA,QAJhD,EAAA;cAG4C,GAAA,EAAK,UAAA,EACD,MAAA,EAAQ,aAAA;EA2J7D;EAAA,IArJC,SAAA,IAAa,WAAA;EAyML;;;;;;EA/LZ,WAAA,CAAY,EAAA,EAAI,WAAA,GAAc,YAAA;EAhB0B;;;;;;;;EA8BxD,OAAA,CAAQ,IAAA,WAAe,YAAA;EAdvB;;;;;;;;EAgCM,GAAA,CAAI,GAAA,UAAa,aAAA,YAAyB,qBAAA,WAAgC,OAAA;EAC1E,GAAA,0BAA6B,GAAA,UAAa,aAAA,WAAwB,qBAAA,WAAgC,OAAA,CAAQ,aAAA;EAC1G,GAAA,CAAI,GAAA,UAAa,aAAA,kBAA+B,qBAAA,kBAAuC,OAAA,CAAQ,WAAA;EAC/F,GAAA,CAAI,GAAA,UAAa,aAAA,aAA0B,qBAAA,aAAkC,OAAA,CAAQ,cAAA;EAFrF;;;;;;;;EAoCA,eAAA,qBACJ,GAAA,UACA,aAAA,YAAyB,qBAAA,WACxB,OAAA,CAAQ,gCAAA,SAAyC,QAAA;EAC9C,eAAA,8CACJ,GAAA,UACA,aAAA,WAAwB,qBAAA,WACvB,OAAA,CAAQ,gCAAA,CAAiC,aAAA,EAAe,QAAA;EACrD,eAAA,qBACJ,GAAA,UACA,aAAA,kBAA+B,qBAAA,kBAC9B,OAAA,CAAQ,gCAAA,CAAiC,WAAA,EAAa,QAAA;EACnD,eAAA,qBACJ,GAAA,UACA,aAAA,aAA0B,qBAAA,aACzB,OAAA,CAAQ,gCAAA,CAAiC,cAAA,EAAgB,QAAA;EAlDyC;;;;;;;;;;;;;;;;;;;;EAqG/F,GAAA,CACJ,GAAA,UACA,KAAA,WAAgB,WAAA,GAAc,eAAA,GAAkB,cAAA,EAChD,OAAA,GAAU,qBAAA,GACT,OAAA;EA/DA;;;;;;EAgFG,MAAA,CAAO,GAAA,WAAc,OAAA;EA7EM;;;;;;;;;;;;;;;;;;;;;;EA+G3B,IAAA,qBACJ,OAAA,GAAU,sBAAA,GACT,OAAA,CAAQ,qBAAA,CAAsB,QAAA;AAAA"}
|
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
//#region src/quarry/colors.ts
|
|
2
|
+
/** Minimal ANSI color helpers that respect the `NO_COLOR` convention. */
|
|
3
|
+
const isEnabled = () => typeof process !== "undefined" ? !process.env.NO_COLOR : true;
|
|
4
|
+
/** Create an ANSI formatter that wraps text with the given open/close SGR codes. */
|
|
5
|
+
const code = (open, close) => (s) => isEnabled() ? `\x1b[${open}m${s}\x1b[${close}m` : s;
|
|
6
|
+
const bold = code(1, 22);
|
|
7
|
+
const dim = code(2, 22);
|
|
8
|
+
const cyan = code(36, 39);
|
|
9
|
+
const green = code(32, 39);
|
|
10
|
+
const red = code(31, 39);
|
|
11
|
+
const yellow = code(33, 39);
|
|
12
|
+
const dimWhite = (s) => isEnabled() ? `\x1b[2;37m${s}\x1b[22;39m` : s;
|
|
13
|
+
//#endregion
|
|
2
14
|
//#region src/quarry/constants.ts
|
|
3
15
|
/**
|
|
4
16
|
* Symbol key for storing internal mutable state on Command instances.
|
|
@@ -190,6 +202,6 @@ var Command = class {
|
|
|
190
202
|
}
|
|
191
203
|
};
|
|
192
204
|
//#endregion
|
|
193
|
-
export { CommandError as n, COMMAND_INTERNALS as r, Command as t };
|
|
205
|
+
export { cyan as a, green as c, bold as i, red as l, CommandError as n, dim as o, COMMAND_INTERNALS as r, dimWhite as s, Command as t, yellow as u };
|
|
194
206
|
|
|
195
|
-
//# sourceMappingURL=command-
|
|
207
|
+
//# sourceMappingURL=command-BvmUAPPQ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-BvmUAPPQ.mjs","names":[],"sources":["../src/quarry/colors.ts","../src/quarry/constants.ts","../src/quarry/errors/command.error.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","import { bold, cyan, dim, green, red, yellow } from './colors'\nimport { COMMAND_INTERNALS } from './constants'\nimport { CommandError } from './errors/command.error'\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 /** Write an informational message to output */\n info(message: string): void {\n this[COMMAND_INTERNALS].output.push(cyan(message))\n }\n\n /** Write a success message to output */\n success(message: string): void {\n this[COMMAND_INTERNALS].output.push(`${green(bold('✔'))} ${green(message)}`)\n }\n\n /** Write a warning message to output */\n warn(message: string): void {\n this[COMMAND_INTERNALS].output.push(`${yellow(bold('⚠'))} ${yellow(message)}`)\n }\n\n /** Write an error message to errors */\n error(message: string): void {\n this[COMMAND_INTERNALS].errors.push(red(message))\n }\n\n /** Write a plain line to output */\n line(message?: string): void {\n this[COMMAND_INTERNALS].output.push(message ?? '')\n }\n\n /** Write an empty line to output */\n newLine(): void {\n this[COMMAND_INTERNALS].output.push('')\n }\n\n /** Write a comment-style line to output */\n comment(message: string): void {\n this[COMMAND_INTERNALS].output.push(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[COMMAND_INTERNALS].output.push(bold(formatRow(headers)))\n this[COMMAND_INTERNALS].output.push(dim(colWidths.map((w) => '-'.repeat(w)).join(' ')))\n for (const row of rows) {\n this[COMMAND_INTERNALS].output.push(formatRow(row))\n }\n }\n\n /** Write an error message and set exit code */\n fail(message: string, exitCode = 1): void {\n this[COMMAND_INTERNALS].errors.push(`${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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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Sn as CommandResult, bn as CommandInput, xn as CommandInternals } from "./index-B_JoEl3V.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/quarry/constants.d.ts
|
|
4
4
|
/**
|
|
@@ -118,4 +118,4 @@ declare abstract class Command {
|
|
|
118
118
|
}
|
|
119
119
|
//#endregion
|
|
120
120
|
export { Command as t };
|
|
121
|
-
//# sourceMappingURL=command-
|
|
121
|
+
//# sourceMappingURL=command-CPhFHjG3.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-CPhFHjG3.d.mts","names":[],"sources":["../src/quarry/constants.ts","../src/quarry/command.ts"],"mappings":";;;;;;AAIA;cAAa,iBAAA;;;;AAAb;;;;AAAwE;;;;ACoBxE;;;;;;;;;;uBAAsB,OAAA;EA8LmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAAA,OA7JhD,OAAA;EA4HC;EAAA,OA1HD,WAAA;EA+HD;EAAA,OA7HC,OAAA;EAAA,CAEN,iBAAA,GAAoB,gBAAA;;EA4IC;;;;EAAA,SA3Hb,MAAA,oBAA0B,OAAA;EAsIa;;;EA/HhD,KAAA,IAAS,IAAA,WAAe,CAAA;;;;EAOxB,MAAA,CAAO,IAAA;;;;EAcP,OAAA,CAAQ,IAAA;;;;EAcR,MAAA,CAAO,IAAA;;;;EAeP,KAAA,CAAM,IAAA;;EAcN,IAAA,CAAK,OAAA;;EAKL,OAAA,CAAQ,OAAA;;EAKR,IAAA,CAAK,OAAA;;EAKL,KAAA,CAAM,OAAA;;EAKN,IAAA,CAAK,OAAA;;EAKL,OAAA;;EAKA,OAAA,CAAQ,OAAA;;EAKR,KAAA,CAAM,OAAA,YAAmB,IAAA;;EAiBzB,IAAA,CAAK,OAAA,UAAiB,QAAA;;;;;EAWhB,IAAA,CAAK,IAAA,UAAc,KAAA,GAAQ,YAAA,GAAe,OAAA,CAAQ,aAAA;AAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/quarry/errors/command-not-found.error.ts
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when a command is not found in the Quarry registry.
|
|
4
|
+
*/
|
|
5
|
+
var CommandNotFoundError = class extends Error {
|
|
6
|
+
constructor(name) {
|
|
7
|
+
super(`Command "${name}" is not registered.`);
|
|
8
|
+
this.name = "CommandNotFoundError";
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
//#endregion
|
|
12
|
+
export { CommandNotFoundError as t };
|
|
13
|
+
|
|
14
|
+
//# sourceMappingURL=command-not-found.error-ONAZ2Bpk.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-not-found.error-ONAZ2Bpk.mjs","names":[],"sources":["../src/quarry/errors/command-not-found.error.ts"],"sourcesContent":["/**\n * Thrown when a command is not found in the Quarry registry.\n */\nexport class CommandNotFoundError extends Error {\n constructor(name: string) {\n super(`Command \"${name}\" is not registered.`)\n this.name = 'CommandNotFoundError'\n }\n}\n"],"mappings":";;;;AAGA,IAAa,uBAAb,cAA0C,MAAM;CAC9C,YAAY,MAAc;EACxB,MAAM,YAAY,KAAK,qBAAqB;EAC5C,KAAK,OAAO;CACd;AACF"}
|
package/dist/config/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as Macroable } from "../index-
|
|
3
|
-
import { a as z } from "../zod-
|
|
1
|
+
import { Dr as ApplicationError, Hn as InjectionToken, In as OnInitialize, Nn as ModuleContext, jn as FactoryProvider, kn as DynamicModule } from "../index-B_JoEl3V.mjs";
|
|
2
|
+
import { t as Macroable } from "../index-0ItCjaqw.mjs";
|
|
3
|
+
import { a as z } from "../zod-wecrEVAs.mjs";
|
|
4
4
|
|
|
5
5
|
//#region src/config/config.error.d.ts
|
|
6
6
|
declare class ConfigError extends ApplicationError {}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/config/config.error.ts","../../src/config/config.tokens.ts","../../src/config/register-as.ts","../../src/config/config.module.ts","../../src/config/config.types.ts","../../src/config/services/config.store.ts","../../src/config/services/config.service.ts"],"mappings":";;;;;cAEa,WAAA,SAAoB,
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/config/config.error.ts","../../src/config/config.tokens.ts","../../src/config/register-as.ts","../../src/config/config.module.ts","../../src/config/config.types.ts","../../src/config/services/config.store.ts","../../src/config/services/config.service.ts"],"mappings":"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"}
|
package/dist/config/index.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { n as __decorateParam, t as __decorate } from "../decorate-
|
|
4
|
-
import "../errors-
|
|
5
|
-
import { t as Macroable } from "../macroable-
|
|
6
|
-
import {
|
|
1
|
+
import { d as inject, o as Request, r as DI_TOKENS, s as Singleton } from "../di-DseMn-z9.mjs";
|
|
2
|
+
import { a as ApplicationError } from "../container-storage-BmOJ4_Na.mjs";
|
|
3
|
+
import { n as __decorateParam, t as __decorate } from "../decorate-CuAoSZvs.mjs";
|
|
4
|
+
import "../errors-mXYxG0XB.mjs";
|
|
5
|
+
import { t as Macroable } from "../macroable-cvDTFZ_A.mjs";
|
|
6
|
+
import { n as Module } from "../module.decorator-CYHY6pG5.mjs";
|
|
7
|
+
import "../module/index.mjs";
|
|
7
8
|
//#region src/config/config.error.ts
|
|
8
9
|
var ConfigError = class extends ApplicationError {};
|
|
9
10
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/config/config.error.ts","../../src/config/config.tokens.ts","../../src/config/config.types.ts","../../src/config/services/config.service.ts","../../src/config/services/config.store.ts","../../src/config/config.module.ts","../../src/config/register-as.ts"],"sourcesContent":["import { ApplicationError } from '../errors'\n\nexport class ConfigError extends ApplicationError {}\n","export const CONFIG_TOKENS = {\n\tConfigService: Symbol.for('stratal:config:service'),\n\tConfigStore: Symbol.for('stratal:config:store'),\n} as const\n","import type { z } from '../i18n/validation/zod'\n\nexport class ConfigValidationError extends Error {\n constructor(\n message: string,\n public readonly errors: z.ZodError\n ) {\n super(message)\n this.name = 'ConfigValidationError'\n }\n}\n\n/**\n * Configuration that can be augmented by applications\n * Apps should augment this interface with their AppConfig type using module augmentation\n *\n * @example\n * ```typescript\n * // In your app (e.g., apps/backend/src/config/types.ts)\n * declare module 'stratal' {\n * interface ModuleConfig {\n * database: { url: string; maxConnections: number }\n * email: { provider: string; from: { name: string; email: string } }\n * }\n * }\n * ```\n */\nexport interface ModuleConfig { }\n\n/**\n * Generate all valid dot-notation paths from a config object type\n * @example ConfigPath<{ database: { url: string } }> = 'database' | 'database.url'\n */\nexport type ConfigPath<T> = {\n [K in keyof T & string]: T[K] extends Record<string, unknown>\n ? K | `${K}.${ConfigPath<T[K]>}`\n : K\n}[keyof T & string]\n\n/**\n * Get the value type at a dot-notation path\n * @example ConfigPathValue<{ database: { url: string } }, 'database.url'> = string\n */\nexport type ConfigPathValue<T, P extends string> = P extends `${infer K}.${infer Rest}`\n ? K extends keyof T\n ? T[K] extends Record<string, unknown>\n ? ConfigPathValue<T[K], Rest>\n : never\n : never\n : P extends keyof T\n ? T[P]\n : never\n\n/**\n * ConfigService interface with dot notation support.\n *\n * Values are initialized on the underlying {@link ConfigStore} via\n * {@link ConfigModule}; the service itself exposes request-scoped\n * reads and per-request overrides.\n */\nexport interface IConfigService<T extends object = ModuleConfig> {\n /**\n * Get config value using dot notation\n * @example config.get('database.url')\n */\n get<P extends ConfigPath<T>>(path: P): ConfigPathValue<T, P>\n\n /**\n * Set config value at runtime (for runtime overrides)\n * @example config.set('email.from.name', 'Custom Name')\n */\n set<P extends ConfigPath<T>>(path: P, value: ConfigPathValue<T, P>): void\n\n /**\n * Reset config to original value\n * @param path - Optional path to reset (resets entire config if omitted)\n */\n reset(path?: ConfigPath<T>): void\n\n /**\n * Get entire config object\n */\n all(): Readonly<T>\n\n /**\n * Check if a config path exists\n */\n has(path: ConfigPath<T>): boolean\n}\n","import { inject } from '../../di'\nimport { Request } from '../../di/decorators'\nimport { Macroable } from '../../macroable/macroable'\nimport { CONFIG_TOKENS } from '../config.tokens'\nimport type { ConfigPath, ConfigPathValue, IConfigService, ModuleConfig } from '../config.types'\nimport { type ConfigStore } from './config.store'\n\n/**\n * ConfigService with dot notation support and per-request overrides.\n *\n * ConfigService is **request-scoped**: each request gets its own\n * instance with a private `overrides` map layered over the shared\n * {@link ConfigStore}. Calls to {@link set} mutate only the current\n * request's overrides, which makes it safe to mutate config from\n * middleware (e.g. to pin `environment.appUrl` to the request host).\n *\n * Extends {@link Macroable} so apps can add domain-specific getters\n * and methods via `ConfigService.getter()` / `ConfigService.macro()`.\n *\n * @example\n * ```typescript\n * // Read with dot notation\n * const url = config.get('database.url')\n * const fromName = config.get('email.from.name')\n *\n * // Per-request override (e.g. in middleware)\n * config.set('environment.appUrl', `${proto}://${host}`)\n *\n * // Reset the override for the current request\n * config.reset('environment.appUrl')\n * ```\n */\n@Request(CONFIG_TOKENS.ConfigService)\nexport class ConfigService<T extends object = ModuleConfig> extends Macroable implements IConfigService<T> {\n private overrides = new Map<string, unknown>()\n\n constructor(\n @inject(CONFIG_TOKENS.ConfigStore) private readonly store: ConfigStore<T>,\n ) {\n super()\n }\n\n /**\n * Get config value using dot notation. Request overrides take\n * precedence over the shared store.\n */\n get<P extends ConfigPath<T>>(path: P): ConfigPathValue<T, P> {\n const override = this.readOverride(path)\n if (override !== undefined) {\n return override as ConfigPathValue<T, P>\n }\n return this.store.get(path)\n }\n\n /**\n * Set a config value for the lifetime of the current request.\n * Does not mutate the shared store.\n */\n set<P extends ConfigPath<T>>(path: P, value: ConfigPathValue<T, P>): void {\n if (this.hasDangerousSegment(path)) return\n this.overrides.set(path, value)\n }\n\n /**\n * Clear a single override, or all overrides for this request.\n */\n reset(path?: ConfigPath<T>): void {\n if (path) {\n this.overrides.delete(path)\n return\n }\n this.overrides.clear()\n }\n\n /**\n * Get the full config object, with request overrides merged in.\n */\n all(): Readonly<T> {\n const base = this.store.all() as T\n if (this.overrides.size === 0) {\n return base as Readonly<T>\n }\n const merged = this.deepClone(base)\n for (const [path, value] of this.overrides) {\n this.writeByPath(merged, path, value)\n }\n return merged as Readonly<T>\n }\n\n /**\n * Check if a config path exists (in overrides or the store).\n */\n has(path: ConfigPath<T>): boolean {\n if (this.readOverride(path) !== undefined) return true\n return this.store.has(path)\n }\n\n private readOverride(path: string): unknown {\n if (this.hasDangerousSegment(path)) return undefined\n if (this.overrides.has(path)) {\n return this.overrides.get(path)\n }\n // Support partial-path reads: if an ancestor was overridden, walk into it.\n const segments = path.split('.')\n for (let i = segments.length - 1; i > 0; i--) {\n const parent = segments.slice(0, i).join('.')\n if (this.overrides.has(parent)) {\n const parentValue = this.overrides.get(parent)\n return this.walk(parentValue, segments.slice(i))\n }\n }\n return undefined\n }\n\n private walk(value: unknown, keys: string[]): unknown {\n let current = value\n for (const key of keys) {\n if (current === null || current === undefined) return undefined\n current = (current as Record<string, unknown>)[key]\n }\n return current\n }\n\n private writeByPath(obj: unknown, path: string, value: unknown): void {\n const keys = path.split('.')\n if (keys.some((key) => this.isDangerousKey(key))) return\n let current = obj as Record<string, unknown>\n for (let i = 0; i < keys.length - 1; i++) {\n const key = keys[i]\n if (!Object.hasOwn(current, key) || typeof current[key] !== 'object' || current[key] === null) {\n Object.defineProperty(current, key, {\n value: {},\n writable: true,\n enumerable: true,\n configurable: true,\n })\n }\n current = current[key] as Record<string, unknown>\n }\n Object.defineProperty(current, keys[keys.length - 1], {\n value,\n writable: true,\n enumerable: true,\n configurable: true,\n })\n }\n\n private hasDangerousSegment(path: string): boolean {\n return path.split('.').some((key) => this.isDangerousKey(key))\n }\n\n private isDangerousKey(key: string): boolean {\n return key === '__proto__' || key === 'constructor' || key === 'prototype'\n }\n\n private deepClone<V>(obj: V): V {\n if (obj === null || typeof obj !== 'object') {\n return obj\n }\n return JSON.parse(JSON.stringify(obj)) as V\n }\n}\n","import { Singleton } from '../../di/decorators'\nimport { CONFIG_TOKENS } from '../config.tokens'\nimport type { ConfigPath, ConfigPathValue, ModuleConfig } from '../config.types'\nimport { ConfigError } from '../config.error'\n\n/**\n * ConfigStore\n *\n * Singleton-scoped holder of validated, merged configuration.\n *\n * ConfigStore is the source of truth for configuration values. It is\n * initialized once during application startup by {@link ConfigModule}\n * and never mutated afterwards.\n *\n * Per-request overrides live on {@link ConfigService}, which reads\n * through to this store for any key not explicitly overridden.\n *\n * If the store is never initialized (no `ConfigModule.forRoot()`), it\n * behaves like an empty config: `has()` returns `false`, `all()` returns\n * `{}`, and `get()` throws {@link ConfigError} for any path —\n * the same error you'd get for a missing key on an initialized store.\n * Resolving the store via DI never throws on its own.\n */\n@Singleton(CONFIG_TOKENS.ConfigStore)\nexport class ConfigStore<T extends object = ModuleConfig> {\n private data: T | undefined\n\n /**\n * Initialize the store with validated configuration.\n * Called by {@link ConfigModule} during initialization.\n */\n initialize(config: T): void {\n this.data = this.deepClone(config)\n }\n\n /**\n * Get config value using dot notation. Throws\n * {@link ConfigError} if the path is absent.\n */\n get<P extends ConfigPath<T>>(path: P): ConfigPathValue<T, P> {\n const value = this.getByPath(this.data ?? {}, path)\n if (value === undefined) {\n throw new ConfigError(`Configuration key \"${path}\" was not found`)\n }\n return value as ConfigPathValue<T, P>\n }\n\n /**\n * Check if a config path exists. Returns `false` when the store has\n * not been initialized.\n */\n has(path: ConfigPath<T>): boolean {\n return this.getByPath(this.data ?? {}, path) !== undefined\n }\n\n /**\n * Get the entire config object (readonly snapshot). Returns an empty\n * object when the store has not been initialized.\n */\n all(): Readonly<T> {\n return (this.data ?? ({} as T)) as Readonly<T>\n }\n\n /**\n * True once {@link initialize} has been called.\n */\n isInitialized(): boolean {\n return this.data !== undefined\n }\n\n private getByPath(obj: unknown, path: string): unknown {\n const keys = path.split('.')\n let current = obj\n for (const key of keys) {\n if (this.isDangerousKey(key)) return undefined\n if (current === null || current === undefined) return undefined\n current = (current as Record<string, unknown>)[key]\n }\n return current\n }\n\n private isDangerousKey(key: string): boolean {\n return key === '__proto__' || key === 'constructor' || key === 'prototype'\n }\n\n private deepClone<V>(obj: V): V {\n if (obj === null || typeof obj !== 'object') {\n return obj\n }\n return JSON.parse(JSON.stringify(obj)) as V\n }\n}\n","import { DI_TOKENS } from '../di/tokens'\nimport type { z } from '../i18n/validation/zod'\nimport { Module } from '../module'\nimport type { DynamicModule, ModuleContext, OnInitialize, Provider } from '../module/types'\nimport { CONFIG_TOKENS } from './config.tokens'\nimport { ConfigValidationError, type ModuleConfig } from './config.types'\nimport type { ConfigNamespace } from './register-as'\nimport { ConfigService } from './services/config.service'\nimport { ConfigStore } from './services/config.store'\n\n/**\n * Any config namespace - uses structural typing for flexibility\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyConfigNamespace = ConfigNamespace<string, any, object>\n\n/**\n * Options for ConfigModule.forRoot\n */\nexport interface ConfigModuleOptions {\n /**\n * Array of config namespaces created via registerAs()\n */\n load: AnyConfigNamespace[]\n\n /**\n * Optional Zod schema for validating the merged config\n * Validates the entire config object after all namespaces are merged\n */\n validateSchema?: z.ZodType<object>\n}\n\n// Store options for use in onInitialize\nlet moduleOptions: ConfigModuleOptions | null = null\n\n/**\n * ConfigModule\n *\n * Provides configuration management with namespace support.\n * Uses registerAs() to create typed config namespaces that can be injected.\n *\n * @example\n * ```typescript\n * // Define config namespaces\n * const databaseConfig = registerAs('database', (env) => ({\n * url: env.DATABASE_URL,\n * maxConnections: 10\n * }))\n *\n * const emailConfig = registerAs('email', (env) => ({\n * provider: env.EMAIL_PROVIDER,\n * from: { name: 'App', email: 'noreply@example.com' }\n * }))\n *\n * // Register in module\n * @Module({\n * imports: [\n * ConfigModule.forRoot({\n * load: [databaseConfig, emailConfig],\n * validateSchema: AppConfigSchema\n * })\n * ]\n * })\n * export class AppModule {}\n *\n * // Inject config\n * constructor(\n * @inject(CONFIG_TOKENS.ConfigService) private config: IConfigService,\n * @inject(databaseConfig.KEY) private dbConfig: DatabaseConfig\n * ) {\n * // Use dot notation\n * const url = this.config.get('database.url')\n *\n * // Or inject namespace directly\n * const url = this.dbConfig.url\n * }\n * ```\n */\n@Module({\n providers: [\n // ConfigStore is the singleton source of truth for validated config.\n {\n provide: CONFIG_TOKENS.ConfigStore,\n useClass: ConfigStore,\n },\n // ConfigService is request-scoped: each request gets its own\n // overrides map layered over the shared ConfigStore.\n {\n provide: CONFIG_TOKENS.ConfigService,\n useClass: ConfigService,\n },\n ],\n})\nexport class ConfigModule implements OnInitialize {\n /**\n * Configure ConfigModule with namespace loaders\n *\n * @param options - Configuration options\n * @returns Dynamic module with config infrastructure\n */\n static forRoot(options: ConfigModuleOptions): DynamicModule {\n moduleOptions = options\n\n const providers: Provider[] = []\n\n // Register each namespace config using asProvider()\n for (const namespace of options.load) {\n providers.push(namespace.asProvider())\n }\n\n return {\n module: ConfigModule,\n providers,\n }\n }\n\n /**\n * Initialize config service with merged namespaces.\n * Called after all providers are registered. No-op when the module\n * was imported without `forRoot()` — the store stays empty and\n * `ConfigService.get()` will throw `ConfigError` only if\n * someone actually asks for a key.\n */\n onInitialize(context: ModuleContext): void {\n if (!moduleOptions) {\n return\n }\n\n const env = context.container.resolve<unknown>(DI_TOKENS.CloudflareEnv)\n const configStore = context.container.resolve<ConfigStore>(CONFIG_TOKENS.ConfigStore)\n\n // Build merged config from all namespaces\n const mergedConfig: Record<string, unknown> = {}\n\n for (const namespace of moduleOptions.load) {\n mergedConfig[namespace.namespace] = namespace.factory(env)\n }\n\n // Validate if schema provided\n if (moduleOptions.validateSchema) {\n const result = moduleOptions.validateSchema.safeParse(mergedConfig)\n if (!result.success) {\n throw new ConfigValidationError(\n 'Configuration validation failed',\n result.error\n )\n }\n }\n\n // Initialize the shared ConfigStore with merged config.\n configStore.initialize(mergedConfig as ModuleConfig)\n\n context.logger.debug('ConfigModule initialized', {\n namespaces: moduleOptions.load.map((n) => n.namespace),\n })\n }\n}\n","import type { InjectionToken } from '../di/types'\nimport { DI_TOKENS } from '../di/tokens'\nimport type { FactoryProvider } from '../module/types'\n\n/**\n * Configuration namespace registration result\n */\nexport interface ConfigNamespace<TKey extends string, TEnv, TConfig extends object> {\n /** Auto-derived injection token (e.g., 'database' -> Symbol('stratal:config:database')) */\n readonly KEY: InjectionToken<TConfig>\n /** The namespace key */\n readonly namespace: TKey\n /** The factory function that receives env and returns config */\n readonly factory: (env: TEnv) => TConfig\n /**\n * Returns a provider configuration for use in module registration\n * Automatically injects DI_TOKENS.CloudflareEnv\n */\n asProvider(): FactoryProvider<TConfig>\n}\n\n/**\n * Create a namespaced configuration factory\n * Similar to NestJS registerAs\n *\n * @param namespace - Configuration namespace (e.g., 'database', 'email')\n * @param factory - Factory function receiving env and returning config object\n * @returns ConfigNamespace with token, factory, and asProvider() method\n *\n * @example\n * ```typescript\n * // apps/backend/src/config/database.config.ts\n * export const databaseConfig = registerAs('database', (env: Env) => ({\n * url: env.DATABASE_URL,\n * maxConnections: parseInt(env.DATABASE_MAX_CONNECTIONS || '10'),\n * }))\n *\n * // Auto-generates: databaseConfig.KEY = Symbol('stratal:config:database')\n *\n * // Usage in module:\n * // Option 1: Manual provider\n * {\n * provide: databaseConfig.KEY,\n * useFactory: databaseConfig.factory,\n * inject: [DI_TOKENS.CloudflareEnv]\n * }\n *\n * // Option 2: asProvider() helper\n * databaseConfig.asProvider()\n * // Returns: { provide: databaseConfig.KEY, useFactory: ..., inject: [DI_TOKENS.CloudflareEnv] }\n * ```\n */\nexport function registerAs<TKey extends string, TEnv, TConfig extends object>(\n namespace: TKey,\n factory: (env: TEnv) => TConfig\n): ConfigNamespace<TKey, TEnv, TConfig> {\n const KEY = Symbol.for(`stratal:config:${namespace}`) as InjectionToken<TConfig>\n\n return {\n KEY,\n namespace,\n factory,\n asProvider(): FactoryProvider<TConfig> {\n return {\n provide: KEY,\n useFactory: factory,\n inject: [DI_TOKENS.CloudflareEnv],\n }\n },\n }\n}\n\n/**\n * Helper to derive config type from registerAs result\n *\n * @example\n * ```typescript\n * const databaseConfig = registerAs('database', (env) => ({ url: env.DATABASE_URL }))\n * type DatabaseConfig = InferConfigType<typeof databaseConfig>\n * // { url: string }\n * ```\n */\nexport type InferConfigType<T> = T extends ConfigNamespace<string, unknown, infer C> ? C : never\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/config/config.error.ts","../../src/config/config.tokens.ts","../../src/config/config.types.ts","../../src/config/services/config.service.ts","../../src/config/services/config.store.ts","../../src/config/config.module.ts","../../src/config/register-as.ts"],"sourcesContent":["import { ApplicationError } from '../errors'\n\nexport class ConfigError extends ApplicationError {}\n","export const CONFIG_TOKENS = {\n\tConfigService: Symbol.for('stratal:config:service'),\n\tConfigStore: Symbol.for('stratal:config:store'),\n} as const\n","import type { z } from '../i18n/validation/zod'\n\nexport class ConfigValidationError extends Error {\n constructor(\n message: string,\n public readonly errors: z.ZodError\n ) {\n super(message)\n this.name = 'ConfigValidationError'\n }\n}\n\n/**\n * Configuration that can be augmented by applications\n * Apps should augment this interface with their AppConfig type using module augmentation\n *\n * @example\n * ```typescript\n * // In your app (e.g., apps/backend/src/config/types.ts)\n * declare module 'stratal' {\n * interface ModuleConfig {\n * database: { url: string; maxConnections: number }\n * email: { provider: string; from: { name: string; email: string } }\n * }\n * }\n * ```\n */\nexport interface ModuleConfig { }\n\n/**\n * Generate all valid dot-notation paths from a config object type\n * @example ConfigPath<{ database: { url: string } }> = 'database' | 'database.url'\n */\nexport type ConfigPath<T> = {\n [K in keyof T & string]: T[K] extends Record<string, unknown>\n ? K | `${K}.${ConfigPath<T[K]>}`\n : K\n}[keyof T & string]\n\n/**\n * Get the value type at a dot-notation path\n * @example ConfigPathValue<{ database: { url: string } }, 'database.url'> = string\n */\nexport type ConfigPathValue<T, P extends string> = P extends `${infer K}.${infer Rest}`\n ? K extends keyof T\n ? T[K] extends Record<string, unknown>\n ? ConfigPathValue<T[K], Rest>\n : never\n : never\n : P extends keyof T\n ? T[P]\n : never\n\n/**\n * ConfigService interface with dot notation support.\n *\n * Values are initialized on the underlying {@link ConfigStore} via\n * {@link ConfigModule}; the service itself exposes request-scoped\n * reads and per-request overrides.\n */\nexport interface IConfigService<T extends object = ModuleConfig> {\n /**\n * Get config value using dot notation\n * @example config.get('database.url')\n */\n get<P extends ConfigPath<T>>(path: P): ConfigPathValue<T, P>\n\n /**\n * Set config value at runtime (for runtime overrides)\n * @example config.set('email.from.name', 'Custom Name')\n */\n set<P extends ConfigPath<T>>(path: P, value: ConfigPathValue<T, P>): void\n\n /**\n * Reset config to original value\n * @param path - Optional path to reset (resets entire config if omitted)\n */\n reset(path?: ConfigPath<T>): void\n\n /**\n * Get entire config object\n */\n all(): Readonly<T>\n\n /**\n * Check if a config path exists\n */\n has(path: ConfigPath<T>): boolean\n}\n","import { inject } from '../../di'\nimport { Request } from '../../di/decorators'\nimport { Macroable } from '../../macroable/macroable'\nimport { CONFIG_TOKENS } from '../config.tokens'\nimport type { ConfigPath, ConfigPathValue, IConfigService, ModuleConfig } from '../config.types'\nimport { type ConfigStore } from './config.store'\n\n/**\n * ConfigService with dot notation support and per-request overrides.\n *\n * ConfigService is **request-scoped**: each request gets its own\n * instance with a private `overrides` map layered over the shared\n * {@link ConfigStore}. Calls to {@link set} mutate only the current\n * request's overrides, which makes it safe to mutate config from\n * middleware (e.g. to pin `environment.appUrl` to the request host).\n *\n * Extends {@link Macroable} so apps can add domain-specific getters\n * and methods via `ConfigService.getter()` / `ConfigService.macro()`.\n *\n * @example\n * ```typescript\n * // Read with dot notation\n * const url = config.get('database.url')\n * const fromName = config.get('email.from.name')\n *\n * // Per-request override (e.g. in middleware)\n * config.set('environment.appUrl', `${proto}://${host}`)\n *\n * // Reset the override for the current request\n * config.reset('environment.appUrl')\n * ```\n */\n@Request(CONFIG_TOKENS.ConfigService)\nexport class ConfigService<T extends object = ModuleConfig> extends Macroable implements IConfigService<T> {\n private overrides = new Map<string, unknown>()\n\n constructor(\n @inject(CONFIG_TOKENS.ConfigStore) private readonly store: ConfigStore<T>,\n ) {\n super()\n }\n\n /**\n * Get config value using dot notation. Request overrides take\n * precedence over the shared store.\n */\n get<P extends ConfigPath<T>>(path: P): ConfigPathValue<T, P> {\n const override = this.readOverride(path)\n if (override !== undefined) {\n return override as ConfigPathValue<T, P>\n }\n return this.store.get(path)\n }\n\n /**\n * Set a config value for the lifetime of the current request.\n * Does not mutate the shared store.\n */\n set<P extends ConfigPath<T>>(path: P, value: ConfigPathValue<T, P>): void {\n if (this.hasDangerousSegment(path)) return\n this.overrides.set(path, value)\n }\n\n /**\n * Clear a single override, or all overrides for this request.\n */\n reset(path?: ConfigPath<T>): void {\n if (path) {\n this.overrides.delete(path)\n return\n }\n this.overrides.clear()\n }\n\n /**\n * Get the full config object, with request overrides merged in.\n */\n all(): Readonly<T> {\n const base = this.store.all() as T\n if (this.overrides.size === 0) {\n return base as Readonly<T>\n }\n const merged = this.deepClone(base)\n for (const [path, value] of this.overrides) {\n this.writeByPath(merged, path, value)\n }\n return merged as Readonly<T>\n }\n\n /**\n * Check if a config path exists (in overrides or the store).\n */\n has(path: ConfigPath<T>): boolean {\n if (this.readOverride(path) !== undefined) return true\n return this.store.has(path)\n }\n\n private readOverride(path: string): unknown {\n if (this.hasDangerousSegment(path)) return undefined\n if (this.overrides.has(path)) {\n return this.overrides.get(path)\n }\n // Support partial-path reads: if an ancestor was overridden, walk into it.\n const segments = path.split('.')\n for (let i = segments.length - 1; i > 0; i--) {\n const parent = segments.slice(0, i).join('.')\n if (this.overrides.has(parent)) {\n const parentValue = this.overrides.get(parent)\n return this.walk(parentValue, segments.slice(i))\n }\n }\n return undefined\n }\n\n private walk(value: unknown, keys: string[]): unknown {\n let current = value\n for (const key of keys) {\n if (current === null || current === undefined) return undefined\n current = (current as Record<string, unknown>)[key]\n }\n return current\n }\n\n private writeByPath(obj: unknown, path: string, value: unknown): void {\n const keys = path.split('.')\n if (keys.some((key) => this.isDangerousKey(key))) return\n let current = obj as Record<string, unknown>\n for (let i = 0; i < keys.length - 1; i++) {\n const key = keys[i]\n if (!Object.hasOwn(current, key) || typeof current[key] !== 'object' || current[key] === null) {\n Object.defineProperty(current, key, {\n value: {},\n writable: true,\n enumerable: true,\n configurable: true,\n })\n }\n current = current[key] as Record<string, unknown>\n }\n Object.defineProperty(current, keys[keys.length - 1], {\n value,\n writable: true,\n enumerable: true,\n configurable: true,\n })\n }\n\n private hasDangerousSegment(path: string): boolean {\n return path.split('.').some((key) => this.isDangerousKey(key))\n }\n\n private isDangerousKey(key: string): boolean {\n return key === '__proto__' || key === 'constructor' || key === 'prototype'\n }\n\n private deepClone<V>(obj: V): V {\n if (obj === null || typeof obj !== 'object') {\n return obj\n }\n return JSON.parse(JSON.stringify(obj)) as V\n }\n}\n","import { Singleton } from '../../di/decorators'\nimport { CONFIG_TOKENS } from '../config.tokens'\nimport type { ConfigPath, ConfigPathValue, ModuleConfig } from '../config.types'\nimport { ConfigError } from '../config.error'\n\n/**\n * ConfigStore\n *\n * Singleton-scoped holder of validated, merged configuration.\n *\n * ConfigStore is the source of truth for configuration values. It is\n * initialized once during application startup by {@link ConfigModule}\n * and never mutated afterwards.\n *\n * Per-request overrides live on {@link ConfigService}, which reads\n * through to this store for any key not explicitly overridden.\n *\n * If the store is never initialized (no `ConfigModule.forRoot()`), it\n * behaves like an empty config: `has()` returns `false`, `all()` returns\n * `{}`, and `get()` throws {@link ConfigError} for any path —\n * the same error you'd get for a missing key on an initialized store.\n * Resolving the store via DI never throws on its own.\n */\n@Singleton(CONFIG_TOKENS.ConfigStore)\nexport class ConfigStore<T extends object = ModuleConfig> {\n private data: T | undefined\n\n /**\n * Initialize the store with validated configuration.\n * Called by {@link ConfigModule} during initialization.\n */\n initialize(config: T): void {\n this.data = this.deepClone(config)\n }\n\n /**\n * Get config value using dot notation. Throws\n * {@link ConfigError} if the path is absent.\n */\n get<P extends ConfigPath<T>>(path: P): ConfigPathValue<T, P> {\n const value = this.getByPath(this.data ?? {}, path)\n if (value === undefined) {\n throw new ConfigError(`Configuration key \"${path}\" was not found`)\n }\n return value as ConfigPathValue<T, P>\n }\n\n /**\n * Check if a config path exists. Returns `false` when the store has\n * not been initialized.\n */\n has(path: ConfigPath<T>): boolean {\n return this.getByPath(this.data ?? {}, path) !== undefined\n }\n\n /**\n * Get the entire config object (readonly snapshot). Returns an empty\n * object when the store has not been initialized.\n */\n all(): Readonly<T> {\n return (this.data ?? ({} as T)) as Readonly<T>\n }\n\n /**\n * True once {@link initialize} has been called.\n */\n isInitialized(): boolean {\n return this.data !== undefined\n }\n\n private getByPath(obj: unknown, path: string): unknown {\n const keys = path.split('.')\n let current = obj\n for (const key of keys) {\n if (this.isDangerousKey(key)) return undefined\n if (current === null || current === undefined) return undefined\n current = (current as Record<string, unknown>)[key]\n }\n return current\n }\n\n private isDangerousKey(key: string): boolean {\n return key === '__proto__' || key === 'constructor' || key === 'prototype'\n }\n\n private deepClone<V>(obj: V): V {\n if (obj === null || typeof obj !== 'object') {\n return obj\n }\n return JSON.parse(JSON.stringify(obj)) as V\n }\n}\n","import { DI_TOKENS } from '../di/tokens'\nimport type { z } from '../i18n/validation/zod'\nimport { Module } from '../module'\nimport type { DynamicModule, ModuleContext, OnInitialize, Provider } from '../module/types'\nimport { CONFIG_TOKENS } from './config.tokens'\nimport { ConfigValidationError, type ModuleConfig } from './config.types'\nimport type { ConfigNamespace } from './register-as'\nimport { ConfigService } from './services/config.service'\nimport { ConfigStore } from './services/config.store'\n\n/**\n * Any config namespace - uses structural typing for flexibility\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyConfigNamespace = ConfigNamespace<string, any, object>\n\n/**\n * Options for ConfigModule.forRoot\n */\nexport interface ConfigModuleOptions {\n /**\n * Array of config namespaces created via registerAs()\n */\n load: AnyConfigNamespace[]\n\n /**\n * Optional Zod schema for validating the merged config\n * Validates the entire config object after all namespaces are merged\n */\n validateSchema?: z.ZodType<object>\n}\n\n// Store options for use in onInitialize\nlet moduleOptions: ConfigModuleOptions | null = null\n\n/**\n * ConfigModule\n *\n * Provides configuration management with namespace support.\n * Uses registerAs() to create typed config namespaces that can be injected.\n *\n * @example\n * ```typescript\n * // Define config namespaces\n * const databaseConfig = registerAs('database', (env) => ({\n * url: env.DATABASE_URL,\n * maxConnections: 10\n * }))\n *\n * const emailConfig = registerAs('email', (env) => ({\n * provider: env.EMAIL_PROVIDER,\n * from: { name: 'App', email: 'noreply@example.com' }\n * }))\n *\n * // Register in module\n * @Module({\n * imports: [\n * ConfigModule.forRoot({\n * load: [databaseConfig, emailConfig],\n * validateSchema: AppConfigSchema\n * })\n * ]\n * })\n * export class AppModule {}\n *\n * // Inject config\n * constructor(\n * @inject(CONFIG_TOKENS.ConfigService) private config: IConfigService,\n * @inject(databaseConfig.KEY) private dbConfig: DatabaseConfig\n * ) {\n * // Use dot notation\n * const url = this.config.get('database.url')\n *\n * // Or inject namespace directly\n * const url = this.dbConfig.url\n * }\n * ```\n */\n@Module({\n providers: [\n // ConfigStore is the singleton source of truth for validated config.\n {\n provide: CONFIG_TOKENS.ConfigStore,\n useClass: ConfigStore,\n },\n // ConfigService is request-scoped: each request gets its own\n // overrides map layered over the shared ConfigStore.\n {\n provide: CONFIG_TOKENS.ConfigService,\n useClass: ConfigService,\n },\n ],\n})\nexport class ConfigModule implements OnInitialize {\n /**\n * Configure ConfigModule with namespace loaders\n *\n * @param options - Configuration options\n * @returns Dynamic module with config infrastructure\n */\n static forRoot(options: ConfigModuleOptions): DynamicModule {\n moduleOptions = options\n\n const providers: Provider[] = []\n\n // Register each namespace config using asProvider()\n for (const namespace of options.load) {\n providers.push(namespace.asProvider())\n }\n\n return {\n module: ConfigModule,\n providers,\n }\n }\n\n /**\n * Initialize config service with merged namespaces.\n * Called after all providers are registered. No-op when the module\n * was imported without `forRoot()` — the store stays empty and\n * `ConfigService.get()` will throw `ConfigError` only if\n * someone actually asks for a key.\n */\n onInitialize(context: ModuleContext): void {\n if (!moduleOptions) {\n return\n }\n\n const env = context.container.resolve<unknown>(DI_TOKENS.CloudflareEnv)\n const configStore = context.container.resolve<ConfigStore>(CONFIG_TOKENS.ConfigStore)\n\n // Build merged config from all namespaces\n const mergedConfig: Record<string, unknown> = {}\n\n for (const namespace of moduleOptions.load) {\n mergedConfig[namespace.namespace] = namespace.factory(env)\n }\n\n // Validate if schema provided\n if (moduleOptions.validateSchema) {\n const result = moduleOptions.validateSchema.safeParse(mergedConfig)\n if (!result.success) {\n throw new ConfigValidationError(\n 'Configuration validation failed',\n result.error\n )\n }\n }\n\n // Initialize the shared ConfigStore with merged config.\n configStore.initialize(mergedConfig as ModuleConfig)\n\n context.logger.debug('ConfigModule initialized', {\n namespaces: moduleOptions.load.map((n) => n.namespace),\n })\n }\n}\n","import type { InjectionToken } from '../di/types'\nimport { DI_TOKENS } from '../di/tokens'\nimport type { FactoryProvider } from '../module/types'\n\n/**\n * Configuration namespace registration result\n */\nexport interface ConfigNamespace<TKey extends string, TEnv, TConfig extends object> {\n /** Auto-derived injection token (e.g., 'database' -> Symbol('stratal:config:database')) */\n readonly KEY: InjectionToken<TConfig>\n /** The namespace key */\n readonly namespace: TKey\n /** The factory function that receives env and returns config */\n readonly factory: (env: TEnv) => TConfig\n /**\n * Returns a provider configuration for use in module registration\n * Automatically injects DI_TOKENS.CloudflareEnv\n */\n asProvider(): FactoryProvider<TConfig>\n}\n\n/**\n * Create a namespaced configuration factory\n * Similar to NestJS registerAs\n *\n * @param namespace - Configuration namespace (e.g., 'database', 'email')\n * @param factory - Factory function receiving env and returning config object\n * @returns ConfigNamespace with token, factory, and asProvider() method\n *\n * @example\n * ```typescript\n * // apps/backend/src/config/database.config.ts\n * export const databaseConfig = registerAs('database', (env: Env) => ({\n * url: env.DATABASE_URL,\n * maxConnections: parseInt(env.DATABASE_MAX_CONNECTIONS || '10'),\n * }))\n *\n * // Auto-generates: databaseConfig.KEY = Symbol('stratal:config:database')\n *\n * // Usage in module:\n * // Option 1: Manual provider\n * {\n * provide: databaseConfig.KEY,\n * useFactory: databaseConfig.factory,\n * inject: [DI_TOKENS.CloudflareEnv]\n * }\n *\n * // Option 2: asProvider() helper\n * databaseConfig.asProvider()\n * // Returns: { provide: databaseConfig.KEY, useFactory: ..., inject: [DI_TOKENS.CloudflareEnv] }\n * ```\n */\nexport function registerAs<TKey extends string, TEnv, TConfig extends object>(\n namespace: TKey,\n factory: (env: TEnv) => TConfig\n): ConfigNamespace<TKey, TEnv, TConfig> {\n const KEY = Symbol.for(`stratal:config:${namespace}`) as InjectionToken<TConfig>\n\n return {\n KEY,\n namespace,\n factory,\n asProvider(): FactoryProvider<TConfig> {\n return {\n provide: KEY,\n useFactory: factory,\n inject: [DI_TOKENS.CloudflareEnv],\n }\n },\n }\n}\n\n/**\n * Helper to derive config type from registerAs result\n *\n * @example\n * ```typescript\n * const databaseConfig = registerAs('database', (env) => ({ url: env.DATABASE_URL }))\n * type DatabaseConfig = InferConfigType<typeof databaseConfig>\n * // { url: string }\n * ```\n */\nexport type InferConfigType<T> = T extends ConfigNamespace<string, unknown, infer C> ? C : never\n"],"mappings":"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"}
|
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
import { t as StratalEnv } from "./env-ug22bJj7.mjs";
|
|
2
|
+
import { t as Constructor } from "./types-CmV_9xBD.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/queue/queue-binding.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* String keys of `StratalEnv` whose value type is `Queue`.
|
|
7
|
+
*/
|
|
8
|
+
type QueueBindingFromEnv = Extract<{ [K in keyof StratalEnv]: StratalEnv[K] extends Queue ? K : never }[keyof StratalEnv], string>;
|
|
9
|
+
/**
|
|
10
|
+
* Type-safe queue binding identifier.
|
|
11
|
+
*
|
|
12
|
+
* Resolves to the union of `Queue`-typed binding keys on the augmented
|
|
13
|
+
* `StratalEnv`. Falls back to `string` when no `Queue` bindings are visible
|
|
14
|
+
* (e.g. library code compiled outside an app's env context).
|
|
15
|
+
*/
|
|
16
|
+
type QueueBinding = [QueueBindingFromEnv] extends [never] ? string : QueueBindingFromEnv;
|
|
17
|
+
//#endregion
|
|
1
18
|
//#region src/queue/queue-consumer.d.ts
|
|
2
19
|
/**
|
|
3
20
|
* Queue message structure
|
|
@@ -8,8 +25,6 @@
|
|
|
8
25
|
interface QueueMessage<T = unknown> {
|
|
9
26
|
/** Unique message identifier (UUID) */
|
|
10
27
|
id: string;
|
|
11
|
-
/** Timestamp when message was dispatched (milliseconds since epoch) */
|
|
12
|
-
timestamp: number;
|
|
13
28
|
/** Message type for routing to consumers */
|
|
14
29
|
type: string;
|
|
15
30
|
/** Message payload */
|
|
@@ -17,6 +32,14 @@ interface QueueMessage<T = unknown> {
|
|
|
17
32
|
/** Optional metadata including locale for i18n */
|
|
18
33
|
metadata?: {
|
|
19
34
|
locale?: string;
|
|
35
|
+
idempotencyKey?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Producer binding the message was dispatched through (stamped by
|
|
38
|
+
* {@link QueueSender}). Recorded on failed jobs so `queue:retry` can
|
|
39
|
+
* re-enqueue to the correct Cloudflare binding — the queue *name* a
|
|
40
|
+
* consumer sees is not a valid producer binding key.
|
|
41
|
+
*/
|
|
42
|
+
binding?: QueueBinding;
|
|
20
43
|
[key: string]: unknown;
|
|
21
44
|
};
|
|
22
45
|
}
|
|
@@ -62,6 +85,11 @@ interface IQueueConsumer<T = unknown> {
|
|
|
62
85
|
}
|
|
63
86
|
//#endregion
|
|
64
87
|
//#region src/queue/consumer-registry.d.ts
|
|
88
|
+
/** A registered consumer class together with the message types it handles. */
|
|
89
|
+
interface ConsumerRegistration {
|
|
90
|
+
consumerClass: Constructor<IQueueConsumer>;
|
|
91
|
+
messageTypes: string[];
|
|
92
|
+
}
|
|
65
93
|
/**
|
|
66
94
|
* Consumer Registry
|
|
67
95
|
*
|
|
@@ -94,29 +122,35 @@ interface IQueueConsumer<T = unknown> {
|
|
|
94
122
|
* ```
|
|
95
123
|
*/
|
|
96
124
|
declare class ConsumerRegistry {
|
|
97
|
-
/** Map from message type to
|
|
98
|
-
private
|
|
99
|
-
/**
|
|
100
|
-
private
|
|
125
|
+
/** Map from message type to consumer classes handling that type */
|
|
126
|
+
private classesByType;
|
|
127
|
+
/** All registrations (for iteration / listing) */
|
|
128
|
+
private registrations;
|
|
129
|
+
/** Registered consumer classes (dedupe) */
|
|
130
|
+
private registeredClasses;
|
|
101
131
|
/**
|
|
102
|
-
* Register a queue consumer
|
|
132
|
+
* Register a queue consumer class.
|
|
103
133
|
*
|
|
104
|
-
* Indexes the
|
|
134
|
+
* Indexes the class by each of its declared message types. A fresh instance
|
|
135
|
+
* is resolved per message from the request-scoped container at dispatch time —
|
|
136
|
+
* consumers are never held as long-lived singletons, so they may safely inject
|
|
137
|
+
* request-scoped providers (`@InjectQueue`, i18n, auth context, …).
|
|
105
138
|
*
|
|
106
|
-
* @param
|
|
139
|
+
* @param consumerClass - Queue consumer class to register
|
|
140
|
+
* @param messageTypes - Message types the consumer handles
|
|
107
141
|
*/
|
|
108
|
-
register(
|
|
142
|
+
register(consumerClass: Constructor<IQueueConsumer>, messageTypes: string[]): void;
|
|
109
143
|
/**
|
|
110
|
-
* Get all
|
|
144
|
+
* Get all consumer classes that can handle a specific message type.
|
|
111
145
|
*
|
|
112
|
-
* Returns
|
|
113
|
-
*
|
|
114
|
-
* -
|
|
146
|
+
* Returns classes that either declare the message type explicitly or use the
|
|
147
|
+
* `'*'` wildcard. Callers resolve a fresh instance per message from the
|
|
148
|
+
* request-scoped container.
|
|
115
149
|
*
|
|
116
150
|
* @param messageType - The message type to find consumers for
|
|
117
|
-
* @returns Array of
|
|
151
|
+
* @returns Array of consumer classes that can handle this message type
|
|
118
152
|
*/
|
|
119
|
-
|
|
153
|
+
getConsumerClasses(messageType: string): Constructor<IQueueConsumer>[];
|
|
120
154
|
/**
|
|
121
155
|
* Check if any consumers can handle a message type
|
|
122
156
|
*
|
|
@@ -131,12 +165,10 @@ declare class ConsumerRegistry {
|
|
|
131
165
|
*/
|
|
132
166
|
getMessageTypes(): string[];
|
|
133
167
|
/**
|
|
134
|
-
* Get all
|
|
135
|
-
*
|
|
136
|
-
* @returns Array of all registered consumers
|
|
168
|
+
* Get all consumer registrations (class + message types) for listing.
|
|
137
169
|
*/
|
|
138
|
-
|
|
170
|
+
getRegistrations(): readonly ConsumerRegistration[];
|
|
139
171
|
}
|
|
140
172
|
//#endregion
|
|
141
|
-
export {
|
|
142
|
-
//# sourceMappingURL=consumer-registry-
|
|
173
|
+
export { QueueBinding as a, QueueMessage as i, ConsumerRegistry as n, IQueueConsumer as r, ConsumerRegistration as t };
|
|
174
|
+
//# sourceMappingURL=consumer-registry-D3iMTSdy.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consumer-registry-D3iMTSdy.d.mts","names":[],"sources":["../src/queue/queue-binding.ts","../src/queue/queue-consumer.ts","../src/queue/consumer-registry.ts"],"mappings":";;;;;;;KAwBK,mBAAA,GAAsB,OAAA,eACX,UAAA,GAAa,UAAA,CAAW,CAAA,UAAW,KAAA,GAAQ,CAAA,iBAAkB,UAAA;AAAU;AAWvF;;;;AAEuB;;AAbgE,KAW3E,YAAA,IAAgB,mBAAA,6BAExB,mBAAmB;;;;;;AAnBiB;;;UCXvB,YAAA;EDiBY;ECf3B,EAAA;EDeiD;ECbjD,IAAA;EDa2E;ECX3E,OAAA,EAAS,CAAA;EDUuB;ECRhC,QAAA;IACE,MAAA;IACA,cAAA;IDOY;;;;;;ICAZ,OAAA,GAAU,YAAY;IAAA,CACrB,GAAA;EAAA;AAAA;;;ADYkB;;;;AC9BvB;;;;;;;;;;;;UAwCiB,cAAA;EAvBH;;;AACE;AAsBhB;;EAvBc,SA8BH,YAAA;EAOoB;;;;;EAA7B,MAAA,CAAO,OAAA,EAAS,YAAA,CAAa,CAAA,IAAK,OAAA;EAQgB;;;;;;EAAlD,OAAA,EAAS,KAAA,EAAO,KAAA,EAAO,OAAA,EAAS,YAAA,CAAa,CAAA,IAAK,OAAA;AAAA;;;;UChEnC,oBAAA;EACf,aAAA,EAAe,WAAW,CAAC,cAAA;EAC3B,YAAA;AAAA;;;;;;;;;;;;;;;;;;;AFiBqF;AAWvF;;;;AAEuB;;;;AC9BvB;;;;cCmCa,gBAAA;EDjCX;EAAA,QCmCQ,aAAA;ED/BR;EAAA,QCkCQ,aAAA;EDhCR;EAAA,QCmCQ,iBAAA;EDjCN;;;;;AAQY;AAsBhB;;;;;ECgBE,QAAA,CAAS,aAAA,EAAe,WAAA,CAAY,cAAA,GAAiB,YAAA;EDMrC;;;;;;;;;;ECmBhB,kBAAA,CAAmB,WAAA,WAAsB,WAAA,CAAY,cAAA;ED3B9C;;;;;;ECyCP,YAAA,CAAa,WAAA;EDjCU;;;AAAkC;;EC0CzD,eAAA;;AA1GF;;EAiHE,gBAAA,aAA6B,oBAAA;AAAA"}
|
|
@@ -49,4 +49,4 @@ function runWithContainer(container, fn) {
|
|
|
49
49
|
//#endregion
|
|
50
50
|
export { ApplicationError as a, ContainerNotInitializedError as i, getContainer as n, runWithContainer as r, containerStorage as t };
|
|
51
51
|
|
|
52
|
-
//# sourceMappingURL=container-storage-
|
|
52
|
+
//# sourceMappingURL=container-storage-BmOJ4_Na.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"container-storage-
|
|
1
|
+
{"version":3,"file":"container-storage-BmOJ4_Na.mjs","names":[],"sources":["../src/errors/application-error.ts","../src/errors/container-not-initialized.error.ts","../src/di/container-storage.ts"],"sourcesContent":["export class ApplicationError extends Error {\n public readonly timestamp: string\n\n constructor(message?: string, cause?: unknown) {\n super(message ?? 'Internal Server Error', cause !== undefined ? { cause } : undefined)\n\n Object.setPrototypeOf(this, new.target.prototype)\n\n this.name = this.constructor.name\n this.timestamp = new Date().toISOString()\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- captureStackTrace is V8-specific, not always present\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, this.constructor)\n }\n }\n}\n","import { ApplicationError } from './application-error'\n\nexport class ContainerNotInitializedError extends ApplicationError {\n constructor() {\n super('Application container has not been initialized')\n }\n}\n","import { AsyncLocalStorage } from 'node:async_hooks'\nimport { ContainerNotInitializedError } from '../errors/container-not-initialized.error'\nimport type { Container } from './container'\n\n/**\n * AsyncLocalStorage for the application container.\n *\n * Set by `Application.initialize()` — all code from that point onward\n * (Stratal handlers, TestingModuleBuilder, standalone functions like `route()`)\n * can access the container without DI or static singletons.\n */\nexport const containerStorage = new AsyncLocalStorage<Container>()\n\n/**\n * Get the application container from AsyncLocalStorage.\n *\n * @throws ContainerNotInitializedError if called outside `Application.initialize()` scope\n */\nexport function getContainer(): Container {\n const container = containerStorage.getStore()\n if (!container) {\n throw new ContainerNotInitializedError()\n }\n return container\n}\n\n/**\n * Run a function within a container context.\n *\n * @param container - The application container to store\n * @param fn - The function to execute with container access\n */\nexport function runWithContainer<T>(container: Container, fn: () => T): T {\n return containerStorage.run(container, fn)\n}\n"],"mappings":";;AAAA,IAAa,mBAAb,cAAsC,MAAM;CAC1C;CAEA,YAAY,SAAkB,OAAiB;EAC7C,MAAM,WAAW,yBAAyB,UAAU,KAAA,IAAY,EAAE,MAAM,IAAI,KAAA,CAAS;EAErF,OAAO,eAAe,MAAM,IAAI,OAAO,SAAS;EAEhD,KAAK,OAAO,KAAK,YAAY;EAC7B,KAAK,6BAAY,IAAI,KAAK,GAAE,YAAY;EAGxC,IAAI,MAAM,mBACR,MAAM,kBAAkB,MAAM,KAAK,WAAW;CAElD;AACF;;;ACdA,IAAa,+BAAb,cAAkD,iBAAiB;CACjE,cAAc;EACZ,MAAM,gDAAgD;CACxD;AACF;;;;;;;;;;ACKA,MAAa,mBAAmB,IAAI,kBAA6B;;;;;;AAOjE,SAAgB,eAA0B;CACxC,MAAM,YAAY,iBAAiB,SAAS;CAC5C,IAAI,CAAC,WACH,MAAM,IAAI,6BAA6B;CAEzC,OAAO;AACT;;;;;;;AAQA,SAAgB,iBAAoB,WAAsB,IAAgB;CACxE,OAAO,iBAAiB,IAAI,WAAW,EAAE;AAC3C"}
|