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
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { n as __decorateParam, t as __decorate } from "../decorate-
|
|
1
|
+
import { c as Transient, d as inject } 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 "../errors-
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
5
|
+
import "../errors-mXYxG0XB.mjs";
|
|
6
|
+
import { n as Module } from "../module.decorator-CYHY6pG5.mjs";
|
|
7
|
+
import "../module/index.mjs";
|
|
8
|
+
import { t as QUEUE_TOKENS } from "../queue.tokens-DjHnFmre.mjs";
|
|
9
9
|
import "../queue/index.mjs";
|
|
10
|
-
import { p as STORAGE_TOKENS } from "../storage-
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
10
|
+
import { p as STORAGE_TOKENS } from "../storage-MDZypIE9.mjs";
|
|
11
|
+
import { r as z } from "../zod-eKqqhZ5_.mjs";
|
|
12
|
+
import { t as withZodI18n } from "../validation-CpOjviyT.mjs";
|
|
13
13
|
//#region src/email/email.error.ts
|
|
14
14
|
var EmailError = class extends ApplicationError {};
|
|
15
15
|
//#endregion
|
|
@@ -48,6 +48,18 @@ const EMAIL_TOKENS = {
|
|
|
48
48
|
};
|
|
49
49
|
//#endregion
|
|
50
50
|
//#region src/email/consumers/email.consumer.ts
|
|
51
|
+
/**
|
|
52
|
+
* Strictly decode a base64 attachment payload. `Buffer.from(_, 'base64')` is
|
|
53
|
+
* lenient and silently drops invalid characters, which would ship a corrupt
|
|
54
|
+
* attachment. We re-encode and compare (modulo canonical padding) to reject
|
|
55
|
+
* malformed input loudly instead.
|
|
56
|
+
*/
|
|
57
|
+
function decodeBase64Attachment(content, filename) {
|
|
58
|
+
const buffer = Buffer.from(content, "base64");
|
|
59
|
+
const normalize = (value) => value.replace(/=+$/, "");
|
|
60
|
+
if (normalize(buffer.toString("base64")) !== normalize(content)) throw new EmailError(`Invalid base64 content for attachment "${filename}"`);
|
|
61
|
+
return buffer;
|
|
62
|
+
}
|
|
51
63
|
let EmailConsumer = class EmailConsumer {
|
|
52
64
|
logger;
|
|
53
65
|
providerFactory;
|
|
@@ -70,7 +82,7 @@ let EmailConsumer = class EmailConsumer {
|
|
|
70
82
|
});
|
|
71
83
|
try {
|
|
72
84
|
const resolvedAttachments = await this.resolveAttachments(payload.attachments);
|
|
73
|
-
const result = await
|
|
85
|
+
const result = await this.providerFactory.create().send({
|
|
74
86
|
...payload,
|
|
75
87
|
attachments: resolvedAttachments
|
|
76
88
|
});
|
|
@@ -109,7 +121,7 @@ let EmailConsumer = class EmailConsumer {
|
|
|
109
121
|
return Promise.all(attachments.map(async (attachment) => {
|
|
110
122
|
if ("content" in attachment) return {
|
|
111
123
|
filename: attachment.filename,
|
|
112
|
-
content:
|
|
124
|
+
content: decodeBase64Attachment(attachment.content, attachment.filename),
|
|
113
125
|
contentType: attachment.contentType
|
|
114
126
|
};
|
|
115
127
|
const result = await this.storage.download(attachment.storageKey, attachment.disk);
|
|
@@ -143,11 +155,16 @@ let EmailService = class EmailService {
|
|
|
143
155
|
* @param input - Email message details
|
|
144
156
|
*/
|
|
145
157
|
async send({ template, ...input }) {
|
|
158
|
+
let html;
|
|
159
|
+
if (template) {
|
|
160
|
+
const { render } = await import("@react-email/render");
|
|
161
|
+
html = await render(template);
|
|
162
|
+
}
|
|
146
163
|
await this.queue.dispatch({
|
|
147
164
|
type: "email.send",
|
|
148
165
|
payload: {
|
|
149
166
|
...input,
|
|
150
|
-
html
|
|
167
|
+
html
|
|
151
168
|
}
|
|
152
169
|
});
|
|
153
170
|
}
|
|
@@ -165,30 +182,433 @@ let EmailService = class EmailService {
|
|
|
165
182
|
};
|
|
166
183
|
EmailService = __decorate([Transient(EMAIL_TOKENS.EmailService), __decorateParam(0, inject(EMAIL_TOKENS.EmailQueue))], EmailService);
|
|
167
184
|
//#endregion
|
|
185
|
+
//#region src/email/smtp/smtp-client.ts
|
|
186
|
+
/** Abort a server read if it stalls, so a hung endpoint can't wedge the Worker. */
|
|
187
|
+
const RESPONSE_TIMEOUT_MS = 3e4;
|
|
188
|
+
function parseSmtpUrl(config) {
|
|
189
|
+
try {
|
|
190
|
+
const parsed = new URL(config.url);
|
|
191
|
+
const secure = parsed.protocol === "smtps:";
|
|
192
|
+
return {
|
|
193
|
+
host: parsed.hostname,
|
|
194
|
+
port: parsed.port ? parseInt(parsed.port, 10) : secure ? 465 : 587,
|
|
195
|
+
secure,
|
|
196
|
+
username: parsed.username ? decodeURIComponent(parsed.username) : void 0,
|
|
197
|
+
password: parsed.password ? decodeURIComponent(parsed.password) : void 0
|
|
198
|
+
};
|
|
199
|
+
} catch {
|
|
200
|
+
throw new EmailError(`Invalid SMTP URL: ${config.url}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/** Parse an EHLO reply into the capabilities/mechanisms it advertises. */
|
|
204
|
+
function parseCapabilities(ehloText) {
|
|
205
|
+
const auth = /* @__PURE__ */ new Set();
|
|
206
|
+
let startTls = false;
|
|
207
|
+
for (const rawLine of ehloText.split("\r\n")) {
|
|
208
|
+
const line = rawLine.slice(4).trim().toUpperCase();
|
|
209
|
+
if (!line) continue;
|
|
210
|
+
const [keyword, ...rest] = line.split(/\s+/);
|
|
211
|
+
if (keyword === "STARTTLS") startTls = true;
|
|
212
|
+
if (keyword === "AUTH") rest.forEach((m) => auth.add(m));
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
startTls,
|
|
216
|
+
auth
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
var SmtpClient = class {
|
|
220
|
+
parsed;
|
|
221
|
+
constructor(config) {
|
|
222
|
+
this.parsed = parseSmtpUrl(config);
|
|
223
|
+
}
|
|
224
|
+
async send(mimeRaw, options) {
|
|
225
|
+
const { connect } = await import("cloudflare:sockets");
|
|
226
|
+
const { host, port, secure, username, password } = this.parsed;
|
|
227
|
+
const implicitTls = secure || port === 465;
|
|
228
|
+
const socket = connect({
|
|
229
|
+
hostname: host,
|
|
230
|
+
port
|
|
231
|
+
}, {
|
|
232
|
+
secureTransport: implicitTls ? "on" : "starttls",
|
|
233
|
+
allowHalfOpen: false
|
|
234
|
+
});
|
|
235
|
+
let activeSocket = socket;
|
|
236
|
+
let reader = socket.readable.getReader();
|
|
237
|
+
let writer = socket.writable.getWriter();
|
|
238
|
+
const decoder = new TextDecoder();
|
|
239
|
+
const encoder = new TextEncoder();
|
|
240
|
+
const readChunk = async () => {
|
|
241
|
+
const read = reader.read();
|
|
242
|
+
read.catch(() => {});
|
|
243
|
+
let timer;
|
|
244
|
+
const timeout = new Promise((_, reject) => {
|
|
245
|
+
timer = setTimeout(() => reject(new EmailError(`SMTP timeout: no response within ${RESPONSE_TIMEOUT_MS}ms`)), RESPONSE_TIMEOUT_MS);
|
|
246
|
+
});
|
|
247
|
+
try {
|
|
248
|
+
return await Promise.race([read, timeout]);
|
|
249
|
+
} finally {
|
|
250
|
+
clearTimeout(timer);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
let leftover = "";
|
|
254
|
+
const readResponse = async () => {
|
|
255
|
+
let buffer = leftover;
|
|
256
|
+
leftover = "";
|
|
257
|
+
while (true) {
|
|
258
|
+
if (buffer.indexOf("\r\n") !== -1) {
|
|
259
|
+
const lines = buffer.split("\r\n");
|
|
260
|
+
let consumed = 0;
|
|
261
|
+
for (let i = 0; i < lines.length - 1; i++) {
|
|
262
|
+
const line = lines[i];
|
|
263
|
+
consumed += line.length + 2;
|
|
264
|
+
if (line[3] === " ") {
|
|
265
|
+
const code = parseInt(line.slice(0, 3), 10);
|
|
266
|
+
leftover = buffer.slice(consumed);
|
|
267
|
+
return {
|
|
268
|
+
code,
|
|
269
|
+
text: buffer.slice(0, consumed)
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const result = await readChunk();
|
|
275
|
+
if (result.done) throw new EmailError("SMTP connection closed unexpectedly");
|
|
276
|
+
buffer += decoder.decode(result.value, { stream: true });
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
const sendCommand = async (command) => {
|
|
280
|
+
await writer.write(encoder.encode(command + "\r\n"));
|
|
281
|
+
return readResponse();
|
|
282
|
+
};
|
|
283
|
+
const expectCode = async (command, expected) => {
|
|
284
|
+
const response = await sendCommand(command);
|
|
285
|
+
if (response.code !== expected) throw new EmailError(`SMTP error: expected ${expected}, got ${response.code}: ${response.text.trim()}`);
|
|
286
|
+
return response.text;
|
|
287
|
+
};
|
|
288
|
+
try {
|
|
289
|
+
const greeting = await readResponse();
|
|
290
|
+
if (greeting.code !== 220) throw new EmailError(`SMTP server rejected connection: ${greeting.text.trim()}`);
|
|
291
|
+
let capabilities = parseCapabilities(await expectCode(`EHLO ${host}`, 250));
|
|
292
|
+
let encrypted = implicitTls;
|
|
293
|
+
if (!implicitTls && capabilities.startTls) {
|
|
294
|
+
await expectCode("STARTTLS", 220);
|
|
295
|
+
reader.releaseLock();
|
|
296
|
+
writer.releaseLock();
|
|
297
|
+
activeSocket = socket.startTls();
|
|
298
|
+
reader = activeSocket.readable.getReader();
|
|
299
|
+
writer = activeSocket.writable.getWriter();
|
|
300
|
+
leftover = "";
|
|
301
|
+
capabilities = parseCapabilities(await expectCode(`EHLO ${host}`, 250));
|
|
302
|
+
encrypted = true;
|
|
303
|
+
}
|
|
304
|
+
if (username && password) {
|
|
305
|
+
if (!encrypted) throw new EmailError("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.");
|
|
306
|
+
await this.authenticate(expectCode, capabilities, username, password);
|
|
307
|
+
}
|
|
308
|
+
await expectCode(`MAIL FROM:<${options.from}>`, 250);
|
|
309
|
+
for (const recipient of options.to) await expectCode(`RCPT TO:<${recipient}>`, 250);
|
|
310
|
+
await expectCode("DATA", 354);
|
|
311
|
+
const dotStuffed = (mimeRaw.startsWith(".") ? "." + mimeRaw : mimeRaw).replace(/\r\n\./g, "\r\n..");
|
|
312
|
+
await writer.write(encoder.encode(dotStuffed + "\r\n.\r\n"));
|
|
313
|
+
const dataResponse = await readResponse();
|
|
314
|
+
if (dataResponse.code !== 250) throw new EmailError(`SMTP DATA rejected: ${dataResponse.text.trim()}`);
|
|
315
|
+
const messageId = dataResponse.text.match(/<([^>]+)>/)?.[1] ?? `${Date.now()}@${host}`;
|
|
316
|
+
await sendCommand("QUIT").catch(() => {});
|
|
317
|
+
return { messageId };
|
|
318
|
+
} finally {
|
|
319
|
+
reader.releaseLock();
|
|
320
|
+
writer.releaseLock();
|
|
321
|
+
await activeSocket.close().catch(() => {});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/** Authenticate using a server-advertised SASL mechanism (PLAIN or LOGIN). */
|
|
325
|
+
async authenticate(expectCode, capabilities, username, password) {
|
|
326
|
+
if (capabilities.auth.has("PLAIN")) {
|
|
327
|
+
await expectCode(`AUTH PLAIN ${Buffer.from(`\0${username}\0${password}`).toString("base64")}`, 235);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (capabilities.auth.has("LOGIN")) {
|
|
331
|
+
await expectCode("AUTH LOGIN", 334);
|
|
332
|
+
await expectCode(Buffer.from(username).toString("base64"), 334);
|
|
333
|
+
await expectCode(Buffer.from(password).toString("base64"), 235);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
throw new EmailError(capabilities.auth.size === 0 ? "SMTP credentials were provided but the server does not advertise AUTH." : `SMTP server does not support a known AUTH mechanism (offered: ${[...capabilities.auth].join(", ")}). Supported: PLAIN, LOGIN.`);
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
//#endregion
|
|
340
|
+
//#region src/email/smtp/mime.ts
|
|
341
|
+
/**
|
|
342
|
+
* Hard cap on a single attachment's resolved size (20 MB). Attachments are fully
|
|
343
|
+
* buffered into memory before base64 encoding, so an unbounded attachment would
|
|
344
|
+
* let a single message exhaust the Worker's memory. Exceeding this throws.
|
|
345
|
+
*/
|
|
346
|
+
const MAX_ATTACHMENT_SIZE_BYTES = 20 * 1024 * 1024;
|
|
347
|
+
function generateBoundary() {
|
|
348
|
+
return `----=_Part_${crypto.randomUUID().replace(/-/g, "")}`;
|
|
349
|
+
}
|
|
350
|
+
function generateMessageId(fromEmail) {
|
|
351
|
+
const domain = fromEmail.split("@")[1] || "localhost";
|
|
352
|
+
return `<${crypto.randomUUID()}@${domain}>`;
|
|
353
|
+
}
|
|
354
|
+
function isAscii(str) {
|
|
355
|
+
return /^[ -~]*$/.test(str);
|
|
356
|
+
}
|
|
357
|
+
/** Remove CR/LF so a value can't inject extra headers (header smuggling). */
|
|
358
|
+
function stripCrlf(value) {
|
|
359
|
+
return value.replace(/[\r\n]/g, "");
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Encode a header value as one or more RFC 2047 base64 encoded-words, folding so
|
|
363
|
+
* no produced line exceeds the 76-char limit strict MTAs enforce. The UTF-8 byte
|
|
364
|
+
* stream is chunked at <=45 source bytes per encoded-word (<=60 base64 chars,
|
|
365
|
+
* keeping `=?UTF-8?B?...?=` <=75 chars) WITHOUT splitting a multibyte sequence.
|
|
366
|
+
* Multiple encoded-words are folded with CRLF + a single space.
|
|
367
|
+
*/
|
|
368
|
+
function encodeEncodedWords(clean) {
|
|
369
|
+
const bytes = Buffer.from(clean, "utf-8");
|
|
370
|
+
const MAX_SOURCE_BYTES = 45;
|
|
371
|
+
const words = [];
|
|
372
|
+
let i = 0;
|
|
373
|
+
while (i < bytes.length) {
|
|
374
|
+
let end = Math.min(i + MAX_SOURCE_BYTES, bytes.length);
|
|
375
|
+
while (end > i && end < bytes.length && (bytes[end] & 192) === 128) end--;
|
|
376
|
+
const slice = bytes.subarray(i, end);
|
|
377
|
+
words.push(`=?UTF-8?B?${slice.toString("base64")}?=`);
|
|
378
|
+
i = end;
|
|
379
|
+
}
|
|
380
|
+
return words.join("\r\n ");
|
|
381
|
+
}
|
|
382
|
+
function encodeHeaderValue(value) {
|
|
383
|
+
const clean = stripCrlf(value);
|
|
384
|
+
if (isAscii(clean)) return clean;
|
|
385
|
+
return encodeEncodedWords(clean);
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Validate an envelope address (used verbatim in `MAIL FROM`/`RCPT TO` SMTP
|
|
389
|
+
* commands). Raw CR/LF would allow SMTP command injection, so we throw rather
|
|
390
|
+
* than silently strip — a corrupted envelope must never reach the wire.
|
|
391
|
+
*/
|
|
392
|
+
function assertNoCrlf(address) {
|
|
393
|
+
if (/[\r\n]/.test(address)) throw new EmailError("Email envelope address contains CR/LF, which would allow SMTP command injection");
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Escape a value for an RFC 5322 quoted-string: backslash MUST be escaped first
|
|
397
|
+
* (so the escapes added for `"` aren't themselves re-escaped), then `"`.
|
|
398
|
+
*/
|
|
399
|
+
function escapeQuotedString(value) {
|
|
400
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Encode a MIME parameter (e.g. `name`/`filename`) safely. ASCII values are
|
|
404
|
+
* emitted as a quoted-string with `"`/`\` escaped; non-ASCII values use the
|
|
405
|
+
* RFC 2231 extended syntax (`param*=UTF-8''…`). CR/LF are always stripped so a
|
|
406
|
+
* crafted filename can't inject headers or break the MIME structure.
|
|
407
|
+
*/
|
|
408
|
+
function encodeMimeParam(param, value) {
|
|
409
|
+
const clean = stripCrlf(value);
|
|
410
|
+
if (isAscii(clean)) return `${param}="${escapeQuotedString(clean)}"`;
|
|
411
|
+
return `${param}*=UTF-8''${Array.from(Buffer.from(clean, "utf-8")).map((b) => /[A-Za-z0-9!#$&+\-.^_`|~]/.test(String.fromCharCode(b)) ? String.fromCharCode(b) : `%${b.toString(16).toUpperCase().padStart(2, "0")}`).join("")}`;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Extract the bare address for the SMTP envelope and validate it has no raw
|
|
415
|
+
* CR/LF before it is written into a `MAIL FROM`/`RCPT TO` command.
|
|
416
|
+
*/
|
|
417
|
+
function extractEmail(address) {
|
|
418
|
+
assertNoCrlf(address);
|
|
419
|
+
const match = address.match(/<([^>]+)>/);
|
|
420
|
+
const email = (match ? match[1] : address).trim();
|
|
421
|
+
if (/[<>\s]/.test(email)) throw new EmailError(`Invalid email address for SMTP envelope: ${JSON.stringify(address)}`);
|
|
422
|
+
return email;
|
|
423
|
+
}
|
|
424
|
+
function formatAddress(email, name) {
|
|
425
|
+
const cleanEmail = stripCrlf(email);
|
|
426
|
+
if (!name) return cleanEmail;
|
|
427
|
+
const cleanName = stripCrlf(name);
|
|
428
|
+
return `${isAscii(cleanName) ? `"${escapeQuotedString(cleanName)}"` : encodeHeaderValue(cleanName)} <${cleanEmail}>`;
|
|
429
|
+
}
|
|
430
|
+
function formatDate(date) {
|
|
431
|
+
return date.toUTCString().replace("GMT", "+0000");
|
|
432
|
+
}
|
|
433
|
+
function wrapBase64(base64) {
|
|
434
|
+
const lines = [];
|
|
435
|
+
for (let i = 0; i < base64.length; i += 76) lines.push(base64.slice(i, i + 76));
|
|
436
|
+
return lines.join("\r\n");
|
|
437
|
+
}
|
|
438
|
+
async function resolveContent(content) {
|
|
439
|
+
const buffer = Buffer.isBuffer(content) ? content : Buffer.from(await new Response(content).arrayBuffer());
|
|
440
|
+
if (buffer.byteLength > 20971520) throw new EmailError(`Email attachment exceeds the maximum size of ${MAX_ATTACHMENT_SIZE_BYTES} bytes (got ${buffer.byteLength} bytes)`);
|
|
441
|
+
return buffer;
|
|
442
|
+
}
|
|
443
|
+
/** Base64-encode a text body so arbitrary content (long lines, leading dots,
|
|
444
|
+
* 8-bit data) is transported safely regardless of line length or SMTP escaping. */
|
|
445
|
+
function encodeTextBody(content) {
|
|
446
|
+
return wrapBase64(Buffer.from(content, "utf-8").toString("base64"));
|
|
447
|
+
}
|
|
448
|
+
function buildBodyPart(text, html) {
|
|
449
|
+
if (text && html) {
|
|
450
|
+
const boundary = generateBoundary();
|
|
451
|
+
return {
|
|
452
|
+
content: [
|
|
453
|
+
`--${boundary}`,
|
|
454
|
+
"Content-Type: text/plain; charset=utf-8",
|
|
455
|
+
"Content-Transfer-Encoding: base64",
|
|
456
|
+
"",
|
|
457
|
+
encodeTextBody(text),
|
|
458
|
+
`--${boundary}`,
|
|
459
|
+
"Content-Type: text/html; charset=utf-8",
|
|
460
|
+
"Content-Transfer-Encoding: base64",
|
|
461
|
+
"",
|
|
462
|
+
encodeTextBody(html),
|
|
463
|
+
`--${boundary}--`
|
|
464
|
+
].join("\r\n"),
|
|
465
|
+
contentType: `multipart/alternative; boundary="${boundary}"`
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
if (html) return {
|
|
469
|
+
content: encodeTextBody(html),
|
|
470
|
+
contentType: "text/html; charset=utf-8",
|
|
471
|
+
cte: "base64"
|
|
472
|
+
};
|
|
473
|
+
return {
|
|
474
|
+
content: encodeTextBody(text ?? ""),
|
|
475
|
+
contentType: "text/plain; charset=utf-8",
|
|
476
|
+
cte: "base64"
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
async function buildAttachmentPart(attachment) {
|
|
480
|
+
const base64 = wrapBase64((await resolveContent(attachment.content)).toString("base64"));
|
|
481
|
+
return [
|
|
482
|
+
`Content-Type: ${attachment.contentType || "application/octet-stream"}; ${encodeMimeParam("name", attachment.filename)}`,
|
|
483
|
+
"Content-Transfer-Encoding: base64",
|
|
484
|
+
`Content-Disposition: attachment; ${encodeMimeParam("filename", attachment.filename)}`,
|
|
485
|
+
"",
|
|
486
|
+
base64
|
|
487
|
+
].join("\r\n");
|
|
488
|
+
}
|
|
489
|
+
async function buildMimeMessage(message, defaultFrom) {
|
|
490
|
+
const fromAddr = message.from ? formatAddress(message.from.email, message.from.name) : formatAddress(defaultFrom.email, defaultFrom.name);
|
|
491
|
+
const fromEmail = extractEmail(message.from?.email ?? defaultFrom.email);
|
|
492
|
+
const toList = Array.isArray(message.to) ? message.to : [message.to];
|
|
493
|
+
const headers = [
|
|
494
|
+
`From: ${fromAddr}`,
|
|
495
|
+
`To: ${toList.map(stripCrlf).join(", ")}`,
|
|
496
|
+
`Subject: ${encodeHeaderValue(message.subject)}`,
|
|
497
|
+
`Date: ${formatDate(/* @__PURE__ */ new Date())}`,
|
|
498
|
+
`Message-ID: ${generateMessageId(fromEmail)}`,
|
|
499
|
+
"MIME-Version: 1.0"
|
|
500
|
+
];
|
|
501
|
+
if (message.replyTo) headers.push(`Reply-To: ${stripCrlf(message.replyTo)}`);
|
|
502
|
+
if (message.cc?.length) headers.push(`Cc: ${message.cc.map(stripCrlf).join(", ")}`);
|
|
503
|
+
const allRecipients = [
|
|
504
|
+
...toList,
|
|
505
|
+
...message.cc ?? [],
|
|
506
|
+
...message.bcc ?? []
|
|
507
|
+
].map(extractEmail);
|
|
508
|
+
const body = buildBodyPart(message.text, message.html);
|
|
509
|
+
if (!(message.attachments && message.attachments.length > 0)) {
|
|
510
|
+
headers.push(`Content-Type: ${body.contentType}`);
|
|
511
|
+
if (body.cte) headers.push(`Content-Transfer-Encoding: ${body.cte}`);
|
|
512
|
+
return {
|
|
513
|
+
raw: headers.join("\r\n") + "\r\n\r\n" + body.content,
|
|
514
|
+
envelope: {
|
|
515
|
+
from: fromEmail,
|
|
516
|
+
to: allRecipients
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
const mixedBoundary = generateBoundary();
|
|
521
|
+
headers.push(`Content-Type: multipart/mixed; boundary="${mixedBoundary}"`);
|
|
522
|
+
const parts = [`--${mixedBoundary}`, `Content-Type: ${body.contentType}`];
|
|
523
|
+
if (body.cte) parts.push(`Content-Transfer-Encoding: ${body.cte}`);
|
|
524
|
+
parts.push("", body.content);
|
|
525
|
+
for (const attachment of message.attachments) {
|
|
526
|
+
parts.push(`--${mixedBoundary}`);
|
|
527
|
+
parts.push(await buildAttachmentPart(attachment));
|
|
528
|
+
}
|
|
529
|
+
parts.push(`--${mixedBoundary}--`);
|
|
530
|
+
return {
|
|
531
|
+
raw: headers.join("\r\n") + "\r\n\r\n" + parts.join("\r\n"),
|
|
532
|
+
envelope: {
|
|
533
|
+
from: fromEmail,
|
|
534
|
+
to: allRecipients
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
//#endregion
|
|
539
|
+
//#region src/email/providers/base-email.provider.ts
|
|
540
|
+
/**
|
|
541
|
+
* Base Email Provider
|
|
542
|
+
*
|
|
543
|
+
* Abstract base class for email providers.
|
|
544
|
+
* Provides shared implementation of sendBatch() to reduce code duplication.
|
|
545
|
+
*/
|
|
546
|
+
var BaseEmailProvider = class {
|
|
547
|
+
/**
|
|
548
|
+
* Send multiple emails in a batch
|
|
549
|
+
*
|
|
550
|
+
* Default implementation sends emails sequentially.
|
|
551
|
+
* Concrete providers can override for optimized batch sending.
|
|
552
|
+
*/
|
|
553
|
+
async sendBatch(messages) {
|
|
554
|
+
const results = [];
|
|
555
|
+
let successful = 0;
|
|
556
|
+
let failed = 0;
|
|
557
|
+
for (const message of messages) try {
|
|
558
|
+
const result = await this.send(message);
|
|
559
|
+
results.push(result);
|
|
560
|
+
successful++;
|
|
561
|
+
} catch (error) {
|
|
562
|
+
results.push({
|
|
563
|
+
messageId: "",
|
|
564
|
+
accepted: false,
|
|
565
|
+
metadata: { error: error instanceof Error ? error.message : "Unknown error" }
|
|
566
|
+
});
|
|
567
|
+
failed++;
|
|
568
|
+
}
|
|
569
|
+
return {
|
|
570
|
+
total: messages.length,
|
|
571
|
+
successful,
|
|
572
|
+
failed,
|
|
573
|
+
results
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
//#endregion
|
|
578
|
+
//#region src/email/providers/smtp.provider.ts
|
|
579
|
+
var SmtpProvider = class extends BaseEmailProvider {
|
|
580
|
+
options;
|
|
581
|
+
constructor(options) {
|
|
582
|
+
super();
|
|
583
|
+
this.options = options;
|
|
584
|
+
if (!this.options.smtp?.url) throw new EmailError("SMTP URL is required");
|
|
585
|
+
}
|
|
586
|
+
async send(message) {
|
|
587
|
+
const mime = await buildMimeMessage(message, this.options.from);
|
|
588
|
+
try {
|
|
589
|
+
return {
|
|
590
|
+
messageId: (await new SmtpClient(this.options.smtp).send(mime.raw, {
|
|
591
|
+
from: mime.envelope.from,
|
|
592
|
+
to: mime.envelope.to
|
|
593
|
+
})).messageId,
|
|
594
|
+
accepted: true,
|
|
595
|
+
metadata: { provider: "smtp" }
|
|
596
|
+
};
|
|
597
|
+
} catch (error) {
|
|
598
|
+
if (error instanceof EmailError) throw error;
|
|
599
|
+
throw new EmailError(`SMTP send failed: ${error.message}`);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
//#endregion
|
|
168
604
|
//#region src/email/services/email-provider-factory.ts
|
|
169
605
|
let EmailProviderFactory = class EmailProviderFactory {
|
|
170
606
|
options;
|
|
171
607
|
constructor(options) {
|
|
172
608
|
this.options = options;
|
|
173
609
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
*
|
|
177
|
-
* @returns Email provider implementation
|
|
178
|
-
* @throws EmailError if provider is not supported
|
|
179
|
-
*/
|
|
180
|
-
async create() {
|
|
181
|
-
switch (this.options.provider) {
|
|
182
|
-
case "resend": {
|
|
183
|
-
const { ResendProvider } = await import("../resend.provider-Ur6tU7fK.mjs");
|
|
184
|
-
return new ResendProvider(this.options);
|
|
185
|
-
}
|
|
186
|
-
case "smtp": {
|
|
187
|
-
const { SmtpProvider } = await import("../smtp.provider-C129sNBT.mjs");
|
|
188
|
-
return new SmtpProvider(this.options);
|
|
189
|
-
}
|
|
190
|
-
default: throw new EmailError(`Email provider "${String(this.options.provider)}" is not supported`);
|
|
191
|
-
}
|
|
610
|
+
create() {
|
|
611
|
+
return new SmtpProvider(this.options);
|
|
192
612
|
}
|
|
193
613
|
};
|
|
194
614
|
EmailProviderFactory = __decorate([Transient(EMAIL_TOKENS.EmailProviderFactory), __decorateParam(0, inject(EMAIL_TOKENS.Options))], EmailProviderFactory);
|
|
@@ -198,16 +618,15 @@ EmailProviderFactory = __decorate([Transient(EMAIL_TOKENS.EmailProviderFactory),
|
|
|
198
618
|
* Email Module
|
|
199
619
|
*
|
|
200
620
|
* Provides email sending capabilities with provider abstraction.
|
|
201
|
-
*
|
|
621
|
+
* Sends email via SMTP. Emails are dispatched asynchronously via Cloudflare Queues.
|
|
202
622
|
* Emails are sent asynchronously via Cloudflare Queues.
|
|
203
623
|
*
|
|
204
624
|
* **Usage:**
|
|
205
625
|
* ```typescript
|
|
206
626
|
* // In AppModule imports with static options
|
|
207
627
|
* EmailModule.forRoot({
|
|
208
|
-
* provider: 'resend',
|
|
209
|
-
* apiKey: 'your-api-key',
|
|
210
628
|
* from: { name: 'App', email: 'noreply@example.com' },
|
|
629
|
+
* smtp: { url: 'smtp://user:pass@smtp.example.com:587' },
|
|
211
630
|
* queue: 'NOTIFICATIONS_QUEUE',
|
|
212
631
|
* })
|
|
213
632
|
*
|
|
@@ -215,9 +634,8 @@ EmailProviderFactory = __decorate([Transient(EMAIL_TOKENS.EmailProviderFactory),
|
|
|
215
634
|
* EmailModule.forRootAsync({
|
|
216
635
|
* inject: [emailConfig.KEY],
|
|
217
636
|
* useFactory: (email) => ({
|
|
218
|
-
* provider: email.provider,
|
|
219
|
-
* apiKey: email.apiKey,
|
|
220
637
|
* from: email.from,
|
|
638
|
+
* smtp: email.smtp,
|
|
221
639
|
* queue: email.queue,
|
|
222
640
|
* }),
|
|
223
641
|
* })
|
|
@@ -244,9 +662,8 @@ let EmailModule = _EmailModule = class EmailModule {
|
|
|
244
662
|
* @example
|
|
245
663
|
* ```typescript
|
|
246
664
|
* EmailModule.forRoot({
|
|
247
|
-
* provider: 'resend',
|
|
248
|
-
* apiKey: env.RESEND_API_KEY,
|
|
249
665
|
* from: { name: 'App', email: 'noreply@example.com' },
|
|
666
|
+
* smtp: { url: 'smtp://user:pass@smtp.example.com:587' },
|
|
250
667
|
* queue: 'NOTIFICATIONS_QUEUE',
|
|
251
668
|
* })
|
|
252
669
|
* ```
|
|
@@ -276,8 +693,6 @@ let EmailModule = _EmailModule = class EmailModule {
|
|
|
276
693
|
* EmailModule.forRootAsync({
|
|
277
694
|
* inject: [emailConfig.KEY],
|
|
278
695
|
* useFactory: (email) => ({
|
|
279
|
-
* provider: email.provider,
|
|
280
|
-
* apiKey: email.apiKey,
|
|
281
696
|
* from: email.from,
|
|
282
697
|
* smtp: email.smtp,
|
|
283
698
|
* queue: email.queue,
|
|
@@ -450,6 +865,6 @@ const sendBatchEmailInputSchema = z.object({
|
|
|
450
865
|
*/
|
|
451
866
|
messages: z.array(sendEmailInputSchema).min(1).max(100) });
|
|
452
867
|
//#endregion
|
|
453
|
-
export { EMAIL_TOKENS, EmailError, EmailModule, EmailProviderFactory, EmailService, emailAddressSchema, emailAttachmentSchema, emailMessageSchema, inlineEmailAttachmentSchema, sendBatchEmailInputSchema, sendEmailInputSchema, storageEmailAttachmentSchema };
|
|
868
|
+
export { BaseEmailProvider, EMAIL_TOKENS, EmailError, EmailModule, EmailProviderFactory, EmailService, emailAddressSchema, emailAttachmentSchema, emailMessageSchema, inlineEmailAttachmentSchema, sendBatchEmailInputSchema, sendEmailInputSchema, storageEmailAttachmentSchema };
|
|
454
869
|
|
|
455
870
|
//# sourceMappingURL=index.mjs.map
|