void 0.10.12 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS_TEMPLATE.md +6 -0
- package/{skills/void/docs/node_modules/void/node_modules/pglite-server/LICENSE.md → LICENSE} +1 -1
- package/README.md +34 -8
- package/dist/{auth-qgMlYp7Z.d.mts → auth-DkcFflXV.d.mts} +10 -11
- package/dist/auth-W9WII-mN.mjs +630 -0
- package/dist/{auth-cmd-BqsdZJp5.mjs → auth-cmd-CYVhSNFy.mjs} +4 -3
- package/dist/{auth-migrations-BTZ-ATvQ.mjs → auth-migrations-9vif1uj8.mjs} +53 -12
- package/dist/{better-auth-shared-BQooDxbw.mjs → better-auth-shared-CYw1T3k4.mjs} +7 -10
- package/dist/{better-auth-shared-DealXecJ.d.mts → better-auth-shared-DSCeohOK.d.mts} +10 -5
- package/dist/{build-cmd-Bujrv5q-.mjs → build-cmd-CkVD1uOh.mjs} +6 -4
- package/dist/{cache-C11V8Fxq.mjs → cache-QcUfR-ff.mjs} +9 -5
- package/dist/{cancel-deploy-fwFYF04b.mjs → cancel-deploy-DsvWKFTe.mjs} +3 -2
- package/dist/cf-access-AJ1ehiFR.mjs +42 -0
- package/dist/cf-access-DsSsZUPr.mjs +67 -0
- package/dist/cli/cli.d.mts +1 -1
- package/dist/cli/cli.mjs +1592 -153
- package/dist/cli/env-schema-probe.d.mts +48 -61
- package/dist/cli/env-schema-probe.mjs +10 -58
- package/dist/{client-Gb71-XkG.mjs → client-CyCHWSO_.mjs} +61 -463
- package/dist/cloudflare-auth-DRkGe7-s.mjs +170 -0
- package/dist/cloudflare-cmd-D3ME2GAe.mjs +62 -0
- package/dist/cloudflare-connect-BivMefMA.mjs +56 -0
- package/dist/cloudflare-operations-CPTpRW6d.mjs +566 -0
- package/dist/{config-BkTvs43g.mjs → config-BQFq7QvD.mjs} +85 -41
- package/dist/{config-CutEMNGJ.mjs → config-NOG_U1aK.mjs} +10 -15
- package/dist/connect-D9Yr-T5f.mjs +79 -0
- package/dist/{create-project-DsYvl3TB.mjs → create-project-DMV-csEm.mjs} +26 -16
- package/dist/database-provider.d.mts +21 -0
- package/dist/database-provider.mjs +6 -0
- package/dist/{db-DOiJMRt2.mjs → db-BxoUWL0F.mjs} +600 -115
- package/dist/{delete-mh6p-zkQ.mjs → delete-iJOqxBz1.mjs} +9 -6
- package/dist/{deploy-u7Rv9q_q.mjs → deploy-CcDoeBIf.mjs} +3451 -1986
- package/dist/dev-inbox-DkgRWLkW.mjs +307 -0
- package/dist/{discover-CJHyvYfR.mjs → discover-xvfrgJeo.mjs} +3 -3
- package/dist/{magic-string.es-ZQjdJFFn.mjs → dist-BR1quN_w.mjs} +570 -216
- package/dist/{dist-DaKKDf8D.mjs → dist-BrsS7cai.mjs} +17 -2
- package/dist/{dist-BuiRJkTd.mjs → dist-m40_XgNh.mjs} +48 -33
- package/dist/{domain-DiaNQbrl.mjs → domain-gu_iaHmn.mjs} +41 -4
- package/dist/dotenv-VQxupEUv.mjs +181 -0
- package/dist/edge.d.mts +2 -0
- package/dist/edge.mjs +2 -0
- package/dist/email-Ce6SQq-i.mjs +182 -0
- package/dist/email-r6DHJyAB.mjs +262 -0
- package/dist/{entry-D7yy4xVH.mjs → entry-DU3oDoQ3.mjs} +2 -2
- package/dist/env-D4Emu-M_.mjs +95 -0
- package/dist/env-DmU2To0C.mjs +73 -0
- package/dist/env-helpers-CyKtOBpj.d.mts +22 -0
- package/dist/env-public-D_6u46fX.d.mts +135 -0
- package/dist/{env-types-QBj-ndax.mjs → env-types-BNPhro-M.mjs} +2 -2
- package/dist/{env-validation-Dea3v3ej.mjs → env-validation-ENpMy6Ez.mjs} +68 -122
- package/dist/{gen-o_w-8yI8.mjs → gen-BKw6qHIg.mjs} +70 -15
- package/dist/{github-cmd-DKcGUNsj.mjs → github-cmd-DJS5Ab-H.mjs} +39 -31
- package/dist/{handler-Cjh8uM3Y.d.mts → handler-D1hLsObx.d.mts} +124 -122
- package/dist/{head-nmvOgFjd.d.mts → head-Do8P4puT.d.mts} +9 -8
- package/dist/{headers-nsHIFixA.mjs → headers-D8QfRX9Y.mjs} +12 -10
- package/dist/inbound-BJ70in1n.d.mts +354 -0
- package/dist/inbound-CNKxb3FY.mjs +694 -0
- package/dist/index.d.mts +61 -34
- package/dist/index.mjs +1048 -321
- package/dist/{init-3rgHKBVi.mjs → init-Dx5cgKqK.mjs} +329 -271
- package/dist/link-D_rm5sRH.mjs +52 -0
- package/dist/{list-CPwFDZ_c.mjs → list-M8XShti7.mjs} +37 -5
- package/dist/{runner-kapo9aPs.mjs → local-d1-BE8KBbMy.mjs} +116 -6
- package/dist/{login-BT3H8PN3.mjs → login-C8-UuLnp.mjs} +17 -15
- package/dist/{logs-Bt313ax7.mjs → logs-DwFU7dMW.mjs} +16 -2
- package/dist/mime-BJD7d_qL.mjs +1216 -0
- package/dist/neon-DHwd2zvC.mjs +54 -0
- package/dist/{node-Da0UcsGA.mjs → node-BkaXcpAc.mjs} +29 -8
- package/dist/operator-cmd-DYWRbWUA.mjs +348 -0
- package/dist/{agents-CtgBYqld.mjs → output-tFQLLj26.mjs} +676 -211
- package/dist/{package-json-Cx1osYo6.mjs → package-json-CPoWX79C.mjs} +1 -1
- package/dist/pages/client.d.mts +43 -41
- package/dist/pages/client.mjs +40 -33
- package/dist/pages/head-client.d.mts +10 -12
- package/dist/pages/head.d.mts +1 -1
- package/dist/pages/index.d.mts +37 -14
- package/dist/pages/index.mjs +5 -5
- package/dist/pages/islands-plugin.d.mts +25 -27
- package/dist/pages/islands-plugin.mjs +6 -4
- package/dist/pages/prefetch.d.mts +6 -7
- package/dist/pages/protocol.d.mts +2 -2
- package/dist/pages/protocol.mjs +2 -1
- package/dist/pages/serialize.d.mts +7 -8
- package/dist/platform-cmd-BEOVv1PN.mjs +123 -0
- package/dist/platform-domain-BszUfiS8.mjs +228 -0
- package/dist/platform-lifecycle-CjSr6xf0.mjs +4450 -0
- package/dist/platform-management-W30iCaTL.mjs +698 -0
- package/dist/platform-recovery-pHHv4ZSg.mjs +99 -0
- package/dist/{plugin-inference-DMeavIJ6.mjs → plugin-inference-BDRfZngg.mjs} +35 -19
- package/dist/prepare-BfJvFUtJ.mjs +14 -0
- package/dist/{prepare-BoKHgMNx.mjs → prepare-CBetXvsN.mjs} +15 -24
- package/dist/{preset-BjyR3lzz.mjs → preset-lAy0B0BQ.mjs} +25 -212
- package/dist/{project-cmd-D_w-4w5B.mjs → project-cmd-DCpk1cDt.mjs} +25 -12
- package/dist/{project-paths-BQd7OmIo.mjs → project-paths-SK8nMHPp.mjs} +3 -1
- package/dist/{project-tsconfig-B-QtXjLQ.mjs → project-tsconfig-Ql2XsSQp.mjs} +2 -2
- package/dist/{protocol-Bnb0LFp3.d.mts → protocol-C-pqYJjE.d.mts} +3 -4
- package/dist/{provision-rShh6MKY.mjs → provision-Blnstcm2.mjs} +78 -45
- package/dist/r2-conditions-D1Wk8i7b.mjs +30 -0
- package/dist/{requests-B8sZxaFM.mjs → requests-Dn8Vheh1.mjs} +5 -3
- package/dist/{resolve-project-BBMtLLV9.mjs → resolve-project--Vxawf7z.mjs} +2 -2
- package/dist/rollback-CtlPXEBi.mjs +166 -0
- package/dist/{rolldown-runtime-DJK8HYOj.mjs → rolldown-runtime-rQ84J-ij.mjs} +1 -1
- package/dist/{route-types-COI2DsZv.mjs → route-types-Da-DpyUp.mjs} +85 -26
- package/dist/routes-stub.d.mts +22 -23
- package/dist/runner-mysql-CgRFl3s6.mjs +61 -0
- package/dist/{runner-pg-CHM76xuC.mjs → runner-pg-DCkWPsWS.mjs} +18 -6
- package/dist/runtime/ai.d.mts +21 -14
- package/dist/runtime/ai.mjs +5 -4
- package/dist/runtime/auth-client-react.d.mts +3 -5
- package/dist/runtime/auth-client-solid.d.mts +3 -5
- package/dist/runtime/auth-client-svelte.d.mts +3 -5
- package/dist/runtime/auth-client-vue.d.mts +3 -5
- package/dist/runtime/auth-client.d.mts +3 -5
- package/dist/runtime/auth.d.mts +1 -1
- package/dist/runtime/better-auth-mysql.d.mts +10 -0
- package/dist/runtime/better-auth-mysql.mjs +49 -0
- package/dist/runtime/better-auth-pg.d.mts +8 -9
- package/dist/runtime/better-auth-pg.mjs +2 -2
- package/dist/runtime/better-auth.d.mts +8 -9
- package/dist/runtime/better-auth.mjs +2 -2
- package/dist/runtime/client-react.d.mts +1 -1
- package/dist/runtime/client-solid.d.mts +1 -1
- package/dist/runtime/client-svelte.d.mts +1 -1
- package/dist/runtime/client-vue.d.mts +1 -1
- package/dist/runtime/client.d.mts +1 -1
- package/dist/runtime/db-mysql.d.mts +2 -0
- package/dist/runtime/db-mysql.mjs +1 -0
- package/dist/runtime/db.d.mts +10 -11
- package/dist/runtime/durable.d.mts +47 -0
- package/dist/runtime/durable.mjs +146 -0
- package/dist/runtime/email/testing.d.mts +112 -0
- package/dist/runtime/email/testing.mjs +283 -0
- package/dist/runtime/email.d.mts +30 -0
- package/dist/runtime/email.mjs +573 -0
- package/dist/runtime/env-helpers.d.mts +2 -2
- package/dist/runtime/env-helpers.mjs +5 -42
- package/dist/runtime/env-public-client.d.mts +10 -11
- package/dist/runtime/env-public-client.mjs +1 -1
- package/dist/runtime/env-public.d.mts +2 -2
- package/dist/runtime/env-public.mjs +104 -49
- package/dist/runtime/env.d.mts +19 -18
- package/dist/runtime/env.mjs +15 -2
- package/dist/runtime/fetch-stream.d.mts +20 -21
- package/dist/runtime/fetch.d.mts +15 -16
- package/dist/runtime/handler.d.mts +1 -1
- package/dist/runtime/isr.d.mts +21 -22
- package/dist/runtime/isr.mjs +26 -8
- package/dist/runtime/kv.d.mts +9 -10
- package/dist/runtime/live-client.d.mts +5 -7
- package/dist/runtime/live-client.mjs +9 -7
- package/dist/runtime/live-server.d.mts +4 -5
- package/dist/runtime/live.d.mts +22 -24
- package/dist/runtime/live.mjs +1 -1
- package/dist/runtime/log.d.mts +16 -17
- package/dist/runtime/migration-handler-mysql.d.mts +4 -0
- package/dist/runtime/migration-handler-mysql.mjs +81 -0
- package/dist/runtime/migration-handler-pg.d.mts +2 -4
- package/dist/runtime/migration-handler.d.mts +5 -6
- package/dist/runtime/migration-handler.mjs +4 -3
- package/dist/runtime/queues.d.mts +3 -4
- package/dist/runtime/queues.mjs +2 -1
- package/dist/runtime/remote/binding-handler.d.mts +10 -12
- package/dist/runtime/remote/binding-handler.mjs +24 -3
- package/dist/runtime/remote/index.d.mts +5 -6
- package/dist/runtime/remote/index.mjs +21 -18
- package/dist/runtime/response.d.mts +10 -11
- package/dist/runtime/sandbox.d.mts +56 -55
- package/dist/runtime/sandbox.mjs +57 -49
- package/dist/runtime/schema-mysql.d.mts +1 -0
- package/dist/runtime/schema-mysql.mjs +2 -0
- package/dist/runtime/seed.d.mts +14 -9
- package/dist/runtime/sse-client.d.mts +6 -7
- package/dist/runtime/sse.d.mts +11 -12
- package/dist/runtime/storage.d.mts +3 -4
- package/dist/runtime/validator.d.mts +1 -1
- package/dist/runtime/ws-server.d.mts +12 -12
- package/dist/runtime/ws-server.mjs +32 -4
- package/dist/runtime/ws.d.mts +19 -21
- package/dist/{scan-DYXkrasO.mjs → scan-BMH4rzlv.mjs} +53 -31
- package/dist/{scan-DEwlM_Xy.mjs → scan-CpK-57ug.mjs} +9 -5
- package/dist/{secret-Dt32J6RI.mjs → secret-BqTxGqki.mjs} +62 -5
- package/dist/{skills-CLjN0uUO.mjs → skills-Q46GZMO-.mjs} +6 -4
- package/dist/{standard-schema-DJ0HW7QP.d.mts → standard-schema-Fo_vCAZh.d.mts} +6 -6
- package/dist/{subcommand-prompt-BzV8iQZo.mjs → subcommand-prompt-WfySCQ7S.mjs} +67 -48
- package/dist/sveltekit.d.mts +12 -11
- package/dist/sveltekit.mjs +1 -1
- package/dist/types-BAp5AEBU.d.mts +79 -0
- package/dist/types-CKWnYgfy.d.mts +1 -0
- package/dist/{validate-Cw_RLeTj.mjs → validate-Bihr8WBi.mjs} +3 -3
- package/dist/wrangler--imS8n0d.mjs +1796 -0
- package/dist/{yarn-pnp-DJn3SAHF.mjs → yarn-pnp-DxSInkzL.mjs} +1 -1
- package/package.json +79 -65
- package/schema.json +35 -3
- package/skills/migrate-vite-cloudflare-to-void/SKILL.md +1 -1
- package/skills/void/SKILL.md +59 -2
- package/skills/void/docs/guide/ai.md +32 -14
- package/skills/void/docs/guide/app-types.md +6 -6
- package/skills/void/docs/guide/auth.md +14 -16
- package/skills/void/docs/guide/database/d1.md +6 -0
- package/skills/void/docs/guide/database/mysql.md +60 -0
- package/skills/void/docs/guide/database/postgresql.md +14 -9
- package/skills/void/docs/guide/database.md +39 -26
- package/skills/void/docs/guide/deployment.md +163 -22
- package/skills/void/docs/guide/durable-state.md +140 -0
- package/skills/void/docs/guide/edge/headers.md +5 -5
- package/skills/void/docs/guide/edge/prerendering.md +2 -0
- package/skills/void/docs/guide/edge/revalidation.md +21 -6
- package/skills/void/docs/guide/edge/rewrites.md +18 -14
- package/skills/void/docs/guide/edge/static-assets.md +23 -8
- package/skills/void/docs/guide/email.md +609 -0
- package/skills/void/docs/guide/env-migration.md +109 -0
- package/skills/void/docs/guide/env-vars.md +70 -248
- package/skills/void/docs/guide/index.md +15 -17
- package/skills/void/docs/guide/jobs.md +8 -5
- package/skills/void/docs/guide/live.md +7 -15
- package/skills/void/docs/guide/pages-routing/actions-and-forms.md +8 -4
- package/skills/void/docs/guide/pages-routing/islands.md +3 -3
- package/skills/void/docs/guide/pages-routing/loaders.md +6 -4
- package/skills/void/docs/guide/pages-routing/overview.md +7 -7
- package/skills/void/docs/guide/platform-administration.md +212 -0
- package/skills/void/docs/guide/platform-development.md +235 -0
- package/skills/void/docs/guide/queues.md +10 -10
- package/skills/void/docs/guide/quickstart.md +46 -67
- package/skills/void/docs/guide/remote-dev.md +8 -6
- package/skills/void/docs/guide/sandboxes.md +33 -16
- package/skills/void/docs/guide/self-hosted-platform.md +553 -0
- package/skills/void/docs/guide/server-routing.md +25 -5
- package/skills/void/docs/guide/sse.md +4 -4
- package/skills/void/docs/guide/ssg.md +5 -3
- package/skills/void/docs/guide/storage.md +2 -2
- package/skills/void/docs/guide/websockets.md +18 -7
- package/skills/void/docs/index.md +3 -3
- package/skills/void/docs/integrations/agents.md +6 -64
- package/skills/void/docs/integrations/cloudflare.md +165 -146
- package/skills/void/docs/integrations/frameworks/analog.md +4 -4
- package/skills/void/docs/integrations/frameworks/astro.md +5 -5
- package/skills/void/docs/integrations/frameworks/nuxt.md +5 -5
- package/skills/void/docs/integrations/frameworks/overview.md +3 -3
- package/skills/void/docs/integrations/frameworks/react-router.md +3 -3
- package/skills/void/docs/integrations/frameworks/sveltekit.md +3 -3
- package/skills/void/docs/integrations/frameworks/tanstack-start.md +2 -2
- package/skills/void/docs/integrations/nodejs-bun-deno.md +11 -4
- package/skills/void/docs/reference/api.md +42 -6
- package/skills/void/docs/reference/cli.md +637 -159
- package/skills/void/docs/reference/config.md +80 -25
- package/skills/void/docs/reference/resource-inference.md +13 -9
- package/skills/void/docs/reference/structure.md +10 -11
- package/AGENT_PROMPT.md +0 -19
- package/dist/cf-access-Bqw81xAf.mjs +0 -22
- package/dist/env-CZy5MorI.mjs +0 -299
- package/dist/env-helpers-z4stu8uc.d.mts +0 -52
- package/dist/env-mask-Dd47NbR6.mjs +0 -90
- package/dist/env-public-BfiLcMBk.d.mts +0 -140
- package/dist/link-CdGHSIy-.mjs +0 -45
- package/dist/mcp-DoM3_nhd.mjs +0 -377
- package/dist/project-paths-GpziKeQQ.d.mts +0 -25
- package/dist/providers-BNKRacMr.d.mts +0 -7
- package/dist/proxy-D-3_D-Gl.mjs +0 -5
- package/dist/rollback-CkvTFXx5.mjs +0 -90
- package/dist/runtime/isr-cache.d.mts +0 -207
- package/dist/runtime/isr-cache.mjs +0 -523
- package/dist/types-lLjNE9Qp.d.mts +0 -51
- package/getting-started-prompt.txt +0 -28
- package/skills/void/command/void.md +0 -7
- package/skills/void/docs/integrations/auth-providers.md +0 -0
- package/skills/void/docs/integrations/payment-processors.md +0 -0
- package/skills/void/docs/node_modules/@iconify/vue/README.md +0 -408
- package/skills/void/docs/node_modules/@iconify/vue/offline/readme.md +0 -5
- package/skills/void/docs/node_modules/@voidzero-dev/vitepress-theme/README.md +0 -103
- package/skills/void/docs/node_modules/oxc-minify/README.md +0 -78
- package/skills/void/docs/node_modules/reka-ui/README.md +0 -80
- package/skills/void/docs/node_modules/vitepress/README.md +0 -28
- package/skills/void/docs/node_modules/vitepress/template/api-examples.md +0 -49
- package/skills/void/docs/node_modules/vitepress/template/index.md +0 -28
- package/skills/void/docs/node_modules/vitepress/template/markdown-examples.md +0 -85
- package/skills/void/docs/node_modules/vitepress-plugin-group-icons/README.md +0 -101
- package/skills/void/docs/node_modules/void/AGENT_PROMPT.md +0 -19
- package/skills/void/docs/node_modules/void/CLAUDE.md +0 -219
- package/skills/void/docs/node_modules/void/README.md +0 -90
- package/skills/void/docs/node_modules/void/node_modules/@clack/prompts/CHANGELOG.md +0 -685
- package/skills/void/docs/node_modules/void/node_modules/@clack/prompts/README.md +0 -396
- package/skills/void/docs/node_modules/void/node_modules/@cloudflare/sandbox/README.md +0 -219
- package/skills/void/docs/node_modules/void/node_modules/@cloudflare/vite-plugin/README.md +0 -37
- package/skills/void/docs/node_modules/void/node_modules/@cloudflare/workers-types/README.md +0 -135
- package/skills/void/docs/node_modules/void/node_modules/@electric-sql/pglite/README.md +0 -189
- package/skills/void/docs/node_modules/void/node_modules/@hono/oauth-providers/CHANGELOG.md +0 -143
- package/skills/void/docs/node_modules/void/node_modules/@hono/oauth-providers/README.md +0 -1272
- package/skills/void/docs/node_modules/void/node_modules/@napi-rs/keyring/README.md +0 -19
- package/skills/void/docs/node_modules/void/node_modules/@types/better-sqlite3/README.md +0 -15
- package/skills/void/docs/node_modules/void/node_modules/@types/node/README.md +0 -15
- package/skills/void/docs/node_modules/void/node_modules/@types/pg/README.md +0 -15
- package/skills/void/docs/node_modules/void/node_modules/@types/proper-lockfile/README.md +0 -51
- package/skills/void/docs/node_modules/void/node_modules/@typescript/native-preview/README.md +0 -22
- package/skills/void/docs/node_modules/void/node_modules/@typescript/native-preview/vendor/vscode-jsonrpc/README.md +0 -69
- package/skills/void/docs/node_modules/void/node_modules/@void/md/README.md +0 -153
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/@shikijs/engine-javascript/README.md +0 -9
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/@shikijs/transformers/README.md +0 -9
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/@types/node/README.md +0 -15
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/gray-matter/CHANGELOG.md +0 -24
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/gray-matter/README.md +0 -565
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/markdown-exit/README.md +0 -127
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/markdown-it-anchor/README.md +0 -600
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/markdown-it-attrs/README.md +0 -386
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/markdown-it-container/README.md +0 -95
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/markdown-it-emoji/README.md +0 -101
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/markdown-it-footnote/README.md +0 -135
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/pathslash/README.md +0 -64
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/shiki/README.md +0 -15
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/tinyglobby/README.md +0 -25
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/AGENTS.md +0 -16
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/README.md +0 -220
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/build.md +0 -21
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/check.md +0 -35
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/create.md +0 -70
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/fmt.md +0 -20
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/index.md +0 -35
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/lint.md +0 -26
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/pack.md +0 -17
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/run.md +0 -364
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/staged.md +0 -15
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/test.md +0 -18
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/automatic-data-tracking.md +0 -145
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/build.md +0 -40
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/cache.md +0 -107
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/check.md +0 -60
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/ci.md +0 -62
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/commit-hooks.md +0 -60
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/create.md +0 -341
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/dev.md +0 -24
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/docker.md +0 -175
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/env.md +0 -167
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/fmt.md +0 -41
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/github-actions-cache.md +0 -165
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/ide-integration.md +0 -101
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/implode.md +0 -23
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/index.md +0 -134
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/install.md +0 -199
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/lint.md +0 -50
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/migrate-rules.md +0 -347
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/migrate.md +0 -197
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/monorepo.md +0 -176
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/pack.md +0 -69
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/run.md +0 -356
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/test.md +0 -35
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/troubleshooting.md +0 -108
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/upgrade.md +0 -101
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/vpx.md +0 -66
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/why.md +0 -39
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/index.md +0 -12
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/team.md +0 -35
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/templates/generator/README.md +0 -35
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/templates/monorepo/README.md +0 -29
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vue/README.md +0 -58
- package/skills/void/docs/node_modules/void/node_modules/arktype/README.md +0 -165
- package/skills/void/docs/node_modules/void/node_modules/better-auth/LICENSE.md +0 -20
- package/skills/void/docs/node_modules/void/node_modules/better-auth/README.md +0 -32
- package/skills/void/docs/node_modules/void/node_modules/better-sqlite3/README.md +0 -99
- package/skills/void/docs/node_modules/void/node_modules/blake3-jit/README.md +0 -108
- package/skills/void/docs/node_modules/void/node_modules/drizzle-arktype/README.md +0 -51
- package/skills/void/docs/node_modules/void/node_modules/drizzle-kit/README.md +0 -79
- package/skills/void/docs/node_modules/void/node_modules/drizzle-orm/README.md +0 -44
- package/skills/void/docs/node_modules/void/node_modules/drizzle-valibot/README.md +0 -51
- package/skills/void/docs/node_modules/void/node_modules/drizzle-zod/README.md +0 -65
- package/skills/void/docs/node_modules/void/node_modules/es-module-lexer/README.md +0 -403
- package/skills/void/docs/node_modules/void/node_modules/estree-walker/README.md +0 -48
- package/skills/void/docs/node_modules/void/node_modules/hono/README.md +0 -85
- package/skills/void/docs/node_modules/void/node_modules/ignore/README.md +0 -452
- package/skills/void/docs/node_modules/void/node_modules/jsonc-parser/CHANGELOG.md +0 -76
- package/skills/void/docs/node_modules/void/node_modules/jsonc-parser/LICENSE.md +0 -21
- package/skills/void/docs/node_modules/void/node_modules/jsonc-parser/README.md +0 -364
- package/skills/void/docs/node_modules/void/node_modules/jsonc-parser/SECURITY.md +0 -41
- package/skills/void/docs/node_modules/void/node_modules/magic-string/README.md +0 -325
- package/skills/void/docs/node_modules/void/node_modules/ofetch/README.md +0 -398
- package/skills/void/docs/node_modules/void/node_modules/pathslash/README.md +0 -64
- package/skills/void/docs/node_modules/void/node_modules/pg/README.md +0 -96
- package/skills/void/docs/node_modules/void/node_modules/pglite-server/README.md +0 -135
- package/skills/void/docs/node_modules/void/node_modules/picocolors/README.md +0 -21
- package/skills/void/docs/node_modules/void/node_modules/proper-lockfile/CHANGELOG.md +0 -108
- package/skills/void/docs/node_modules/void/node_modules/proper-lockfile/README.md +0 -183
- package/skills/void/docs/node_modules/void/node_modules/tinyglobby/README.md +0 -25
- package/skills/void/docs/node_modules/void/node_modules/valibot/LICENSE.md +0 -9
- package/skills/void/docs/node_modules/void/node_modules/valibot/README.md +0 -94
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/AGENTS.md +0 -16
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/README.md +0 -220
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/build.md +0 -21
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/check.md +0 -35
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/create.md +0 -70
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/fmt.md +0 -20
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/index.md +0 -35
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/lint.md +0 -26
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/pack.md +0 -17
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/run.md +0 -364
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/staged.md +0 -15
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/test.md +0 -18
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/automatic-data-tracking.md +0 -145
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/build.md +0 -40
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/cache.md +0 -107
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/check.md +0 -60
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/ci.md +0 -62
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/commit-hooks.md +0 -60
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/create.md +0 -341
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/dev.md +0 -24
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/docker.md +0 -175
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/env.md +0 -167
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/fmt.md +0 -41
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/github-actions-cache.md +0 -165
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/ide-integration.md +0 -101
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/implode.md +0 -23
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/index.md +0 -134
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/install.md +0 -199
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/lint.md +0 -50
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/migrate-rules.md +0 -347
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/migrate.md +0 -197
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/monorepo.md +0 -176
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/pack.md +0 -69
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/run.md +0 -356
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/test.md +0 -35
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/troubleshooting.md +0 -108
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/upgrade.md +0 -101
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/vpx.md +0 -66
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/why.md +0 -39
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/index.md +0 -12
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/team.md +0 -35
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/templates/generator/README.md +0 -35
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/templates/monorepo/README.md +0 -29
- package/skills/void/docs/node_modules/void/node_modules/wrangler/README.md +0 -63
- package/skills/void/docs/node_modules/void/node_modules/zod/README.md +0 -191
- package/skills/void/docs/node_modules/void/skills/migrate-vite-cloudflare-to-void/SKILL.md +0 -175
- package/skills/void/docs/node_modules/void/skills/void/SKILL.md +0 -76
- package/skills/void/docs/node_modules/void/skills/void/command/void.md +0 -7
- package/skills/void/docs/node_modules/void/test/e2e/README.md +0 -85
|
@@ -0,0 +1,694 @@
|
|
|
1
|
+
import { i as normalizeEmail, n as MimeError, o as validateAddressParts, r as buildMime, t as MAX_MESSAGE_BYTES } from "./mime-BJD7d_qL.mjs";
|
|
2
|
+
import { a as getRawRuntimeEnv } from "./env-raw-Cx8ElDdj.mjs";
|
|
3
|
+
//#region src/runtime/email/inbound.ts
|
|
4
|
+
const VALID_SLUG = /^[a-z0-9](?:[a-z0-9-]{0,54}[a-z0-9])?$/;
|
|
5
|
+
/**
|
|
6
|
+
* Guardrails on what one handler may queue.
|
|
7
|
+
*
|
|
8
|
+
* These are NOT the security boundary. Recording happens inside the tenant's
|
|
9
|
+
* own worker, and on the platform a Workers-for-Platforms tenant can ship any
|
|
10
|
+
* `WorkerEntrypoint` whose `email()` returns any array at all, bypassing this
|
|
11
|
+
* file entirely — so the load-bearing caps are the matching ones in the
|
|
12
|
+
* platform router (`MAX_ACTIONS` / `MAX_ACTION_ADDRESS_LENGTH` in
|
|
13
|
+
* `email-router.ts`). What these buy is a fast, precise failure at the call
|
|
14
|
+
* that went wrong, instead of a whole action list silently dropped at the
|
|
15
|
+
* router with only a platform-side log to show for it.
|
|
16
|
+
*/
|
|
17
|
+
const MAX_ACTIONS = 100;
|
|
18
|
+
const MAX_ACTION_ADDRESS_LENGTH = 320;
|
|
19
|
+
const MAX_FORWARD_HEADER_LENGTH = 1024;
|
|
20
|
+
/**
|
|
21
|
+
* Cap on a reject reason, restated from the router's
|
|
22
|
+
* `MAX_REJECT_REASON_LENGTH` in `email-router.ts` for the same reason as
|
|
23
|
+
* above, and pinned by the same test. See `normalizeRejectReason`.
|
|
24
|
+
*/
|
|
25
|
+
const MAX_REJECT_REASON_LENGTH = 1e3;
|
|
26
|
+
/**
|
|
27
|
+
* The router's ADMISSION bounds — what it refuses BEFORE the handler runs
|
|
28
|
+
* (`MAX_INBOUND_BYTES` / `MAX_INBOUND_HEADERS` in `email-router.ts`,
|
|
29
|
+
* restated for the same reason as above and pinned by the same test).
|
|
30
|
+
* `routeInbound` rejects a message over either one and never dispatches it,
|
|
31
|
+
* so a handler's behaviour on such a message is unreachable after a managed
|
|
32
|
+
* deploy; the test harness applies both before it invokes the handler.
|
|
33
|
+
*
|
|
34
|
+
* `MAX_INBOUND_BYTES` is NOT `MAX_MESSAGE_BYTES`, though the numbers agree
|
|
35
|
+
* today. That one is Cloudflare's cap on an OUTBOUND message (and the
|
|
36
|
+
* `parseEmail` guard); this one is the router's own heap headroom on an
|
|
37
|
+
* inbound one, set well under Cloudflare's ~25 MB inbound ceiling, and the
|
|
38
|
+
* two can move independently.
|
|
39
|
+
*/
|
|
40
|
+
const MAX_INBOUND_BYTES = 10485760;
|
|
41
|
+
const MAX_INBOUND_HEADERS = 1024;
|
|
42
|
+
/**
|
|
43
|
+
* Cumulative reply-byte budget for ONE dispatch.
|
|
44
|
+
*
|
|
45
|
+
* `MAX_MESSAGE_BYTES` bounds a single `reply()` call, not the queue: N replies
|
|
46
|
+
* at the cap is N x 10 MiB of `Uint8Array` riding one RPC response. workerd
|
|
47
|
+
* caps a serialized RPC payload at 32 MiB, so the queue that clears our
|
|
48
|
+
* per-reply check still fails at the boundary — opaquely. The router cannot
|
|
49
|
+
* tell that failure from any other dispatch error, so the whole message comes
|
|
50
|
+
* back to the sender as a generic "project handler error" reject with nothing
|
|
51
|
+
* in it a handler author could act on. Two full-size replies is already well
|
|
52
|
+
* past any legitimate use, and failing here names the exact call that
|
|
53
|
+
* overran.
|
|
54
|
+
*/
|
|
55
|
+
const MAX_REPLY_BYTES_TOTAL = 2 * MAX_MESSAGE_BYTES;
|
|
56
|
+
function readRawProjectSlug() {
|
|
57
|
+
let raw;
|
|
58
|
+
try {
|
|
59
|
+
raw = getRawRuntimeEnv()?.__VOID_PROJECT_SLUG;
|
|
60
|
+
} catch {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
return typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
64
|
+
}
|
|
65
|
+
function tryReadProjectSlug() {
|
|
66
|
+
const raw = readRawProjectSlug();
|
|
67
|
+
return raw !== void 0 && VALID_SLUG.test(raw) ? raw : void 0;
|
|
68
|
+
}
|
|
69
|
+
function tryReadEmailDomain() {
|
|
70
|
+
let raw;
|
|
71
|
+
try {
|
|
72
|
+
raw = getRawRuntimeEnv()?.__VOID_EMAIL_DOMAIN;
|
|
73
|
+
} catch {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
return typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Identity helper that types the inbound email handler. Use as the default
|
|
80
|
+
* export of `email/<address>.ts` or `email/_default.ts`.
|
|
81
|
+
*/
|
|
82
|
+
function defineEmail(handler) {
|
|
83
|
+
return handler;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Typed error thrown by `parseEmail`. Lets handler authors distinguish
|
|
87
|
+
* "too big" from "malformed body" without string-matching error messages.
|
|
88
|
+
*/
|
|
89
|
+
var ParseEmailError = class extends Error {
|
|
90
|
+
code;
|
|
91
|
+
constructor(code, message, options) {
|
|
92
|
+
super(message, options);
|
|
93
|
+
this.name = "ParseEmailError";
|
|
94
|
+
this.code = code;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Parse a `ForwardableEmailMessage` into structured fields. postal-mime is
|
|
99
|
+
* loaded lazily so workers that never call this don't bundle it.
|
|
100
|
+
*
|
|
101
|
+
* For proxy messages (built via `createMessageProxy`) the raw bytes are
|
|
102
|
+
* cached on first read so subsequent `parseEmail`/`message.raw` accesses
|
|
103
|
+
* still work. For real CF `ForwardableEmailMessage`s the raw stream is
|
|
104
|
+
* one-shot.
|
|
105
|
+
*/
|
|
106
|
+
async function parseEmail(message) {
|
|
107
|
+
if (message.rawSize > 10485760) throw new ParseEmailError("INBOUND_TOO_LARGE", `parseEmail: message size ${message.rawSize} bytes exceeds maximum of ${MAX_MESSAGE_BYTES} bytes.`);
|
|
108
|
+
const { default: PostalMime } = await import("postal-mime");
|
|
109
|
+
const cached = proxyRawCache.get(message);
|
|
110
|
+
try {
|
|
111
|
+
const source = cached ? await cached.read() : message.raw;
|
|
112
|
+
return await PostalMime.parse(source);
|
|
113
|
+
} catch (cause) {
|
|
114
|
+
throw new ParseEmailError("PARSE_FAILED", `parseEmail: could not parse the message body. ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const proxyRawCache = /* @__PURE__ */ new WeakMap();
|
|
118
|
+
/**
|
|
119
|
+
* Remove the platform-injected `<slug>+` prefix from a recipient address.
|
|
120
|
+
* The platform's shared sender (e.g. `mail.void.cloud`) rewrites every recipient to
|
|
121
|
+
* `<slug>+<tag>@<domain>`; user handlers expect `<tag>@<domain>`. If the
|
|
122
|
+
* slug is unknown, missing, or doesn't match the local-part prefix, the
|
|
123
|
+
* address is returned unchanged so callers without slug context (e.g. the
|
|
124
|
+
* test harness when no slug is set) don't accidentally mangle the address.
|
|
125
|
+
*/
|
|
126
|
+
function stripSlugFromAddress(address, slug) {
|
|
127
|
+
if (!slug) return address;
|
|
128
|
+
const atIdx = address.indexOf("@");
|
|
129
|
+
if (atIdx <= 0) return address;
|
|
130
|
+
const localPart = address.slice(0, atIdx);
|
|
131
|
+
const domain = address.slice(atIdx);
|
|
132
|
+
const prefix = `${slug.toLowerCase()}+`;
|
|
133
|
+
if (localPart.toLowerCase().startsWith(prefix)) return localPart.slice(prefix.length) + domain;
|
|
134
|
+
return address;
|
|
135
|
+
}
|
|
136
|
+
function replyTooLarge(size) {
|
|
137
|
+
return new MimeError("MIME_ERROR", `reply: message size ${size} bytes exceeds Cloudflare limit of ${MAX_MESSAGE_BYTES} bytes.`);
|
|
138
|
+
}
|
|
139
|
+
function replyBudgetExhausted(size) {
|
|
140
|
+
return new MimeError("MIME_ERROR", `reply: cumulative reply size ${size} bytes exceeds this delivery's budget of ${MAX_REPLY_BYTES_TOTAL} bytes.`);
|
|
141
|
+
}
|
|
142
|
+
function queueFull() {
|
|
143
|
+
return new MimeError("MIME_ERROR", `email: handler queued more than ${MAX_ACTIONS} actions for one message.`);
|
|
144
|
+
}
|
|
145
|
+
function addressTooLong(field, length) {
|
|
146
|
+
return new MimeError(field === "from" ? "INVALID_FROM" : "INVALID_TO", `${field} address is ${length} characters, over the ${MAX_ACTION_ADDRESS_LENGTH}-character limit.`);
|
|
147
|
+
}
|
|
148
|
+
function invalidForwardHeaders(cause) {
|
|
149
|
+
const detail = cause instanceof Error ? cause.message.slice(0, 320) : "not a valid header set";
|
|
150
|
+
return new MimeError("MIME_ERROR", `forward: invalid header — ${detail}`);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* The router's `forwardHeadersWithinBounds`, as a throw. It runs on the
|
|
154
|
+
* record about to be recorded — the very object the router receives, so the
|
|
155
|
+
* count is the router's count: a `Headers` folded its duplicate names before
|
|
156
|
+
* it got here, and a record keeps `X-A` and `x-a` as two, both there and
|
|
157
|
+
* here. Runs after the `new Headers()` validity check, so every name is a
|
|
158
|
+
* token and every value is header-safe; what is left is size. The router
|
|
159
|
+
* drops the WHOLE list on any of these — a handler that passed here and
|
|
160
|
+
* failed there deployed as one whose managed delivery performed no actions.
|
|
161
|
+
*/
|
|
162
|
+
function assertForwardHeadersWithinBounds(headers) {
|
|
163
|
+
const names = Object.keys(headers);
|
|
164
|
+
if (names.length > 32) throw new MimeError("MIME_ERROR", `forward: ${names.length} headers, over the 32-header limit.`);
|
|
165
|
+
for (const name of names) {
|
|
166
|
+
if (name.length > 1024) throw new MimeError("MIME_ERROR", `forward: header name is ${name.length} characters, over the ${MAX_FORWARD_HEADER_LENGTH}-character limit.`);
|
|
167
|
+
const value = headers[name];
|
|
168
|
+
if (typeof value !== "string") throw new MimeError("MIME_ERROR", `forward: header "${name.slice(0, 64)}" value is not a string.`);
|
|
169
|
+
if (value.length > 1024) throw new MimeError("MIME_ERROR", `forward: header "${name.slice(0, 64)}" value is ${value.length} characters, over the ${MAX_FORWARD_HEADER_LENGTH}-character limit.`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* The router's rule for a reject reason (`validateActions` in
|
|
174
|
+
* `email-router.ts`: `sanitize(raw, MAX_REJECT_REASON_LENGTH)`, and
|
|
175
|
+
* `'rejected by handler'` for an empty one), applied when the reason is
|
|
176
|
+
* RECORDED so the native replay carries the string the managed one does.
|
|
177
|
+
*
|
|
178
|
+
* The reason goes into the SMTP reply to the connecting client, and a
|
|
179
|
+
* handler may build it from the inbound message: a remote sender's CRLF is
|
|
180
|
+
* then a line injected into that reply, and a multi-megabyte reason is a
|
|
181
|
+
* transient-heap amplifier. The router already refuses both, but on your
|
|
182
|
+
* own account `enactEmailActions` handed the recorded string to the native
|
|
183
|
+
* `setReject()` untouched, so the same handler injected there and was
|
|
184
|
+
* clean here. NORMALIZED rather than refused, unlike the forward bounds
|
|
185
|
+
* above: a reject is the handler's terminal decision, and throwing out of
|
|
186
|
+
* it would drop that decision and bounce the message as a dispatch error
|
|
187
|
+
* (see the `MAX_ACTIONS` note in `setReject`). Slice FIRST, then replace: a
|
|
188
|
+
* global replace materializes ~66 bytes per match, so the slice is what
|
|
189
|
+
* bounds the work (see `prefixReplySubject`). The result can be SHORTER
|
|
190
|
+
* than the cap: a run of controls inside the window collapses to one space
|
|
191
|
+
* and the window is not refilled from beyond it — the router's own trade,
|
|
192
|
+
* and it is DSN text.
|
|
193
|
+
*/
|
|
194
|
+
function normalizeRejectReason(reason) {
|
|
195
|
+
const raw = typeof reason === "string" ? reason : "";
|
|
196
|
+
if (raw.length === 0) return "rejected by handler";
|
|
197
|
+
return raw.slice(0, MAX_REJECT_REASON_LENGTH).replace(/[\x00-\x1f\x7f]+/g, " ");
|
|
198
|
+
}
|
|
199
|
+
async function readStreamToBytes(stream, limit = Infinity, onOverflow = replyTooLarge) {
|
|
200
|
+
const reader = stream.getReader();
|
|
201
|
+
const chunks = [];
|
|
202
|
+
let total = 0;
|
|
203
|
+
for (;;) {
|
|
204
|
+
const { value, done } = await reader.read();
|
|
205
|
+
if (done) break;
|
|
206
|
+
total += value.byteLength;
|
|
207
|
+
if (total > limit) {
|
|
208
|
+
await reader.cancel().catch(() => {});
|
|
209
|
+
throw onOverflow(total);
|
|
210
|
+
}
|
|
211
|
+
chunks.push(value);
|
|
212
|
+
}
|
|
213
|
+
const out = new Uint8Array(total);
|
|
214
|
+
let offset = 0;
|
|
215
|
+
for (const c of chunks) {
|
|
216
|
+
out.set(c, offset);
|
|
217
|
+
offset += c.byteLength;
|
|
218
|
+
}
|
|
219
|
+
return out;
|
|
220
|
+
}
|
|
221
|
+
function bytesToReadableStream(bytes) {
|
|
222
|
+
return new ReadableStream({ start(controller) {
|
|
223
|
+
controller.enqueue(bytes);
|
|
224
|
+
controller.close();
|
|
225
|
+
} });
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Build a proxy `ForwardableEmailMessage` from an envelope. The proxy's
|
|
229
|
+
* `setReject` / `forward` / `reply` methods don't dispatch directly — they
|
|
230
|
+
* record `EmailAction` entries that the router (which holds the real
|
|
231
|
+
* message) enacts after the handler returns.
|
|
232
|
+
*
|
|
233
|
+
* `reply()` accepts a duck-typed `{ from, to, raw }` with `raw` a
|
|
234
|
+
* `Uint8Array` or `ReadableStream` — what `replyEmail()` passes. It does NOT
|
|
235
|
+
* accept a native `EmailMessage` from `cloudflare:email`: that class exposes
|
|
236
|
+
* only `from` and `to` (workers-types declares nothing else, and the body
|
|
237
|
+
* has no public accessor), so its bytes cannot be recorded here, and nothing
|
|
238
|
+
* could carry them over RPC to the platform router. The reply MIME bytes are
|
|
239
|
+
* buffered before being recorded so the action is fully self-contained.
|
|
240
|
+
*
|
|
241
|
+
* When `opts.slug` is provided, `message.to` is rewritten to remove the
|
|
242
|
+
* platform-injected `<slug>+` prefix so handlers see `<tag>@<domain>`
|
|
243
|
+
* instead of `<slug>+<tag>@<domain>`. The original wire-form recipient is
|
|
244
|
+
* preserved on a non-enumerable `__voidPlatformTo` property.
|
|
245
|
+
*/
|
|
246
|
+
function createMessageProxy(envelope, opts = {}) {
|
|
247
|
+
const actions = [];
|
|
248
|
+
const headers = new Headers(envelope.headers);
|
|
249
|
+
const userVisibleTo = stripSlugFromAddress(envelope.to, opts.slug);
|
|
250
|
+
let cachedRaw;
|
|
251
|
+
let bufferingPromise;
|
|
252
|
+
function readRawOnce() {
|
|
253
|
+
if (cachedRaw) return Promise.resolve(cachedRaw);
|
|
254
|
+
if (bufferingPromise) return bufferingPromise;
|
|
255
|
+
const source = envelope.raw;
|
|
256
|
+
if (source instanceof Uint8Array) {
|
|
257
|
+
cachedRaw = source;
|
|
258
|
+
return Promise.resolve(cachedRaw);
|
|
259
|
+
}
|
|
260
|
+
bufferingPromise = readStreamToBytes(source).then((bytes) => {
|
|
261
|
+
cachedRaw = bytes;
|
|
262
|
+
return bytes;
|
|
263
|
+
});
|
|
264
|
+
return bufferingPromise;
|
|
265
|
+
}
|
|
266
|
+
if (envelope.raw instanceof Uint8Array) cachedRaw = envelope.raw;
|
|
267
|
+
let rejected = false;
|
|
268
|
+
let replyBytesUsed = 0;
|
|
269
|
+
function assertQueueHasRoom() {
|
|
270
|
+
if (actions.length >= MAX_ACTIONS) throw queueFull();
|
|
271
|
+
}
|
|
272
|
+
const message = {
|
|
273
|
+
from: envelope.from,
|
|
274
|
+
to: userVisibleTo,
|
|
275
|
+
headers,
|
|
276
|
+
get raw() {
|
|
277
|
+
if (cachedRaw) return bytesToReadableStream(cachedRaw);
|
|
278
|
+
const pending = readRawOnce();
|
|
279
|
+
return new ReadableStream({ async start(controller) {
|
|
280
|
+
const bytes = await pending;
|
|
281
|
+
controller.enqueue(bytes);
|
|
282
|
+
controller.close();
|
|
283
|
+
} });
|
|
284
|
+
},
|
|
285
|
+
rawSize: envelope.rawSize,
|
|
286
|
+
setReject(reason) {
|
|
287
|
+
if (rejected) return;
|
|
288
|
+
rejected = true;
|
|
289
|
+
let dropped = 0;
|
|
290
|
+
for (let i = actions.length - 1; i >= 0; i--) {
|
|
291
|
+
const a = actions[i];
|
|
292
|
+
if (a.type === "forward" || a.type === "reply") {
|
|
293
|
+
actions.splice(i, 1);
|
|
294
|
+
dropped++;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (dropped > 0) console.warn(`[void/email] setReject() after ${dropped} forward/reply call(s) — those actions were dropped. CF rejects are terminal: anything queued before setReject() never runs. Move the reject earlier in the handler.`);
|
|
298
|
+
actions.push({
|
|
299
|
+
type: "reject",
|
|
300
|
+
reason: normalizeRejectReason(reason)
|
|
301
|
+
});
|
|
302
|
+
},
|
|
303
|
+
async forward(to, fwdHeaders) {
|
|
304
|
+
if (rejected) {
|
|
305
|
+
console.warn("[void/email] forward() called after setReject() — ignored. CF rejects are terminal: nothing else runs once setReject fires.");
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
assertQueueHasRoom();
|
|
309
|
+
if (to.length > MAX_ACTION_ADDRESS_LENGTH) throw addressTooLong("to", to.length);
|
|
310
|
+
validateAddressParts("to", to);
|
|
311
|
+
let headersObj;
|
|
312
|
+
if (fwdHeaders instanceof Headers) headersObj = Object.fromEntries(fwdHeaders.entries());
|
|
313
|
+
else if (fwdHeaders) {
|
|
314
|
+
try {
|
|
315
|
+
new Headers(fwdHeaders);
|
|
316
|
+
} catch (e) {
|
|
317
|
+
throw invalidForwardHeaders(e);
|
|
318
|
+
}
|
|
319
|
+
headersObj = { ...fwdHeaders };
|
|
320
|
+
}
|
|
321
|
+
if (headersObj) assertForwardHeadersWithinBounds(headersObj);
|
|
322
|
+
actions.push({
|
|
323
|
+
type: "forward",
|
|
324
|
+
to,
|
|
325
|
+
...headersObj ? { headers: headersObj } : {}
|
|
326
|
+
});
|
|
327
|
+
},
|
|
328
|
+
async reply(reply) {
|
|
329
|
+
if (rejected) {
|
|
330
|
+
console.warn("[void/email] reply() called after setReject() — ignored. CF rejects are terminal: nothing else runs once setReject fires.");
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
assertQueueHasRoom();
|
|
334
|
+
if (reply.from.length > MAX_ACTION_ADDRESS_LENGTH) throw addressTooLong("from", reply.from.length);
|
|
335
|
+
if (reply.to.length > MAX_ACTION_ADDRESS_LENGTH) throw addressTooLong("to", reply.to.length);
|
|
336
|
+
validateAddressParts("from", reply.from);
|
|
337
|
+
validateAddressParts("to", reply.to);
|
|
338
|
+
const remaining = MAX_REPLY_BYTES_TOTAL - replyBytesUsed;
|
|
339
|
+
let rawBytes;
|
|
340
|
+
if (reply.raw instanceof Uint8Array) {
|
|
341
|
+
if (reply.raw.byteLength > 10485760) throw replyTooLarge(reply.raw.byteLength);
|
|
342
|
+
if (reply.raw.byteLength > remaining) throw replyBudgetExhausted(replyBytesUsed + reply.raw.byteLength);
|
|
343
|
+
rawBytes = reply.raw;
|
|
344
|
+
} else {
|
|
345
|
+
const raw = reply.raw;
|
|
346
|
+
if (typeof raw !== "object" || raw === null || typeof raw.getReader !== "function") throw new MimeError("MIME_ERROR", "reply: expected { from, to, raw } with raw as a Uint8Array or ReadableStream. A native EmailMessage cannot be replayed from a Void handler — its body has no public accessor. Use replyEmail() from void/email, or pass the MIME bytes as raw.");
|
|
347
|
+
const limit = Math.min(MAX_MESSAGE_BYTES, remaining);
|
|
348
|
+
rawBytes = await readStreamToBytes(reply.raw, limit, (total) => limit === 10485760 ? replyTooLarge(total) : replyBudgetExhausted(replyBytesUsed + total));
|
|
349
|
+
}
|
|
350
|
+
replyBytesUsed += rawBytes.byteLength;
|
|
351
|
+
actions.push({
|
|
352
|
+
type: "reply",
|
|
353
|
+
from: reply.from,
|
|
354
|
+
to: reply.to,
|
|
355
|
+
raw: rawBytes
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
if (envelope.to !== userVisibleTo) Object.defineProperty(message, "__voidPlatformTo", {
|
|
360
|
+
value: envelope.to,
|
|
361
|
+
enumerable: false,
|
|
362
|
+
writable: false,
|
|
363
|
+
configurable: false
|
|
364
|
+
});
|
|
365
|
+
proxyRawCache.set(message, { read: readRawOnce });
|
|
366
|
+
return {
|
|
367
|
+
message,
|
|
368
|
+
getActions: () => [...actions]
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Whether `Name: value` can be emitted without breaking RFC 5322 §2.1.1.
|
|
373
|
+
*
|
|
374
|
+
* Folding only happens at whitespace (§3.2.2), so the widest unbreakable run
|
|
375
|
+
* is what decides it — a `References` chain of ordinary ids folds fine however
|
|
376
|
+
* long, while one oversized id inside it does not.
|
|
377
|
+
*/
|
|
378
|
+
function fitsHeaderLine(name, value) {
|
|
379
|
+
if (!value) return false;
|
|
380
|
+
const budget = 998 - (name.length + 2);
|
|
381
|
+
let run = 0;
|
|
382
|
+
for (let i = 0; i < value.length; i++) {
|
|
383
|
+
const c = value.charCodeAt(i);
|
|
384
|
+
if (c === 32 || c === 9) {
|
|
385
|
+
run = 0;
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
if (c < 128) run += 1;
|
|
389
|
+
else if (c < 2048) run += 2;
|
|
390
|
+
else if (c >= 55296 && c <= 56319 && i + 1 < value.length && (value.charCodeAt(i + 1) & 64512) === 56320) {
|
|
391
|
+
run += 4;
|
|
392
|
+
i++;
|
|
393
|
+
} else run += 3;
|
|
394
|
+
if (run > budget) return false;
|
|
395
|
+
}
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Whether `value` carries a C0 control other than HTAB, or DEL. The
|
|
400
|
+
* sibling of `fitsHeaderLine` for the other way a remote-controlled id can
|
|
401
|
+
* be unrepresentable: a threading value is emitted verbatim, so a control
|
|
402
|
+
* inside it reached the reply's `In-Reply-To` / `References` line raw —
|
|
403
|
+
* `\x01` and DEL pass a WHATWG `Headers`, which refuses only CR, LF and
|
|
404
|
+
* NUL — and an RFC 5322 §3.6.4 `msg-id` is `dot-atom-text`, which admits no
|
|
405
|
+
* control at all. HTAB is not one here: it is the whitespace that separates
|
|
406
|
+
* ids (§3.2.2 FWS), the same break `fitsHeaderLine` and `replyReferences`
|
|
407
|
+
* cut at. One pass, no allocation, so it costs nothing a hostile header can
|
|
408
|
+
* multiply; never a throw, for the same reason as the line guard.
|
|
409
|
+
*/
|
|
410
|
+
function hasControl(value) {
|
|
411
|
+
for (let i = 0; i < value.length; i++) {
|
|
412
|
+
const c = value.charCodeAt(i);
|
|
413
|
+
if (c < 32 && c !== 9 || c === 127) return true;
|
|
414
|
+
}
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* `chain` without any id that `hasControl` refuses, the rest in their order
|
|
419
|
+
* and joined by single spaces; the chain itself when there is nothing to
|
|
420
|
+
* drop, without allocating. Only ONE id is lost — the others still thread —
|
|
421
|
+
* whereas an id no line can carry drops the whole chain for the parent's id
|
|
422
|
+
* (`replyReferences`), because that guard is about the rendered line and
|
|
423
|
+
* this one is about one token. Called only on a chain already cut to
|
|
424
|
+
* `MAX_REPLY_REFERENCES_CHARS`, so the per-id slices in the rare bad case
|
|
425
|
+
* are bounded by that budget, not by the header a remote sender delivered.
|
|
426
|
+
*/
|
|
427
|
+
function dropControlIds(chain) {
|
|
428
|
+
if (!hasControl(chain)) return chain;
|
|
429
|
+
const kept = [];
|
|
430
|
+
let start = -1;
|
|
431
|
+
let tainted = false;
|
|
432
|
+
for (let i = 0; i <= chain.length; i++) {
|
|
433
|
+
const c = i < chain.length ? chain.charCodeAt(i) : 32;
|
|
434
|
+
if (c === 32 || c === 9) {
|
|
435
|
+
if (start >= 0 && !tainted) kept.push(chain.slice(start, i));
|
|
436
|
+
start = -1;
|
|
437
|
+
tainted = false;
|
|
438
|
+
} else {
|
|
439
|
+
if (start < 0) start = i;
|
|
440
|
+
if (c < 32 || c === 127) tainted = true;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return kept.join(" ");
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Longest decoded inbound `Subject` the default reply subject is built from;
|
|
447
|
+
* anything longer falls back to a bare `Re:`.
|
|
448
|
+
*
|
|
449
|
+
* RFC 5322 §2.1.1 caps a header LINE at 998 octets, and only folding lets a
|
|
450
|
+
* header run past it — so a subject is something that fits on a line or
|
|
451
|
+
* two. Real ones are tens to hundreds of characters; the longest automated
|
|
452
|
+
* ones (a ticket title under a stack of list tags) reach a few hundred, and
|
|
453
|
+
* Exchange rejects past 255. Four thousand keeps every one of those with an
|
|
454
|
+
* order of magnitude to spare and refuses the shape that is only a subject
|
|
455
|
+
* in name. What the bound buys is proportional cost: the control-character
|
|
456
|
+
* replacements below cost 66 bytes of heap per match, so at this size they
|
|
457
|
+
* cost at most 135 KB, and the reply's own `Subject` line is at most ~20 KB
|
|
458
|
+
* — 4096 code units x 3 UTF-8 octets, base64'd in 45-octet encoded-words
|
|
459
|
+
* with their framing — against a `MAX_MESSAGE_BYTES` of 10 MiB, so the
|
|
460
|
+
* subject alone can never push the reply over the limit.
|
|
461
|
+
*/
|
|
462
|
+
const MAX_REPLY_SUBJECT_CHARS = 4096;
|
|
463
|
+
function prefixReplySubject(original) {
|
|
464
|
+
const decoded = decodeRfc2047(original);
|
|
465
|
+
if (decoded.length > MAX_REPLY_SUBJECT_CHARS) return "Re:";
|
|
466
|
+
const cleaned = decoded.replace(/[\x00-\x1f\x7f]+/g, " ").replace(/ {2,}/g, " ").trim();
|
|
467
|
+
if (!cleaned) return "Re:";
|
|
468
|
+
const prefixed = /^re:/i.test(cleaned) ? cleaned : `Re: ${cleaned}`;
|
|
469
|
+
if (/^[\x20-\x7e]*$/.test(prefixed) && !fitsHeaderLine("Subject", prefixed)) return "Re:";
|
|
470
|
+
return prefixed;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Longest `References` value a reply carries, in UTF-16 code units — the
|
|
474
|
+
* measure `MAX_REPLY_SUBJECT_CHARS` uses. It is checked on `length` alone,
|
|
475
|
+
* so a chain the size of the inbound message costs one property read before
|
|
476
|
+
* anything is scanned or sliced.
|
|
477
|
+
*
|
|
478
|
+
* RFC 5322 puts no limit on the field, but everything downstream does. RFC
|
|
479
|
+
* 5537 §3.4.4 has a followup agent trim it to 998 characters, and Postfix's
|
|
480
|
+
* `header_size_limit` discards header bytes past 100 KiB. A message-id is
|
|
481
|
+
* tens of characters, so 8 KiB carries a chain over a hundred replies deep
|
|
482
|
+
* with every id intact — an order of magnitude past any conversation — and
|
|
483
|
+
* a value this size, folded and encoded, is at most ~25 KB of a 10 MiB
|
|
484
|
+
* reply, so the chain alone can never push the reply over the limit.
|
|
485
|
+
* Unbounded, it could: the inbound header can be the whole 10 MiB message,
|
|
486
|
+
* and 10 MiB of short ids passes the line guard — every id folds — yet
|
|
487
|
+
* `foldHeader`'s continuations put the rendered reply over
|
|
488
|
+
* `MAX_MESSAGE_BYTES`, and `buildMime` threw (measured: 10,777,129 bytes
|
|
489
|
+
* from a 10,485,557-character chain) for a reply the guide promises is sent.
|
|
490
|
+
*
|
|
491
|
+
* Past the bound the NEWEST ids are kept, whole, and the parent's id still
|
|
492
|
+
* ends the chain. That is what a mail client threads on: it links the reply
|
|
493
|
+
* under any id it recognises, and the parent is also `In-Reply-To`. The
|
|
494
|
+
* root id RFC 5537 keeps is a Netnews convention (a newsreader identifies a
|
|
495
|
+
* thread by it); in mail it would spend the budget on an id older than
|
|
496
|
+
* every client in the conversation.
|
|
497
|
+
*/
|
|
498
|
+
const MAX_REPLY_REFERENCES_CHARS = 8192;
|
|
499
|
+
/**
|
|
500
|
+
* The `References` value a reply carries, or `''` when nothing can be
|
|
501
|
+
* threaded. In order: the whole inbound chain continued by the parent's
|
|
502
|
+
* `messageId` when it fits `MAX_REPLY_REFERENCES_CHARS`; otherwise the
|
|
503
|
+
* newest ids of the chain that fit, none of them split; and the parent's id
|
|
504
|
+
* alone (which may be empty) when even those hold an id no line can carry.
|
|
505
|
+
*/
|
|
506
|
+
function replyReferences(originalReferences, messageId) {
|
|
507
|
+
const parent = messageId ? ` ${messageId}` : "";
|
|
508
|
+
let chain = originalReferences;
|
|
509
|
+
const budget = MAX_REPLY_REFERENCES_CHARS - parent.length;
|
|
510
|
+
if (chain.length > budget) {
|
|
511
|
+
let cut = chain.length - budget;
|
|
512
|
+
while (cut < chain.length) {
|
|
513
|
+
const c = chain.charCodeAt(cut);
|
|
514
|
+
if (c === 32 || c === 9) break;
|
|
515
|
+
cut++;
|
|
516
|
+
}
|
|
517
|
+
chain = chain.slice(cut);
|
|
518
|
+
}
|
|
519
|
+
chain = dropControlIds(chain);
|
|
520
|
+
chain = `${chain}${parent}`.trim();
|
|
521
|
+
return fitsHeaderLine("References", chain) ? chain : messageId;
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Minimal RFC 2047 encoded-word decoder. Handles `=?charset?B?base64?=`
|
|
525
|
+
* and `=?charset?Q?quoted-printable?=` segments separated by optional
|
|
526
|
+
* whitespace. Unknown charsets pass through (best-effort UTF-8 decode);
|
|
527
|
+
* malformed inputs return the original.
|
|
528
|
+
*
|
|
529
|
+
* Scope is intentionally narrow — used only for cosmetic subject
|
|
530
|
+
* normalization in `prefixReplySubject`. Anything that needs robust
|
|
531
|
+
* decoding (e.g. searching subject text) should use postal-mime's
|
|
532
|
+
* parsed view via `parseEmail`.
|
|
533
|
+
*/
|
|
534
|
+
function decodeRfc2047(value) {
|
|
535
|
+
if (!value.includes("=?")) return value;
|
|
536
|
+
try {
|
|
537
|
+
const parts = [];
|
|
538
|
+
let last = 0;
|
|
539
|
+
for (const m of value.matchAll(/=\?([\w-]+)\?([BbQq])\?([^?]*)\?=(?:\s+(?==\?))?/g)) {
|
|
540
|
+
const [match, charset, enc, payload] = m;
|
|
541
|
+
const bytes = enc.toUpperCase() === "B" ? base64Decode(payload) : quotedPrintableDecode(payload);
|
|
542
|
+
let decoder;
|
|
543
|
+
try {
|
|
544
|
+
decoder = new TextDecoder(charset || "utf-8", { fatal: false });
|
|
545
|
+
} catch {
|
|
546
|
+
decoder = new TextDecoder("utf-8", { fatal: false });
|
|
547
|
+
}
|
|
548
|
+
parts.push(value.slice(last, m.index), decoder.decode(bytes));
|
|
549
|
+
last = m.index + match.length;
|
|
550
|
+
}
|
|
551
|
+
parts.push(value.slice(last));
|
|
552
|
+
return parts.join("");
|
|
553
|
+
} catch {
|
|
554
|
+
return value;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
function base64Decode(b64) {
|
|
558
|
+
const binary = atob(b64);
|
|
559
|
+
const out = new Uint8Array(binary.length);
|
|
560
|
+
for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
|
|
561
|
+
return out;
|
|
562
|
+
}
|
|
563
|
+
function quotedPrintableDecode(qp) {
|
|
564
|
+
const out = new Uint8Array(qp.length);
|
|
565
|
+
let kept = 0;
|
|
566
|
+
for (let i = 0; i < qp.length; i++) {
|
|
567
|
+
const ch = qp.charCodeAt(i);
|
|
568
|
+
if (ch === 95) out[kept++] = 32;
|
|
569
|
+
else if (ch === 61 && i + 2 < qp.length) {
|
|
570
|
+
const hex = qp.slice(i + 1, i + 3);
|
|
571
|
+
if (/^[0-9A-Fa-f]{2}$/.test(hex)) {
|
|
572
|
+
out[kept++] = parseInt(hex, 16);
|
|
573
|
+
i += 2;
|
|
574
|
+
} else out[kept++] = ch;
|
|
575
|
+
} else out[kept++] = ch;
|
|
576
|
+
}
|
|
577
|
+
return out.subarray(0, kept);
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Pure helper: build the reply MIME from an inbound message + reply opts,
|
|
581
|
+
* with `Subject: Re: …` (no double-prefix), `In-Reply-To`, and `References`
|
|
582
|
+
* threaded from the original. Threading is best-effort (the inbound values
|
|
583
|
+
* are remote-sender controlled): an id no header line can carry is dropped,
|
|
584
|
+
* and a `References` chain over `MAX_REPLY_REFERENCES_CHARS` is trimmed to
|
|
585
|
+
* its newest ids. Exposed separately so the dispatch step (`message.reply`)
|
|
586
|
+
* can be tested without `cloudflare:email`.
|
|
587
|
+
*/
|
|
588
|
+
async function buildReplyMessage(message, opts) {
|
|
589
|
+
const headers = message.headers;
|
|
590
|
+
const originalSubject = headers.get("Subject") ?? "";
|
|
591
|
+
const originalMessageId = headers.get("Message-ID") ?? "";
|
|
592
|
+
const originalReferences = headers.get("References") ?? "";
|
|
593
|
+
let fromAddr;
|
|
594
|
+
if (opts.from !== void 0) fromAddr = opts.from;
|
|
595
|
+
else {
|
|
596
|
+
const slug = tryReadProjectSlug();
|
|
597
|
+
if (slug) {
|
|
598
|
+
const domain = tryReadEmailDomain();
|
|
599
|
+
if (!domain) throw new Error("replyEmail: 'from' is required when __VOID_PROJECT_SLUG is set but __VOID_EMAIL_DOMAIN is not. Set `from` explicitly, or deploy via Void so the platform-injected __VOID_PROJECT_SLUG and __VOID_EMAIL_DOMAIN can default it to <slug>+noreply@<domain>.");
|
|
600
|
+
fromAddr = `${slug}+noreply@${domain}`;
|
|
601
|
+
} else if (readRawProjectSlug() !== void 0) throw new Error("replyEmail: 'from' is required — this project's slug cannot build a default reply sender (`<slug>+noreply@<domain>` must stay within the 64-octet local part, so the slug must be at most 56 characters). Set `from` explicitly.");
|
|
602
|
+
else fromAddr = message.to;
|
|
603
|
+
}
|
|
604
|
+
const toAddr = opts.to ?? message.from;
|
|
605
|
+
const subject = opts.subject ?? prefixReplySubject(originalSubject);
|
|
606
|
+
const threading = {};
|
|
607
|
+
const messageId = fitsHeaderLine("In-Reply-To", originalMessageId) && !hasControl(originalMessageId) ? originalMessageId : "";
|
|
608
|
+
if (messageId) threading.inReplyTo = messageId;
|
|
609
|
+
const references = replyReferences(originalReferences, messageId);
|
|
610
|
+
if (references) threading.references = references;
|
|
611
|
+
const normalized = await normalizeEmail({
|
|
612
|
+
from: fromAddr,
|
|
613
|
+
to: toAddr,
|
|
614
|
+
subject,
|
|
615
|
+
text: opts.text,
|
|
616
|
+
html: opts.html,
|
|
617
|
+
headers: opts.headers,
|
|
618
|
+
attachments: opts.attachments,
|
|
619
|
+
threading: threading.inReplyTo || threading.references ? threading : void 0
|
|
620
|
+
});
|
|
621
|
+
const { raw } = buildMime(normalized);
|
|
622
|
+
return {
|
|
623
|
+
from: normalized.from.email,
|
|
624
|
+
to: normalized.to[0].email,
|
|
625
|
+
raw,
|
|
626
|
+
subject
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Send a threading-aware reply to an inbound message. Builds the reply MIME
|
|
631
|
+
* (correct `In-Reply-To` / `References` / `Subject: Re: …`) and records a
|
|
632
|
+
* reply intent on the message proxy. The platform router enacts the reply
|
|
633
|
+
* via the real `ForwardableEmailMessage.reply()` once the handler returns.
|
|
634
|
+
*
|
|
635
|
+
* The threading headers are best-effort, since the inbound values they are
|
|
636
|
+
* built from are remote-sender controlled: an id no header line can carry
|
|
637
|
+
* is dropped, and a `References` chain over 8 KiB keeps only its newest
|
|
638
|
+
* ids. The reply is sent either way.
|
|
639
|
+
*/
|
|
640
|
+
async function replyEmail(message, opts) {
|
|
641
|
+
const { from, to, raw } = await buildReplyMessage(message, opts);
|
|
642
|
+
await message.reply({
|
|
643
|
+
from,
|
|
644
|
+
to,
|
|
645
|
+
raw
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Whether `value` is a real `ForwardableEmailMessage` — one Cloudflare
|
|
650
|
+
* delivered to this worker's `email()` directly — rather than an
|
|
651
|
+
* `EmailEnvelope` from the platform router. Functions do not survive RPC,
|
|
652
|
+
* so their presence is the discriminator.
|
|
653
|
+
*/
|
|
654
|
+
function isForwardableEmailMessage(value) {
|
|
655
|
+
if (typeof value !== "object" || value === null) return false;
|
|
656
|
+
const m = value;
|
|
657
|
+
return typeof m.setReject === "function" && typeof m.forward === "function" && typeof m.reply === "function";
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Replay recorded `EmailAction`s on a real `ForwardableEmailMessage`, in
|
|
661
|
+
* order. The proxy already made `setReject` terminal (earlier forward/reply
|
|
662
|
+
* entries are purged, later ones dropped), so a reject is never followed by
|
|
663
|
+
* anything. `reply` needs a genuine `EmailMessage` — Cloudflare rejects a
|
|
664
|
+
* duck-typed object — so `cloudflare:email` is loaded here, and only when
|
|
665
|
+
* a reply was recorded.
|
|
666
|
+
*
|
|
667
|
+
* The `new Headers()` below is the one step this loop constructs from
|
|
668
|
+
* handler input, and it cannot fail on a recorded action: the proxy's
|
|
669
|
+
* `forward()` already ran the record through that constructor, so a bad
|
|
670
|
+
* record failed inside the handler, before any replay. (`EmailMessage`
|
|
671
|
+
* construction neither validates nor reads its stream — measured under
|
|
672
|
+
* workerd.) A reject's reason reaches the native `setReject()` as the proxy
|
|
673
|
+
* recorded it — already sliced and stripped the way the platform router
|
|
674
|
+
* would (`normalizeRejectReason`), so the SMTP reply is the same in both
|
|
675
|
+
* deployments. What Cloudflare itself refuses at `forward()` / `reply()`
|
|
676
|
+
* still throws out of the loop, as any native call would.
|
|
677
|
+
*/
|
|
678
|
+
async function enactEmailActions(message, actions) {
|
|
679
|
+
for (const action of actions) switch (action.type) {
|
|
680
|
+
case "reject":
|
|
681
|
+
message.setReject(action.reason);
|
|
682
|
+
break;
|
|
683
|
+
case "forward":
|
|
684
|
+
await message.forward(action.to, action.headers ? new Headers(action.headers) : void 0);
|
|
685
|
+
break;
|
|
686
|
+
case "reply": {
|
|
687
|
+
const { EmailMessage } = await import("cloudflare:email");
|
|
688
|
+
await message.reply(new EmailMessage(action.from, action.to, bytesToReadableStream(action.raw)));
|
|
689
|
+
break;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
//#endregion
|
|
694
|
+
export { defineEmail as a, parseEmail as c, createMessageProxy as i, replyEmail as l, MAX_INBOUND_HEADERS as n, enactEmailActions as o, ParseEmailError as r, isForwardableEmailMessage as s, MAX_INBOUND_BYTES as t };
|