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/email/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/email/email.error.ts","../../src/email/email.tokens.ts","../../src/email/consumers/email.consumer.ts","../../src/email/services/email.service.ts","../../src/email/services/email-provider-factory.ts","../../src/email/email.module.ts","../../src/email/contracts/email-attachment.ts","../../src/email/contracts/email-message.contract.ts","../../src/email/contracts/send-email.input.ts"],"sourcesContent":["import { ApplicationError } from '../errors'\n\nexport class EmailError extends ApplicationError {}\n","/**\n * Dependency Injection Tokens for Email Module\n *\n * These Symbol-based tokens ensure type-safe dependency injection\n * throughout the email module and prevent naming collisions.\n */\n\n/**\n * Email Module DI Tokens\n */\nexport const EMAIL_TOKENS = {\n /**\n * Email module configuration options\n */\n Options: Symbol.for('stratal:email:options'),\n\n /**\n * Main email service - facade for sending emails via queues\n */\n EmailService: Symbol.for('stratal:email:service'),\n\n /**\n * Factory for creating email provider instances based on configuration\n */\n EmailProviderFactory: Symbol.for('stratal:email:provider:factory'),\n\n /**\n * Email provider interface - abstracts provider implementation\n */\n EmailProvider: Symbol.for('stratal:email:provider'),\n\n /**\n * Queue sender for email dispatch.\n * Bound via EmailModule.forRoot({ queue: 'QUEUE_BINDING_NAME' })\n */\n EmailQueue: Symbol.for('stratal:email:queue'),\n} as const\n","import { inject } from '../../di'\nimport { Transient } from '../../di/decorators'\nimport { LOGGER_TOKENS, type LoggerService } from '../../logger'\nimport type { IQueueConsumer, QueueMessage } from '../../queue/queue-consumer'\nimport { STORAGE_TOKENS, type StorageService } from '../../storage'\nimport type { EmailAttachment, ResolvedEmailAttachment, SendEmailInput } from '../contracts'\nimport { EMAIL_TOKENS } from '../email.tokens'\nimport type { EmailProviderFactory } from '../services/email-provider-factory'\n\n/**\n * Email Consumer\n *\n * Generic queue consumer that handles email.send and email.batch.send messages\n * from ANY queue. Message routing is based on message type, not queue name.\n *\n * This consumer:\n * - Resolves storage-based attachments to streams\n * - Creates email provider instances via factory\n * - Sends emails with proper logging (no PII)\n * - Handles errors with retry support\n *\n * @example\n * ```typescript\n * // Registered in EmailModule\n * @Module({\n * consumers: [EmailConsumer]\n * })\n * ```\n */\n@Transient()\nexport class EmailConsumer implements IQueueConsumer<SendEmailInput> {\n readonly messageTypes = ['email.send', 'email.batch.send']\n\n constructor(\n @inject(LOGGER_TOKENS.LoggerService)\n private readonly logger: LoggerService,\n @inject(EMAIL_TOKENS.EmailProviderFactory)\n private readonly providerFactory: EmailProviderFactory,\n @inject(STORAGE_TOKENS.StorageService)\n private readonly storage: StorageService\n ) { }\n\n async handle(message: QueueMessage<SendEmailInput>): Promise<void> {\n const { type, payload } = message\n const recipientCount = Array.isArray(payload.to) ? payload.to.length : 1\n\n this.logger.info('Processing email message', {\n type,\n recipientCount,\n hasHtml: !!payload.html,\n hasText: !!payload.text,\n hasAttachments: !!payload.attachments?.length,\n })\n\n try {\n // Resolve storage-based attachments before sending\n const resolvedAttachments = await this.resolveAttachments(payload.attachments)\n\n const provider = await this.providerFactory.create()\n const result = await provider.send({\n ...payload,\n attachments: resolvedAttachments,\n })\n\n this.logger.info('Email sent successfully', {\n type,\n recipientCount,\n messageId: result.messageId,\n })\n }\n catch (error) {\n this.logger.error('Failed to send email', {\n type,\n recipientCount,\n error: (error as Error).message,\n })\n throw error // Retry via queue\n }\n }\n\n onError(error: Error, message: QueueMessage<SendEmailInput>): Promise<void> {\n const recipientCount = Array.isArray(message.payload.to)\n ? message.payload.to.length\n : 1\n\n this.logger.error('Email send failed after retries', {\n recipientCount,\n error: error.message,\n stack: error.stack,\n })\n\n return Promise.resolve()\n }\n\n /**\n * Resolve email attachments\n *\n * Converts attachment schemas to resolved attachments.\n * - Inline attachments: decode base64 to Buffer\n * - Storage attachments: pass stream directly (providers support streams)\n */\n private async resolveAttachments(\n attachments: EmailAttachment[] | undefined\n ): Promise<ResolvedEmailAttachment[] | undefined> {\n if (!attachments?.length) return undefined\n\n return Promise.all(attachments.map(async (attachment) => {\n // Check for inline attachment (has content property)\n if ('content' in attachment) {\n return {\n filename: attachment.filename,\n content: Buffer.from(attachment.content, 'base64'),\n contentType: attachment.contentType,\n }\n }\n\n // Storage attachment - pass stream directly to provider\n const result = await this.storage.download(\n attachment.storageKey,\n attachment.disk\n )\n\n return {\n filename: attachment.filename,\n content: result.toStream() ?? Buffer.alloc(0),\n contentType: result.contentType,\n }\n }))\n }\n}\n","import { render } from '@react-email/render'\nimport type { ReactElement } from 'react'\nimport { inject } from '../../di'\nimport { Transient } from '../../di/decorators'\nimport type { IQueueSender } from '../../queue'\nimport type { SendBatchEmailInput, SendEmailInput } from '../contracts'\nimport { EMAIL_TOKENS } from '../email.tokens'\n\nexport type SendEmailInputWithTemplate = Omit<SendEmailInput, 'html' | 'text'> & {\n template?: ReactElement\n}\n\nexport type SendBatchEmailInputWithTemplate = Omit<SendBatchEmailInput, 'messages'> & {\n messages: SendEmailInputWithTemplate[]\n}\n\n/**\n * Email Service\n *\n * Main facade for sending emails. Routes emails to queues for async processing.\n * The queue is injected via EMAIL_TOKENS.EmailQueue, configured by the application\n * via EmailModule.forRoot({ queue: 'QUEUE_BINDING_NAME' }).\n *\n * @example Basic usage\n * ```typescript\n * @inject(EMAIL_TOKENS.EmailService)\n * private readonly emailService: EmailService\n *\n * await this.emailService.send({\n * to: 'user@example.com',\n * subject: 'Welcome',\n * template: <WelcomeEmail name=\"John\" />\n * })\n * ```\n */\n@Transient(EMAIL_TOKENS.EmailService)\nexport class EmailService {\n constructor(\n @inject(EMAIL_TOKENS.EmailQueue)\n protected readonly queue: IQueueSender\n ) { }\n\n /**\n * Send a single email\n *\n * Dispatches the email to the queue for async processing.\n * Supports optional React template rendering.\n *\n * @param input - Email message details\n */\n async send({ template, ...input }: SendEmailInputWithTemplate): Promise<void> {\n await this.queue.dispatch({\n type: 'email.send',\n payload: { ...input, html: template ? await render(template) : undefined },\n })\n }\n\n /**\n * Send multiple emails in a batch\n *\n * Dispatches all emails to the queue for async processing.\n * Supports React template rendering for each message.\n *\n * @param input - Batch email details\n */\n async sendBatch(input: SendBatchEmailInputWithTemplate): Promise<void> {\n for (const message of input.messages) {\n await this.send(message)\n }\n }\n}\n","import { inject } from '../../di'\nimport { Transient } from '../../di/decorators'\nimport type { EmailModuleOptions } from '../email.module'\nimport { EMAIL_TOKENS } from '../email.tokens'\nimport { EmailError } from '../email.error'\nimport type { IEmailProvider } from '../providers/email-provider.interface'\n\n/**\n * Email Provider Factory\n *\n * Creates email provider instances based on configuration.\n * Supports automatic provider selection from module options.\n *\n * Providers are loaded lazily via dynamic imports to avoid pulling in\n * heavy Node.js-only dependencies (e.g. nodemailer) at module parse time,\n * which would break Cloudflare Workers and vitest-pool-workers environments.\n */\n@Transient(EMAIL_TOKENS.EmailProviderFactory)\nexport class EmailProviderFactory {\n constructor(\n @inject(EMAIL_TOKENS.Options)\n private readonly options: EmailModuleOptions\n ) { }\n\n /**\n * Create email provider instance based on configuration\n *\n * @returns Email provider implementation\n * @throws EmailError if provider is not supported\n */\n async create(): Promise<IEmailProvider> {\n switch (this.options.provider) {\n case 'resend': {\n const { ResendProvider } = await import('../providers/resend.provider')\n return new ResendProvider(this.options)\n }\n\n case 'smtp': {\n const { SmtpProvider } = await import('../providers/smtp.provider')\n return new SmtpProvider(this.options)\n }\n\n default:\n throw new EmailError(`Email provider \"${String(this.options.provider)}\" is not supported`)\n }\n }\n}\n","/**\n * Email Module\n *\n * Provides email sending capabilities with provider abstraction.\n * Supports multiple email providers (Resend, SMTP) with automatic provider selection.\n * Emails are sent asynchronously via Cloudflare Queues.\n *\n * **Usage:**\n * ```typescript\n * // In AppModule imports with static options\n * EmailModule.forRoot({\n * provider: 'resend',\n * apiKey: 'your-api-key',\n * from: { name: 'App', email: 'noreply@example.com' },\n * queue: 'NOTIFICATIONS_QUEUE',\n * })\n *\n * // Or with async options from config namespaces\n * EmailModule.forRootAsync({\n * inject: [emailConfig.KEY],\n * useFactory: (email) => ({\n * provider: email.provider,\n * apiKey: email.apiKey,\n * from: email.from,\n * queue: email.queue,\n * }),\n * })\n *\n * // In your service\n * @inject(EMAIL_TOKENS.EmailService)\n * private readonly emailService: EmailService\n *\n * await this.emailService.send({\n * to: 'user@example.com',\n * subject: 'Welcome',\n * template: <WelcomeEmail name=\"John\" />\n * })\n * ```\n */\n\nimport { Module } from '../module'\nimport type { AsyncModuleOptions, DynamicModule } from '../module/types'\nimport type { QueueBinding } from '../queue'\nimport { QUEUE_TOKENS, type QueueRegistry } from '../queue'\nimport { EmailConsumer } from './consumers/email.consumer'\nimport { EMAIL_TOKENS } from './email.tokens'\nimport { EmailProviderFactory, EmailService } from './services'\n\n/**\n * SMTP configuration options\n */\nexport interface SmtpConfig {\n /** SMTP server host */\n host: string\n /** SMTP server port */\n port: number\n /** Use TLS */\n secure?: boolean\n /** SMTP username */\n username?: string\n /** SMTP password */\n password?: string\n}\n\n/**\n * Email module configuration options\n */\nexport interface EmailModuleOptions {\n /** Email provider type */\n provider: 'resend' | 'smtp'\n\n /** Default from address */\n from: { name: string; email: string }\n\n /** Resend API key (required for resend provider) */\n apiKey?: string\n\n /** SMTP configuration (required for smtp provider) */\n smtp?: SmtpConfig\n\n /** Default reply-to address */\n replyTo?: string\n\n /**\n * Queue binding for email dispatch.\n * The binding must be registered via QueueModule.registerQueue(binding).\n */\n queue: QueueBinding\n}\n\n@Module({\n providers: [\n { provide: EMAIL_TOKENS.EmailService, useClass: EmailService },\n { provide: EMAIL_TOKENS.EmailProviderFactory, useClass: EmailProviderFactory },\n ],\n consumers: [EmailConsumer],\n})\nexport class EmailModule {\n /**\n * Configure EmailModule with static options\n *\n * @param options - Email configuration options\n * @returns Dynamic module with email infrastructure\n *\n * @example\n * ```typescript\n * EmailModule.forRoot({\n * provider: 'resend',\n * apiKey: env.RESEND_API_KEY,\n * from: { name: 'App', email: 'noreply@example.com' },\n * queue: 'NOTIFICATIONS_QUEUE',\n * })\n * ```\n */\n static forRoot(options: EmailModuleOptions): DynamicModule {\n return {\n module: EmailModule,\n providers: [\n { provide: EMAIL_TOKENS.Options, useValue: options },\n { provide: EMAIL_TOKENS.EmailQueue, useExisting: options.queue },\n ],\n }\n }\n\n /**\n * Configure EmailModule with async factory\n *\n * Use when configuration depends on other services.\n *\n * @param options - Async configuration with factory and inject tokens\n * @returns Dynamic module with email infrastructure\n *\n * @example\n * ```typescript\n * EmailModule.forRootAsync({\n * inject: [emailConfig.KEY],\n * useFactory: (email) => ({\n * provider: email.provider,\n * apiKey: email.apiKey,\n * from: email.from,\n * smtp: email.smtp,\n * queue: email.queue,\n * })\n * })\n * ```\n */\n static forRootAsync(options: AsyncModuleOptions<EmailModuleOptions>): DynamicModule {\n return {\n module: EmailModule,\n providers: [\n {\n provide: EMAIL_TOKENS.Options,\n useFactory: options.useFactory,\n inject: options.inject,\n },\n // Resolve queue from QueueRegistry using the binding from options\n {\n provide: EMAIL_TOKENS.EmailQueue,\n useFactory: (emailOptions: EmailModuleOptions, registry: QueueRegistry) =>\n registry.getQueue(emailOptions.queue),\n inject: [EMAIL_TOKENS.Options, QUEUE_TOKENS.QueueRegistry],\n },\n ],\n }\n }\n}\n","import { z } from '../../i18n/validation'\n\n/**\n * Inline Email Attachment Schema\n *\n * Attachment with content embedded as base64 string.\n * Use for small files that can fit in queue message.\n */\nexport const inlineEmailAttachmentSchema = z.object({\n /**\n * Filename to display for the attachment\n */\n filename: z.string().min(1).max(255),\n\n /**\n * Base64 encoded content of the attachment\n */\n content: z.string(),\n\n /**\n * MIME type of the attachment (e.g., 'application/pdf', 'image/png')\n */\n contentType: z.string(),\n\n /**\n * Optional size of the attachment in bytes\n */\n size: z.number().positive().optional(),\n})\n\n/**\n * Storage Email Attachment Schema\n *\n * Attachment stored in cloud storage.\n * Content type and size are derived from storage metadata.\n * Use for large files to avoid queue message size limits.\n */\nexport const storageEmailAttachmentSchema = z.object({\n /**\n * Filename to display for the attachment\n */\n filename: z.string().min(1).max(255),\n\n /**\n * Path to the file in storage\n */\n storageKey: z.string(),\n\n /**\n * Optional storage disk name (uses default if not provided)\n */\n disk: z.string().optional(),\n})\n\n/**\n * Email Attachment Schema\n *\n * Union type - type is inferred from presence of `content` vs `storageKey`.\n * - If `content` is present: inline attachment\n * - If `storageKey` is present: storage-based attachment\n */\nexport const emailAttachmentSchema = z.union([\n inlineEmailAttachmentSchema,\n storageEmailAttachmentSchema,\n])\n\n/**\n * Type definitions\n */\nexport type InlineEmailAttachment = z.infer<typeof inlineEmailAttachmentSchema>\nexport type StorageEmailAttachment = z.infer<typeof storageEmailAttachmentSchema>\nexport type EmailAttachment = z.infer<typeof emailAttachmentSchema>\n\n/**\n * Resolved Email Attachment\n *\n * Attachment after resolution, ready for email provider.\n * Content can be Buffer (for inline) or ReadableStream (for storage-based).\n * Both nodemailer and Resend support these formats directly.\n */\nexport interface ResolvedEmailAttachment {\n filename: string\n content: Buffer | ReadableStream\n contentType: string\n}\n","import { withZodI18n, z } from '../../i18n/validation'\nimport { emailAttachmentSchema, type ResolvedEmailAttachment } from './email-attachment'\n\n/**\n * Email Address Schema\n *\n * Represents an email address with optional name\n */\nexport const emailAddressSchema = z.object({\n name: z.string().optional(),\n email: z.string().email(),\n})\n\n/**\n * Base Email Message Schema\n *\n * Defines the core structure for email messages.\n * Ensures either html or text content is provided.\n */\nexport const emailMessageSchema = z\n .object({\n /**\n * Recipient email address(es)\n * Can be a single email string or array of emails\n */\n to: z.union([z.string().email(), z.array(z.string().email())]),\n\n /**\n * Sender email address with optional name\n * Falls back to default from config if not provided\n */\n from: emailAddressSchema.optional(),\n\n /**\n * Email subject line\n */\n subject: z.string().min(1).max(500),\n\n /**\n * HTML content of the email\n * Either html or text must be provided\n */\n html: z.string().optional(),\n\n /**\n * Plain text content of the email\n * Either html or text must be provided\n */\n text: z.string().optional(),\n\n /**\n * Reply-to email address\n */\n replyTo: z.string().email().optional(),\n\n /**\n * CC recipients\n */\n cc: z.array(z.string().email()).optional(),\n\n /**\n * BCC recipients\n */\n bcc: z.array(z.string().email()).optional(),\n\n /**\n * Email attachments\n */\n attachments: z.array(emailAttachmentSchema).optional(),\n })\n .refine(\n (data) => data.html ?? data.text,\n withZodI18n('zodI18n.errors.custom.emailOrTextRequired')\n )\n\n/**\n * Type definition for email message\n */\nexport type EmailMessage = z.infer<typeof emailMessageSchema>\n\n/**\n * Type definition for email address\n */\nexport type EmailAddress = z.infer<typeof emailAddressSchema>\n\n/**\n * Resolved Email Message\n *\n * Email message with attachments resolved to Buffer content.\n * Used by providers after the consumer resolves storage-based attachments.\n */\nexport type ResolvedEmailMessage = Omit<EmailMessage, 'attachments'> & {\n attachments?: ResolvedEmailAttachment[]\n}\n","import { z } from '../../i18n/validation'\nimport { emailMessageSchema } from './email-message.contract'\n\n/**\n * Send Email Input Schema\n *\n * Input schema for sending emails through the EmailService.\n * Extends the base email message with optional metadata.\n * Uses safeExtend() because emailMessageSchema contains refinements.\n */\nexport const sendEmailInputSchema = emailMessageSchema.safeExtend({\n /**\n * Optional metadata to include with the email\n * Can be used for tracking, categorization, etc.\n */\n metadata: z.record(z.string(), z.unknown()).optional(),\n})\n\n/**\n * Type definition for send email input\n */\nexport type SendEmailInput = z.infer<typeof sendEmailInputSchema>\n\n/**\n * Send Batch Email Input Schema\n *\n * Schema for sending multiple emails in a batch\n */\nexport const sendBatchEmailInputSchema = z.object({\n /**\n * Array of email messages to send\n */\n messages: z.array(sendEmailInputSchema).min(1).max(100),\n})\n\n/**\n * Type definition for send batch email input\n */\nexport type SendBatchEmailInput = z.infer<typeof sendBatchEmailInputSchema>\n"],"mappings":";;;;;;;;;;;;;AAEA,IAAa,aAAb,cAAgC,iBAAiB;;;;;;;;;;;;ACQjD,MAAa,eAAe;;;;CAI1B,SAAS,OAAO,IAAI,wBAAwB;;;;CAK5C,cAAc,OAAO,IAAI,wBAAwB;;;;CAKjD,sBAAsB,OAAO,IAAI,iCAAiC;;;;CAKlE,eAAe,OAAO,IAAI,yBAAyB;;;;;CAMnD,YAAY,OAAO,IAAI,sBAAsB;CAC9C;;;ACNM,IAAA,gBAAA,MAAM,cAAwD;CAKhD;CAEA;CAEA;CARnB,eAAwB,CAAC,cAAc,mBAAmB;CAE1D,YACE,QAEA,iBAEA,SAEA;EALiB,KAAA,SAAA;EAEA,KAAA,kBAAA;EAEA,KAAA,UAAA;;CAGnB,MAAM,OAAO,SAAsD;EACjE,MAAM,EAAE,MAAM,YAAY;EAC1B,MAAM,iBAAiB,MAAM,QAAQ,QAAQ,GAAG,GAAG,QAAQ,GAAG,SAAS;EAEvE,KAAK,OAAO,KAAK,4BAA4B;GAC3C;GACA;GACA,SAAS,CAAC,CAAC,QAAQ;GACnB,SAAS,CAAC,CAAC,QAAQ;GACnB,gBAAgB,CAAC,CAAC,QAAQ,aAAa;GACxC,CAAC;EAEF,IAAI;GAEF,MAAM,sBAAsB,MAAM,KAAK,mBAAmB,QAAQ,YAAY;GAG9E,MAAM,SAAS,OAAM,MADE,KAAK,gBAAgB,QAAQ,EACtB,KAAK;IACjC,GAAG;IACH,aAAa;IACd,CAAC;GAEF,KAAK,OAAO,KAAK,2BAA2B;IAC1C;IACA;IACA,WAAW,OAAO;IACnB,CAAC;WAEG,OAAO;GACZ,KAAK,OAAO,MAAM,wBAAwB;IACxC;IACA;IACA,OAAQ,MAAgB;IACzB,CAAC;GACF,MAAM;;;CAIV,QAAQ,OAAc,SAAsD;EAC1E,MAAM,iBAAiB,MAAM,QAAQ,QAAQ,QAAQ,GAAG,GACpD,QAAQ,QAAQ,GAAG,SACnB;EAEJ,KAAK,OAAO,MAAM,mCAAmC;GACnD;GACA,OAAO,MAAM;GACb,OAAO,MAAM;GACd,CAAC;EAEF,OAAO,QAAQ,SAAS;;;;;;;;;CAU1B,MAAc,mBACZ,aACgD;EAChD,IAAI,CAAC,aAAa,QAAQ,OAAO,KAAA;EAEjC,OAAO,QAAQ,IAAI,YAAY,IAAI,OAAO,eAAe;GAEvD,IAAI,aAAa,YACf,OAAO;IACL,UAAU,WAAW;IACrB,SAAS,OAAO,KAAK,WAAW,SAAS,SAAS;IAClD,aAAa,WAAW;IACzB;GAIH,MAAM,SAAS,MAAM,KAAK,QAAQ,SAChC,WAAW,YACX,WAAW,KACZ;GAED,OAAO;IACL,UAAU,WAAW;IACrB,SAAS,OAAO,UAAU,IAAI,OAAO,MAAM,EAAE;IAC7C,aAAa,OAAO;IACrB;IACD,CAAC;;;;CAlGN,WAAW;oBAKP,OAAO,cAAc,cAAc,CAAA;oBAEnC,OAAO,aAAa,qBAAqB,CAAA;oBAEzC,OAAO,eAAe,eAAe,CAAA;;;;ACFnC,IAAA,eAAA,MAAM,aAAa;CAGH;CAFrB,YACE,OAEA;EADmB,KAAA,QAAA;;;;;;;;;;CAWrB,MAAM,KAAK,EAAE,UAAU,GAAG,SAAoD;EAC5E,MAAM,KAAK,MAAM,SAAS;GACxB,MAAM;GACN,SAAS;IAAE,GAAG;IAAO,MAAM,WAAW,MAAM,OAAO,SAAS,GAAG,KAAA;IAAW;GAC3E,CAAC;;;;;;;;;;CAWJ,MAAM,UAAU,OAAuD;EACrE,KAAK,MAAM,WAAW,MAAM,UAC1B,MAAM,KAAK,KAAK,QAAQ;;;2BAhC7B,UAAU,aAAa,aAAa,EAAA,gBAAA,GAGhC,OAAO,aAAa,WAAW,CAAA,CAAA,EAAA,aAAA;;;ACpB7B,IAAA,uBAAA,MAAM,qBAAqB;CAGb;CAFnB,YACE,SAEA;EADiB,KAAA,UAAA;;;;;;;;CASnB,MAAM,SAAkC;EACtC,QAAQ,KAAK,QAAQ,UAArB;GACE,KAAK,UAAU;IACb,MAAM,EAAE,mBAAmB,MAAM,OAAO;IACxC,OAAO,IAAI,eAAe,KAAK,QAAQ;;GAGzC,KAAK,QAAQ;IACX,MAAM,EAAE,iBAAiB,MAAM,OAAO;IACtC,OAAO,IAAI,aAAa,KAAK,QAAQ;;GAGvC,SACE,MAAM,IAAI,WAAW,mBAAmB,OAAO,KAAK,QAAQ,SAAS,CAAC,oBAAoB;;;;mCA1BjG,UAAU,aAAa,qBAAqB,EAAA,gBAAA,GAGxC,OAAO,aAAa,QAAQ,CAAA,CAAA,EAAA,qBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC6E1B,IAAA,cAAA,eAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;CAiBvB,OAAO,QAAQ,SAA4C;EACzD,OAAO;GACL,QAAA;GACA,WAAW,CACT;IAAE,SAAS,aAAa;IAAS,UAAU;IAAS,EACpD;IAAE,SAAS,aAAa;IAAY,aAAa,QAAQ;IAAO,CACjE;GACF;;;;;;;;;;;;;;;;;;;;;;;;CAyBH,OAAO,aAAa,SAAgE;EAClF,OAAO;GACL,QAAA;GACA,WAAW,CACT;IACE,SAAS,aAAa;IACtB,YAAY,QAAQ;IACpB,QAAQ,QAAQ;IACjB,EAED;IACE,SAAS,aAAa;IACtB,aAAa,cAAkC,aAC7C,SAAS,SAAS,aAAa,MAAM;IACvC,QAAQ,CAAC,aAAa,SAAS,aAAa,cAAc;IAC3D,CACF;GACF;;;yCAzEJ,OAAO;CACN,WAAW,CACT;EAAE,SAAS,aAAa;EAAc,UAAU;EAAc,EAC9D;EAAE,SAAS,aAAa;EAAsB,UAAU;EAAsB,CAC/E;CACD,WAAW,CAAC,cAAc;CAC3B,CAAC,CAAA,EAAA,YAAA;;;;;;;;;ACxFF,MAAa,8BAA8B,EAAE,OAAO;;;;CAIlD,UAAU,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,IAAI;;;;CAKpC,SAAS,EAAE,QAAQ;;;;CAKnB,aAAa,EAAE,QAAQ;;;;CAKvB,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,UAAU;CACvC,CAAC;;;;;;;;AASF,MAAa,+BAA+B,EAAE,OAAO;;;;CAInD,UAAU,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,IAAI;;;;CAKpC,YAAY,EAAE,QAAQ;;;;CAKtB,MAAM,EAAE,QAAQ,CAAC,UAAU;CAC5B,CAAC;;;;;;;;AASF,MAAa,wBAAwB,EAAE,MAAM,CAC3C,6BACA,6BACD,CAAC;;;;;;;;ACxDF,MAAa,qBAAqB,EAAE,OAAO;CACzC,MAAM,EAAE,QAAQ,CAAC,UAAU;CAC3B,OAAO,EAAE,QAAQ,CAAC,OAAO;CAC1B,CAAC;;;;;;;AAQF,MAAa,qBAAqB,EAC/B,OAAO;;;;;CAKN,IAAI,EAAE,MAAM,CAAC,EAAE,QAAQ,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;;;;;CAM9D,MAAM,mBAAmB,UAAU;;;;CAKnC,SAAS,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,IAAI;;;;;CAMnC,MAAM,EAAE,QAAQ,CAAC,UAAU;;;;;CAM3B,MAAM,EAAE,QAAQ,CAAC,UAAU;;;;CAK3B,SAAS,EAAE,QAAQ,CAAC,OAAO,CAAC,UAAU;;;;CAKtC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC,UAAU;;;;CAK1C,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC,UAAU;;;;CAK3C,aAAa,EAAE,MAAM,sBAAsB,CAAC,UAAU;CACvD,CAAC,CACD,QACE,SAAS,KAAK,QAAQ,KAAK,MAC5B,YAAY,4CAA4C,CACzD;;;;;;;;;;AC/DH,MAAa,uBAAuB,mBAAmB,WAAW;;;;;AAKhE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,SAAS,CAAC,CAAC,UAAU,EACvD,CAAC;;;;;;AAYF,MAAa,4BAA4B,EAAE,OAAO;;;;AAIhD,UAAU,EAAE,MAAM,qBAAqB,CAAC,IAAI,EAAE,CAAC,IAAI,IAAI,EACxD,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/email/email.error.ts","../../src/email/email.tokens.ts","../../src/email/consumers/email.consumer.ts","../../src/email/services/email.service.ts","../../src/email/smtp/smtp-client.ts","../../src/email/smtp/mime.ts","../../src/email/providers/base-email.provider.ts","../../src/email/providers/smtp.provider.ts","../../src/email/services/email-provider-factory.ts","../../src/email/email.module.ts","../../src/email/contracts/email-attachment.ts","../../src/email/contracts/email-message.contract.ts","../../src/email/contracts/send-email.input.ts"],"sourcesContent":["import { ApplicationError } from '../errors'\n\nexport class EmailError extends ApplicationError {}\n","/**\n * Dependency Injection Tokens for Email Module\n *\n * These Symbol-based tokens ensure type-safe dependency injection\n * throughout the email module and prevent naming collisions.\n */\n\n/**\n * Email Module DI Tokens\n */\nexport const EMAIL_TOKENS = {\n /**\n * Email module configuration options\n */\n Options: Symbol.for('stratal:email:options'),\n\n /**\n * Main email service - facade for sending emails via queues\n */\n EmailService: Symbol.for('stratal:email:service'),\n\n /**\n * Factory for creating email provider instances based on configuration\n */\n EmailProviderFactory: Symbol.for('stratal:email:provider:factory'),\n\n /**\n * Email provider interface - abstracts provider implementation\n */\n EmailProvider: Symbol.for('stratal:email:provider'),\n\n /**\n * Queue sender for email dispatch.\n * Bound via EmailModule.forRoot({ queue: 'QUEUE_BINDING_NAME' })\n */\n EmailQueue: Symbol.for('stratal:email:queue'),\n} as const\n","import { inject } from '../../di'\nimport { Transient } from '../../di/decorators'\nimport { LOGGER_TOKENS, type LoggerService } from '../../logger'\nimport type { IQueueConsumer, QueueMessage } from '../../queue/queue-consumer'\nimport { STORAGE_TOKENS, type StorageService } from '../../storage'\nimport type { EmailAttachment, ResolvedEmailAttachment, SendEmailInput } from '../contracts'\nimport { EmailError } from '../email.error'\nimport { EMAIL_TOKENS } from '../email.tokens'\nimport type { EmailProviderFactory } from '../services/email-provider-factory'\n\n/**\n * Strictly decode a base64 attachment payload. `Buffer.from(_, 'base64')` is\n * lenient and silently drops invalid characters, which would ship a corrupt\n * attachment. We re-encode and compare (modulo canonical padding) to reject\n * malformed input loudly instead.\n */\nfunction decodeBase64Attachment(content: string, filename: string): Buffer {\n const buffer = Buffer.from(content, 'base64')\n // Canonicalise the input (strip padding) and the round-tripped output, then\n // compare: any dropped/invalid byte produces a mismatch.\n const normalize = (value: string): string => value.replace(/=+$/, '')\n if (normalize(buffer.toString('base64')) !== normalize(content)) {\n throw new EmailError(`Invalid base64 content for attachment \"${filename}\"`)\n }\n return buffer\n}\n\n/**\n * Email Consumer\n *\n * Generic queue consumer that handles email.send and email.batch.send messages\n * from ANY queue. Message routing is based on message type, not queue name.\n *\n * This consumer:\n * - Resolves storage-based attachments to streams\n * - Creates email provider instances via factory\n * - Sends emails with proper logging (no PII)\n * - Handles errors with retry support\n *\n * @example\n * ```typescript\n * // Registered in EmailModule\n * @Module({\n * consumers: [EmailConsumer]\n * })\n * ```\n */\n@Transient()\nexport class EmailConsumer implements IQueueConsumer<SendEmailInput> {\n readonly messageTypes = ['email.send', 'email.batch.send']\n\n constructor(\n @inject(LOGGER_TOKENS.LoggerService)\n private readonly logger: LoggerService,\n @inject(EMAIL_TOKENS.EmailProviderFactory)\n private readonly providerFactory: EmailProviderFactory,\n @inject(STORAGE_TOKENS.StorageService)\n private readonly storage: StorageService\n ) { }\n\n async handle(message: QueueMessage<SendEmailInput>): Promise<void> {\n const { type, payload } = message\n const recipientCount = Array.isArray(payload.to) ? payload.to.length : 1\n\n this.logger.info('Processing email message', {\n type,\n recipientCount,\n hasHtml: !!payload.html,\n hasText: !!payload.text,\n hasAttachments: !!payload.attachments?.length,\n })\n\n try {\n // Resolve storage-based attachments before sending\n const resolvedAttachments = await this.resolveAttachments(payload.attachments)\n\n const provider = this.providerFactory.create()\n const result = await provider.send({\n ...payload,\n attachments: resolvedAttachments,\n })\n\n this.logger.info('Email sent successfully', {\n type,\n recipientCount,\n messageId: result.messageId,\n })\n }\n catch (error) {\n this.logger.error('Failed to send email', {\n type,\n recipientCount,\n error: (error as Error).message,\n })\n throw error // Retry via queue\n }\n }\n\n onError(error: Error, message: QueueMessage<SendEmailInput>): Promise<void> {\n const recipientCount = Array.isArray(message.payload.to)\n ? message.payload.to.length\n : 1\n\n this.logger.error('Email send failed after retries', {\n recipientCount,\n error: error.message,\n stack: error.stack,\n })\n\n return Promise.resolve()\n }\n\n /**\n * Resolve email attachments\n *\n * Converts attachment schemas to resolved attachments.\n * - Inline attachments: decode base64 to Buffer\n * - Storage attachments: pass stream directly (providers support streams)\n */\n private async resolveAttachments(\n attachments: EmailAttachment[] | undefined\n ): Promise<ResolvedEmailAttachment[] | undefined> {\n if (!attachments?.length) return undefined\n\n return Promise.all(attachments.map(async (attachment) => {\n // Check for inline attachment (has content property)\n if ('content' in attachment) {\n return {\n filename: attachment.filename,\n content: decodeBase64Attachment(attachment.content, attachment.filename),\n contentType: attachment.contentType,\n }\n }\n\n // Storage attachment - pass stream directly to provider\n const result = await this.storage.download(\n attachment.storageKey,\n attachment.disk\n )\n\n return {\n filename: attachment.filename,\n content: result.toStream() ?? Buffer.alloc(0),\n contentType: result.contentType,\n }\n }))\n }\n}\n","import type { ReactElement } from 'react'\nimport { inject } from '../../di'\nimport { Transient } from '../../di/decorators'\nimport type { IQueueSender } from '../../queue'\nimport type { SendBatchEmailInput, SendEmailInput } from '../contracts'\nimport { EMAIL_TOKENS } from '../email.tokens'\n\nexport type SendEmailInputWithTemplate = Omit<SendEmailInput, 'html' | 'text'> & {\n template?: ReactElement\n}\n\nexport type SendBatchEmailInputWithTemplate = Omit<SendBatchEmailInput, 'messages'> & {\n messages: SendEmailInputWithTemplate[]\n}\n\n/**\n * Email Service\n *\n * Main facade for sending emails. Routes emails to queues for async processing.\n * The queue is injected via EMAIL_TOKENS.EmailQueue, configured by the application\n * via EmailModule.forRoot({ queue: 'QUEUE_BINDING_NAME' }).\n *\n * @example Basic usage\n * ```typescript\n * @inject(EMAIL_TOKENS.EmailService)\n * private readonly emailService: EmailService\n *\n * await this.emailService.send({\n * to: 'user@example.com',\n * subject: 'Welcome',\n * template: <WelcomeEmail name=\"John\" />\n * })\n * ```\n */\n@Transient(EMAIL_TOKENS.EmailService)\nexport class EmailService {\n constructor(\n @inject(EMAIL_TOKENS.EmailQueue)\n protected readonly queue: IQueueSender\n ) { }\n\n /**\n * Send a single email\n *\n * Dispatches the email to the queue for async processing.\n * Supports optional React template rendering.\n *\n * @param input - Email message details\n */\n async send({ template, ...input }: SendEmailInputWithTemplate): Promise<void> {\n // `@react-email/render` (plus its html-to-text/htmlparser2 deps) is heavy and\n // only needed when a React template is supplied. Defer it to a dynamic import\n // so it lands in its own chunk and is never parsed/evaluated at worker cold\n // start — email sending is off the request hot path.\n let html: string | undefined\n if (template) {\n const { render } = await import('@react-email/render')\n html = await render(template)\n }\n await this.queue.dispatch({\n type: 'email.send',\n payload: { ...input, html },\n })\n }\n\n /**\n * Send multiple emails in a batch\n *\n * Dispatches all emails to the queue for async processing.\n * Supports React template rendering for each message.\n *\n * @param input - Batch email details\n */\n async sendBatch(input: SendBatchEmailInputWithTemplate): Promise<void> {\n for (const message of input.messages) {\n await this.send(message)\n }\n }\n}\n","import { EmailError } from '../email.error';\nimport type { SmtpConfig } from '../email.module';\n\ninterface SmtpSendOptions {\n from: string\n to: string[]\n}\n\ninterface SmtpSendResult {\n messageId: string\n}\n\ninterface ParsedSmtpUrl {\n host: string\n port: number\n secure: boolean\n username?: string\n password?: string\n}\n\n/** Advertised EHLO capabilities and the SASL mechanisms the server accepts. */\ninterface SmtpCapabilities {\n startTls: boolean\n auth: Set<string>\n}\n\n/** Abort a server read if it stalls, so a hung endpoint can't wedge the Worker. */\nconst RESPONSE_TIMEOUT_MS = 30_000\n\nfunction parseSmtpUrl(config: SmtpConfig): ParsedSmtpUrl {\n try {\n const parsed = new URL(config.url)\n const secure = parsed.protocol === 'smtps:'\n return {\n host: parsed.hostname,\n port: parsed.port ? parseInt(parsed.port, 10) : (secure ? 465 : 587),\n secure,\n username: parsed.username ? decodeURIComponent(parsed.username) : undefined,\n password: parsed.password ? decodeURIComponent(parsed.password) : undefined,\n }\n }\n catch {\n throw new EmailError(`Invalid SMTP URL: ${config.url}`)\n }\n}\n\n/** Parse an EHLO reply into the capabilities/mechanisms it advertises. */\nfunction parseCapabilities(ehloText: string): SmtpCapabilities {\n const auth = new Set<string>()\n let startTls = false\n for (const rawLine of ehloText.split('\\r\\n')) {\n // Lines look like `250-STARTTLS` / `250 AUTH PLAIN LOGIN`; drop the code+sep.\n const line = rawLine.slice(4).trim().toUpperCase()\n if (!line) continue\n const [keyword, ...rest] = line.split(/\\s+/)\n if (keyword === 'STARTTLS') startTls = true\n if (keyword === 'AUTH') rest.forEach((m) => auth.add(m))\n }\n return { startTls, auth }\n}\n\nexport class SmtpClient {\n private readonly parsed: ParsedSmtpUrl\n\n constructor(config: SmtpConfig) {\n this.parsed = parseSmtpUrl(config)\n }\n\n async send(mimeRaw: string, options: SmtpSendOptions): Promise<SmtpSendResult> {\n const { connect } = await import('cloudflare:sockets')\n const { host, port, secure, username, password } = this.parsed\n const implicitTls = secure || port === 465\n const socket = connect(\n { hostname: host, port },\n { secureTransport: implicitTls ? 'on' : 'starttls', allowHalfOpen: false },\n )\n\n // `startTls()` returns a brand-new socket and closes the original, so these\n // must be reassignable: after the upgrade we re-derive the reader/writer\n // from the secure socket (the originals stop working).\n let activeSocket = socket\n let reader = socket.readable.getReader()\n let writer = socket.writable.getWriter()\n const decoder = new TextDecoder()\n const encoder = new TextEncoder()\n\n const readChunk = async (): Promise<ReadableStreamReadResult<Uint8Array>> => {\n const read = reader.read() as Promise<ReadableStreamReadResult<Uint8Array>>\n // Swallow a late settlement if the timeout wins, so it can't become an\n // unhandled rejection after we've already moved on / closed the socket.\n read.catch(() => { /* swallow late settlement; the timeout already rejected */ })\n let timer: ReturnType<typeof setTimeout> | undefined\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(\n () => reject(new EmailError(`SMTP timeout: no response within ${RESPONSE_TIMEOUT_MS}ms`)),\n RESPONSE_TIMEOUT_MS,\n )\n })\n try {\n return await Promise.race([read, timeout])\n }\n finally {\n clearTimeout(timer)\n }\n }\n\n // Bytes the server pipelined after a reply's terminating line must survive\n // across readResponse() calls, or the next read drops them and the protocol\n // desyncs. This leftover persists for the lifetime of this send().\n let leftover = ''\n\n const readResponse = async (): Promise<{ code: number; text: string }> => {\n let buffer = leftover\n leftover = ''\n while (true) {\n // The leftover may already contain a complete reply (the server packed\n // multiple replies into one TCP segment), so scan before reading more.\n const newlineIdx = buffer.indexOf('\\r\\n')\n if (newlineIdx !== -1) {\n const lines = buffer.split('\\r\\n')\n let consumed = 0\n for (let i = 0; i < lines.length - 1; i++) {\n const line = lines[i]\n consumed += line.length + 2 // include the '\\r\\n'\n // A terminating reply line has a space (not '-') after the 3-digit code.\n if (line[3] === ' ') {\n const code = parseInt(line.slice(0, 3), 10)\n leftover = buffer.slice(consumed)\n return { code, text: buffer.slice(0, consumed) }\n }\n }\n }\n\n const result = await readChunk()\n if (result.done) throw new EmailError('SMTP connection closed unexpectedly')\n buffer += decoder.decode(result.value, { stream: true })\n }\n }\n\n const sendCommand = async (command: string): Promise<{ code: number; text: string }> => {\n await writer.write(encoder.encode(command + '\\r\\n'))\n return readResponse()\n }\n\n const expectCode = async (command: string, expected: number): Promise<string> => {\n const response = await sendCommand(command)\n if (response.code !== expected) {\n throw new EmailError(`SMTP error: expected ${expected}, got ${response.code}: ${response.text.trim()}`)\n }\n return response.text\n }\n\n try {\n const greeting = await readResponse()\n if (greeting.code !== 220) {\n throw new EmailError(`SMTP server rejected connection: ${greeting.text.trim()}`)\n }\n\n let capabilities = parseCapabilities(await expectCode(`EHLO ${host}`, 250))\n\n // Upgrade to TLS when the server offers STARTTLS, then re-issue EHLO — the\n // post-TLS capability list is the authoritative one (servers commonly only\n // advertise AUTH after the channel is encrypted).\n let encrypted = implicitTls\n if (!implicitTls && capabilities.startTls) {\n await expectCode('STARTTLS', 220)\n // Swap onto the secure socket the runtime hands back. Release the old\n // locks first, then re-derive reader/writer from the new socket. Reset\n // `leftover` so no byte received before the handshake is trusted as part\n // of the encrypted session (STARTTLS plaintext-injection, CVE-2011-0411).\n reader.releaseLock()\n writer.releaseLock()\n activeSocket = socket.startTls()\n reader = activeSocket.readable.getReader()\n writer = activeSocket.writable.getWriter()\n leftover = ''\n capabilities = parseCapabilities(await expectCode(`EHLO ${host}`, 250))\n encrypted = true\n }\n\n if (username && password) {\n // Never put credentials on the wire in cleartext. If the URL is `smtp://`\n // and the server didn't offer STARTTLS, this is a misconfiguration (or a\n // STARTTLS-stripping downgrade attack) — fail loudly rather than leak the\n // password. Use `smtps://` (implicit TLS) for servers without STARTTLS.\n if (!encrypted) {\n throw new EmailError(\n 'Refusing to send SMTP credentials over an unencrypted connection: the server did not offer STARTTLS. Use an smtps:// URL or a server that supports STARTTLS.',\n )\n }\n await this.authenticate(expectCode, capabilities, username, password)\n }\n\n await expectCode(`MAIL FROM:<${options.from}>`, 250)\n\n for (const recipient of options.to) {\n await expectCode(`RCPT TO:<${recipient}>`, 250)\n }\n\n await expectCode('DATA', 354)\n\n const dotStuffed = (mimeRaw.startsWith('.') ? '.' + mimeRaw : mimeRaw).replace(/\\r\\n\\./g, '\\r\\n..')\n await writer.write(encoder.encode(dotStuffed + '\\r\\n.\\r\\n'))\n const dataResponse = await readResponse()\n if (dataResponse.code !== 250) {\n throw new EmailError(`SMTP DATA rejected: ${dataResponse.text.trim()}`)\n }\n\n const messageIdMatch = dataResponse.text.match(/<([^>]+)>/)\n const messageId = messageIdMatch?.[1] ?? `${Date.now()}@${host}`\n\n // QUIT is best-effort: the message is already accepted, so a failed/closed\n // QUIT must not turn a successful send into a thrown error.\n await sendCommand('QUIT').catch(() => { /* best-effort; message already accepted */ })\n\n return { messageId }\n }\n finally {\n reader.releaseLock()\n writer.releaseLock()\n await activeSocket.close().catch(() => { /* best-effort close */ })\n }\n }\n\n /** Authenticate using a server-advertised SASL mechanism (PLAIN or LOGIN). */\n private async authenticate(\n expectCode: (command: string, expected: number) => Promise<string>,\n capabilities: SmtpCapabilities,\n username: string,\n password: string,\n ): Promise<void> {\n if (capabilities.auth.has('PLAIN')) {\n const credentials = Buffer.from(`\\0${username}\\0${password}`).toString('base64')\n await expectCode(`AUTH PLAIN ${credentials}`, 235)\n return\n }\n if (capabilities.auth.has('LOGIN')) {\n await expectCode('AUTH LOGIN', 334)\n await expectCode(Buffer.from(username).toString('base64'), 334)\n await expectCode(Buffer.from(password).toString('base64'), 235)\n return\n }\n throw new EmailError(\n capabilities.auth.size === 0\n ? 'SMTP credentials were provided but the server does not advertise AUTH.'\n : `SMTP server does not support a known AUTH mechanism (offered: ${[...capabilities.auth].join(', ')}). Supported: PLAIN, LOGIN.`,\n )\n }\n}\n","import { EmailError } from '../email.error'\nimport type { ResolvedEmailAttachment, ResolvedEmailMessage } from '../contracts'\n\ninterface MimeEnvelope {\n from: string\n to: string[]\n}\n\ninterface MimeResult {\n raw: string\n envelope: MimeEnvelope\n}\n\n/**\n * Hard cap on a single attachment's resolved size (20 MB). Attachments are fully\n * buffered into memory before base64 encoding, so an unbounded attachment would\n * let a single message exhaust the Worker's memory. Exceeding this throws.\n */\nexport const MAX_ATTACHMENT_SIZE_BYTES = 20 * 1024 * 1024\n\nfunction generateBoundary(): string {\n return `----=_Part_${crypto.randomUUID().replace(/-/g, '')}`\n}\n\nfunction generateMessageId(fromEmail: string): string {\n const domain = fromEmail.split('@')[1] || 'localhost'\n return `<${crypto.randomUUID()}@${domain}>`\n}\n\nfunction isAscii(str: string): boolean {\n return /^[ -~]*$/.test(str)\n}\n\n/** Remove CR/LF so a value can't inject extra headers (header smuggling). */\nfunction stripCrlf(value: string): string {\n return value.replace(/[\\r\\n]/g, '')\n}\n\n/**\n * Encode a header value as one or more RFC 2047 base64 encoded-words, folding so\n * no produced line exceeds the 76-char limit strict MTAs enforce. The UTF-8 byte\n * stream is chunked at <=45 source bytes per encoded-word (<=60 base64 chars,\n * keeping `=?UTF-8?B?...?=` <=75 chars) WITHOUT splitting a multibyte sequence.\n * Multiple encoded-words are folded with CRLF + a single space.\n */\nfunction encodeEncodedWords(clean: string): string {\n const bytes = Buffer.from(clean, 'utf-8')\n const MAX_SOURCE_BYTES = 45\n const words: string[] = []\n let i = 0\n while (i < bytes.length) {\n let end = Math.min(i + MAX_SOURCE_BYTES, bytes.length)\n // Don't split a multibyte UTF-8 sequence: continuation bytes are 0b10xxxxxx.\n // Back off `end` until it sits on a sequence boundary (or we hit the start).\n while (end > i && end < bytes.length && (bytes[end] & 0xc0) === 0x80) {\n end--\n }\n const slice = bytes.subarray(i, end)\n words.push(`=?UTF-8?B?${slice.toString('base64')}?=`)\n i = end\n }\n return words.join('\\r\\n ')\n}\n\nfunction encodeHeaderValue(value: string): string {\n const clean = stripCrlf(value)\n if (isAscii(clean)) return clean\n return encodeEncodedWords(clean)\n}\n\n/**\n * Validate an envelope address (used verbatim in `MAIL FROM`/`RCPT TO` SMTP\n * commands). Raw CR/LF would allow SMTP command injection, so we throw rather\n * than silently strip — a corrupted envelope must never reach the wire.\n */\nfunction assertNoCrlf(address: string): void {\n if (/[\\r\\n]/.test(address)) {\n throw new EmailError('Email envelope address contains CR/LF, which would allow SMTP command injection')\n }\n}\n\n/**\n * Escape a value for an RFC 5322 quoted-string: backslash MUST be escaped first\n * (so the escapes added for `\"` aren't themselves re-escaped), then `\"`.\n */\nfunction escapeQuotedString(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"')\n}\n\n/**\n * Encode a MIME parameter (e.g. `name`/`filename`) safely. ASCII values are\n * emitted as a quoted-string with `\"`/`\\` escaped; non-ASCII values use the\n * RFC 2231 extended syntax (`param*=UTF-8''…`). CR/LF are always stripped so a\n * crafted filename can't inject headers or break the MIME structure.\n */\nfunction encodeMimeParam(param: string, value: string): string {\n const clean = stripCrlf(value)\n if (isAscii(clean)) {\n return `${param}=\"${escapeQuotedString(clean)}\"`\n }\n const encoded = Array.from(Buffer.from(clean, 'utf-8'))\n .map((b) => (/[A-Za-z0-9!#$&+\\-.^_`|~]/.test(String.fromCharCode(b))\n ? String.fromCharCode(b)\n : `%${b.toString(16).toUpperCase().padStart(2, '0')}`))\n .join('')\n return `${param}*=UTF-8''${encoded}`\n}\n\n/**\n * Extract the bare address for the SMTP envelope and validate it has no raw\n * CR/LF before it is written into a `MAIL FROM`/`RCPT TO` command.\n */\nfunction extractEmail(address: string): string {\n assertNoCrlf(address)\n const match = address.match(/<([^>]+)>/)\n const email = (match ? match[1] : address).trim()\n // The bare address is written verbatim into `MAIL FROM:<…>` / `RCPT TO:<…>`.\n // Beyond CR/LF (checked above), reject whitespace and stray angle brackets\n // that could break out of the brackets or desync the command (e.g.\n // `a>b@x.com` → `MAIL FROM:<a>b@x.com>`).\n if (/[<>\\s]/.test(email)) {\n throw new EmailError(`Invalid email address for SMTP envelope: ${JSON.stringify(address)}`)\n }\n return email\n}\n\nfunction formatAddress(email: string, name?: string): string {\n const cleanEmail = stripCrlf(email)\n if (!name) return cleanEmail\n const cleanName = stripCrlf(name)\n const encodedName = isAscii(cleanName) ? `\"${escapeQuotedString(cleanName)}\"` : encodeHeaderValue(cleanName)\n return `${encodedName} <${cleanEmail}>`\n}\n\nfunction formatDate(date: Date): string {\n return date.toUTCString().replace('GMT', '+0000')\n}\n\nfunction wrapBase64(base64: string): string {\n const lines: string[] = []\n for (let i = 0; i < base64.length; i += 76) {\n lines.push(base64.slice(i, i + 76))\n }\n return lines.join('\\r\\n')\n}\n\nasync function resolveContent(content: ResolvedEmailAttachment['content']): Promise<Buffer> {\n const buffer = Buffer.isBuffer(content)\n ? content\n : Buffer.from(await new Response(content).arrayBuffer())\n if (buffer.byteLength > MAX_ATTACHMENT_SIZE_BYTES) {\n throw new EmailError(\n `Email attachment exceeds the maximum size of ${MAX_ATTACHMENT_SIZE_BYTES} bytes (got ${buffer.byteLength} bytes)`,\n )\n }\n return buffer\n}\n\n/** Base64-encode a text body so arbitrary content (long lines, leading dots,\n * 8-bit data) is transported safely regardless of line length or SMTP escaping. */\nfunction encodeTextBody(content: string): string {\n return wrapBase64(Buffer.from(content, 'utf-8').toString('base64'))\n}\n\nfunction buildBodyPart(\n text: string | undefined,\n html: string | undefined,\n): { content: string; contentType: string; cte?: string } {\n if (text && html) {\n const boundary = generateBoundary()\n const content = [\n `--${boundary}`,\n 'Content-Type: text/plain; charset=utf-8',\n 'Content-Transfer-Encoding: base64',\n '',\n encodeTextBody(text),\n `--${boundary}`,\n 'Content-Type: text/html; charset=utf-8',\n 'Content-Transfer-Encoding: base64',\n '',\n encodeTextBody(html),\n `--${boundary}--`,\n ].join('\\r\\n')\n return { content, contentType: `multipart/alternative; boundary=\"${boundary}\"` }\n }\n\n if (html) {\n return { content: encodeTextBody(html), contentType: 'text/html; charset=utf-8', cte: 'base64' }\n }\n\n return { content: encodeTextBody(text ?? ''), contentType: 'text/plain; charset=utf-8', cte: 'base64' }\n}\n\nasync function buildAttachmentPart(attachment: ResolvedEmailAttachment): Promise<string> {\n const buffer = await resolveContent(attachment.content)\n const base64 = wrapBase64(buffer.toString('base64'))\n return [\n `Content-Type: ${attachment.contentType || 'application/octet-stream'}; ${encodeMimeParam('name', attachment.filename)}`,\n 'Content-Transfer-Encoding: base64',\n `Content-Disposition: attachment; ${encodeMimeParam('filename', attachment.filename)}`,\n '',\n base64,\n ].join('\\r\\n')\n}\n\nexport async function buildMimeMessage(\n message: ResolvedEmailMessage,\n defaultFrom: { name: string; email: string },\n): Promise<MimeResult> {\n const fromAddr = message.from\n ? formatAddress(message.from.email, message.from.name)\n : formatAddress(defaultFrom.email, defaultFrom.name)\n\n const fromEmail = extractEmail(message.from?.email ?? defaultFrom.email)\n\n const toList = Array.isArray(message.to) ? message.to : [message.to]\n\n const headers: string[] = [\n `From: ${fromAddr}`,\n `To: ${toList.map(stripCrlf).join(', ')}`,\n `Subject: ${encodeHeaderValue(message.subject)}`,\n `Date: ${formatDate(new Date())}`,\n `Message-ID: ${generateMessageId(fromEmail)}`,\n 'MIME-Version: 1.0',\n ]\n\n if (message.replyTo) headers.push(`Reply-To: ${stripCrlf(message.replyTo)}`)\n if (message.cc?.length) headers.push(`Cc: ${message.cc.map(stripCrlf).join(', ')}`)\n\n const allRecipients = [\n ...toList,\n ...(message.cc ?? []),\n ...(message.bcc ?? []),\n ].map(extractEmail)\n\n const body = buildBodyPart(message.text, message.html)\n const hasAttachments = message.attachments && message.attachments.length > 0\n\n if (!hasAttachments) {\n headers.push(`Content-Type: ${body.contentType}`)\n if (body.cte) {\n headers.push(`Content-Transfer-Encoding: ${body.cte}`)\n }\n\n return {\n raw: headers.join('\\r\\n') + '\\r\\n\\r\\n' + body.content,\n envelope: { from: fromEmail, to: allRecipients },\n }\n }\n\n const mixedBoundary = generateBoundary()\n headers.push(`Content-Type: multipart/mixed; boundary=\"${mixedBoundary}\"`)\n\n const parts: string[] = [\n `--${mixedBoundary}`,\n `Content-Type: ${body.contentType}`,\n ]\n\n if (body.cte) {\n parts.push(`Content-Transfer-Encoding: ${body.cte}`)\n }\n\n parts.push('', body.content)\n\n for (const attachment of message.attachments!) {\n parts.push(`--${mixedBoundary}`)\n parts.push(await buildAttachmentPart(attachment))\n }\n\n parts.push(`--${mixedBoundary}--`)\n\n return {\n raw: headers.join('\\r\\n') + '\\r\\n\\r\\n' + parts.join('\\r\\n'),\n envelope: { from: fromEmail, to: allRecipients },\n }\n}\n","import type { ResolvedEmailMessage } from '../contracts'\nimport type { EmailBatchSendResult, EmailSendResult, IEmailProvider } from './email-provider.interface'\n\n/**\n * Base Email Provider\n *\n * Abstract base class for email providers.\n * Provides shared implementation of sendBatch() to reduce code duplication.\n */\nexport abstract class BaseEmailProvider implements IEmailProvider {\n /**\n * Send a single email - must be implemented by concrete providers\n */\n abstract send(message: ResolvedEmailMessage): Promise<EmailSendResult>\n\n /**\n * Send multiple emails in a batch\n *\n * Default implementation sends emails sequentially.\n * Concrete providers can override for optimized batch sending.\n */\n async sendBatch(messages: ResolvedEmailMessage[]): Promise<EmailBatchSendResult> {\n const results: EmailSendResult[] = []\n let successful = 0\n let failed = 0\n\n for (const message of messages) {\n try {\n const result = await this.send(message)\n results.push(result)\n successful++\n }\n catch (error) {\n results.push({\n messageId: '',\n accepted: false,\n metadata: {\n error: error instanceof Error ? error.message : 'Unknown error',\n },\n })\n failed++\n }\n }\n\n return {\n total: messages.length,\n successful,\n failed,\n results,\n }\n }\n}\n","import type { ResolvedEmailMessage } from '../contracts'\nimport type { EmailModuleOptions } from '../email.module'\nimport { EmailError } from '../email.error'\nimport { SmtpClient } from '../smtp/smtp-client'\nimport { buildMimeMessage } from '../smtp/mime'\nimport { BaseEmailProvider } from './base-email.provider'\nimport type { EmailSendResult } from './email-provider.interface'\n\nexport class SmtpProvider extends BaseEmailProvider {\n constructor(\n private readonly options: EmailModuleOptions\n ) {\n super()\n\n if (!this.options.smtp?.url) {\n throw new EmailError('SMTP URL is required')\n }\n }\n\n async send(message: ResolvedEmailMessage): Promise<EmailSendResult> {\n const mime = await buildMimeMessage(message, this.options.from)\n\n try {\n const client = new SmtpClient(this.options.smtp)\n const result = await client.send(mime.raw, {\n from: mime.envelope.from,\n to: mime.envelope.to,\n })\n\n return {\n messageId: result.messageId,\n accepted: true,\n metadata: { provider: 'smtp' },\n }\n }\n catch (error) {\n if (error instanceof EmailError) throw error\n throw new EmailError(`SMTP send failed: ${(error as Error).message}`)\n }\n }\n}\n","import { inject } from '../../di'\nimport { Transient } from '../../di/decorators'\nimport type { EmailModuleOptions } from '../email.module'\nimport { EMAIL_TOKENS } from '../email.tokens'\nimport type { IEmailProvider } from '../providers/email-provider.interface'\nimport { SmtpProvider } from '../providers/smtp.provider'\n\n@Transient(EMAIL_TOKENS.EmailProviderFactory)\nexport class EmailProviderFactory {\n constructor(\n @inject(EMAIL_TOKENS.Options)\n private readonly options: EmailModuleOptions\n ) { }\n\n create(): IEmailProvider {\n return new SmtpProvider(this.options)\n }\n}\n","/**\n * Email Module\n *\n * Provides email sending capabilities with provider abstraction.\n * Sends email via SMTP. Emails are dispatched asynchronously via Cloudflare Queues.\n * Emails are sent asynchronously via Cloudflare Queues.\n *\n * **Usage:**\n * ```typescript\n * // In AppModule imports with static options\n * EmailModule.forRoot({\n * from: { name: 'App', email: 'noreply@example.com' },\n * smtp: { url: 'smtp://user:pass@smtp.example.com:587' },\n * queue: 'NOTIFICATIONS_QUEUE',\n * })\n *\n * // Or with async options from config namespaces\n * EmailModule.forRootAsync({\n * inject: [emailConfig.KEY],\n * useFactory: (email) => ({\n * from: email.from,\n * smtp: email.smtp,\n * queue: email.queue,\n * }),\n * })\n *\n * // In your service\n * @inject(EMAIL_TOKENS.EmailService)\n * private readonly emailService: EmailService\n *\n * await this.emailService.send({\n * to: 'user@example.com',\n * subject: 'Welcome',\n * template: <WelcomeEmail name=\"John\" />\n * })\n * ```\n */\n\nimport { Module } from '../module'\nimport type { AsyncModuleOptions, DynamicModule } from '../module/types'\nimport type { QueueBinding } from '../queue'\nimport { QUEUE_TOKENS, type QueueRegistry } from '../queue'\nimport { EmailConsumer } from './consumers/email.consumer'\nimport { EMAIL_TOKENS } from './email.tokens'\nimport { EmailProviderFactory, EmailService } from './services'\n\n/**\n * SMTP configuration options\n *\n * URL format: `smtp://user:pass@host:port` or `smtps://user:pass@host:port`\n * - `smtp://` → STARTTLS on port 587 (default)\n * - `smtps://` → implicit TLS on port 465 (default)\n */\nexport interface SmtpConfig {\n url: string\n}\n\n/**\n * Email module configuration options\n */\nexport interface EmailModuleOptions {\n /** Default from address */\n from: { name: string; email: string }\n\n /** SMTP configuration */\n smtp: SmtpConfig\n\n /** Default reply-to address */\n replyTo?: string\n\n /**\n * Queue binding for email dispatch.\n * The binding must be registered via QueueModule.registerQueue(binding).\n */\n queue: QueueBinding\n}\n\n@Module({\n providers: [\n { provide: EMAIL_TOKENS.EmailService, useClass: EmailService },\n { provide: EMAIL_TOKENS.EmailProviderFactory, useClass: EmailProviderFactory },\n ],\n consumers: [EmailConsumer],\n})\nexport class EmailModule {\n /**\n * Configure EmailModule with static options\n *\n * @param options - Email configuration options\n * @returns Dynamic module with email infrastructure\n *\n * @example\n * ```typescript\n * EmailModule.forRoot({\n * from: { name: 'App', email: 'noreply@example.com' },\n * smtp: { url: 'smtp://user:pass@smtp.example.com:587' },\n * queue: 'NOTIFICATIONS_QUEUE',\n * })\n * ```\n */\n static forRoot(options: EmailModuleOptions): DynamicModule {\n return {\n module: EmailModule,\n providers: [\n { provide: EMAIL_TOKENS.Options, useValue: options },\n { provide: EMAIL_TOKENS.EmailQueue, useExisting: options.queue },\n ],\n }\n }\n\n /**\n * Configure EmailModule with async factory\n *\n * Use when configuration depends on other services.\n *\n * @param options - Async configuration with factory and inject tokens\n * @returns Dynamic module with email infrastructure\n *\n * @example\n * ```typescript\n * EmailModule.forRootAsync({\n * inject: [emailConfig.KEY],\n * useFactory: (email) => ({\n * from: email.from,\n * smtp: email.smtp,\n * queue: email.queue,\n * })\n * })\n * ```\n */\n static forRootAsync(options: AsyncModuleOptions<EmailModuleOptions>): DynamicModule {\n return {\n module: EmailModule,\n providers: [\n {\n provide: EMAIL_TOKENS.Options,\n useFactory: options.useFactory,\n inject: options.inject,\n },\n // Resolve queue from QueueRegistry using the binding from options\n {\n provide: EMAIL_TOKENS.EmailQueue,\n useFactory: (emailOptions: EmailModuleOptions, registry: QueueRegistry) =>\n registry.getQueue(emailOptions.queue),\n inject: [EMAIL_TOKENS.Options, QUEUE_TOKENS.QueueRegistry],\n },\n ],\n }\n }\n}\n","import { z } from '../../i18n/validation'\n\n/**\n * Inline Email Attachment Schema\n *\n * Attachment with content embedded as base64 string.\n * Use for small files that can fit in queue message.\n */\nexport const inlineEmailAttachmentSchema = z.object({\n /**\n * Filename to display for the attachment\n */\n filename: z.string().min(1).max(255),\n\n /**\n * Base64 encoded content of the attachment\n */\n content: z.string(),\n\n /**\n * MIME type of the attachment (e.g., 'application/pdf', 'image/png')\n */\n contentType: z.string(),\n\n /**\n * Optional size of the attachment in bytes\n */\n size: z.number().positive().optional(),\n})\n\n/**\n * Storage Email Attachment Schema\n *\n * Attachment stored in cloud storage.\n * Content type and size are derived from storage metadata.\n * Use for large files to avoid queue message size limits.\n */\nexport const storageEmailAttachmentSchema = z.object({\n /**\n * Filename to display for the attachment\n */\n filename: z.string().min(1).max(255),\n\n /**\n * Path to the file in storage\n */\n storageKey: z.string(),\n\n /**\n * Optional storage disk name (uses default if not provided)\n */\n disk: z.string().optional(),\n})\n\n/**\n * Email Attachment Schema\n *\n * Union type - type is inferred from presence of `content` vs `storageKey`.\n * - If `content` is present: inline attachment\n * - If `storageKey` is present: storage-based attachment\n */\nexport const emailAttachmentSchema = z.union([\n inlineEmailAttachmentSchema,\n storageEmailAttachmentSchema,\n])\n\n/**\n * Type definitions\n */\nexport type InlineEmailAttachment = z.infer<typeof inlineEmailAttachmentSchema>\nexport type StorageEmailAttachment = z.infer<typeof storageEmailAttachmentSchema>\nexport type EmailAttachment = z.infer<typeof emailAttachmentSchema>\n\n/**\n * Resolved Email Attachment\n *\n * Attachment after resolution, ready for email provider.\n * Content can be Buffer (for inline) or ReadableStream (for storage-based).\n */\nexport interface ResolvedEmailAttachment {\n filename: string\n content: Buffer | ReadableStream\n contentType: string\n}\n","import { withZodI18n, z } from '../../i18n/validation'\nimport { emailAttachmentSchema, type ResolvedEmailAttachment } from './email-attachment'\n\n/**\n * Email Address Schema\n *\n * Represents an email address with optional name\n */\nexport const emailAddressSchema = z.object({\n name: z.string().optional(),\n email: z.string().email(),\n})\n\n/**\n * Base Email Message Schema\n *\n * Defines the core structure for email messages.\n * Ensures either html or text content is provided.\n */\nexport const emailMessageSchema = z\n .object({\n /**\n * Recipient email address(es)\n * Can be a single email string or array of emails\n */\n to: z.union([z.string().email(), z.array(z.string().email())]),\n\n /**\n * Sender email address with optional name\n * Falls back to default from config if not provided\n */\n from: emailAddressSchema.optional(),\n\n /**\n * Email subject line\n */\n subject: z.string().min(1).max(500),\n\n /**\n * HTML content of the email\n * Either html or text must be provided\n */\n html: z.string().optional(),\n\n /**\n * Plain text content of the email\n * Either html or text must be provided\n */\n text: z.string().optional(),\n\n /**\n * Reply-to email address\n */\n replyTo: z.string().email().optional(),\n\n /**\n * CC recipients\n */\n cc: z.array(z.string().email()).optional(),\n\n /**\n * BCC recipients\n */\n bcc: z.array(z.string().email()).optional(),\n\n /**\n * Email attachments\n */\n attachments: z.array(emailAttachmentSchema).optional(),\n })\n .refine(\n (data) => data.html ?? data.text,\n withZodI18n('zodI18n.errors.custom.emailOrTextRequired')\n )\n\n/**\n * Type definition for email message\n */\nexport type EmailMessage = z.infer<typeof emailMessageSchema>\n\n/**\n * Type definition for email address\n */\nexport type EmailAddress = z.infer<typeof emailAddressSchema>\n\n/**\n * Resolved Email Message\n *\n * Email message with attachments resolved to Buffer content.\n * Used by providers after the consumer resolves storage-based attachments.\n */\nexport type ResolvedEmailMessage = Omit<EmailMessage, 'attachments'> & {\n attachments?: ResolvedEmailAttachment[]\n}\n","import { z } from '../../i18n/validation'\nimport { emailMessageSchema } from './email-message.contract'\n\n/**\n * Send Email Input Schema\n *\n * Input schema for sending emails through the EmailService.\n * Extends the base email message with optional metadata.\n * Uses safeExtend() because emailMessageSchema contains refinements.\n */\nexport const sendEmailInputSchema = emailMessageSchema.safeExtend({\n /**\n * Optional metadata to include with the email\n * Can be used for tracking, categorization, etc.\n */\n metadata: z.record(z.string(), z.unknown()).optional(),\n})\n\n/**\n * Type definition for send email input\n */\nexport type SendEmailInput = z.infer<typeof sendEmailInputSchema>\n\n/**\n * Send Batch Email Input Schema\n *\n * Schema for sending multiple emails in a batch\n */\nexport const sendBatchEmailInputSchema = z.object({\n /**\n * Array of email messages to send\n */\n messages: z.array(sendEmailInputSchema).min(1).max(100),\n})\n\n/**\n * Type definition for send batch email input\n */\nexport type SendBatchEmailInput = z.infer<typeof sendBatchEmailInputSchema>\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as __exportAll } from "./chunk-
|
|
1
|
+
import { t as __exportAll } from "./chunk-BBjsoOtd.mjs";
|
|
2
2
|
//#region src/i18n/messages/en/common.ts
|
|
3
3
|
/**
|
|
4
4
|
* System Common Messages - English
|
|
@@ -199,4 +199,4 @@ var en_exports = /* @__PURE__ */ __exportAll({
|
|
|
199
199
|
//#endregion
|
|
200
200
|
export { common as a, emails as i, zodI18n as n, validation as r, en_exports as t };
|
|
201
201
|
|
|
202
|
-
//# sourceMappingURL=en-
|
|
202
|
+
//# sourceMappingURL=en-CDZBMcc1.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"en-
|
|
1
|
+
{"version":3,"file":"en-CDZBMcc1.mjs","names":[],"sources":["../src/i18n/messages/en/common.ts","../src/i18n/messages/en/emails.ts","../src/i18n/messages/en/validation.ts","../src/i18n/messages/en/zod.ts","../src/i18n/messages/en/index.ts"],"sourcesContent":["/**\n * System Common Messages - English\n *\n * Common messages used by packages/modules infrastructure.\n * These are automatically merged with application-specific messages.\n */\n\nexport const common = {\n api: {\n title: 'Stratal API',\n description: 'Platform API',\n serverDescription: 'API server',\n security: {\n bearerAuth: 'JWT Bearer token authentication',\n apiKey: 'API key for service authentication',\n sessionCookie: 'Session cookie for browser authentication'\n }\n }\n} as const\n","/**\n * System Email Messages - English\n *\n * Email-related messages used by packages/modules infrastructure.\n * These are automatically merged with application-specific messages.\n */\n\nexport const emails = {\n magicLink: {\n subject: 'Your sign-in link'\n }\n} as const\n","/**\n * Form validation messages - English\n */\n\nexport const validation = {\n required: 'This field is required',\n email: 'Invalid email address',\n minLength: 'Must be at least {min} characters',\n maxLength: 'Must not exceed {max} characters',\n min: 'Must be at least {min}',\n max: 'Must not exceed {max}',\n pattern: 'Invalid format',\n numeric: 'Must be a number',\n url: 'Invalid URL',\n date: 'Invalid date',\n passwordStrength: 'Password must contain at least one uppercase letter, one lowercase letter, and one number',\n passwordMatch: 'Passwords do not match',\n unique: 'This value already exists',\n phone: 'Invalid phone number',\n fileRequired: 'Please upload a file',\n fileTooLarge: 'File must be smaller than {max}',\n invalidFileType: 'Please upload a PDF, JPG, or PNG file',\n schoolTypes: {\n required: 'School type is required',\n atLeastOne: 'Please select at least one school type',\n invalidCode: 'Invalid school type: {code}',\n notAvailableInCountry: '{schoolType} is not available in {country}',\n countryNotSupported: 'Country {country} is not supported'\n },\n timezone: {\n required: 'Timezone is required',\n invalid: 'Invalid timezone. Please select a valid IANA timezone.'\n },\n locale: {\n required: 'Language is required',\n invalid: 'Invalid language. Supported languages: {locales}'\n }\n} as const\n","/**\n * Zod validation error messages - English\n *\n * Comprehensive messages for all Zod validation error codes\n * Structured to match Zod's issue types and validation contexts\n */\n\nexport const zodI18n = {\n errors: {\n // General errors\n required: 'Required',\n invalid_type: 'Expected {expected}, received {received}',\n invalid_literal: 'Invalid literal value, expected {expected}',\n custom: {\n default: 'Invalid value',\n // Email validation\n emailOrTextRequired: 'Either html or text content must be provided',\n invalidFromEmail: 'Invalid from email address',\n // Storage validation\n fileInstanceRequired: 'File must be a File instance',\n filePathRequired: 'File path is required',\n diskNameRequired: 'Disk name is required',\n endpointRequired: 'Endpoint URL is required for S3',\n bucketNameRequired: 'Bucket name is required',\n accessKeyRequired: 'Access key ID is required',\n secretKeyRequired: 'Secret access key is required',\n storageDiskRequired: 'At least one storage disk is required',\n // Database validation\n databaseUrlRequired: 'Database URL is required',\n // Domain validation\n domainRequired: 'Domain is required',\n domainTooLong: 'Domain too long',\n invalidDomainFormat: 'Invalid domain format',\n },\n invalid_union: 'Invalid input',\n invalid_union_discriminator: 'Invalid discriminator value. Expected {options}',\n invalid_enum_value: 'Invalid enum value. Expected {options}, received {received}',\n unrecognized_keys: 'Unrecognized key(s) in object: {keys}',\n invalid_arguments: 'Invalid function arguments',\n invalid_return_type: 'Invalid function return type',\n invalid_date: 'Invalid date',\n invalid_intersection_types: 'Intersection results could not be merged',\n not_multiple_of: 'Number must be a multiple of {multipleOf}',\n not_finite: 'Number must be finite',\n\n // String-specific validation errors\n invalid_string: {\n email: 'Invalid email address',\n url: 'Invalid URL',\n uuid: 'Invalid UUID',\n cuid: 'Invalid CUID',\n cuid2: 'Invalid CUID2',\n ulid: 'Invalid ULID',\n regex: 'Invalid format',\n datetime: 'Invalid datetime',\n ip: 'Invalid IP address',\n emoji: 'Invalid emoji',\n startsWith: 'Must start with \"{startsWith}\"',\n endsWith: 'Must end with \"{endsWith}\"',\n includes: 'Must include \"{includes}\"',\n base64: 'Invalid Base64',\n nanoid: 'Invalid NanoID',\n cidr: 'Invalid CIDR',\n jwt: 'Invalid JWT',\n time: 'Invalid time',\n },\n\n // Size validation errors (strings, arrays, numbers)\n too_small: {\n string: {\n exact: 'Must be exactly {minimum} characters',\n inclusive: 'Must be at least {minimum} characters',\n not_inclusive: 'Must be more than {minimum} characters',\n },\n number: {\n exact: 'Must be exactly {minimum}',\n inclusive: 'Must be at least {minimum}',\n not_inclusive: 'Must be greater than {minimum}',\n },\n array: {\n exact: 'Must contain exactly {minimum} item(s)',\n inclusive: 'Must contain at least {minimum} item(s)',\n not_inclusive: 'Must contain more than {minimum} item(s)',\n },\n set: {\n exact: 'Must contain exactly {minimum} item(s)',\n inclusive: 'Must contain at least {minimum} item(s)',\n not_inclusive: 'Must contain more than {minimum} item(s)',\n },\n date: {\n exact: 'Date must be {minimum}',\n inclusive: 'Date must be {minimum} or later',\n not_inclusive: 'Date must be after {minimum}',\n },\n bigint: {\n exact: 'Must be exactly {minimum}',\n inclusive: 'Must be at least {minimum}',\n not_inclusive: 'Must be greater than {minimum}',\n },\n },\n\n too_big: {\n string: {\n exact: 'Must be exactly {maximum} characters',\n inclusive: 'Must be at most {maximum} characters',\n not_inclusive: 'Must be less than {maximum} characters',\n },\n number: {\n exact: 'Must be exactly {maximum}',\n inclusive: 'Must be at most {maximum}',\n not_inclusive: 'Must be less than {maximum}',\n },\n array: {\n exact: 'Must contain exactly {maximum} item(s)',\n inclusive: 'Must contain at most {maximum} item(s)',\n not_inclusive: 'Must contain less than {maximum} item(s)',\n },\n set: {\n exact: 'Must contain exactly {maximum} item(s)',\n inclusive: 'Must contain at most {maximum} item(s)',\n not_inclusive: 'Must contain less than {maximum} item(s)',\n },\n date: {\n exact: 'Date must be {maximum}',\n inclusive: 'Date must be {maximum} or earlier',\n not_inclusive: 'Date must be before {maximum}',\n },\n bigint: {\n exact: 'Must be exactly {maximum}',\n inclusive: 'Must be at most {maximum}',\n not_inclusive: 'Must be less than {maximum}',\n },\n },\n },\n} as const\n","/**\n * System Messages - English\n *\n * Re-exports all system message categories.\n * These messages are used by packages/modules infrastructure\n * and are automatically merged with application-specific messages.\n */\n\nexport { common } from './common'\nexport { emails } from './emails'\nexport { validation } from './validation'\nexport { zodI18n } from './zod'\n"],"mappings":";;;;;;;;AAOA,MAAa,SAAS,EACpB,KAAK;CACH,OAAO;CACP,aAAa;CACb,mBAAmB;CACnB,UAAU;EACR,YAAY;EACZ,QAAQ;EACR,eAAe;CACjB;AACF,EACF;;;;;;;;;ACXA,MAAa,SAAS,EACpB,WAAW,EACT,SAAS,oBACX,EACF;;;;;;ACPA,MAAa,aAAa;CACxB,UAAU;CACV,OAAO;CACP,WAAW;CACX,WAAW;CACX,KAAK;CACL,KAAK;CACL,SAAS;CACT,SAAS;CACT,KAAK;CACL,MAAM;CACN,kBAAkB;CAClB,eAAe;CACf,QAAQ;CACR,OAAO;CACP,cAAc;CACd,cAAc;CACd,iBAAiB;CACjB,aAAa;EACX,UAAU;EACV,YAAY;EACZ,aAAa;EACb,uBAAuB;EACvB,qBAAqB;CACvB;CACA,UAAU;EACR,UAAU;EACV,SAAS;CACX;CACA,QAAQ;EACN,UAAU;EACV,SAAS;CACX;AACF;;;;;;;;;AC9BA,MAAa,UAAU,EACrB,QAAQ;CAEN,UAAU;CACV,cAAc;CACd,iBAAiB;CACjB,QAAQ;EACN,SAAS;EAET,qBAAqB;EACrB,kBAAkB;EAElB,sBAAsB;EACtB,kBAAkB;EAClB,kBAAkB;EAClB,kBAAkB;EAClB,oBAAoB;EACpB,mBAAmB;EACnB,mBAAmB;EACnB,qBAAqB;EAErB,qBAAqB;EAErB,gBAAgB;EAChB,eAAe;EACf,qBAAqB;CACvB;CACA,eAAe;CACf,6BAA6B;CAC7B,oBAAoB;CACpB,mBAAmB;CACnB,mBAAmB;CACnB,qBAAqB;CACrB,cAAc;CACd,4BAA4B;CAC5B,iBAAiB;CACjB,YAAY;CAGZ,gBAAgB;EACd,OAAO;EACP,KAAK;EACL,MAAM;EACN,MAAM;EACN,OAAO;EACP,MAAM;EACN,OAAO;EACP,UAAU;EACV,IAAI;EACJ,OAAO;EACP,YAAY;EACZ,UAAU;EACV,UAAU;EACV,QAAQ;EACR,QAAQ;EACR,MAAM;EACN,KAAK;EACL,MAAM;CACR;CAGA,WAAW;EACT,QAAQ;GACN,OAAO;GACP,WAAW;GACX,eAAe;EACjB;EACA,QAAQ;GACN,OAAO;GACP,WAAW;GACX,eAAe;EACjB;EACA,OAAO;GACL,OAAO;GACP,WAAW;GACX,eAAe;EACjB;EACA,KAAK;GACH,OAAO;GACP,WAAW;GACX,eAAe;EACjB;EACA,MAAM;GACJ,OAAO;GACP,WAAW;GACX,eAAe;EACjB;EACA,QAAQ;GACN,OAAO;GACP,WAAW;GACX,eAAe;EACjB;CACF;CAEA,SAAS;EACP,QAAQ;GACN,OAAO;GACP,WAAW;GACX,eAAe;EACjB;EACA,QAAQ;GACN,OAAO;GACP,WAAW;GACX,eAAe;EACjB;EACA,OAAO;GACL,OAAO;GACP,WAAW;GACX,eAAe;EACjB;EACA,KAAK;GACH,OAAO;GACP,WAAW;GACX,eAAe;EACjB;EACA,MAAM;GACJ,OAAO;GACP,WAAW;GACX,eAAe;EACjB;EACA,QAAQ;GACN,OAAO;GACP,WAAW;GACX,eAAe;EACjB;CACF;AACF,EACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-ug22bJj7.d.mts","names":[],"sources":["../src/env.ts"],"mappings":";;AAgBA;;;;;;;;;AAGY;;;;;;UAHK,UAAA;EACf,WAAA;EACA,KAAA,EAAO,WAAW;EAClB,UAAA;AAAA"}
|
package/dist/errors/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as ErrorResponse, C as HttpExceptionContext, D as createHttpExceptionContext, E as createCronExceptionContext, O as createQueueExceptionContext, S as ExceptionContext, T as createCliExceptionContext, _ as Reportable, a as isApplicationError, b as CliExceptionContext, c as HttpException, d as ExceptionHandler, f as ApplicationErrorConstructor, g as RenderableCallback, h as LogSeverity, i as ContainerNotInitializedError, j as isErrorResponse, k as Environment, l as abort, m as ErrorPageCallback, n as DatabaseError, o as InternalError, p as ContextCallback, r as StratalNotInitializedError, s as HTTP_STATUS_MESSAGES, t as AuthError, u as DefaultExceptionHandler, v as ReportableCallback, w as QueueExceptionContext, x as CronExceptionContext,
|
|
1
|
+
import { A as ErrorResponse, C as HttpExceptionContext, D as createHttpExceptionContext, Dr as ApplicationError, E as createCronExceptionContext, O as createQueueExceptionContext, S as ExceptionContext, T as createCliExceptionContext, _ as Reportable, a as isApplicationError, b as CliExceptionContext, c as HttpException, d as ExceptionHandler, f as ApplicationErrorConstructor, g as RenderableCallback, h as LogSeverity, i as ContainerNotInitializedError, j as isErrorResponse, k as Environment, l as abort, m as ErrorPageCallback, n as DatabaseError, o as InternalError, p as ContextCallback, r as StratalNotInitializedError, s as HTTP_STATUS_MESSAGES, t as AuthError, u as DefaultExceptionHandler, v as ReportableCallback, w as QueueExceptionContext, x as CronExceptionContext, y as RespondCallback } from "../index-B_JoEl3V.mjs";
|
|
2
2
|
export { ApplicationError, ApplicationErrorConstructor, AuthError, CliExceptionContext, ContainerNotInitializedError, ContextCallback, CronExceptionContext, DatabaseError, DefaultExceptionHandler, Environment, ErrorPageCallback, ErrorResponse, ExceptionContext, ExceptionHandler, HTTP_STATUS_MESSAGES, HttpException, HttpExceptionContext, InternalError, LogSeverity, QueueExceptionContext, RenderableCallback, Reportable, ReportableCallback, RespondCallback, StratalNotInitializedError, abort, createCliExceptionContext, createCronExceptionContext, createHttpExceptionContext, createQueueExceptionContext, isApplicationError, isErrorResponse };
|
package/dist/errors/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as ApplicationError, i as ContainerNotInitializedError } from "../container-storage-
|
|
2
|
-
import { a as DefaultExceptionHandler, c as InternalError, d as abort, i as isErrorResponse, l as HTTP_STATUS_MESSAGES, n as DatabaseError, o as ExceptionHandler, r as StratalNotInitializedError, s as isApplicationError, t as AuthError, u as HttpException } from "../errors-
|
|
3
|
-
import { i as createQueueExceptionContext, n as createCronExceptionContext, r as createHttpExceptionContext, t as createCliExceptionContext } from "../exception-context-
|
|
1
|
+
import { a as ApplicationError, i as ContainerNotInitializedError } from "../container-storage-BmOJ4_Na.mjs";
|
|
2
|
+
import { a as DefaultExceptionHandler, c as InternalError, d as abort, i as isErrorResponse, l as HTTP_STATUS_MESSAGES, n as DatabaseError, o as ExceptionHandler, r as StratalNotInitializedError, s as isApplicationError, t as AuthError, u as HttpException } from "../errors-mXYxG0XB.mjs";
|
|
3
|
+
import { i as createQueueExceptionContext, n as createCronExceptionContext, r as createHttpExceptionContext, t as createCliExceptionContext } from "../exception-context-kEoMFwze.mjs";
|
|
4
4
|
export { ApplicationError, AuthError, ContainerNotInitializedError, DatabaseError, DefaultExceptionHandler, ExceptionHandler, HTTP_STATUS_MESSAGES, HttpException, InternalError, StratalNotInitializedError, abort, createCliExceptionContext, createCronExceptionContext, createHttpExceptionContext, createQueueExceptionContext, isApplicationError, isErrorResponse };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { n as __decorateParam, t as __decorate } from "./decorate-
|
|
1
|
+
import { c as Transient, d as inject, n as CONTAINER_TOKEN, r as DI_TOKENS } 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
4
|
import { LOGGER_TOKENS } from "./logger/index.mjs";
|
|
5
|
-
import "./exception-context-
|
|
5
|
+
import "./exception-context-kEoMFwze.mjs";
|
|
6
6
|
//#region src/errors/http-exception.ts
|
|
7
7
|
const HTTP_STATUS_MESSAGES = {
|
|
8
8
|
400: "Bad Request",
|
|
@@ -330,4 +330,4 @@ var AuthError = class extends ApplicationError {
|
|
|
330
330
|
//#endregion
|
|
331
331
|
export { DefaultExceptionHandler as a, InternalError as c, abort as d, isErrorResponse as i, HTTP_STATUS_MESSAGES as l, DatabaseError as n, ExceptionHandler as o, StratalNotInitializedError as r, isApplicationError as s, AuthError as t, HttpException as u };
|
|
332
332
|
|
|
333
|
-
//# sourceMappingURL=errors-
|
|
333
|
+
//# sourceMappingURL=errors-mXYxG0XB.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors-BBZTnjdq.mjs","names":[],"sources":["../src/errors/http-exception.ts","../src/errors/internal-error.ts","../src/errors/is-application-error.ts","../src/errors/exception-handler.ts","../src/errors/default-exception-handler.ts","../src/errors/error-response.ts","../src/errors/stratal-not-initialized.error.ts","../src/errors/database.error.ts","../src/errors/auth.error.ts"],"sourcesContent":["import type { ContentfulStatusCode } from 'hono/utils/http-status'\nimport { ApplicationError } from './application-error'\n\nexport const HTTP_STATUS_MESSAGES: Partial<Record<number, string>> = {\n 400: 'Bad Request',\n 401: 'Unauthorized',\n 402: 'Payment Required',\n 403: 'Forbidden',\n 404: 'Not Found',\n 409: 'Conflict',\n 413: 'Content Too Large',\n 422: 'Unprocessable Entity',\n 423: 'Locked',\n 429: 'Too Many Requests',\n 500: 'Internal Server Error',\n 502: 'Bad Gateway',\n 503: 'Service Unavailable',\n}\n\nexport class HttpException extends ApplicationError {\n public readonly httpStatus: ContentfulStatusCode\n\n constructor(httpStatus: ContentfulStatusCode, message?: string, cause?: unknown) {\n super(message ?? HTTP_STATUS_MESSAGES[httpStatus] ?? 'Internal Server Error', cause)\n this.httpStatus = httpStatus\n }\n}\n\nexport function abort(\n status: ContentfulStatusCode,\n message?: string,\n): never {\n throw new HttpException(status, message)\n}\n","import { ApplicationError } from './application-error'\n\nexport class InternalError extends ApplicationError {\n constructor(cause?: unknown) {\n super('Internal Server Error', cause)\n }\n}\n","import { ApplicationError } from './application-error'\n\nexport function isApplicationError(error: unknown): error is ApplicationError {\n if (error instanceof ApplicationError) return true\n return error instanceof Error\n && typeof (error as ApplicationError).timestamp === 'string'\n}\n","import type { ContentfulStatusCode } from 'hono/utils/http-status';\nimport { inject } from '../di';\nimport { CONTAINER_TOKEN, type Container } from '../di';\nimport { Transient } from '../di/decorators';\nimport { DI_TOKENS } from '../di/tokens';\nimport { type StratalEnv } from '../env';\nimport type { StratalExecutionContext } from '../execution-context';\nimport { LOGGER_TOKENS, type LoggerService } from '../logger';\nimport type { ApplicationError } from './application-error';\nimport type { Environment, ErrorResponse } from './error-response';\nimport type { ExceptionContext, HttpExceptionContext } from './exception-context';\nimport type {\n ApplicationErrorConstructor,\n ContextCallback,\n ErrorPageCallback,\n LogSeverity,\n RenderableCallback,\n Reportable,\n ReportableCallback,\n RespondCallback,\n} from './exception-handler.types';\nimport { HTTP_STATUS_MESSAGES, HttpException } from './http-exception';\nimport { InternalError } from './internal-error';\nimport { isApplicationError } from './is-application-error';\n\ninterface ReportableEntry {\n errorClass: ApplicationErrorConstructor\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n callback: ReportableCallback<any>\n shouldStop: boolean\n}\n\ninterface RenderableEntry {\n errorClass: ApplicationErrorConstructor\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n callback: RenderableCallback<any>\n}\n\n@Transient()\nexport abstract class ExceptionHandler {\n private readonly reportables: ReportableEntry[] = []\n private readonly renderables: RenderableEntry[] = []\n private readonly dontReportSet = new Set<ApplicationErrorConstructor>()\n private readonly levelOverrides = new Map<ApplicationErrorConstructor, LogSeverity>()\n private readonly contextCallbacks: ContextCallback[] = []\n private readonly respondCallbacks: RespondCallback[] = []\n private readonly errorPages: ErrorPageCallback[] = []\n private readonly environment: Environment\n\n constructor(\n @inject(LOGGER_TOKENS.LoggerService) protected readonly logger: LoggerService,\n @inject(DI_TOKENS.CloudflareEnv) protected readonly env: StratalEnv,\n @inject(CONTAINER_TOKEN) private readonly container: Container,\n @inject(DI_TOKENS.ExecutionContext) private readonly executionContext: StratalExecutionContext,\n ) {\n this.environment = this.env.ENVIRONMENT as Environment\n }\n\n abstract register(): void\n\n // ── Public Configuration API ──────────────────────────────────────\n\n reportable<T extends ApplicationError>(\n errorClass: ApplicationErrorConstructor<T>,\n callback: ReportableCallback<T>,\n ): Reportable {\n const entry: ReportableEntry = { errorClass, callback, shouldStop: false }\n this.reportables.push(entry)\n return {\n stop: () => { entry.shouldStop = true },\n }\n }\n\n renderable<T extends ApplicationError>(\n errorClass: ApplicationErrorConstructor<T>,\n callback: RenderableCallback<T>,\n ): void {\n this.renderables.push({ errorClass, callback })\n }\n\n dontReport(errorClasses: ApplicationErrorConstructor[]): void {\n for (const cls of errorClasses) {\n this.dontReportSet.add(cls)\n }\n }\n\n level(errorClass: ApplicationErrorConstructor, severity: LogSeverity): void {\n this.levelOverrides.set(errorClass, severity)\n }\n\n context(callback: ContextCallback): void {\n this.contextCallbacks.push(callback)\n }\n\n respond(callback: RespondCallback): void {\n this.respondCallbacks.push(callback)\n }\n\n errorPage(callback: ErrorPageCallback): void {\n this.errorPages.push(callback)\n }\n\n resolve<T>(token: symbol | (new (...args: unknown[]) => T)): T {\n return this.container.resolve<T>(token)\n }\n\n // ── Pipeline Entry Point ──────────────────────────────────────────\n\n async handle(error: unknown, context: ExceptionContext): Promise<Response> {\n const appError = this.normalizeError(error)\n\n this.executionContext.waitUntil(this.performReport(appError, context))\n\n const response = await this.performRender(appError, context)\n\n return this.applyRespondCallbacks(response, appError, context)\n }\n\n // ── Internals ─────────────────────────────────────────────────────\n\n private normalizeError(error: unknown): ApplicationError {\n if (isApplicationError(error)) {\n return error\n }\n\n return new InternalError(error)\n }\n\n private async performReport(error: ApplicationError, context: ExceptionContext): Promise<void> {\n if (this.shouldNotReport(error)) return\n\n const entry = this.findReportable(error)\n if (entry) {\n await entry.callback(error, context)\n if (entry.shouldStop) return\n }\n\n this.defaultReport(error)\n }\n\n private async performRender(error: ApplicationError, context: ExceptionContext): Promise<Response> {\n const entry = this.findRenderable(error)\n if (entry) {\n const result = entry.callback(error, context)\n if (result !== undefined) {\n return this.toResponse(await result, error)\n }\n }\n\n return await this.defaultRender(error, context)\n }\n\n private applyRespondCallbacks(\n response: Response,\n error: ApplicationError,\n context: ExceptionContext,\n ): Response {\n let result = response\n for (const callback of this.respondCallbacks) {\n result = callback(result, error, context)\n }\n return result\n }\n\n private shouldNotReport(error: ApplicationError): boolean {\n for (const cls of this.dontReportSet) {\n if (error instanceof cls) return true\n }\n return false\n }\n\n private findReportable(error: ApplicationError): ReportableEntry | undefined {\n let best: ReportableEntry | undefined\n for (const entry of this.reportables) {\n if (this.matchesErrorClass(error, entry.errorClass)) {\n if (!best || !this.matchesErrorClass(error, best.errorClass) || entry.errorClass.prototype instanceof best.errorClass) {\n best = entry\n }\n }\n }\n return best\n }\n\n private findRenderable(error: ApplicationError): RenderableEntry | undefined {\n let best: RenderableEntry | undefined\n for (const entry of this.renderables) {\n if (this.matchesErrorClass(error, entry.errorClass)) {\n if (!best || !this.matchesErrorClass(error, best.errorClass) || entry.errorClass.prototype instanceof best.errorClass) {\n best = entry\n }\n }\n }\n return best\n }\n\n private matchesErrorClass(error: ApplicationError, cls: ApplicationErrorConstructor): boolean {\n if (error instanceof cls) return true\n return (error as Error).constructor.name === cls.name\n }\n\n private defaultReport(error: ApplicationError): void {\n const severity = this.resolveSeverity(error)\n const globalContext = this.gatherContext()\n\n const logData: Record<string, unknown> = {\n message: error.message,\n timestamp: error.timestamp,\n name: error.name,\n stack: error.stack,\n ...globalContext,\n }\n\n const chain = serializeErrorChain(error)\n if (chain) {\n logData.cause = chain\n }\n\n switch (severity) {\n case 'debug':\n this.logger.debug('[ApplicationError]', logData)\n break\n case 'info':\n this.logger.info('[ApplicationError]', logData)\n break\n case 'warn':\n this.logger.warn('[ApplicationError]', logData)\n break\n case 'error':\n this.logger.error('[ApplicationError]', logData)\n break\n }\n }\n\n private async defaultRender(error: ApplicationError, context: ExceptionContext): Promise<Response> {\n const status = this.resolveStatus(error)\n const errorResponse = this.buildErrorResponse(error, status)\n\n if (context.type === 'http' && this.wantsHtml(context)) {\n for (const callback of this.errorPages) {\n try {\n const result = await callback(errorResponse, status, context, error)\n if (result !== undefined) return result\n } catch (callbackError) {\n this.logger.warn('errorPage callback failed, falling back to next handler', {\n error: callbackError instanceof Error ? callbackError.message : String(callbackError),\n })\n }\n }\n return this.renderDefaultHtml(errorResponse, status)\n }\n\n return Response.json(errorResponse, { status })\n }\n\n // ── Content Negotiation ──────────────────────────────────────────\n\n protected wantsHtml(context: HttpExceptionContext): boolean {\n const accept = context.ctx.c.req.header('accept') ?? ''\n return accept.includes('text/html')\n }\n\n protected renderDefaultHtml(\n errorResponse: ErrorResponse,\n status: ContentfulStatusCode,\n ): Response {\n const title = this.escapeHtml(errorResponse.message)\n const html = `<!DOCTYPE html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<title>${status} - ${title}</title>\n<link rel=\"icon\" type=\"image/x-icon\" href=\"/favicon.svg\">\n<style>*{margin:0;padding:0;box-sizing:border-box}body{font-family:system-ui,-apple-system,sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center;background:#f8fafc;color:#334155}.container{text-align:center;padding:2rem}.status{font-size:6rem;font-weight:800;color:#13c397;line-height:1}.message{font-size:1.25rem;color:#64748b;margin-top:1rem}</style>\n</head><body><div class=\"container\"><div class=\"status\">${status}</div><div class=\"message\">${title}</div></div></body></html>`\n return new Response(html, {\n status,\n headers: { 'content-type': 'text/html; charset=utf-8' },\n })\n }\n\n private resolveStatus(error: ApplicationError): ContentfulStatusCode {\n if (error instanceof HttpException) return error.httpStatus\n const httpStatus = (error as { httpStatus?: number }).httpStatus\n if (typeof httpStatus === 'number') return httpStatus as ContentfulStatusCode\n return 500\n }\n\n private buildErrorResponse(error: ApplicationError, status: ContentfulStatusCode): ErrorResponse {\n const isServerError = status >= 500\n const isDev = this.environment === 'development'\n const message = isServerError && !isDev\n ? (HTTP_STATUS_MESSAGES[status] ?? 'Internal Server Error')\n : error.message\n\n return {\n message,\n timestamp: error.timestamp,\n stack: isDev ? error.stack : undefined,\n }\n }\n\n private escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n }\n\n private toResponse(result: Response | ErrorResponse, error: ApplicationError): Response {\n if (result instanceof Response) return result\n const status = this.resolveStatus(error)\n return Response.json(result, { status })\n }\n\n private resolveSeverity(error: ApplicationError): LogSeverity {\n let bestClass: ApplicationErrorConstructor | undefined\n let bestSeverity: LogSeverity | undefined\n\n for (const [cls, severity] of this.levelOverrides) {\n if (error instanceof cls) {\n if (!bestClass || cls.prototype instanceof bestClass) {\n bestClass = cls\n bestSeverity = severity\n }\n }\n }\n\n if (bestSeverity) return bestSeverity\n\n const status = this.resolveStatus(error)\n return this.getDefaultSeverity(status)\n }\n\n private getDefaultSeverity(status: number): LogSeverity {\n if (status >= 500) return 'error'\n if (status >= 400) return 'warn'\n return 'error'\n }\n\n private gatherContext(): Record<string, unknown> {\n if (this.contextCallbacks.length === 0) return {}\n const merged: Record<string, unknown> = {}\n for (const callback of this.contextCallbacks) {\n Object.assign(merged, callback())\n }\n return merged\n }\n}\n\ninterface SerializedErrorNode {\n name: string\n message: string\n stack?: string\n cause?: SerializedErrorNode | { value: unknown }\n errors?: SerializedErrorNode[]\n}\n\nconst MAX_CAUSE_DEPTH = 5\n\nfunction serializeErrorChain(error: Error): SerializedErrorNode | undefined {\n const cause = (error as { cause?: unknown }).cause\n const aggregated = error instanceof AggregateError && Array.isArray(error.errors) && error.errors.length > 0\n if (cause === undefined && !aggregated) {\n return undefined\n }\n if (cause !== undefined) {\n return cause instanceof Error\n ? serializeErrorNode(cause, 0)\n : ({ value: cause } as unknown as SerializedErrorNode)\n }\n return {\n name: error.name,\n message: error.message,\n errors: (error as AggregateError).errors.map((inner) =>\n inner instanceof Error\n ? serializeErrorNode(inner, 0)\n : { name: 'Unknown', message: String(inner) },\n ),\n }\n}\n\nfunction serializeErrorNode(error: Error, depth: number): SerializedErrorNode {\n const out: SerializedErrorNode = {\n name: error.name,\n message: error.message,\n stack: error.stack,\n }\n\n if (depth >= MAX_CAUSE_DEPTH) return out\n\n if (error instanceof AggregateError && Array.isArray(error.errors) && error.errors.length > 0) {\n out.errors = error.errors.map((inner) =>\n inner instanceof Error\n ? serializeErrorNode(inner, depth + 1)\n : { name: 'Unknown', message: String(inner) },\n )\n }\n\n const cause = (error as { cause?: unknown }).cause\n if (cause !== undefined) {\n out.cause = cause instanceof Error\n ? serializeErrorNode(cause, depth + 1)\n : { value: cause }\n }\n\n return out\n}\n","import { Transient } from '../di/decorators';\nimport { ExceptionHandler } from './exception-handler';\n\n/**\n * DefaultExceptionHandler — the built-in exception handler used when no\n * custom handler is provided via `ApplicationConfig.exceptionHandler`.\n *\n * Has an empty `register()` method, so all exceptions flow through the\n * default pipeline: severity-based logging, i18n translation, and JSON\n * error response serialization.\n *\n * To customize exception handling, extend {@link ExceptionHandler} and\n * override `register()`, then pass your class to the Stratal config:\n *\n * @example\n * ```typescript\n * new Stratal({\n * module: AppModule,\n * exceptionHandler: AppExceptionHandler,\n * })\n * ```\n */\n@Transient()\nexport class DefaultExceptionHandler extends ExceptionHandler {\n register(): void {\n // No custom configuration — uses all defaults\n }\n}\n","export type Environment = 'development' | 'staging' | 'production'\n\nexport interface ErrorResponse {\n message: string\n timestamp: string\n stack?: string\n}\n\nexport function isErrorResponse(obj: unknown): obj is ErrorResponse {\n return (\n typeof obj === 'object' &&\n obj !== null &&\n 'message' in obj &&\n typeof (obj as ErrorResponse).message === 'string' &&\n 'timestamp' in obj &&\n typeof (obj as ErrorResponse).timestamp === 'string'\n )\n}\n","import { ApplicationError } from './application-error'\n\nexport class StratalNotInitializedError extends ApplicationError {\n constructor() {\n super('Stratal has not been initialized. Ensure you export a Stratal instance as the default export.')\n }\n}\n","import { ApplicationError } from './application-error'\n\nexport class DatabaseError extends ApplicationError {\n constructor(message?: string, cause?: unknown) {\n super(message ?? 'Database error', cause)\n }\n}\n","import { ApplicationError } from './application-error'\n\nexport class AuthError extends ApplicationError {\n constructor(message?: string, cause?: unknown) {\n super(message ?? 'Authentication error', cause)\n }\n}\n"],"mappings":";;;;;;AAGA,MAAa,uBAAwD;CACnE,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACN;AAED,IAAa,gBAAb,cAAmC,iBAAiB;CAClD;CAEA,YAAY,YAAkC,SAAkB,OAAiB;EAC/E,MAAM,WAAW,qBAAqB,eAAe,yBAAyB,MAAM;EACpF,KAAK,aAAa;;;AAItB,SAAgB,MACd,QACA,SACO;CACP,MAAM,IAAI,cAAc,QAAQ,QAAQ;;;;AC9B1C,IAAa,gBAAb,cAAmC,iBAAiB;CAClD,YAAY,OAAiB;EAC3B,MAAM,yBAAyB,MAAM;;;;;ACFzC,SAAgB,mBAAmB,OAA2C;CAC5E,IAAI,iBAAiB,kBAAkB,OAAO;CAC9C,OAAO,iBAAiB,SACnB,OAAQ,MAA2B,cAAc;;;;ACkCjD,IAAA,mBAAA,MAAe,iBAAiB;CAWqB;CACJ;CACV;CACW;CAbvD,cAAkD,EAAE;CACpD,cAAkD,EAAE;CACpD,gCAAiC,IAAI,KAAkC;CACvE,iCAAkC,IAAI,KAA+C;CACrF,mBAAuD,EAAE;CACzD,mBAAuD,EAAE;CACzD,aAAmD,EAAE;CACrD;CAEA,YACE,QACA,KACA,WACA,kBACA;EAJwD,KAAA,SAAA;EACJ,KAAA,MAAA;EACV,KAAA,YAAA;EACW,KAAA,mBAAA;EAErD,KAAK,cAAc,KAAK,IAAI;;CAO9B,WACE,YACA,UACY;EACZ,MAAM,QAAyB;GAAE;GAAY;GAAU,YAAY;GAAO;EAC1E,KAAK,YAAY,KAAK,MAAM;EAC5B,OAAO,EACL,YAAY;GAAE,MAAM,aAAa;KAClC;;CAGH,WACE,YACA,UACM;EACN,KAAK,YAAY,KAAK;GAAE;GAAY;GAAU,CAAC;;CAGjD,WAAW,cAAmD;EAC5D,KAAK,MAAM,OAAO,cAChB,KAAK,cAAc,IAAI,IAAI;;CAI/B,MAAM,YAAyC,UAA6B;EAC1E,KAAK,eAAe,IAAI,YAAY,SAAS;;CAG/C,QAAQ,UAAiC;EACvC,KAAK,iBAAiB,KAAK,SAAS;;CAGtC,QAAQ,UAAiC;EACvC,KAAK,iBAAiB,KAAK,SAAS;;CAGtC,UAAU,UAAmC;EAC3C,KAAK,WAAW,KAAK,SAAS;;CAGhC,QAAW,OAAoD;EAC7D,OAAO,KAAK,UAAU,QAAW,MAAM;;CAKzC,MAAM,OAAO,OAAgB,SAA8C;EACzE,MAAM,WAAW,KAAK,eAAe,MAAM;EAE3C,KAAK,iBAAiB,UAAU,KAAK,cAAc,UAAU,QAAQ,CAAC;EAEtE,MAAM,WAAW,MAAM,KAAK,cAAc,UAAU,QAAQ;EAE5D,OAAO,KAAK,sBAAsB,UAAU,UAAU,QAAQ;;CAKhE,eAAuB,OAAkC;EACvD,IAAI,mBAAmB,MAAM,EAC3B,OAAO;EAGT,OAAO,IAAI,cAAc,MAAM;;CAGjC,MAAc,cAAc,OAAyB,SAA0C;EAC7F,IAAI,KAAK,gBAAgB,MAAM,EAAE;EAEjC,MAAM,QAAQ,KAAK,eAAe,MAAM;EACxC,IAAI,OAAO;GACT,MAAM,MAAM,SAAS,OAAO,QAAQ;GACpC,IAAI,MAAM,YAAY;;EAGxB,KAAK,cAAc,MAAM;;CAG3B,MAAc,cAAc,OAAyB,SAA8C;EACjG,MAAM,QAAQ,KAAK,eAAe,MAAM;EACxC,IAAI,OAAO;GACT,MAAM,SAAS,MAAM,SAAS,OAAO,QAAQ;GAC7C,IAAI,WAAW,KAAA,GACb,OAAO,KAAK,WAAW,MAAM,QAAQ,MAAM;;EAI/C,OAAO,MAAM,KAAK,cAAc,OAAO,QAAQ;;CAGjD,sBACE,UACA,OACA,SACU;EACV,IAAI,SAAS;EACb,KAAK,MAAM,YAAY,KAAK,kBAC1B,SAAS,SAAS,QAAQ,OAAO,QAAQ;EAE3C,OAAO;;CAGT,gBAAwB,OAAkC;EACxD,KAAK,MAAM,OAAO,KAAK,eACrB,IAAI,iBAAiB,KAAK,OAAO;EAEnC,OAAO;;CAGT,eAAuB,OAAsD;EAC3E,IAAI;EACJ,KAAK,MAAM,SAAS,KAAK,aACvB,IAAI,KAAK,kBAAkB,OAAO,MAAM,WAAW;OAC7C,CAAC,QAAQ,CAAC,KAAK,kBAAkB,OAAO,KAAK,WAAW,IAAI,MAAM,WAAW,qBAAqB,KAAK,YACzG,OAAO;;EAIb,OAAO;;CAGT,eAAuB,OAAsD;EAC3E,IAAI;EACJ,KAAK,MAAM,SAAS,KAAK,aACvB,IAAI,KAAK,kBAAkB,OAAO,MAAM,WAAW;OAC7C,CAAC,QAAQ,CAAC,KAAK,kBAAkB,OAAO,KAAK,WAAW,IAAI,MAAM,WAAW,qBAAqB,KAAK,YACzG,OAAO;;EAIb,OAAO;;CAGT,kBAA0B,OAAyB,KAA2C;EAC5F,IAAI,iBAAiB,KAAK,OAAO;EACjC,OAAQ,MAAgB,YAAY,SAAS,IAAI;;CAGnD,cAAsB,OAA+B;EACnD,MAAM,WAAW,KAAK,gBAAgB,MAAM;EAC5C,MAAM,gBAAgB,KAAK,eAAe;EAE1C,MAAM,UAAmC;GACvC,SAAS,MAAM;GACf,WAAW,MAAM;GACjB,MAAM,MAAM;GACZ,OAAO,MAAM;GACb,GAAG;GACJ;EAED,MAAM,QAAQ,oBAAoB,MAAM;EACxC,IAAI,OACF,QAAQ,QAAQ;EAGlB,QAAQ,UAAR;GACE,KAAK;IACH,KAAK,OAAO,MAAM,sBAAsB,QAAQ;IAChD;GACF,KAAK;IACH,KAAK,OAAO,KAAK,sBAAsB,QAAQ;IAC/C;GACF,KAAK;IACH,KAAK,OAAO,KAAK,sBAAsB,QAAQ;IAC/C;GACF,KAAK;IACH,KAAK,OAAO,MAAM,sBAAsB,QAAQ;IAChD;;;CAIN,MAAc,cAAc,OAAyB,SAA8C;EACjG,MAAM,SAAS,KAAK,cAAc,MAAM;EACxC,MAAM,gBAAgB,KAAK,mBAAmB,OAAO,OAAO;EAE5D,IAAI,QAAQ,SAAS,UAAU,KAAK,UAAU,QAAQ,EAAE;GACtD,KAAK,MAAM,YAAY,KAAK,YAC1B,IAAI;IACF,MAAM,SAAS,MAAM,SAAS,eAAe,QAAQ,SAAS,MAAM;IACpE,IAAI,WAAW,KAAA,GAAW,OAAO;YAC1B,eAAe;IACtB,KAAK,OAAO,KAAK,2DAA2D,EAC1E,OAAO,yBAAyB,QAAQ,cAAc,UAAU,OAAO,cAAc,EACtF,CAAC;;GAGN,OAAO,KAAK,kBAAkB,eAAe,OAAO;;EAGtD,OAAO,SAAS,KAAK,eAAe,EAAE,QAAQ,CAAC;;CAKjD,UAAoB,SAAwC;EAE1D,QADe,QAAQ,IAAI,EAAE,IAAI,OAAO,SAAS,IAAI,IACvC,SAAS,YAAY;;CAGrC,kBACE,eACA,QACU;EACV,MAAM,QAAQ,KAAK,WAAW,cAAc,QAAQ;EACpD,MAAM,OAAO;;SAER,OAAO,KAAK,MAAM;;;0DAG+B,OAAO,6BAA6B,MAAM;EAChG,OAAO,IAAI,SAAS,MAAM;GACxB;GACA,SAAS,EAAE,gBAAgB,4BAA4B;GACxD,CAAC;;CAGJ,cAAsB,OAA+C;EACnE,IAAI,iBAAiB,eAAe,OAAO,MAAM;EACjD,MAAM,aAAc,MAAkC;EACtD,IAAI,OAAO,eAAe,UAAU,OAAO;EAC3C,OAAO;;CAGT,mBAA2B,OAAyB,QAA6C;EAC/F,MAAM,gBAAgB,UAAU;EAChC,MAAM,QAAQ,KAAK,gBAAgB;EAKnC,OAAO;GACL,SALc,iBAAiB,CAAC,QAC7B,qBAAqB,WAAW,0BACjC,MAAM;GAIR,WAAW,MAAM;GACjB,OAAO,QAAQ,MAAM,QAAQ,KAAA;GAC9B;;CAGH,WAAmB,KAAqB;EACtC,OAAO,IACJ,QAAQ,MAAM,QAAQ,CACtB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,SAAS;;CAG5B,WAAmB,QAAkC,OAAmC;EACtF,IAAI,kBAAkB,UAAU,OAAO;EACvC,MAAM,SAAS,KAAK,cAAc,MAAM;EACxC,OAAO,SAAS,KAAK,QAAQ,EAAE,QAAQ,CAAC;;CAG1C,gBAAwB,OAAsC;EAC5D,IAAI;EACJ,IAAI;EAEJ,KAAK,MAAM,CAAC,KAAK,aAAa,KAAK,gBACjC,IAAI,iBAAiB;OACf,CAAC,aAAa,IAAI,qBAAqB,WAAW;IACpD,YAAY;IACZ,eAAe;;;EAKrB,IAAI,cAAc,OAAO;EAEzB,MAAM,SAAS,KAAK,cAAc,MAAM;EACxC,OAAO,KAAK,mBAAmB,OAAO;;CAGxC,mBAA2B,QAA6B;EACtD,IAAI,UAAU,KAAK,OAAO;EAC1B,IAAI,UAAU,KAAK,OAAO;EAC1B,OAAO;;CAGT,gBAAiD;EAC/C,IAAI,KAAK,iBAAiB,WAAW,GAAG,OAAO,EAAE;EACjD,MAAM,SAAkC,EAAE;EAC1C,KAAK,MAAM,YAAY,KAAK,kBAC1B,OAAO,OAAO,QAAQ,UAAU,CAAC;EAEnC,OAAO;;;;CAlTV,WAAW;oBAYP,OAAO,cAAc,cAAc,CAAA;oBACnC,OAAO,UAAU,cAAc,CAAA;oBAC/B,OAAO,gBAAgB,CAAA;oBACvB,OAAO,UAAU,iBAAiB,CAAA;;AA+SvC,MAAM,kBAAkB;AAExB,SAAS,oBAAoB,OAA+C;CAC1E,MAAM,QAAS,MAA8B;CAC7C,MAAM,aAAa,iBAAiB,kBAAkB,MAAM,QAAQ,MAAM,OAAO,IAAI,MAAM,OAAO,SAAS;CAC3G,IAAI,UAAU,KAAA,KAAa,CAAC,YAC1B;CAEF,IAAI,UAAU,KAAA,GACZ,OAAO,iBAAiB,QACpB,mBAAmB,OAAO,EAAE,GAC3B,EAAE,OAAO,OAAO;CAEvB,OAAO;EACL,MAAM,MAAM;EACZ,SAAS,MAAM;EACf,QAAS,MAAyB,OAAO,KAAK,UAC5C,iBAAiB,QACb,mBAAmB,OAAO,EAAE,GAC5B;GAAE,MAAM;GAAW,SAAS,OAAO,MAAM;GAAE,CAChD;EACF;;AAGH,SAAS,mBAAmB,OAAc,OAAoC;CAC5E,MAAM,MAA2B;EAC/B,MAAM,MAAM;EACZ,SAAS,MAAM;EACf,OAAO,MAAM;EACd;CAED,IAAI,SAAS,iBAAiB,OAAO;CAErC,IAAI,iBAAiB,kBAAkB,MAAM,QAAQ,MAAM,OAAO,IAAI,MAAM,OAAO,SAAS,GAC1F,IAAI,SAAS,MAAM,OAAO,KAAK,UAC7B,iBAAiB,QACb,mBAAmB,OAAO,QAAQ,EAAE,GACpC;EAAE,MAAM;EAAW,SAAS,OAAO,MAAM;EAAE,CAChD;CAGH,MAAM,QAAS,MAA8B;CAC7C,IAAI,UAAU,KAAA,GACZ,IAAI,QAAQ,iBAAiB,QACzB,mBAAmB,OAAO,QAAQ,EAAE,GACpC,EAAE,OAAO,OAAO;CAGtB,OAAO;;;;AC7XF,IAAA,0BAAA,MAAM,gCAAgC,iBAAiB;CAC5D,WAAiB;;sCAFlB,WAAW,CAAA,EAAA,wBAAA;;;ACdZ,SAAgB,gBAAgB,KAAoC;CAClE,OACE,OAAO,QAAQ,YACf,QAAQ,QACR,aAAa,OACb,OAAQ,IAAsB,YAAY,YAC1C,eAAe,OACf,OAAQ,IAAsB,cAAc;;;;ACbhD,IAAa,6BAAb,cAAgD,iBAAiB;CAC/D,cAAc;EACZ,MAAM,gGAAgG;;;;;ACF1G,IAAa,gBAAb,cAAmC,iBAAiB;CAClD,YAAY,SAAkB,OAAiB;EAC7C,MAAM,WAAW,kBAAkB,MAAM;;;;;ACF7C,IAAa,YAAb,cAA+B,iBAAiB;CAC9C,YAAY,SAAkB,OAAiB;EAC7C,MAAM,WAAW,wBAAwB,MAAM"}
|
|
1
|
+
{"version":3,"file":"errors-mXYxG0XB.mjs","names":[],"sources":["../src/errors/http-exception.ts","../src/errors/internal-error.ts","../src/errors/is-application-error.ts","../src/errors/exception-handler.ts","../src/errors/default-exception-handler.ts","../src/errors/error-response.ts","../src/errors/stratal-not-initialized.error.ts","../src/errors/database.error.ts","../src/errors/auth.error.ts"],"sourcesContent":["import type { ContentfulStatusCode } from 'hono/utils/http-status'\nimport { ApplicationError } from './application-error'\n\nexport const HTTP_STATUS_MESSAGES: Partial<Record<number, string>> = {\n 400: 'Bad Request',\n 401: 'Unauthorized',\n 402: 'Payment Required',\n 403: 'Forbidden',\n 404: 'Not Found',\n 409: 'Conflict',\n 413: 'Content Too Large',\n 422: 'Unprocessable Entity',\n 423: 'Locked',\n 429: 'Too Many Requests',\n 500: 'Internal Server Error',\n 502: 'Bad Gateway',\n 503: 'Service Unavailable',\n}\n\nexport class HttpException extends ApplicationError {\n public readonly httpStatus: ContentfulStatusCode\n\n constructor(httpStatus: ContentfulStatusCode, message?: string, cause?: unknown) {\n super(message ?? HTTP_STATUS_MESSAGES[httpStatus] ?? 'Internal Server Error', cause)\n this.httpStatus = httpStatus\n }\n}\n\nexport function abort(\n status: ContentfulStatusCode,\n message?: string,\n): never {\n throw new HttpException(status, message)\n}\n","import { ApplicationError } from './application-error'\n\nexport class InternalError extends ApplicationError {\n constructor(cause?: unknown) {\n super('Internal Server Error', cause)\n }\n}\n","import { ApplicationError } from './application-error'\n\nexport function isApplicationError(error: unknown): error is ApplicationError {\n if (error instanceof ApplicationError) return true\n return error instanceof Error\n && typeof (error as ApplicationError).timestamp === 'string'\n}\n","import type { ContentfulStatusCode } from 'hono/utils/http-status';\nimport { inject } from '../di';\nimport { CONTAINER_TOKEN, type Container } from '../di';\nimport { Transient } from '../di/decorators';\nimport { DI_TOKENS } from '../di/tokens';\nimport { type StratalEnv } from '../env';\nimport type { StratalExecutionContext } from '../execution-context';\nimport { LOGGER_TOKENS, type LoggerService } from '../logger';\nimport type { ApplicationError } from './application-error';\nimport type { Environment, ErrorResponse } from './error-response';\nimport type { ExceptionContext, HttpExceptionContext } from './exception-context';\nimport type {\n ApplicationErrorConstructor,\n ContextCallback,\n ErrorPageCallback,\n LogSeverity,\n RenderableCallback,\n Reportable,\n ReportableCallback,\n RespondCallback,\n} from './exception-handler.types';\nimport { HTTP_STATUS_MESSAGES, HttpException } from './http-exception';\nimport { InternalError } from './internal-error';\nimport { isApplicationError } from './is-application-error';\n\ninterface ReportableEntry {\n errorClass: ApplicationErrorConstructor\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n callback: ReportableCallback<any>\n shouldStop: boolean\n}\n\ninterface RenderableEntry {\n errorClass: ApplicationErrorConstructor\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n callback: RenderableCallback<any>\n}\n\n@Transient()\nexport abstract class ExceptionHandler {\n private readonly reportables: ReportableEntry[] = []\n private readonly renderables: RenderableEntry[] = []\n private readonly dontReportSet = new Set<ApplicationErrorConstructor>()\n private readonly levelOverrides = new Map<ApplicationErrorConstructor, LogSeverity>()\n private readonly contextCallbacks: ContextCallback[] = []\n private readonly respondCallbacks: RespondCallback[] = []\n private readonly errorPages: ErrorPageCallback[] = []\n private readonly environment: Environment\n\n constructor(\n @inject(LOGGER_TOKENS.LoggerService) protected readonly logger: LoggerService,\n @inject(DI_TOKENS.CloudflareEnv) protected readonly env: StratalEnv,\n @inject(CONTAINER_TOKEN) private readonly container: Container,\n @inject(DI_TOKENS.ExecutionContext) private readonly executionContext: StratalExecutionContext,\n ) {\n this.environment = this.env.ENVIRONMENT as Environment\n }\n\n abstract register(): void\n\n // ── Public Configuration API ──────────────────────────────────────\n\n reportable<T extends ApplicationError>(\n errorClass: ApplicationErrorConstructor<T>,\n callback: ReportableCallback<T>,\n ): Reportable {\n const entry: ReportableEntry = { errorClass, callback, shouldStop: false }\n this.reportables.push(entry)\n return {\n stop: () => { entry.shouldStop = true },\n }\n }\n\n renderable<T extends ApplicationError>(\n errorClass: ApplicationErrorConstructor<T>,\n callback: RenderableCallback<T>,\n ): void {\n this.renderables.push({ errorClass, callback })\n }\n\n dontReport(errorClasses: ApplicationErrorConstructor[]): void {\n for (const cls of errorClasses) {\n this.dontReportSet.add(cls)\n }\n }\n\n level(errorClass: ApplicationErrorConstructor, severity: LogSeverity): void {\n this.levelOverrides.set(errorClass, severity)\n }\n\n context(callback: ContextCallback): void {\n this.contextCallbacks.push(callback)\n }\n\n respond(callback: RespondCallback): void {\n this.respondCallbacks.push(callback)\n }\n\n errorPage(callback: ErrorPageCallback): void {\n this.errorPages.push(callback)\n }\n\n resolve<T>(token: symbol | (new (...args: unknown[]) => T)): T {\n return this.container.resolve<T>(token)\n }\n\n // ── Pipeline Entry Point ──────────────────────────────────────────\n\n async handle(error: unknown, context: ExceptionContext): Promise<Response> {\n const appError = this.normalizeError(error)\n\n this.executionContext.waitUntil(this.performReport(appError, context))\n\n const response = await this.performRender(appError, context)\n\n return this.applyRespondCallbacks(response, appError, context)\n }\n\n // ── Internals ─────────────────────────────────────────────────────\n\n private normalizeError(error: unknown): ApplicationError {\n if (isApplicationError(error)) {\n return error\n }\n\n return new InternalError(error)\n }\n\n private async performReport(error: ApplicationError, context: ExceptionContext): Promise<void> {\n if (this.shouldNotReport(error)) return\n\n const entry = this.findReportable(error)\n if (entry) {\n await entry.callback(error, context)\n if (entry.shouldStop) return\n }\n\n this.defaultReport(error)\n }\n\n private async performRender(error: ApplicationError, context: ExceptionContext): Promise<Response> {\n const entry = this.findRenderable(error)\n if (entry) {\n const result = entry.callback(error, context)\n if (result !== undefined) {\n return this.toResponse(await result, error)\n }\n }\n\n return await this.defaultRender(error, context)\n }\n\n private applyRespondCallbacks(\n response: Response,\n error: ApplicationError,\n context: ExceptionContext,\n ): Response {\n let result = response\n for (const callback of this.respondCallbacks) {\n result = callback(result, error, context)\n }\n return result\n }\n\n private shouldNotReport(error: ApplicationError): boolean {\n for (const cls of this.dontReportSet) {\n if (error instanceof cls) return true\n }\n return false\n }\n\n private findReportable(error: ApplicationError): ReportableEntry | undefined {\n let best: ReportableEntry | undefined\n for (const entry of this.reportables) {\n if (this.matchesErrorClass(error, entry.errorClass)) {\n if (!best || !this.matchesErrorClass(error, best.errorClass) || entry.errorClass.prototype instanceof best.errorClass) {\n best = entry\n }\n }\n }\n return best\n }\n\n private findRenderable(error: ApplicationError): RenderableEntry | undefined {\n let best: RenderableEntry | undefined\n for (const entry of this.renderables) {\n if (this.matchesErrorClass(error, entry.errorClass)) {\n if (!best || !this.matchesErrorClass(error, best.errorClass) || entry.errorClass.prototype instanceof best.errorClass) {\n best = entry\n }\n }\n }\n return best\n }\n\n private matchesErrorClass(error: ApplicationError, cls: ApplicationErrorConstructor): boolean {\n if (error instanceof cls) return true\n return (error as Error).constructor.name === cls.name\n }\n\n private defaultReport(error: ApplicationError): void {\n const severity = this.resolveSeverity(error)\n const globalContext = this.gatherContext()\n\n const logData: Record<string, unknown> = {\n message: error.message,\n timestamp: error.timestamp,\n name: error.name,\n stack: error.stack,\n ...globalContext,\n }\n\n const chain = serializeErrorChain(error)\n if (chain) {\n logData.cause = chain\n }\n\n switch (severity) {\n case 'debug':\n this.logger.debug('[ApplicationError]', logData)\n break\n case 'info':\n this.logger.info('[ApplicationError]', logData)\n break\n case 'warn':\n this.logger.warn('[ApplicationError]', logData)\n break\n case 'error':\n this.logger.error('[ApplicationError]', logData)\n break\n }\n }\n\n private async defaultRender(error: ApplicationError, context: ExceptionContext): Promise<Response> {\n const status = this.resolveStatus(error)\n const errorResponse = this.buildErrorResponse(error, status)\n\n if (context.type === 'http' && this.wantsHtml(context)) {\n for (const callback of this.errorPages) {\n try {\n const result = await callback(errorResponse, status, context, error)\n if (result !== undefined) return result\n } catch (callbackError) {\n this.logger.warn('errorPage callback failed, falling back to next handler', {\n error: callbackError instanceof Error ? callbackError.message : String(callbackError),\n })\n }\n }\n return this.renderDefaultHtml(errorResponse, status)\n }\n\n return Response.json(errorResponse, { status })\n }\n\n // ── Content Negotiation ──────────────────────────────────────────\n\n protected wantsHtml(context: HttpExceptionContext): boolean {\n const accept = context.ctx.c.req.header('accept') ?? ''\n return accept.includes('text/html')\n }\n\n protected renderDefaultHtml(\n errorResponse: ErrorResponse,\n status: ContentfulStatusCode,\n ): Response {\n const title = this.escapeHtml(errorResponse.message)\n const html = `<!DOCTYPE html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<title>${status} - ${title}</title>\n<link rel=\"icon\" type=\"image/x-icon\" href=\"/favicon.svg\">\n<style>*{margin:0;padding:0;box-sizing:border-box}body{font-family:system-ui,-apple-system,sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center;background:#f8fafc;color:#334155}.container{text-align:center;padding:2rem}.status{font-size:6rem;font-weight:800;color:#13c397;line-height:1}.message{font-size:1.25rem;color:#64748b;margin-top:1rem}</style>\n</head><body><div class=\"container\"><div class=\"status\">${status}</div><div class=\"message\">${title}</div></div></body></html>`\n return new Response(html, {\n status,\n headers: { 'content-type': 'text/html; charset=utf-8' },\n })\n }\n\n private resolveStatus(error: ApplicationError): ContentfulStatusCode {\n if (error instanceof HttpException) return error.httpStatus\n const httpStatus = (error as { httpStatus?: number }).httpStatus\n if (typeof httpStatus === 'number') return httpStatus as ContentfulStatusCode\n return 500\n }\n\n private buildErrorResponse(error: ApplicationError, status: ContentfulStatusCode): ErrorResponse {\n const isServerError = status >= 500\n const isDev = this.environment === 'development'\n const message = isServerError && !isDev\n ? (HTTP_STATUS_MESSAGES[status] ?? 'Internal Server Error')\n : error.message\n\n return {\n message,\n timestamp: error.timestamp,\n stack: isDev ? error.stack : undefined,\n }\n }\n\n private escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n }\n\n private toResponse(result: Response | ErrorResponse, error: ApplicationError): Response {\n if (result instanceof Response) return result\n const status = this.resolveStatus(error)\n return Response.json(result, { status })\n }\n\n private resolveSeverity(error: ApplicationError): LogSeverity {\n let bestClass: ApplicationErrorConstructor | undefined\n let bestSeverity: LogSeverity | undefined\n\n for (const [cls, severity] of this.levelOverrides) {\n if (error instanceof cls) {\n if (!bestClass || cls.prototype instanceof bestClass) {\n bestClass = cls\n bestSeverity = severity\n }\n }\n }\n\n if (bestSeverity) return bestSeverity\n\n const status = this.resolveStatus(error)\n return this.getDefaultSeverity(status)\n }\n\n private getDefaultSeverity(status: number): LogSeverity {\n if (status >= 500) return 'error'\n if (status >= 400) return 'warn'\n return 'error'\n }\n\n private gatherContext(): Record<string, unknown> {\n if (this.contextCallbacks.length === 0) return {}\n const merged: Record<string, unknown> = {}\n for (const callback of this.contextCallbacks) {\n Object.assign(merged, callback())\n }\n return merged\n }\n}\n\ninterface SerializedErrorNode {\n name: string\n message: string\n stack?: string\n cause?: SerializedErrorNode | { value: unknown }\n errors?: SerializedErrorNode[]\n}\n\nconst MAX_CAUSE_DEPTH = 5\n\nfunction serializeErrorChain(error: Error): SerializedErrorNode | undefined {\n const cause = (error as { cause?: unknown }).cause\n const aggregated = error instanceof AggregateError && Array.isArray(error.errors) && error.errors.length > 0\n if (cause === undefined && !aggregated) {\n return undefined\n }\n if (cause !== undefined) {\n return cause instanceof Error\n ? serializeErrorNode(cause, 0)\n : ({ value: cause } as unknown as SerializedErrorNode)\n }\n return {\n name: error.name,\n message: error.message,\n errors: (error as AggregateError).errors.map((inner) =>\n inner instanceof Error\n ? serializeErrorNode(inner, 0)\n : { name: 'Unknown', message: String(inner) },\n ),\n }\n}\n\nfunction serializeErrorNode(error: Error, depth: number): SerializedErrorNode {\n const out: SerializedErrorNode = {\n name: error.name,\n message: error.message,\n stack: error.stack,\n }\n\n if (depth >= MAX_CAUSE_DEPTH) return out\n\n if (error instanceof AggregateError && Array.isArray(error.errors) && error.errors.length > 0) {\n out.errors = error.errors.map((inner) =>\n inner instanceof Error\n ? serializeErrorNode(inner, depth + 1)\n : { name: 'Unknown', message: String(inner) },\n )\n }\n\n const cause = (error as { cause?: unknown }).cause\n if (cause !== undefined) {\n out.cause = cause instanceof Error\n ? serializeErrorNode(cause, depth + 1)\n : { value: cause }\n }\n\n return out\n}\n","import { Transient } from '../di/decorators';\nimport { ExceptionHandler } from './exception-handler';\n\n/**\n * DefaultExceptionHandler — the built-in exception handler used when no\n * custom handler is provided via `ApplicationConfig.exceptionHandler`.\n *\n * Has an empty `register()` method, so all exceptions flow through the\n * default pipeline: severity-based logging, i18n translation, and JSON\n * error response serialization.\n *\n * To customize exception handling, extend {@link ExceptionHandler} and\n * override `register()`, then pass your class to the Stratal config:\n *\n * @example\n * ```typescript\n * new Stratal({\n * module: AppModule,\n * exceptionHandler: AppExceptionHandler,\n * })\n * ```\n */\n@Transient()\nexport class DefaultExceptionHandler extends ExceptionHandler {\n register(): void {\n // No custom configuration — uses all defaults\n }\n}\n","export type Environment = 'development' | 'staging' | 'production'\n\nexport interface ErrorResponse {\n message: string\n timestamp: string\n stack?: string\n}\n\nexport function isErrorResponse(obj: unknown): obj is ErrorResponse {\n return (\n typeof obj === 'object' &&\n obj !== null &&\n 'message' in obj &&\n typeof (obj as ErrorResponse).message === 'string' &&\n 'timestamp' in obj &&\n typeof (obj as ErrorResponse).timestamp === 'string'\n )\n}\n","import { ApplicationError } from './application-error'\n\nexport class StratalNotInitializedError extends ApplicationError {\n constructor() {\n super('Stratal has not been initialized. Ensure you export a Stratal instance as the default export.')\n }\n}\n","import { ApplicationError } from './application-error'\n\nexport class DatabaseError extends ApplicationError {\n constructor(message?: string, cause?: unknown) {\n super(message ?? 'Database error', cause)\n }\n}\n","import { ApplicationError } from './application-error'\n\nexport class AuthError extends ApplicationError {\n constructor(message?: string, cause?: unknown) {\n super(message ?? 'Authentication error', cause)\n }\n}\n"],"mappings":";;;;;;AAGA,MAAa,uBAAwD;CACnE,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;AACP;AAEA,IAAa,gBAAb,cAAmC,iBAAiB;CAClD;CAEA,YAAY,YAAkC,SAAkB,OAAiB;EAC/E,MAAM,WAAW,qBAAqB,eAAe,yBAAyB,KAAK;EACnF,KAAK,aAAa;CACpB;AACF;AAEA,SAAgB,MACd,QACA,SACO;CACP,MAAM,IAAI,cAAc,QAAQ,OAAO;AACzC;;;AC/BA,IAAa,gBAAb,cAAmC,iBAAiB;CAClD,YAAY,OAAiB;EAC3B,MAAM,yBAAyB,KAAK;CACtC;AACF;;;ACJA,SAAgB,mBAAmB,OAA2C;CAC5E,IAAI,iBAAiB,kBAAkB,OAAO;CAC9C,OAAO,iBAAiB,SACnB,OAAQ,MAA2B,cAAc;AACxD;;;ACiCO,IAAA,mBAAA,MAAe,iBAAiB;CAWqB;CACJ;CACV;CACW;CAbvD,cAAkD,CAAC;CACnD,cAAkD,CAAC;CACnD,gCAAiC,IAAI,IAAiC;CACtE,iCAAkC,IAAI,IAA8C;CACpF,mBAAuD,CAAC;CACxD,mBAAuD,CAAC;CACxD,aAAmD,CAAC;CACpD;CAEA,YACE,QACA,KACA,WACA,kBACA;EAJwD,KAAA,SAAA;EACJ,KAAA,MAAA;EACV,KAAA,YAAA;EACW,KAAA,mBAAA;EAErD,KAAK,cAAc,KAAK,IAAI;CAC9B;CAMA,WACE,YACA,UACY;EACZ,MAAM,QAAyB;GAAE;GAAY;GAAU,YAAY;EAAM;EACzE,KAAK,YAAY,KAAK,KAAK;EAC3B,OAAO,EACL,YAAY;GAAE,MAAM,aAAa;EAAK,EACxC;CACF;CAEA,WACE,YACA,UACM;EACN,KAAK,YAAY,KAAK;GAAE;GAAY;EAAS,CAAC;CAChD;CAEA,WAAW,cAAmD;EAC5D,KAAK,MAAM,OAAO,cAChB,KAAK,cAAc,IAAI,GAAG;CAE9B;CAEA,MAAM,YAAyC,UAA6B;EAC1E,KAAK,eAAe,IAAI,YAAY,QAAQ;CAC9C;CAEA,QAAQ,UAAiC;EACvC,KAAK,iBAAiB,KAAK,QAAQ;CACrC;CAEA,QAAQ,UAAiC;EACvC,KAAK,iBAAiB,KAAK,QAAQ;CACrC;CAEA,UAAU,UAAmC;EAC3C,KAAK,WAAW,KAAK,QAAQ;CAC/B;CAEA,QAAW,OAAoD;EAC7D,OAAO,KAAK,UAAU,QAAW,KAAK;CACxC;CAIA,MAAM,OAAO,OAAgB,SAA8C;EACzE,MAAM,WAAW,KAAK,eAAe,KAAK;EAE1C,KAAK,iBAAiB,UAAU,KAAK,cAAc,UAAU,OAAO,CAAC;EAErE,MAAM,WAAW,MAAM,KAAK,cAAc,UAAU,OAAO;EAE3D,OAAO,KAAK,sBAAsB,UAAU,UAAU,OAAO;CAC/D;CAIA,eAAuB,OAAkC;EACvD,IAAI,mBAAmB,KAAK,GAC1B,OAAO;EAGT,OAAO,IAAI,cAAc,KAAK;CAChC;CAEA,MAAc,cAAc,OAAyB,SAA0C;EAC7F,IAAI,KAAK,gBAAgB,KAAK,GAAG;EAEjC,MAAM,QAAQ,KAAK,eAAe,KAAK;EACvC,IAAI,OAAO;GACT,MAAM,MAAM,SAAS,OAAO,OAAO;GACnC,IAAI,MAAM,YAAY;EACxB;EAEA,KAAK,cAAc,KAAK;CAC1B;CAEA,MAAc,cAAc,OAAyB,SAA8C;EACjG,MAAM,QAAQ,KAAK,eAAe,KAAK;EACvC,IAAI,OAAO;GACT,MAAM,SAAS,MAAM,SAAS,OAAO,OAAO;GAC5C,IAAI,WAAW,KAAA,GACb,OAAO,KAAK,WAAW,MAAM,QAAQ,KAAK;EAE9C;EAEA,OAAO,MAAM,KAAK,cAAc,OAAO,OAAO;CAChD;CAEA,sBACE,UACA,OACA,SACU;EACV,IAAI,SAAS;EACb,KAAK,MAAM,YAAY,KAAK,kBAC1B,SAAS,SAAS,QAAQ,OAAO,OAAO;EAE1C,OAAO;CACT;CAEA,gBAAwB,OAAkC;EACxD,KAAK,MAAM,OAAO,KAAK,eACrB,IAAI,iBAAiB,KAAK,OAAO;EAEnC,OAAO;CACT;CAEA,eAAuB,OAAsD;EAC3E,IAAI;EACJ,KAAK,MAAM,SAAS,KAAK,aACvB,IAAI,KAAK,kBAAkB,OAAO,MAAM,UAAU;OAC5C,CAAC,QAAQ,CAAC,KAAK,kBAAkB,OAAO,KAAK,UAAU,KAAK,MAAM,WAAW,qBAAqB,KAAK,YACzG,OAAO;EAAA;EAIb,OAAO;CACT;CAEA,eAAuB,OAAsD;EAC3E,IAAI;EACJ,KAAK,MAAM,SAAS,KAAK,aACvB,IAAI,KAAK,kBAAkB,OAAO,MAAM,UAAU;OAC5C,CAAC,QAAQ,CAAC,KAAK,kBAAkB,OAAO,KAAK,UAAU,KAAK,MAAM,WAAW,qBAAqB,KAAK,YACzG,OAAO;EAAA;EAIb,OAAO;CACT;CAEA,kBAA0B,OAAyB,KAA2C;EAC5F,IAAI,iBAAiB,KAAK,OAAO;EACjC,OAAQ,MAAgB,YAAY,SAAS,IAAI;CACnD;CAEA,cAAsB,OAA+B;EACnD,MAAM,WAAW,KAAK,gBAAgB,KAAK;EAC3C,MAAM,gBAAgB,KAAK,cAAc;EAEzC,MAAM,UAAmC;GACvC,SAAS,MAAM;GACf,WAAW,MAAM;GACjB,MAAM,MAAM;GACZ,OAAO,MAAM;GACb,GAAG;EACL;EAEA,MAAM,QAAQ,oBAAoB,KAAK;EACvC,IAAI,OACF,QAAQ,QAAQ;EAGlB,QAAQ,UAAR;GACE,KAAK;IACH,KAAK,OAAO,MAAM,sBAAsB,OAAO;IAC/C;GACF,KAAK;IACH,KAAK,OAAO,KAAK,sBAAsB,OAAO;IAC9C;GACF,KAAK;IACH,KAAK,OAAO,KAAK,sBAAsB,OAAO;IAC9C;GACF,KAAK;IACH,KAAK,OAAO,MAAM,sBAAsB,OAAO;IAC/C;EACJ;CACF;CAEA,MAAc,cAAc,OAAyB,SAA8C;EACjG,MAAM,SAAS,KAAK,cAAc,KAAK;EACvC,MAAM,gBAAgB,KAAK,mBAAmB,OAAO,MAAM;EAE3D,IAAI,QAAQ,SAAS,UAAU,KAAK,UAAU,OAAO,GAAG;GACtD,KAAK,MAAM,YAAY,KAAK,YAC1B,IAAI;IACF,MAAM,SAAS,MAAM,SAAS,eAAe,QAAQ,SAAS,KAAK;IACnE,IAAI,WAAW,KAAA,GAAW,OAAO;GACnC,SAAS,eAAe;IACtB,KAAK,OAAO,KAAK,2DAA2D,EAC1E,OAAO,yBAAyB,QAAQ,cAAc,UAAU,OAAO,aAAa,EACtF,CAAC;GACH;GAEF,OAAO,KAAK,kBAAkB,eAAe,MAAM;EACrD;EAEA,OAAO,SAAS,KAAK,eAAe,EAAE,OAAO,CAAC;CAChD;CAIA,UAAoB,SAAwC;EAE1D,QADe,QAAQ,IAAI,EAAE,IAAI,OAAO,QAAQ,KAAK,IACvC,SAAS,WAAW;CACpC;CAEA,kBACE,eACA,QACU;EACV,MAAM,QAAQ,KAAK,WAAW,cAAc,OAAO;EACnD,MAAM,OAAO;;SAER,OAAO,KAAK,MAAM;;;0DAG+B,OAAO,6BAA6B,MAAM;EAChG,OAAO,IAAI,SAAS,MAAM;GACxB;GACA,SAAS,EAAE,gBAAgB,2BAA2B;EACxD,CAAC;CACH;CAEA,cAAsB,OAA+C;EACnE,IAAI,iBAAiB,eAAe,OAAO,MAAM;EACjD,MAAM,aAAc,MAAkC;EACtD,IAAI,OAAO,eAAe,UAAU,OAAO;EAC3C,OAAO;CACT;CAEA,mBAA2B,OAAyB,QAA6C;EAC/F,MAAM,gBAAgB,UAAU;EAChC,MAAM,QAAQ,KAAK,gBAAgB;EAKnC,OAAO;GACL,SALc,iBAAiB,CAAC,QAC7B,qBAAqB,WAAW,0BACjC,MAAM;GAIR,WAAW,MAAM;GACjB,OAAO,QAAQ,MAAM,QAAQ,KAAA;EAC/B;CACF;CAEA,WAAmB,KAAqB;EACtC,OAAO,IACJ,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,QAAQ;CAC3B;CAEA,WAAmB,QAAkC,OAAmC;EACtF,IAAI,kBAAkB,UAAU,OAAO;EACvC,MAAM,SAAS,KAAK,cAAc,KAAK;EACvC,OAAO,SAAS,KAAK,QAAQ,EAAE,OAAO,CAAC;CACzC;CAEA,gBAAwB,OAAsC;EAC5D,IAAI;EACJ,IAAI;EAEJ,KAAK,MAAM,CAAC,KAAK,aAAa,KAAK,gBACjC,IAAI,iBAAiB;OACf,CAAC,aAAa,IAAI,qBAAqB,WAAW;IACpD,YAAY;IACZ,eAAe;GACjB;;EAIJ,IAAI,cAAc,OAAO;EAEzB,MAAM,SAAS,KAAK,cAAc,KAAK;EACvC,OAAO,KAAK,mBAAmB,MAAM;CACvC;CAEA,mBAA2B,QAA6B;EACtD,IAAI,UAAU,KAAK,OAAO;EAC1B,IAAI,UAAU,KAAK,OAAO;EAC1B,OAAO;CACT;CAEA,gBAAiD;EAC/C,IAAI,KAAK,iBAAiB,WAAW,GAAG,OAAO,CAAC;EAChD,MAAM,SAAkC,CAAC;EACzC,KAAK,MAAM,YAAY,KAAK,kBAC1B,OAAO,OAAO,QAAQ,SAAS,CAAC;EAElC,OAAO;CACT;AACF;;CApTC,UAAU;oBAYN,OAAO,cAAc,aAAa,CAAA;oBAClC,OAAO,UAAU,aAAa,CAAA;oBAC9B,OAAO,eAAe,CAAA;oBACtB,OAAO,UAAU,gBAAgB,CAAA;;AA+StC,MAAM,kBAAkB;AAExB,SAAS,oBAAoB,OAA+C;CAC1E,MAAM,QAAS,MAA8B;CAC7C,MAAM,aAAa,iBAAiB,kBAAkB,MAAM,QAAQ,MAAM,MAAM,KAAK,MAAM,OAAO,SAAS;CAC3G,IAAI,UAAU,KAAA,KAAa,CAAC,YAC1B;CAEF,IAAI,UAAU,KAAA,GACZ,OAAO,iBAAiB,QACpB,mBAAmB,OAAO,CAAC,IAC1B,EAAE,OAAO,MAAM;CAEtB,OAAO;EACL,MAAM,MAAM;EACZ,SAAS,MAAM;EACf,QAAS,MAAyB,OAAO,KAAK,UAC5C,iBAAiB,QACb,mBAAmB,OAAO,CAAC,IAC3B;GAAE,MAAM;GAAW,SAAS,OAAO,KAAK;EAAE,CAChD;CACF;AACF;AAEA,SAAS,mBAAmB,OAAc,OAAoC;CAC5E,MAAM,MAA2B;EAC/B,MAAM,MAAM;EACZ,SAAS,MAAM;EACf,OAAO,MAAM;CACf;CAEA,IAAI,SAAS,iBAAiB,OAAO;CAErC,IAAI,iBAAiB,kBAAkB,MAAM,QAAQ,MAAM,MAAM,KAAK,MAAM,OAAO,SAAS,GAC1F,IAAI,SAAS,MAAM,OAAO,KAAK,UAC7B,iBAAiB,QACb,mBAAmB,OAAO,QAAQ,CAAC,IACnC;EAAE,MAAM;EAAW,SAAS,OAAO,KAAK;CAAE,CAChD;CAGF,MAAM,QAAS,MAA8B;CAC7C,IAAI,UAAU,KAAA,GACZ,IAAI,QAAQ,iBAAiB,QACzB,mBAAmB,OAAO,QAAQ,CAAC,IACnC,EAAE,OAAO,MAAM;CAGrB,OAAO;AACT;;;AC9XO,IAAA,0BAAA,MAAM,gCAAgC,iBAAiB;CAC5D,WAAiB,CAEjB;AACF;sCALC,UAAU,CAAA,GAAA,uBAAA;;;ACdX,SAAgB,gBAAgB,KAAoC;CAClE,OACE,OAAO,QAAQ,YACf,QAAQ,QACR,aAAa,OACb,OAAQ,IAAsB,YAAY,YAC1C,eAAe,OACf,OAAQ,IAAsB,cAAc;AAEhD;;;ACfA,IAAa,6BAAb,cAAgD,iBAAiB;CAC/D,cAAc;EACZ,MAAM,+FAA+F;CACvG;AACF;;;ACJA,IAAa,gBAAb,cAAmC,iBAAiB;CAClD,YAAY,SAAkB,OAAiB;EAC7C,MAAM,WAAW,kBAAkB,KAAK;CAC1C;AACF;;;ACJA,IAAa,YAAb,cAA+B,iBAAiB;CAC9C,YAAY,SAAkB,OAAiB;EAC7C,MAAM,WAAW,wBAAwB,KAAK;CAChD;AACF"}
|
package/dist/events/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { r as LoggerService } from "../index-
|
|
2
|
-
import { t as Constructor } from "../types-
|
|
1
|
+
import { r as LoggerService } from "../index-BUt92sAE.mjs";
|
|
2
|
+
import { t as Constructor } from "../types-CmV_9xBD.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/events/constants.d.ts
|
|
5
5
|
/**
|
|
@@ -206,5 +206,16 @@ declare class EventRegistry implements IEventRegistry {
|
|
|
206
206
|
private executeHandler;
|
|
207
207
|
}
|
|
208
208
|
//#endregion
|
|
209
|
-
|
|
209
|
+
//#region src/events/events.module.d.ts
|
|
210
|
+
/**
|
|
211
|
+
* Registers the event registry (`DI_TOKENS.EventRegistry`).
|
|
212
|
+
*
|
|
213
|
+
* Lazy: loaded on demand via `LazyModuleLoader` — by the framework's
|
|
214
|
+
* `DatabaseModule` (which needs it to wire DB event emission) and by the
|
|
215
|
+
* application's event-listener trigger paths. Kept out of cold start for apps
|
|
216
|
+
* that neither emit events nor use the database.
|
|
217
|
+
*/
|
|
218
|
+
declare class EventsModule {}
|
|
219
|
+
//#endregion
|
|
220
|
+
export { type CustomEventContext, type CustomEventRegistry, type EventContext, type EventHandler, type EventName, type EventOptions, EventRegistry, EventsModule, type IEventRegistry, LISTENER_METADATA_KEYS, Listener, type ListenerHandlerMetadata, On, type RegisteredHandler, getListenerHandlers, isListener };
|
|
210
221
|
//# sourceMappingURL=index.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/events/constants.ts","../../src/events/decorators/listener.decorator.ts","../../src/events/types.ts","../../src/events/decorators/on.decorator.ts","../../src/events/event-registry.ts"],"mappings":";;;;;;;;AAMA;;cAAa,sBAAA;EAAA,SAGH,WAAA;EAAA,SAAA,cAAA;AAAA;;;;;;AAHV;;;;;;;;ACgBA;;;;;;iBAAgB,QAAA,
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/events/constants.ts","../../src/events/decorators/listener.decorator.ts","../../src/events/types.ts","../../src/events/decorators/on.decorator.ts","../../src/events/event-registry.ts","../../src/events/events.module.ts"],"mappings":";;;;;;;;AAMA;;cAAa,sBAAA;EAAA,SAGH,WAAA;EAAA,SAAA,cAAA;AAAA;;;;;;AAHV;;;;;;;;ACgBA;;;;;;iBAAgB,QAAA,eACa,WAAA,EAAa,MAAA,EAAQ,CAAA,KAAC,CAAA;;;;iBAUnC,UAAA,CAAW,MAAmB,EAAX,WAAW;;;;;;;AD3B9C;;;;;;;;ACgBA;;;;;;;;;;;;;;AACmD;AAUnD;;;;AAA8C;;;;ACS9C;;UAAiB,mBAAA;AAAmB;AAMpC;;;AANoC,KAMxB,SAAA,SAAkB,mBAAA,0BAE1B,OAAA,OAAc,mBAAA;;;;;;;;;AAAmB;AAkBrC;;;UAAiB,kBAAA;EACf,IAAA,EAAM,KAAA;EACN,QAAA,GAAW,MAAM;AAAA;;;;AAAA;AAQnB;KAAY,YAAA,WAAuB,SAAA,GAAY,SAAA,IAC7C,CAAA,eAAgB,mBAAA,GACd,mBAAA,CAAoB,CAAA;EAClB,IAAA;EAAgB,MAAA;AAAA;;;;UASL,YAAA;EAVQ;EAYvB,QAAA;EAduB;;;;;;;;EAuBvB,QAAQ;AAAA;AApBkB;AAS5B;;AAT4B,KA0BhB,YAAA,WAAuB,SAAA,GAAY,SAAA,KAC7C,OAAA,EAAS,YAAA,CAAa,CAAA,MACnB,OAAA;;AARK;AAMV;;UAQiB,iBAAA;EACf,OAAA,EAAS,YAAY;EACrB,QAAA;EACA,QAAA;AAAA;;;;UAMe,cAAA;EACf,EAAA,WAAa,SAAA,EACX,KAAA,EAAO,CAAA,EACP,OAAA,EAAS,YAAA,CAAa,CAAA,GACtB,OAAA,GAAU,YAAA;EAGZ,IAAA,WAAe,SAAA,EACb,KAAA,EAAO,CAAA,EACP,OAAA,GAAU,OAAA,CAAQ,YAAA,CAAa,CAAA,KAC9B,OAAA;EAEH,GAAA,WAAc,SAAA,EAAW,KAAA,EAAO,CAAA,EAAG,OAAA,EAAS,YAAA,CAAa,CAAA;EAEzD,IAAA,WAAe,SAAA,EACb,KAAA,EAAO,CAAA,EACP,OAAA,EAAS,YAAA,CAAa,CAAA,GACtB,OAAA,GAAU,YAAA;AAAA;;;AAhCF;UA2CK,uBAAA;EACf,UAAA;EACA,KAAA;EACA,OAAA,GAAU,YAAY;AAAA;;;;;;AFrJxB;;;;;;;;ACgBA;;;;;;;;;;iBEGgB,EAAA,WAAa,SAAA,EAAW,KAAA,EAAO,CAAA,EAAG,OAAA,GAAU,YAAA,IAExD,MAAA,UACA,WAAA,UACA,WAAA,EAAa,kBAAA;;;;iBAsBD,mBAAA,CAAoB,MAAA,WAAiB,uBAAuB;;;cCrC/D,aAAA,YAAyB,cAAA;EAAA,iBAImB,GAAA;EAAA,iBACC,MAAA;EAAA,QAJhD,QAAA;cAG+C,GAAA,EAAK,gBAAA,EACJ,MAAA,EAAQ,aAAA;EAGhE,EAAA,WAAa,SAAA,EAAW,KAAA,EAAO,CAAA,EAAG,OAAA,EAAS,YAAA,CAAa,CAAA,GAAI,OAAA,GAAU,YAAA;EAkBhE,IAAA,WAAe,SAAA,EACnB,KAAA,EAAO,CAAA,EACP,OAAA,GAAU,OAAA,CAAQ,YAAA,CAAa,CAAA,KAC9B,OAAA;EAmCH,GAAA,WAAc,SAAA,EAAW,KAAA,EAAO,CAAA,EAAG,OAAA,EAAS,YAAA,CAAa,CAAA;EAczD,IAAA,WAAe,SAAA,EAAW,KAAA,EAAO,CAAA,EAAG,OAAA,EAAS,YAAA,CAAa,CAAA,GAAI,OAAA,GAAU,YAAA;;AHtE1E;;;UGmFU,oBAAA;EHlFwC;;;EAAA,QGuHxC,kBAAA;EHvHS;;;EAAA,QG0IH,cAAA;AAAA;;;;;;;AJ3JhB;;;;cKWa,YAAA"}
|
package/dist/events/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { EventRegistry, LISTENER_METADATA_KEYS, Listener, On, getListenerHandlers, isListener };
|
|
1
|
+
import { a as getListenerHandlers, c as LISTENER_METADATA_KEYS, i as On, o as Listener, r as EventRegistry, s as isListener, t as EventsModule } from "../events-BXJGZjpG.mjs";
|
|
2
|
+
export { EventRegistry, EventsModule, LISTENER_METADATA_KEYS, Listener, On, getListenerHandlers, isListener };
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { n as
|
|
1
|
+
import { t as __exportAll } from "./chunk-BBjsoOtd.mjs";
|
|
2
|
+
import { c as Transient, d as inject, r as DI_TOKENS, s as Singleton } from "./di-DseMn-z9.mjs";
|
|
3
|
+
import { n as getMetadata, t as defineMetadata } from "./metadata-DzzprcID.mjs";
|
|
4
|
+
import { n as __decorateParam, t as __decorate } from "./decorate-CuAoSZvs.mjs";
|
|
4
5
|
import { LOGGER_TOKENS } from "./logger/index.mjs";
|
|
6
|
+
import { n as Module } from "./module.decorator-CYHY6pG5.mjs";
|
|
5
7
|
//#region src/events/constants.ts
|
|
6
8
|
/**
|
|
7
9
|
* Metadata keys for event listener decorators.
|
|
@@ -182,11 +184,19 @@ let EventRegistry = class EventRegistry {
|
|
|
182
184
|
}
|
|
183
185
|
};
|
|
184
186
|
EventRegistry = __decorate([
|
|
185
|
-
|
|
187
|
+
Singleton(DI_TOKENS.EventRegistry),
|
|
186
188
|
__decorateParam(0, inject(DI_TOKENS.ExecutionContext)),
|
|
187
189
|
__decorateParam(1, inject(LOGGER_TOKENS.LoggerService))
|
|
188
190
|
], EventRegistry);
|
|
189
191
|
//#endregion
|
|
190
|
-
|
|
192
|
+
//#region src/events/events.module.ts
|
|
193
|
+
var events_module_exports = /* @__PURE__ */ __exportAll({ EventsModule: () => EventsModule });
|
|
194
|
+
let EventsModule = class EventsModule {};
|
|
195
|
+
EventsModule = __decorate([Module({ providers: [{
|
|
196
|
+
provide: DI_TOKENS.EventRegistry,
|
|
197
|
+
useClass: EventRegistry
|
|
198
|
+
}] })], EventsModule);
|
|
199
|
+
//#endregion
|
|
200
|
+
export { getListenerHandlers as a, LISTENER_METADATA_KEYS as c, On as i, events_module_exports as n, Listener as o, EventRegistry as r, isListener as s, EventsModule as t };
|
|
191
201
|
|
|
192
|
-
//# sourceMappingURL=events-
|
|
202
|
+
//# sourceMappingURL=events-BXJGZjpG.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events-BXJGZjpG.mjs","names":[],"sources":["../src/events/constants.ts","../src/events/decorators/listener.decorator.ts","../src/events/decorators/on.decorator.ts","../src/events/event-registry.ts","../src/events/events.module.ts"],"sourcesContent":["/**\n * Metadata keys for event listener decorators.\n *\n * Uses `Symbol.for()` (global symbol registry) so that both core and\n * framework packages can reference the same symbols without cross-imports.\n */\nexport const LISTENER_METADATA_KEYS = {\n IS_LISTENER: Symbol.for('stratal:listener'),\n EVENT_HANDLERS: Symbol.for('stratal:listener:handlers'),\n} as const\n","import { Transient } from '../../di/decorators'\nimport { defineMetadata, getMetadata } from '../../di/metadata'\nimport type { Constructor } from '../../types'\nimport { LISTENER_METADATA_KEYS } from '../constants'\n\n/**\n * Mark a class as an event listener.\n *\n * Applies `@Transient()` for DI and sets metadata so the module system\n * can auto-discover and wire listener handlers at bootstrap time.\n *\n * @example\n * ```typescript\n * @Listener()\n * export class UserCreatedListener {\n * @On('after.User.create')\n * async sendWelcomeEmail(context: EventContext<'after.User.create'>) {\n * // ...\n * }\n * }\n * ```\n */\nexport function Listener() {\n return function <T extends Constructor>(target: T) {\n Transient()(target)\n defineMetadata(LISTENER_METADATA_KEYS.IS_LISTENER, true, target)\n return target\n }\n}\n\n/**\n * Check if a class is decorated with `@Listener()`\n */\nexport function isListener(target: Constructor): boolean {\n return getMetadata(LISTENER_METADATA_KEYS.IS_LISTENER, target) === true\n}\n","import { defineMetadata, getMetadata } from '../../di/metadata'\nimport { LISTENER_METADATA_KEYS } from '../constants'\nimport type { EventName, EventOptions, ListenerHandlerMetadata } from '../types'\n\n/**\n * Register a method as an event handler within a `@Listener()` class.\n *\n * Accumulates handler metadata on the class so the framework can\n * auto-wire handlers with the EventRegistry at bootstrap time.\n *\n * @param event - Event name to listen for (fully typed with autocomplete)\n * @param options - Optional handler options (priority, blocking)\n *\n * @example\n * ```typescript\n * @Listener()\n * export class AuditListener {\n * @On('after.User.create')\n * async logCreate(context: EventContext<'after.User.create'>) { ... }\n *\n * @On('after.User.delete', { priority: 10 })\n * async logDelete(context: EventContext<'after.User.delete'>) { ... }\n * }\n * ```\n */\nexport function On<E extends EventName>(event: E, options?: EventOptions) {\n return function (\n target: object,\n propertyKey: string,\n _descriptor: PropertyDescriptor\n ) {\n const existingHandlers: ListenerHandlerMetadata[] =\n getMetadata<ListenerHandlerMetadata[]>(LISTENER_METADATA_KEYS.EVENT_HANDLERS, target.constructor) ?? []\n\n existingHandlers.push({\n methodName: propertyKey,\n event: event as string,\n options,\n })\n\n defineMetadata(\n LISTENER_METADATA_KEYS.EVENT_HANDLERS,\n existingHandlers,\n target.constructor\n )\n }\n}\n\n/**\n * Get all `@On()` handler metadata from a listener class\n */\nexport function getListenerHandlers(target: object): ListenerHandlerMetadata[] {\n const metadataTarget = typeof target === 'function' ? target : target.constructor\n return getMetadata<ListenerHandlerMetadata[]>(LISTENER_METADATA_KEYS.EVENT_HANDLERS, metadataTarget) ?? []\n}\n","import { inject } from '../di'\nimport { Singleton } from '../di/decorators'\nimport { DI_TOKENS } from '../di/tokens'\nimport { LOGGER_TOKENS, type LoggerService } from '../logger'\nimport type {\n EventContext,\n EventHandler,\n EventName,\n EventOptions,\n IEventRegistry,\n RegisteredHandler\n} from './types'\n\n@Singleton(DI_TOKENS.EventRegistry)\nexport class EventRegistry implements IEventRegistry {\n private handlers = new Map<string, RegisteredHandler[]>()\n\n constructor(\n @inject(DI_TOKENS.ExecutionContext) private readonly ctx: ExecutionContext,\n @inject(LOGGER_TOKENS.LoggerService) private readonly logger: LoggerService\n ) { }\n\n on<E extends EventName>(event: E, handler: EventHandler<E>, options?: EventOptions): void {\n const registered: RegisteredHandler = {\n handler: handler as EventHandler,\n priority: options?.priority ?? 0,\n blocking: options?.blocking\n }\n\n const existingHandlers = this.handlers.get(event) ?? []\n existingHandlers.push(registered)\n this.handlers.set(event, existingHandlers)\n\n this.logger.debug('Event handler registered', {\n event,\n priority: registered.priority,\n blocking: registered.blocking\n })\n }\n\n async emit<E extends EventName>(\n event: E,\n context?: Partial<EventContext<E>>\n ): Promise<void> {\n // Build full context with caller-provided fields\n const fullContext = {\n ...context\n } as EventContext<E>\n\n // Find matching handlers using pattern matching\n const matchingHandlers = this.findMatchingHandlers(event)\n\n if (matchingHandlers.length === 0) {\n return\n }\n\n // Sort by priority (higher first)\n const sortedHandlers = [...matchingHandlers].sort(\n (a, b) => b.priority - a.priority\n )\n\n // Determine if we should use waitUntil\n const shouldUseWaitUntil = this.shouldUseWaitUntil(event, sortedHandlers)\n\n // Execute handlers\n const promises = sortedHandlers.map((registered) =>\n this.executeHandler(registered.handler, fullContext, event)\n )\n\n if (shouldUseWaitUntil) {\n // Non-blocking: use ctx.waitUntil\n this.ctx.waitUntil(Promise.all(promises))\n } else {\n // Blocking: await all handlers\n await Promise.all(promises)\n }\n }\n\n off<E extends EventName>(event: E, handler: EventHandler<E>): void {\n const existingHandlers = this.handlers.get(event)\n if (!existingHandlers) return\n\n const filtered = existingHandlers.filter((h) => h.handler !== handler)\n if (filtered.length > 0) {\n this.handlers.set(event, filtered)\n } else {\n this.handlers.delete(event)\n }\n\n this.logger.debug('Event handler unregistered', { event })\n }\n\n once<E extends EventName>(event: E, handler: EventHandler<E>, options?: EventOptions): void {\n const wrappedHandler = (async (context: EventContext<E>) => {\n await handler(context)\n this.off(event, wrappedHandler)\n }) as EventHandler<E>\n\n this.on(event, wrappedHandler, options)\n }\n\n /**\n * Find all handlers matching the event using pattern matching.\n * Order: exact match -> model wildcard -> operation wildcard -> global wildcard\n */\n private findMatchingHandlers(event: string): RegisteredHandler[] {\n const handlers: RegisteredHandler[] = []\n\n const parts = event.split('.')\n\n if (parts.length === 3) {\n // Database event: \"phase.model.operation\"\n const [phase, model, operation] = parts\n\n // 1. Exact match: \"after.user.create\"\n handlers.push(...(this.handlers.get(event) ?? []))\n\n // 2. Model wildcard: \"after.user\"\n handlers.push(...(this.handlers.get(`${phase}.${model}`) ?? []))\n\n // 3. Operation wildcard: \"after.create\"\n handlers.push(...(this.handlers.get(`${phase}.${operation}`) ?? []))\n\n // 4. Global wildcard: \"after\"\n handlers.push(...(this.handlers.get(phase) ?? []))\n } else if (parts.length === 2) {\n // Could be wildcard like \"after.user\" or custom event like \"auth.verified\"\n handlers.push(...(this.handlers.get(event) ?? []))\n\n if (parts[0] === 'before' || parts[0] === 'after') {\n handlers.push(...(this.handlers.get(parts[0]) ?? []))\n }\n } else {\n handlers.push(...(this.handlers.get(event) ?? []))\n }\n\n return handlers\n }\n\n /**\n * Determine if we should use ctx.waitUntil (non-blocking) or await (blocking)\n */\n private shouldUseWaitUntil(\n event: string,\n handlers: RegisteredHandler[]\n ): boolean {\n const hasBlockingHandler = handlers.some((h) => h.blocking === true)\n if (hasBlockingHandler) return false\n\n const hasNonBlockingHandler = handlers.some((h) => h.blocking === false)\n if (hasNonBlockingHandler) return true\n\n const phase = event.split('.')[0]\n if (phase === 'before') return false\n if (phase === 'after') return true\n return false // Custom events block by default\n }\n\n /**\n * Execute a single handler with error isolation\n */\n private async executeHandler<E extends EventName>(\n handler: EventHandler,\n context: EventContext<E>,\n event: string\n ): Promise<void> {\n try {\n await handler(context as EventContext)\n } catch (error) {\n this.logger.error('Event handler error', {\n event,\n error: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n })\n }\n }\n}\n","import { DI_TOKENS } from '../di/tokens'\nimport { Module } from '../module/module.decorator'\nimport { EventRegistry } from './event-registry'\n\n/**\n * Registers the event registry (`DI_TOKENS.EventRegistry`).\n *\n * Lazy: loaded on demand via `LazyModuleLoader` — by the framework's\n * `DatabaseModule` (which needs it to wire DB event emission) and by the\n * application's event-listener trigger paths. Kept out of cold start for apps\n * that neither emit events nor use the database.\n */\n@Module({\n providers: [\n { provide: DI_TOKENS.EventRegistry, useClass: EventRegistry },\n ],\n})\nexport class EventsModule { }\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as Macroable } from "./macroable-
|
|
1
|
+
import { _ as ContainerError, v as ROUTER_TOKENS } from "./di-DseMn-z9.mjs";
|
|
2
|
+
import { t as Macroable } from "./macroable-cvDTFZ_A.mjs";
|
|
3
3
|
import { deleteCookie, getCookie, setCookie } from "hono/cookie";
|
|
4
4
|
import { stream, streamSSE, streamText } from "hono/streaming";
|
|
5
5
|
//#region src/router/constants.ts
|
|
@@ -426,4 +426,4 @@ function createCliExceptionContext(commandName) {
|
|
|
426
426
|
//#endregion
|
|
427
427
|
export { RouterContext as a, METHOD_STATUS_CODES as c, SECURITY_SCHEMES as d, VERSION_NEUTRAL as f, createQueueExceptionContext as i, ROUTER_CONTEXT_KEYS as l, createCronExceptionContext as n, DEFAULT_CONTENT_TYPE as o, createHttpExceptionContext as r, HTTP_METHODS as s, createCliExceptionContext as t, ROUTE_METADATA_KEYS as u };
|
|
428
428
|
|
|
429
|
-
//# sourceMappingURL=exception-context-
|
|
429
|
+
//# sourceMappingURL=exception-context-kEoMFwze.mjs.map
|