stratal 0.0.21 → 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 +2 -2
- package/dist/bin/cloudflare-workers-loader.mjs +80 -7
- package/dist/bin/cloudflare-workers-loader.mjs.map +1 -1
- package/dist/bin/quarry.mjs +84 -160
- package/dist/bin/quarry.mjs.map +1 -1
- package/dist/cache/index.d.mts +8 -46
- package/dist/cache/index.d.mts.map +1 -1
- package/dist/cache/index.mjs +134 -97
- package/dist/cache/index.mjs.map +1 -1
- package/dist/{cache.service-DsnKuNyO.d.mts → cache.service-uElmBtdS.d.mts} +29 -39
- package/dist/cache.service-uElmBtdS.d.mts.map +1 -0
- package/dist/{command-BgSlsS4M.mjs → command-BvmUAPPQ.mjs} +15 -4
- package/dist/command-BvmUAPPQ.mjs.map +1 -0
- package/dist/{command-Cmmf0oHX.d.mts → command-CPhFHjG3.d.mts} +3 -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 +24 -11
- package/dist/config/index.d.mts.map +1 -1
- package/dist/config/index.mjs +32 -57
- package/dist/config/index.mjs.map +1 -1
- package/dist/{consumer-registry-B7yUNh0q.d.mts → consumer-registry-D3iMTSdy.d.mts} +54 -22
- package/dist/consumer-registry-D3iMTSdy.d.mts.map +1 -0
- package/dist/container-storage-BmOJ4_Na.mjs +52 -0
- package/dist/container-storage-BmOJ4_Na.mjs.map +1 -0
- package/dist/{controller.decorator-B9vwn0zK.mjs → controller.decorator-C5UVeJS3.mjs} +8 -8
- package/dist/controller.decorator-C5UVeJS3.mjs.map +1 -0
- package/dist/cron/index.d.mts +103 -7
- 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-DQSK8uoV.mjs → cron.module-Bgzq5hiT.mjs} +47 -17
- package/dist/cron.module-Bgzq5hiT.mjs.map +1 -0
- package/dist/decorate-CuAoSZvs.mjs +16 -0
- package/dist/deep-merge-ByiAOZ3r.mjs +18 -0
- package/dist/deep-merge-ByiAOZ3r.mjs.map +1 -0
- package/dist/di/index.d.mts +2 -2
- package/dist/di/index.mjs +4 -3
- package/dist/di-DseMn-z9.mjs +524 -0
- package/dist/di-DseMn-z9.mjs.map +1 -0
- package/dist/email/index.d.mts +40 -122
- package/dist/email/index.d.mts.map +1 -1
- package/dist/email/index.mjs +446 -131
- package/dist/email/index.mjs.map +1 -1
- package/dist/en-CDZBMcc1.mjs +202 -0
- package/dist/en-CDZBMcc1.mjs.map +1 -0
- package/dist/{env-D1rcZ8_r.d.mts → env-ug22bJj7.d.mts} +1 -1
- package/dist/env-ug22bJj7.d.mts.map +1 -0
- package/dist/errors/index.d.mts +2 -2
- package/dist/errors/index.mjs +4 -2
- package/dist/errors-mXYxG0XB.mjs +333 -0
- package/dist/errors-mXYxG0XB.mjs.map +1 -0
- 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-CzCV8jI8.mjs → events-BXJGZjpG.mjs} +23 -13
- package/dist/events-BXJGZjpG.mjs.map +1 -0
- package/dist/exception-context-kEoMFwze.mjs +429 -0
- package/dist/exception-context-kEoMFwze.mjs.map +1 -0
- package/dist/{gateway-context-CXmXtaUP.mjs → gateway-context-TMu_AlJt.mjs} +38 -31
- package/dist/gateway-context-TMu_AlJt.mjs.map +1 -0
- 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-DU1_J9YA.mjs → guards-DALPXy3_.mjs} +6 -5
- package/dist/guards-DALPXy3_.mjs.map +1 -0
- package/dist/hono-app-CvV3hOfT.mjs +161 -0
- package/dist/hono-app-CvV3hOfT.mjs.map +1 -0
- package/dist/{http-method.decorator-BrgHMdLQ.mjs → http-method.decorator-ByWZb9DO.mjs} +7 -6
- package/dist/http-method.decorator-ByWZb9DO.mjs.map +1 -0
- package/dist/i18n/index.d.mts +238 -3
- package/dist/i18n/index.d.mts.map +1 -0
- package/dist/i18n/index.mjs +39 -3
- package/dist/i18n/index.mjs.map +1 -0
- package/dist/i18n/messages/en/index.d.mts +2 -2
- package/dist/i18n/messages/en/index.mjs +2 -2
- package/dist/i18n/utils/index.d.mts +4 -26
- package/dist/i18n/utils/index.d.mts.map +1 -1
- package/dist/i18n/utils/index.mjs +2 -2
- package/dist/i18n/validation/index.d.mts +3 -2
- package/dist/i18n/validation/index.mjs +4 -2
- package/dist/i18n.module-DRQAZoSZ.mjs +222 -0
- package/dist/i18n.module-DRQAZoSZ.mjs.map +1 -0
- package/dist/i18n.tokens-CZ_v8oyS.mjs +19 -0
- package/dist/i18n.tokens-CZ_v8oyS.mjs.map +1 -0
- package/dist/{index-7-hU3GTV.d.mts → index-0ItCjaqw.d.mts} +1 -1
- package/dist/index-0ItCjaqw.d.mts.map +1 -0
- package/dist/index-B5JBRcWD.d.mts +544 -0
- package/dist/index-B5JBRcWD.d.mts.map +1 -0
- package/dist/index-BUt92sAE.d.mts +124 -0
- package/dist/index-BUt92sAE.d.mts.map +1 -0
- package/dist/{index-ByOyTmqf.d.mts → index-B_JoEl3V.d.mts} +751 -2229
- package/dist/index-B_JoEl3V.d.mts.map +1 -0
- package/dist/index-DtBNIFuP.d.mts +42 -0
- package/dist/index-DtBNIFuP.d.mts.map +1 -0
- package/dist/{index-C1KvMncZ.d.mts → index-HgOLNruQ.d.mts} +3 -108
- package/dist/index-HgOLNruQ.d.mts.map +1 -0
- package/dist/index.d.mts +6 -43
- package/dist/index.mjs +3 -2
- package/dist/{is-command-C6a7WTPw.mjs → is-command-CEPO9n8c.mjs} +2 -2
- package/dist/{is-command-C6a7WTPw.mjs.map → is-command-CEPO9n8c.mjs.map} +1 -1
- package/dist/{is-seeder-CebjZCDn.mjs → is-seeder-Gvh_AM71.mjs} +1 -1
- package/dist/{is-seeder-CebjZCDn.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 +2 -2
- package/dist/logger/index.mjs +170 -2
- package/dist/logger/index.mjs.map +1 -0
- package/dist/macroable/index.d.mts +2 -2
- package/dist/macroable/index.mjs +1 -1
- package/dist/{macroable-BmufBshB.mjs → macroable-cvDTFZ_A.mjs} +1 -1
- package/dist/{macroable-BmufBshB.mjs.map → macroable-cvDTFZ_A.mjs.map} +1 -1
- package/dist/metadata-DzzprcID.mjs +39 -0
- package/dist/metadata-DzzprcID.mjs.map +1 -0
- package/dist/module/index.d.mts +7 -24
- 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-registry-Dm-pqHd3.mjs +554 -0
- 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 -3
- package/dist/openapi-CstuTM8S.mjs +309 -0
- package/dist/openapi-CstuTM8S.mjs.map +1 -0
- package/dist/{openapi-tools.service-Zs-Ewv7F.mjs → openapi-tools.service-BC5EC3R3.mjs} +8 -2
- package/dist/openapi-tools.service-BC5EC3R3.mjs.map +1 -0
- package/dist/{openapi.service-Bt9bCIrd.d.mts → openapi.service-YhTiJ1bO.d.mts} +3 -3
- package/dist/openapi.service-YhTiJ1bO.d.mts.map +1 -0
- package/dist/quarry/index.d.mts +14 -163
- package/dist/quarry/index.d.mts.map +1 -1
- package/dist/quarry/index.mjs +6 -5
- package/dist/quarry/runner.d.mts +184 -0
- package/dist/quarry/runner.d.mts.map +1 -0
- package/dist/quarry/runner.mjs +945 -0
- package/dist/quarry/runner.mjs.map +1 -0
- package/dist/quarry-registry-CXg0RFXq.d.mts +69 -0
- package/dist/quarry-registry-CXg0RFXq.d.mts.map +1 -0
- package/dist/quarry.module-BuRPGMDm.mjs +312 -0
- package/dist/quarry.module-BuRPGMDm.mjs.map +1 -0
- package/dist/queue/index.d.mts +3 -3
- package/dist/queue/index.mjs +57 -48
- 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-DuonKeYm.mjs → r2-storage.provider-DCxQt9dD.mjs} +6 -6
- package/dist/r2-storage.provider-DCxQt9dD.mjs.map +1 -0
- package/dist/{rate-limit.decorator-6qzNcSOt.mjs → rate-limit.decorator-BPAie_p3.mjs} +6 -11
- package/dist/rate-limit.decorator-BPAie_p3.mjs.map +1 -0
- package/dist/rate-limiter/index.d.mts +11 -50
- package/dist/rate-limiter/index.d.mts.map +1 -1
- package/dist/rate-limiter/index.mjs +33 -42
- 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 -7
- 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 +16 -11
- package/dist/seeder/index.d.mts.map +1 -1
- package/dist/seeder/index.mjs +5 -3
- package/dist/seeder-7ubkms-Y.mjs +81 -0
- 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-BQPbv2In.mjs → signed-url-DIU0sK_6.mjs} +1 -1
- package/dist/{signed-url-BQPbv2In.mjs.map → signed-url-DIU0sK_6.mjs.map} +1 -1
- package/dist/storage/index.d.mts +15 -39
- package/dist/storage/index.d.mts.map +1 -1
- package/dist/storage/index.mjs +3 -3
- 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-D8CBP72Z.mjs → storage-MDZypIE9.mjs} +66 -59
- package/dist/storage-MDZypIE9.mjs.map +1 -0
- package/dist/{storage-provider.interface-Bd6vA4ak.d.mts → storage-provider.interface-ClUwxz4S.d.mts} +2 -3
- package/dist/storage-provider.interface-ClUwxz4S.d.mts.map +1 -0
- package/dist/storage.error-Dnib4VHc.mjs +8 -0
- package/dist/storage.error-Dnib4VHc.mjs.map +1 -0
- package/dist/stratal-DL9M38_s.mjs +383 -0
- package/dist/stratal-DL9M38_s.mjs.map +1 -0
- package/dist/stratal-DwDJPY9N.d.mts +43 -0
- package/dist/stratal-DwDJPY9N.d.mts.map +1 -0
- 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-cySNS_lp.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-BUdlhnCK.mjs → usage-generator-DAWYasuP.mjs} +7 -4
- package/dist/usage-generator-DAWYasuP.mjs.map +1 -0
- package/dist/validation-CpOjviyT.mjs +49 -0
- package/dist/validation-CpOjviyT.mjs.map +1 -0
- package/dist/validation.context-CRvmrhq7.mjs +117 -0
- package/dist/validation.context-CRvmrhq7.mjs.map +1 -0
- package/dist/versioning.service-C6aHky8-.mjs +36 -0
- package/dist/versioning.service-C6aHky8-.mjs.map +1 -0
- package/dist/websocket/index.d.mts +16 -14
- package/dist/websocket/index.d.mts.map +1 -1
- package/dist/websocket/index.mjs +2 -2
- package/dist/workers/index.d.mts +2 -2
- package/dist/workers/index.d.mts.map +1 -1
- package/dist/workers/index.mjs +3 -2
- package/dist/workers/index.mjs.map +1 -1
- package/dist/zod-eKqqhZ5_.mjs +72 -0
- package/dist/zod-eKqqhZ5_.mjs.map +1 -0
- package/dist/{index-Bnpfq6uk.d.mts → zod-wecrEVAs.d.mts} +63 -133
- package/dist/zod-wecrEVAs.d.mts.map +1 -0
- package/package.json +28 -39
- package/dist/base-email.provider-CfQCA08m.mjs +0 -42
- package/dist/base-email.provider-CfQCA08m.mjs.map +0 -1
- package/dist/cache.service-DsnKuNyO.d.mts.map +0 -1
- package/dist/cache.tokens-B7Rw1C9Q.mjs +0 -6
- package/dist/cache.tokens-B7Rw1C9Q.mjs.map +0 -1
- package/dist/colors-DJaRDXoS.mjs +0 -16
- package/dist/colors-DJaRDXoS.mjs.map +0 -1
- package/dist/command-BgSlsS4M.mjs.map +0 -1
- package/dist/command-Cmmf0oHX.d.mts.map +0 -1
- package/dist/consumer-registry-B7yUNh0q.d.mts.map +0 -1
- package/dist/controller.decorator-B9vwn0zK.mjs.map +0 -1
- package/dist/cron-manager-CmTimEjf.d.mts +0 -131
- package/dist/cron-manager-CmTimEjf.d.mts.map +0 -1
- package/dist/cron-manager-DQSK8uoV.mjs.map +0 -1
- package/dist/en-DSH_bhh6.mjs +0 -308
- package/dist/en-DSH_bhh6.mjs.map +0 -1
- package/dist/env-D1rcZ8_r.d.mts.map +0 -1
- package/dist/errors-COW9-Mar.mjs +0 -1739
- package/dist/errors-COW9-Mar.mjs.map +0 -1
- package/dist/errors-ORxu1-Bb.mjs +0 -74
- package/dist/errors-ORxu1-Bb.mjs.map +0 -1
- package/dist/events-CzCV8jI8.mjs.map +0 -1
- package/dist/gateway-context-CXmXtaUP.mjs.map +0 -1
- package/dist/guards-DU1_J9YA.mjs.map +0 -1
- package/dist/http-method.decorator-BrgHMdLQ.mjs.map +0 -1
- package/dist/i18n.module-CzXLW9Hy.mjs +0 -2532
- package/dist/i18n.module-CzXLW9Hy.mjs.map +0 -1
- package/dist/index-7-hU3GTV.d.mts.map +0 -1
- package/dist/index-Bnpfq6uk.d.mts.map +0 -1
- package/dist/index-ByOyTmqf.d.mts.map +0 -1
- package/dist/index-C1KvMncZ.d.mts.map +0 -1
- package/dist/index-DBd_2wv8.d.mts +0 -263
- package/dist/index-DBd_2wv8.d.mts.map +0 -1
- package/dist/index-DUzWs0z7.d.mts +0 -494
- package/dist/index-DUzWs0z7.d.mts.map +0 -1
- package/dist/index.d.mts.map +0 -1
- package/dist/logger-DlV7NtvD.mjs +0 -440
- package/dist/logger-DlV7NtvD.mjs.map +0 -1
- package/dist/module-BzLg57FK.mjs +0 -866
- package/dist/module-BzLg57FK.mjs.map +0 -1
- package/dist/openapi-tools.service-Zs-Ewv7F.mjs.map +0 -1
- package/dist/openapi.service-Bt9bCIrd.d.mts.map +0 -1
- package/dist/quarry-registry-BwY2hOxm.mjs +0 -699
- package/dist/quarry-registry-BwY2hOxm.mjs.map +0 -1
- package/dist/queue.module-BhCjZp6H.mjs +0 -409
- package/dist/queue.module-BhCjZp6H.mjs.map +0 -1
- package/dist/r2-storage.provider-DuonKeYm.mjs.map +0 -1
- package/dist/rate-limit.decorator-6qzNcSOt.mjs.map +0 -1
- package/dist/resend.provider-DB4IlFjG.mjs +0 -68
- package/dist/resend.provider-DB4IlFjG.mjs.map +0 -1
- package/dist/seeder-zoEfEw9i.mjs +0 -138
- package/dist/seeder-zoEfEw9i.mjs.map +0 -1
- package/dist/setup-CefZKV_e.mjs +0 -37
- package/dist/setup-CefZKV_e.mjs.map +0 -1
- package/dist/smtp.provider-B6D7zuWX.mjs +0 -76
- package/dist/smtp.provider-B6D7zuWX.mjs.map +0 -1
- package/dist/storage-D8CBP72Z.mjs.map +0 -1
- package/dist/storage-provider.interface-Bd6vA4ak.d.mts.map +0 -1
- package/dist/stratal-CNwpbSZl.mjs +0 -535
- package/dist/stratal-CNwpbSZl.mjs.map +0 -1
- package/dist/types-cySNS_lp.d.mts.map +0 -1
- package/dist/usage-generator-BUdlhnCK.mjs.map +0 -1
- package/dist/validation-DtJwAv7O.mjs +0 -248
- package/dist/validation-DtJwAv7O.mjs.map +0 -1
- /package/dist/{chunk-D1SwGrFN.mjs → chunk-BBjsoOtd.mjs} +0 -0
|
@@ -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,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Sn as CommandResult, bn as CommandInput, xn as CommandInternals } from "./index-B_JoEl3V.mjs";
|
|
2
|
+
|
|
2
3
|
//#region src/quarry/constants.d.ts
|
|
3
4
|
/**
|
|
4
5
|
* Symbol key for storing internal mutable state on Command instances.
|
|
@@ -117,4 +118,4 @@ declare abstract class Command {
|
|
|
117
118
|
}
|
|
118
119
|
//#endregion
|
|
119
120
|
export { Command as t };
|
|
120
|
-
//# 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,8 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as Macroable } from "../index-
|
|
3
|
-
import {
|
|
4
|
-
import { InjectionToken } from "tsyringe";
|
|
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";
|
|
5
4
|
|
|
5
|
+
//#region src/config/config.error.d.ts
|
|
6
|
+
declare class ConfigError extends ApplicationError {}
|
|
7
|
+
//#endregion
|
|
6
8
|
//#region src/config/config.tokens.d.ts
|
|
7
9
|
declare const CONFIG_TOKENS: {
|
|
8
10
|
readonly ConfigService: symbol;
|
|
@@ -141,8 +143,11 @@ declare class ConfigModule implements OnInitialize {
|
|
|
141
143
|
*/
|
|
142
144
|
static forRoot(options: ConfigModuleOptions): DynamicModule;
|
|
143
145
|
/**
|
|
144
|
-
* Initialize config service with merged namespaces
|
|
145
|
-
* Called after all providers are registered
|
|
146
|
+
* Initialize config service with merged namespaces.
|
|
147
|
+
* Called after all providers are registered. No-op when the module
|
|
148
|
+
* was imported without `forRoot()` — the store stays empty and
|
|
149
|
+
* `ConfigService.get()` will throw `ConfigError` only if
|
|
150
|
+
* someone actually asks for a key.
|
|
146
151
|
*/
|
|
147
152
|
onInitialize(context: ModuleContext): void;
|
|
148
153
|
}
|
|
@@ -223,6 +228,12 @@ interface IConfigService<T extends object = ModuleConfig> {
|
|
|
223
228
|
*
|
|
224
229
|
* Per-request overrides live on {@link ConfigService}, which reads
|
|
225
230
|
* through to this store for any key not explicitly overridden.
|
|
231
|
+
*
|
|
232
|
+
* If the store is never initialized (no `ConfigModule.forRoot()`), it
|
|
233
|
+
* behaves like an empty config: `has()` returns `false`, `all()` returns
|
|
234
|
+
* `{}`, and `get()` throws {@link ConfigError} for any path —
|
|
235
|
+
* the same error you'd get for a missing key on an initialized store.
|
|
236
|
+
* Resolving the store via DI never throws on its own.
|
|
226
237
|
*/
|
|
227
238
|
declare class ConfigStore<T extends object = ModuleConfig> {
|
|
228
239
|
private data;
|
|
@@ -232,15 +243,18 @@ declare class ConfigStore<T extends object = ModuleConfig> {
|
|
|
232
243
|
*/
|
|
233
244
|
initialize(config: T): void;
|
|
234
245
|
/**
|
|
235
|
-
* Get config value using dot notation.
|
|
246
|
+
* Get config value using dot notation. Throws
|
|
247
|
+
* {@link ConfigError} if the path is absent.
|
|
236
248
|
*/
|
|
237
249
|
get<P extends ConfigPath<T>>(path: P): ConfigPathValue<T, P>;
|
|
238
250
|
/**
|
|
239
|
-
* Check if a config path exists.
|
|
251
|
+
* Check if a config path exists. Returns `false` when the store has
|
|
252
|
+
* not been initialized.
|
|
240
253
|
*/
|
|
241
254
|
has(path: ConfigPath<T>): boolean;
|
|
242
255
|
/**
|
|
243
|
-
* Get the entire config object (readonly snapshot).
|
|
256
|
+
* Get the entire config object (readonly snapshot). Returns an empty
|
|
257
|
+
* object when the store has not been initialized.
|
|
244
258
|
*/
|
|
245
259
|
all(): Readonly<T>;
|
|
246
260
|
/**
|
|
@@ -249,7 +263,6 @@ declare class ConfigStore<T extends object = ModuleConfig> {
|
|
|
249
263
|
isInitialized(): boolean;
|
|
250
264
|
private getByPath;
|
|
251
265
|
private isDangerousKey;
|
|
252
|
-
private ensureInitialized;
|
|
253
266
|
private deepClone;
|
|
254
267
|
}
|
|
255
268
|
//#endregion
|
|
@@ -313,5 +326,5 @@ declare class ConfigService<T extends object = ModuleConfig> extends Macroable i
|
|
|
313
326
|
private deepClone;
|
|
314
327
|
}
|
|
315
328
|
//#endregion
|
|
316
|
-
export { CONFIG_TOKENS, ConfigModule, type ConfigModuleOptions, type ConfigNamespace, type ConfigPath, type ConfigPathValue, ConfigService, ConfigStore, ConfigValidationError, type IConfigService, type InferConfigType, type ModuleConfig, registerAs };
|
|
329
|
+
export { CONFIG_TOKENS, ConfigError, ConfigModule, type ConfigModuleOptions, type ConfigNamespace, type ConfigPath, type ConfigPathValue, ConfigService, ConfigStore, ConfigValidationError, type IConfigService, type InferConfigType, type ModuleConfig, registerAs };
|
|
317
330
|
//# sourceMappingURL=index.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../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":"
|
|
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,8 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as
|
|
3
|
-
import { t as
|
|
4
|
-
import
|
|
5
|
-
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";
|
|
8
|
+
//#region src/config/config.error.ts
|
|
9
|
+
var ConfigError = class extends ApplicationError {};
|
|
10
|
+
//#endregion
|
|
6
11
|
//#region src/config/config.tokens.ts
|
|
7
12
|
const CONFIG_TOKENS = {
|
|
8
13
|
ConfigService: Symbol.for("stratal:config:service"),
|
|
@@ -19,32 +24,6 @@ var ConfigValidationError = class extends Error {
|
|
|
19
24
|
}
|
|
20
25
|
};
|
|
21
26
|
//#endregion
|
|
22
|
-
//#region src/config/errors/config-module-not-initialized.error.ts
|
|
23
|
-
/**
|
|
24
|
-
* Error thrown when ConfigModule's onInitialize runs but forRoot() was never called.
|
|
25
|
-
*
|
|
26
|
-
* This means the module was imported without calling ConfigModule.forRoot({ load: [...] }).
|
|
27
|
-
*/
|
|
28
|
-
var ConfigModuleNotInitializedError = class extends ApplicationError {
|
|
29
|
-
constructor() {
|
|
30
|
-
super("errors.configModuleNotInitialized", ERROR_CODES.SYSTEM.CONFIG_MODULE_NOT_INITIALIZED);
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
//#endregion
|
|
34
|
-
//#region src/config/errors/config-not-initialized.error.ts
|
|
35
|
-
/**
|
|
36
|
-
* ConfigNotInitializedError
|
|
37
|
-
*
|
|
38
|
-
* Thrown when attempting to access ConfigService before it has been initialized.
|
|
39
|
-
* This typically indicates that ConfigModule's onInitialize hook hasn't run yet,
|
|
40
|
-
* or the module wasn't registered properly.
|
|
41
|
-
*/
|
|
42
|
-
var ConfigNotInitializedError = class extends ApplicationError {
|
|
43
|
-
constructor() {
|
|
44
|
-
super("errors.configNotInitialized", ERROR_CODES.SYSTEM.CONFIG_NOT_INITIALIZED);
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
//#endregion
|
|
48
27
|
//#region src/config/services/config.service.ts
|
|
49
28
|
let ConfigService = class ConfigService extends Macroable {
|
|
50
29
|
store;
|
|
@@ -149,11 +128,7 @@ let ConfigService = class ConfigService extends Macroable {
|
|
|
149
128
|
return JSON.parse(JSON.stringify(obj));
|
|
150
129
|
}
|
|
151
130
|
};
|
|
152
|
-
ConfigService = __decorate([
|
|
153
|
-
Transient(CONFIG_TOKENS.ConfigService),
|
|
154
|
-
__decorateParam(0, inject(CONFIG_TOKENS.ConfigStore)),
|
|
155
|
-
__decorateMetadata("design:paramtypes", [Object])
|
|
156
|
-
], ConfigService);
|
|
131
|
+
ConfigService = __decorate([Request(CONFIG_TOKENS.ConfigService), __decorateParam(0, inject(CONFIG_TOKENS.ConfigStore))], ConfigService);
|
|
157
132
|
//#endregion
|
|
158
133
|
//#region src/config/services/config.store.ts
|
|
159
134
|
let ConfigStore = class ConfigStore {
|
|
@@ -166,25 +141,27 @@ let ConfigStore = class ConfigStore {
|
|
|
166
141
|
this.data = this.deepClone(config);
|
|
167
142
|
}
|
|
168
143
|
/**
|
|
169
|
-
* Get config value using dot notation.
|
|
144
|
+
* Get config value using dot notation. Throws
|
|
145
|
+
* {@link ConfigError} if the path is absent.
|
|
170
146
|
*/
|
|
171
147
|
get(path) {
|
|
172
|
-
this.
|
|
173
|
-
|
|
148
|
+
const value = this.getByPath(this.data ?? {}, path);
|
|
149
|
+
if (value === void 0) throw new ConfigError(`Configuration key "${path}" was not found`);
|
|
150
|
+
return value;
|
|
174
151
|
}
|
|
175
152
|
/**
|
|
176
|
-
* Check if a config path exists.
|
|
153
|
+
* Check if a config path exists. Returns `false` when the store has
|
|
154
|
+
* not been initialized.
|
|
177
155
|
*/
|
|
178
156
|
has(path) {
|
|
179
|
-
this.
|
|
180
|
-
return this.getByPath(this.data, path) !== void 0;
|
|
157
|
+
return this.getByPath(this.data ?? {}, path) !== void 0;
|
|
181
158
|
}
|
|
182
159
|
/**
|
|
183
|
-
* Get the entire config object (readonly snapshot).
|
|
160
|
+
* Get the entire config object (readonly snapshot). Returns an empty
|
|
161
|
+
* object when the store has not been initialized.
|
|
184
162
|
*/
|
|
185
163
|
all() {
|
|
186
|
-
this.
|
|
187
|
-
return this.data;
|
|
164
|
+
return this.data ?? {};
|
|
188
165
|
}
|
|
189
166
|
/**
|
|
190
167
|
* True once {@link initialize} has been called.
|
|
@@ -205,15 +182,12 @@ let ConfigStore = class ConfigStore {
|
|
|
205
182
|
isDangerousKey(key) {
|
|
206
183
|
return key === "__proto__" || key === "constructor" || key === "prototype";
|
|
207
184
|
}
|
|
208
|
-
ensureInitialized() {
|
|
209
|
-
if (this.data === void 0) throw new ConfigNotInitializedError();
|
|
210
|
-
}
|
|
211
185
|
deepClone(obj) {
|
|
212
186
|
if (obj === null || typeof obj !== "object") return obj;
|
|
213
187
|
return JSON.parse(JSON.stringify(obj));
|
|
214
188
|
}
|
|
215
189
|
};
|
|
216
|
-
ConfigStore = __decorate([
|
|
190
|
+
ConfigStore = __decorate([Singleton(CONFIG_TOKENS.ConfigStore)], ConfigStore);
|
|
217
191
|
//#endregion
|
|
218
192
|
//#region src/config/config.module.ts
|
|
219
193
|
var _ConfigModule;
|
|
@@ -235,11 +209,14 @@ let ConfigModule = _ConfigModule = class ConfigModule {
|
|
|
235
209
|
};
|
|
236
210
|
}
|
|
237
211
|
/**
|
|
238
|
-
* Initialize config service with merged namespaces
|
|
239
|
-
* Called after all providers are registered
|
|
212
|
+
* Initialize config service with merged namespaces.
|
|
213
|
+
* Called after all providers are registered. No-op when the module
|
|
214
|
+
* was imported without `forRoot()` — the store stays empty and
|
|
215
|
+
* `ConfigService.get()` will throw `ConfigError` only if
|
|
216
|
+
* someone actually asks for a key.
|
|
240
217
|
*/
|
|
241
218
|
onInitialize(context) {
|
|
242
|
-
if (!moduleOptions)
|
|
219
|
+
if (!moduleOptions) return;
|
|
243
220
|
const env = context.container.resolve(DI_TOKENS.CloudflareEnv);
|
|
244
221
|
const configStore = context.container.resolve(CONFIG_TOKENS.ConfigStore);
|
|
245
222
|
const mergedConfig = {};
|
|
@@ -254,12 +231,10 @@ let ConfigModule = _ConfigModule = class ConfigModule {
|
|
|
254
231
|
};
|
|
255
232
|
ConfigModule = _ConfigModule = __decorate([Module({ providers: [{
|
|
256
233
|
provide: CONFIG_TOKENS.ConfigStore,
|
|
257
|
-
useClass: ConfigStore
|
|
258
|
-
scope: Scope.Singleton
|
|
234
|
+
useClass: ConfigStore
|
|
259
235
|
}, {
|
|
260
236
|
provide: CONFIG_TOKENS.ConfigService,
|
|
261
|
-
useClass: ConfigService
|
|
262
|
-
scope: Scope.Request
|
|
237
|
+
useClass: ConfigService
|
|
263
238
|
}] })], ConfigModule);
|
|
264
239
|
//#endregion
|
|
265
240
|
//#region src/config/register-as.ts
|
|
@@ -310,6 +285,6 @@ function registerAs(namespace, factory) {
|
|
|
310
285
|
};
|
|
311
286
|
}
|
|
312
287
|
//#endregion
|
|
313
|
-
export { CONFIG_TOKENS, ConfigModule, ConfigService, ConfigStore, ConfigValidationError, registerAs };
|
|
288
|
+
export { CONFIG_TOKENS, ConfigError, ConfigModule, ConfigService, ConfigStore, ConfigValidationError, registerAs };
|
|
314
289
|
|
|
315
290
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/config/config.tokens.ts","../../src/config/config.types.ts","../../src/config/errors/config-module-not-initialized.error.ts","../../src/config/errors/config-not-initialized.error.ts","../../src/config/services/config.service.ts","../../src/config/services/config.store.ts","../../src/config/config.module.ts","../../src/config/register-as.ts"],"sourcesContent":["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'\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 { ApplicationError, ERROR_CODES } from '../../errors'\n\n/**\n * Error thrown when ConfigModule's onInitialize runs but forRoot() was never called.\n *\n * This means the module was imported without calling ConfigModule.forRoot({ load: [...] }).\n */\nexport class ConfigModuleNotInitializedError extends ApplicationError {\n constructor() {\n super(\n 'errors.configModuleNotInitialized',\n ERROR_CODES.SYSTEM.CONFIG_MODULE_NOT_INITIALIZED\n )\n }\n}\n","import { ERROR_CODES } from '../../errors'\nimport { ApplicationError } from '../../errors'\n\n/**\n * ConfigNotInitializedError\n *\n * Thrown when attempting to access ConfigService before it has been initialized.\n * This typically indicates that ConfigModule's onInitialize hook hasn't run yet,\n * or the module wasn't registered properly.\n */\nexport class ConfigNotInitializedError extends ApplicationError {\n constructor() {\n super(\n 'errors.configNotInitialized',\n ERROR_CODES.SYSTEM.CONFIG_NOT_INITIALIZED\n )\n }\n}\n","import { inject } from 'tsyringe'\nimport { Transient } 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@Transient(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 { Transient } from '../../di/decorators'\nimport { CONFIG_TOKENS } from '../config.tokens'\nimport type { ConfigPath, ConfigPathValue, ModuleConfig } from '../config.types'\nimport { ConfigNotInitializedError } from '../errors'\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@Transient(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.\n */\n get<P extends ConfigPath<T>>(path: P): ConfigPathValue<T, P> {\n this.ensureInitialized()\n return this.getByPath(this.data, path) as ConfigPathValue<T, P>\n }\n\n /**\n * Check if a config path exists.\n */\n has(path: ConfigPath<T>): boolean {\n this.ensureInitialized()\n return this.getByPath(this.data, path) !== undefined\n }\n\n /**\n * Get the entire config object (readonly snapshot).\n */\n all(): Readonly<T> {\n this.ensureInitialized()\n return this.data 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 ensureInitialized(): void {\n if (this.data === undefined) {\n throw new ConfigNotInitializedError()\n }\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 { Scope } from '../di/types'\nimport type { z } from '../i18n/validation'\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 { ConfigModuleNotInitializedError } from './errors'\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 scope: Scope.Singleton,\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 scope: Scope.Request,\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\n */\n onInitialize(context: ModuleContext): void {\n if (!moduleOptions) {\n throw new ConfigModuleNotInitializedError()\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 'tsyringe'\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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
//#region src/errors/application-error.ts
|
|
3
|
+
var ApplicationError = class extends Error {
|
|
4
|
+
timestamp;
|
|
5
|
+
constructor(message, cause) {
|
|
6
|
+
super(message ?? "Internal Server Error", cause !== void 0 ? { cause } : void 0);
|
|
7
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
8
|
+
this.name = this.constructor.name;
|
|
9
|
+
this.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
10
|
+
if (Error.captureStackTrace) Error.captureStackTrace(this, this.constructor);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/errors/container-not-initialized.error.ts
|
|
15
|
+
var ContainerNotInitializedError = class extends ApplicationError {
|
|
16
|
+
constructor() {
|
|
17
|
+
super("Application container has not been initialized");
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/di/container-storage.ts
|
|
22
|
+
/**
|
|
23
|
+
* AsyncLocalStorage for the application container.
|
|
24
|
+
*
|
|
25
|
+
* Set by `Application.initialize()` — all code from that point onward
|
|
26
|
+
* (Stratal handlers, TestingModuleBuilder, standalone functions like `route()`)
|
|
27
|
+
* can access the container without DI or static singletons.
|
|
28
|
+
*/
|
|
29
|
+
const containerStorage = new AsyncLocalStorage();
|
|
30
|
+
/**
|
|
31
|
+
* Get the application container from AsyncLocalStorage.
|
|
32
|
+
*
|
|
33
|
+
* @throws ContainerNotInitializedError if called outside `Application.initialize()` scope
|
|
34
|
+
*/
|
|
35
|
+
function getContainer() {
|
|
36
|
+
const container = containerStorage.getStore();
|
|
37
|
+
if (!container) throw new ContainerNotInitializedError();
|
|
38
|
+
return container;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Run a function within a container context.
|
|
42
|
+
*
|
|
43
|
+
* @param container - The application container to store
|
|
44
|
+
* @param fn - The function to execute with container access
|
|
45
|
+
*/
|
|
46
|
+
function runWithContainer(container, fn) {
|
|
47
|
+
return containerStorage.run(container, fn);
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
export { ApplicationError as a, ContainerNotInitializedError as i, getContainer as n, runWithContainer as r, containerStorage as t };
|
|
51
|
+
|
|
52
|
+
//# sourceMappingURL=container-storage-BmOJ4_Na.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|