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.
Files changed (431) hide show
  1. package/AGENTS_TEMPLATE.md +6 -0
  2. package/{skills/void/docs/node_modules/void/node_modules/pglite-server/LICENSE.md → LICENSE} +1 -1
  3. package/README.md +34 -8
  4. package/dist/{auth-qgMlYp7Z.d.mts → auth-DkcFflXV.d.mts} +10 -11
  5. package/dist/auth-W9WII-mN.mjs +630 -0
  6. package/dist/{auth-cmd-BqsdZJp5.mjs → auth-cmd-CYVhSNFy.mjs} +4 -3
  7. package/dist/{auth-migrations-BTZ-ATvQ.mjs → auth-migrations-9vif1uj8.mjs} +53 -12
  8. package/dist/{better-auth-shared-BQooDxbw.mjs → better-auth-shared-CYw1T3k4.mjs} +7 -10
  9. package/dist/{better-auth-shared-DealXecJ.d.mts → better-auth-shared-DSCeohOK.d.mts} +10 -5
  10. package/dist/{build-cmd-Bujrv5q-.mjs → build-cmd-CkVD1uOh.mjs} +6 -4
  11. package/dist/{cache-C11V8Fxq.mjs → cache-QcUfR-ff.mjs} +9 -5
  12. package/dist/{cancel-deploy-fwFYF04b.mjs → cancel-deploy-DsvWKFTe.mjs} +3 -2
  13. package/dist/cf-access-AJ1ehiFR.mjs +42 -0
  14. package/dist/cf-access-DsSsZUPr.mjs +67 -0
  15. package/dist/cli/cli.d.mts +1 -1
  16. package/dist/cli/cli.mjs +1592 -153
  17. package/dist/cli/env-schema-probe.d.mts +48 -61
  18. package/dist/cli/env-schema-probe.mjs +10 -58
  19. package/dist/{client-Gb71-XkG.mjs → client-CyCHWSO_.mjs} +61 -463
  20. package/dist/cloudflare-auth-DRkGe7-s.mjs +170 -0
  21. package/dist/cloudflare-cmd-D3ME2GAe.mjs +62 -0
  22. package/dist/cloudflare-connect-BivMefMA.mjs +56 -0
  23. package/dist/cloudflare-operations-CPTpRW6d.mjs +566 -0
  24. package/dist/{config-BkTvs43g.mjs → config-BQFq7QvD.mjs} +85 -41
  25. package/dist/{config-CutEMNGJ.mjs → config-NOG_U1aK.mjs} +10 -15
  26. package/dist/connect-D9Yr-T5f.mjs +79 -0
  27. package/dist/{create-project-DsYvl3TB.mjs → create-project-DMV-csEm.mjs} +26 -16
  28. package/dist/database-provider.d.mts +21 -0
  29. package/dist/database-provider.mjs +6 -0
  30. package/dist/{db-DOiJMRt2.mjs → db-BxoUWL0F.mjs} +600 -115
  31. package/dist/{delete-mh6p-zkQ.mjs → delete-iJOqxBz1.mjs} +9 -6
  32. package/dist/{deploy-u7Rv9q_q.mjs → deploy-CcDoeBIf.mjs} +3451 -1986
  33. package/dist/dev-inbox-DkgRWLkW.mjs +307 -0
  34. package/dist/{discover-CJHyvYfR.mjs → discover-xvfrgJeo.mjs} +3 -3
  35. package/dist/{magic-string.es-ZQjdJFFn.mjs → dist-BR1quN_w.mjs} +570 -216
  36. package/dist/{dist-DaKKDf8D.mjs → dist-BrsS7cai.mjs} +17 -2
  37. package/dist/{dist-BuiRJkTd.mjs → dist-m40_XgNh.mjs} +48 -33
  38. package/dist/{domain-DiaNQbrl.mjs → domain-gu_iaHmn.mjs} +41 -4
  39. package/dist/dotenv-VQxupEUv.mjs +181 -0
  40. package/dist/edge.d.mts +2 -0
  41. package/dist/edge.mjs +2 -0
  42. package/dist/email-Ce6SQq-i.mjs +182 -0
  43. package/dist/email-r6DHJyAB.mjs +262 -0
  44. package/dist/{entry-D7yy4xVH.mjs → entry-DU3oDoQ3.mjs} +2 -2
  45. package/dist/env-D4Emu-M_.mjs +95 -0
  46. package/dist/env-DmU2To0C.mjs +73 -0
  47. package/dist/env-helpers-CyKtOBpj.d.mts +22 -0
  48. package/dist/env-public-D_6u46fX.d.mts +135 -0
  49. package/dist/{env-types-QBj-ndax.mjs → env-types-BNPhro-M.mjs} +2 -2
  50. package/dist/{env-validation-Dea3v3ej.mjs → env-validation-ENpMy6Ez.mjs} +68 -122
  51. package/dist/{gen-o_w-8yI8.mjs → gen-BKw6qHIg.mjs} +70 -15
  52. package/dist/{github-cmd-DKcGUNsj.mjs → github-cmd-DJS5Ab-H.mjs} +39 -31
  53. package/dist/{handler-Cjh8uM3Y.d.mts → handler-D1hLsObx.d.mts} +124 -122
  54. package/dist/{head-nmvOgFjd.d.mts → head-Do8P4puT.d.mts} +9 -8
  55. package/dist/{headers-nsHIFixA.mjs → headers-D8QfRX9Y.mjs} +12 -10
  56. package/dist/inbound-BJ70in1n.d.mts +354 -0
  57. package/dist/inbound-CNKxb3FY.mjs +694 -0
  58. package/dist/index.d.mts +61 -34
  59. package/dist/index.mjs +1048 -321
  60. package/dist/{init-3rgHKBVi.mjs → init-Dx5cgKqK.mjs} +329 -271
  61. package/dist/link-D_rm5sRH.mjs +52 -0
  62. package/dist/{list-CPwFDZ_c.mjs → list-M8XShti7.mjs} +37 -5
  63. package/dist/{runner-kapo9aPs.mjs → local-d1-BE8KBbMy.mjs} +116 -6
  64. package/dist/{login-BT3H8PN3.mjs → login-C8-UuLnp.mjs} +17 -15
  65. package/dist/{logs-Bt313ax7.mjs → logs-DwFU7dMW.mjs} +16 -2
  66. package/dist/mime-BJD7d_qL.mjs +1216 -0
  67. package/dist/neon-DHwd2zvC.mjs +54 -0
  68. package/dist/{node-Da0UcsGA.mjs → node-BkaXcpAc.mjs} +29 -8
  69. package/dist/operator-cmd-DYWRbWUA.mjs +348 -0
  70. package/dist/{agents-CtgBYqld.mjs → output-tFQLLj26.mjs} +676 -211
  71. package/dist/{package-json-Cx1osYo6.mjs → package-json-CPoWX79C.mjs} +1 -1
  72. package/dist/pages/client.d.mts +43 -41
  73. package/dist/pages/client.mjs +40 -33
  74. package/dist/pages/head-client.d.mts +10 -12
  75. package/dist/pages/head.d.mts +1 -1
  76. package/dist/pages/index.d.mts +37 -14
  77. package/dist/pages/index.mjs +5 -5
  78. package/dist/pages/islands-plugin.d.mts +25 -27
  79. package/dist/pages/islands-plugin.mjs +6 -4
  80. package/dist/pages/prefetch.d.mts +6 -7
  81. package/dist/pages/protocol.d.mts +2 -2
  82. package/dist/pages/protocol.mjs +2 -1
  83. package/dist/pages/serialize.d.mts +7 -8
  84. package/dist/platform-cmd-BEOVv1PN.mjs +123 -0
  85. package/dist/platform-domain-BszUfiS8.mjs +228 -0
  86. package/dist/platform-lifecycle-CjSr6xf0.mjs +4450 -0
  87. package/dist/platform-management-W30iCaTL.mjs +698 -0
  88. package/dist/platform-recovery-pHHv4ZSg.mjs +99 -0
  89. package/dist/{plugin-inference-DMeavIJ6.mjs → plugin-inference-BDRfZngg.mjs} +35 -19
  90. package/dist/prepare-BfJvFUtJ.mjs +14 -0
  91. package/dist/{prepare-BoKHgMNx.mjs → prepare-CBetXvsN.mjs} +15 -24
  92. package/dist/{preset-BjyR3lzz.mjs → preset-lAy0B0BQ.mjs} +25 -212
  93. package/dist/{project-cmd-D_w-4w5B.mjs → project-cmd-DCpk1cDt.mjs} +25 -12
  94. package/dist/{project-paths-BQd7OmIo.mjs → project-paths-SK8nMHPp.mjs} +3 -1
  95. package/dist/{project-tsconfig-B-QtXjLQ.mjs → project-tsconfig-Ql2XsSQp.mjs} +2 -2
  96. package/dist/{protocol-Bnb0LFp3.d.mts → protocol-C-pqYJjE.d.mts} +3 -4
  97. package/dist/{provision-rShh6MKY.mjs → provision-Blnstcm2.mjs} +78 -45
  98. package/dist/r2-conditions-D1Wk8i7b.mjs +30 -0
  99. package/dist/{requests-B8sZxaFM.mjs → requests-Dn8Vheh1.mjs} +5 -3
  100. package/dist/{resolve-project-BBMtLLV9.mjs → resolve-project--Vxawf7z.mjs} +2 -2
  101. package/dist/rollback-CtlPXEBi.mjs +166 -0
  102. package/dist/{rolldown-runtime-DJK8HYOj.mjs → rolldown-runtime-rQ84J-ij.mjs} +1 -1
  103. package/dist/{route-types-COI2DsZv.mjs → route-types-Da-DpyUp.mjs} +85 -26
  104. package/dist/routes-stub.d.mts +22 -23
  105. package/dist/runner-mysql-CgRFl3s6.mjs +61 -0
  106. package/dist/{runner-pg-CHM76xuC.mjs → runner-pg-DCkWPsWS.mjs} +18 -6
  107. package/dist/runtime/ai.d.mts +21 -14
  108. package/dist/runtime/ai.mjs +5 -4
  109. package/dist/runtime/auth-client-react.d.mts +3 -5
  110. package/dist/runtime/auth-client-solid.d.mts +3 -5
  111. package/dist/runtime/auth-client-svelte.d.mts +3 -5
  112. package/dist/runtime/auth-client-vue.d.mts +3 -5
  113. package/dist/runtime/auth-client.d.mts +3 -5
  114. package/dist/runtime/auth.d.mts +1 -1
  115. package/dist/runtime/better-auth-mysql.d.mts +10 -0
  116. package/dist/runtime/better-auth-mysql.mjs +49 -0
  117. package/dist/runtime/better-auth-pg.d.mts +8 -9
  118. package/dist/runtime/better-auth-pg.mjs +2 -2
  119. package/dist/runtime/better-auth.d.mts +8 -9
  120. package/dist/runtime/better-auth.mjs +2 -2
  121. package/dist/runtime/client-react.d.mts +1 -1
  122. package/dist/runtime/client-solid.d.mts +1 -1
  123. package/dist/runtime/client-svelte.d.mts +1 -1
  124. package/dist/runtime/client-vue.d.mts +1 -1
  125. package/dist/runtime/client.d.mts +1 -1
  126. package/dist/runtime/db-mysql.d.mts +2 -0
  127. package/dist/runtime/db-mysql.mjs +1 -0
  128. package/dist/runtime/db.d.mts +10 -11
  129. package/dist/runtime/durable.d.mts +47 -0
  130. package/dist/runtime/durable.mjs +146 -0
  131. package/dist/runtime/email/testing.d.mts +112 -0
  132. package/dist/runtime/email/testing.mjs +283 -0
  133. package/dist/runtime/email.d.mts +30 -0
  134. package/dist/runtime/email.mjs +573 -0
  135. package/dist/runtime/env-helpers.d.mts +2 -2
  136. package/dist/runtime/env-helpers.mjs +5 -42
  137. package/dist/runtime/env-public-client.d.mts +10 -11
  138. package/dist/runtime/env-public-client.mjs +1 -1
  139. package/dist/runtime/env-public.d.mts +2 -2
  140. package/dist/runtime/env-public.mjs +104 -49
  141. package/dist/runtime/env.d.mts +19 -18
  142. package/dist/runtime/env.mjs +15 -2
  143. package/dist/runtime/fetch-stream.d.mts +20 -21
  144. package/dist/runtime/fetch.d.mts +15 -16
  145. package/dist/runtime/handler.d.mts +1 -1
  146. package/dist/runtime/isr.d.mts +21 -22
  147. package/dist/runtime/isr.mjs +26 -8
  148. package/dist/runtime/kv.d.mts +9 -10
  149. package/dist/runtime/live-client.d.mts +5 -7
  150. package/dist/runtime/live-client.mjs +9 -7
  151. package/dist/runtime/live-server.d.mts +4 -5
  152. package/dist/runtime/live.d.mts +22 -24
  153. package/dist/runtime/live.mjs +1 -1
  154. package/dist/runtime/log.d.mts +16 -17
  155. package/dist/runtime/migration-handler-mysql.d.mts +4 -0
  156. package/dist/runtime/migration-handler-mysql.mjs +81 -0
  157. package/dist/runtime/migration-handler-pg.d.mts +2 -4
  158. package/dist/runtime/migration-handler.d.mts +5 -6
  159. package/dist/runtime/migration-handler.mjs +4 -3
  160. package/dist/runtime/queues.d.mts +3 -4
  161. package/dist/runtime/queues.mjs +2 -1
  162. package/dist/runtime/remote/binding-handler.d.mts +10 -12
  163. package/dist/runtime/remote/binding-handler.mjs +24 -3
  164. package/dist/runtime/remote/index.d.mts +5 -6
  165. package/dist/runtime/remote/index.mjs +21 -18
  166. package/dist/runtime/response.d.mts +10 -11
  167. package/dist/runtime/sandbox.d.mts +56 -55
  168. package/dist/runtime/sandbox.mjs +57 -49
  169. package/dist/runtime/schema-mysql.d.mts +1 -0
  170. package/dist/runtime/schema-mysql.mjs +2 -0
  171. package/dist/runtime/seed.d.mts +14 -9
  172. package/dist/runtime/sse-client.d.mts +6 -7
  173. package/dist/runtime/sse.d.mts +11 -12
  174. package/dist/runtime/storage.d.mts +3 -4
  175. package/dist/runtime/validator.d.mts +1 -1
  176. package/dist/runtime/ws-server.d.mts +12 -12
  177. package/dist/runtime/ws-server.mjs +32 -4
  178. package/dist/runtime/ws.d.mts +19 -21
  179. package/dist/{scan-DYXkrasO.mjs → scan-BMH4rzlv.mjs} +53 -31
  180. package/dist/{scan-DEwlM_Xy.mjs → scan-CpK-57ug.mjs} +9 -5
  181. package/dist/{secret-Dt32J6RI.mjs → secret-BqTxGqki.mjs} +62 -5
  182. package/dist/{skills-CLjN0uUO.mjs → skills-Q46GZMO-.mjs} +6 -4
  183. package/dist/{standard-schema-DJ0HW7QP.d.mts → standard-schema-Fo_vCAZh.d.mts} +6 -6
  184. package/dist/{subcommand-prompt-BzV8iQZo.mjs → subcommand-prompt-WfySCQ7S.mjs} +67 -48
  185. package/dist/sveltekit.d.mts +12 -11
  186. package/dist/sveltekit.mjs +1 -1
  187. package/dist/types-BAp5AEBU.d.mts +79 -0
  188. package/dist/types-CKWnYgfy.d.mts +1 -0
  189. package/dist/{validate-Cw_RLeTj.mjs → validate-Bihr8WBi.mjs} +3 -3
  190. package/dist/wrangler--imS8n0d.mjs +1796 -0
  191. package/dist/{yarn-pnp-DJn3SAHF.mjs → yarn-pnp-DxSInkzL.mjs} +1 -1
  192. package/package.json +79 -65
  193. package/schema.json +35 -3
  194. package/skills/migrate-vite-cloudflare-to-void/SKILL.md +1 -1
  195. package/skills/void/SKILL.md +59 -2
  196. package/skills/void/docs/guide/ai.md +32 -14
  197. package/skills/void/docs/guide/app-types.md +6 -6
  198. package/skills/void/docs/guide/auth.md +14 -16
  199. package/skills/void/docs/guide/database/d1.md +6 -0
  200. package/skills/void/docs/guide/database/mysql.md +60 -0
  201. package/skills/void/docs/guide/database/postgresql.md +14 -9
  202. package/skills/void/docs/guide/database.md +39 -26
  203. package/skills/void/docs/guide/deployment.md +163 -22
  204. package/skills/void/docs/guide/durable-state.md +140 -0
  205. package/skills/void/docs/guide/edge/headers.md +5 -5
  206. package/skills/void/docs/guide/edge/prerendering.md +2 -0
  207. package/skills/void/docs/guide/edge/revalidation.md +21 -6
  208. package/skills/void/docs/guide/edge/rewrites.md +18 -14
  209. package/skills/void/docs/guide/edge/static-assets.md +23 -8
  210. package/skills/void/docs/guide/email.md +609 -0
  211. package/skills/void/docs/guide/env-migration.md +109 -0
  212. package/skills/void/docs/guide/env-vars.md +70 -248
  213. package/skills/void/docs/guide/index.md +15 -17
  214. package/skills/void/docs/guide/jobs.md +8 -5
  215. package/skills/void/docs/guide/live.md +7 -15
  216. package/skills/void/docs/guide/pages-routing/actions-and-forms.md +8 -4
  217. package/skills/void/docs/guide/pages-routing/islands.md +3 -3
  218. package/skills/void/docs/guide/pages-routing/loaders.md +6 -4
  219. package/skills/void/docs/guide/pages-routing/overview.md +7 -7
  220. package/skills/void/docs/guide/platform-administration.md +212 -0
  221. package/skills/void/docs/guide/platform-development.md +235 -0
  222. package/skills/void/docs/guide/queues.md +10 -10
  223. package/skills/void/docs/guide/quickstart.md +46 -67
  224. package/skills/void/docs/guide/remote-dev.md +8 -6
  225. package/skills/void/docs/guide/sandboxes.md +33 -16
  226. package/skills/void/docs/guide/self-hosted-platform.md +553 -0
  227. package/skills/void/docs/guide/server-routing.md +25 -5
  228. package/skills/void/docs/guide/sse.md +4 -4
  229. package/skills/void/docs/guide/ssg.md +5 -3
  230. package/skills/void/docs/guide/storage.md +2 -2
  231. package/skills/void/docs/guide/websockets.md +18 -7
  232. package/skills/void/docs/index.md +3 -3
  233. package/skills/void/docs/integrations/agents.md +6 -64
  234. package/skills/void/docs/integrations/cloudflare.md +165 -146
  235. package/skills/void/docs/integrations/frameworks/analog.md +4 -4
  236. package/skills/void/docs/integrations/frameworks/astro.md +5 -5
  237. package/skills/void/docs/integrations/frameworks/nuxt.md +5 -5
  238. package/skills/void/docs/integrations/frameworks/overview.md +3 -3
  239. package/skills/void/docs/integrations/frameworks/react-router.md +3 -3
  240. package/skills/void/docs/integrations/frameworks/sveltekit.md +3 -3
  241. package/skills/void/docs/integrations/frameworks/tanstack-start.md +2 -2
  242. package/skills/void/docs/integrations/nodejs-bun-deno.md +11 -4
  243. package/skills/void/docs/reference/api.md +42 -6
  244. package/skills/void/docs/reference/cli.md +637 -159
  245. package/skills/void/docs/reference/config.md +80 -25
  246. package/skills/void/docs/reference/resource-inference.md +13 -9
  247. package/skills/void/docs/reference/structure.md +10 -11
  248. package/AGENT_PROMPT.md +0 -19
  249. package/dist/cf-access-Bqw81xAf.mjs +0 -22
  250. package/dist/env-CZy5MorI.mjs +0 -299
  251. package/dist/env-helpers-z4stu8uc.d.mts +0 -52
  252. package/dist/env-mask-Dd47NbR6.mjs +0 -90
  253. package/dist/env-public-BfiLcMBk.d.mts +0 -140
  254. package/dist/link-CdGHSIy-.mjs +0 -45
  255. package/dist/mcp-DoM3_nhd.mjs +0 -377
  256. package/dist/project-paths-GpziKeQQ.d.mts +0 -25
  257. package/dist/providers-BNKRacMr.d.mts +0 -7
  258. package/dist/proxy-D-3_D-Gl.mjs +0 -5
  259. package/dist/rollback-CkvTFXx5.mjs +0 -90
  260. package/dist/runtime/isr-cache.d.mts +0 -207
  261. package/dist/runtime/isr-cache.mjs +0 -523
  262. package/dist/types-lLjNE9Qp.d.mts +0 -51
  263. package/getting-started-prompt.txt +0 -28
  264. package/skills/void/command/void.md +0 -7
  265. package/skills/void/docs/integrations/auth-providers.md +0 -0
  266. package/skills/void/docs/integrations/payment-processors.md +0 -0
  267. package/skills/void/docs/node_modules/@iconify/vue/README.md +0 -408
  268. package/skills/void/docs/node_modules/@iconify/vue/offline/readme.md +0 -5
  269. package/skills/void/docs/node_modules/@voidzero-dev/vitepress-theme/README.md +0 -103
  270. package/skills/void/docs/node_modules/oxc-minify/README.md +0 -78
  271. package/skills/void/docs/node_modules/reka-ui/README.md +0 -80
  272. package/skills/void/docs/node_modules/vitepress/README.md +0 -28
  273. package/skills/void/docs/node_modules/vitepress/template/api-examples.md +0 -49
  274. package/skills/void/docs/node_modules/vitepress/template/index.md +0 -28
  275. package/skills/void/docs/node_modules/vitepress/template/markdown-examples.md +0 -85
  276. package/skills/void/docs/node_modules/vitepress-plugin-group-icons/README.md +0 -101
  277. package/skills/void/docs/node_modules/void/AGENT_PROMPT.md +0 -19
  278. package/skills/void/docs/node_modules/void/CLAUDE.md +0 -219
  279. package/skills/void/docs/node_modules/void/README.md +0 -90
  280. package/skills/void/docs/node_modules/void/node_modules/@clack/prompts/CHANGELOG.md +0 -685
  281. package/skills/void/docs/node_modules/void/node_modules/@clack/prompts/README.md +0 -396
  282. package/skills/void/docs/node_modules/void/node_modules/@cloudflare/sandbox/README.md +0 -219
  283. package/skills/void/docs/node_modules/void/node_modules/@cloudflare/vite-plugin/README.md +0 -37
  284. package/skills/void/docs/node_modules/void/node_modules/@cloudflare/workers-types/README.md +0 -135
  285. package/skills/void/docs/node_modules/void/node_modules/@electric-sql/pglite/README.md +0 -189
  286. package/skills/void/docs/node_modules/void/node_modules/@hono/oauth-providers/CHANGELOG.md +0 -143
  287. package/skills/void/docs/node_modules/void/node_modules/@hono/oauth-providers/README.md +0 -1272
  288. package/skills/void/docs/node_modules/void/node_modules/@napi-rs/keyring/README.md +0 -19
  289. package/skills/void/docs/node_modules/void/node_modules/@types/better-sqlite3/README.md +0 -15
  290. package/skills/void/docs/node_modules/void/node_modules/@types/node/README.md +0 -15
  291. package/skills/void/docs/node_modules/void/node_modules/@types/pg/README.md +0 -15
  292. package/skills/void/docs/node_modules/void/node_modules/@types/proper-lockfile/README.md +0 -51
  293. package/skills/void/docs/node_modules/void/node_modules/@typescript/native-preview/README.md +0 -22
  294. package/skills/void/docs/node_modules/void/node_modules/@typescript/native-preview/vendor/vscode-jsonrpc/README.md +0 -69
  295. package/skills/void/docs/node_modules/void/node_modules/@void/md/README.md +0 -153
  296. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/@shikijs/engine-javascript/README.md +0 -9
  297. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/@shikijs/transformers/README.md +0 -9
  298. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/@types/node/README.md +0 -15
  299. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/gray-matter/CHANGELOG.md +0 -24
  300. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/gray-matter/README.md +0 -565
  301. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/markdown-exit/README.md +0 -127
  302. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/markdown-it-anchor/README.md +0 -600
  303. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/markdown-it-attrs/README.md +0 -386
  304. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/markdown-it-container/README.md +0 -95
  305. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/markdown-it-emoji/README.md +0 -101
  306. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/markdown-it-footnote/README.md +0 -135
  307. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/pathslash/README.md +0 -64
  308. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/shiki/README.md +0 -15
  309. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/tinyglobby/README.md +0 -25
  310. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/AGENTS.md +0 -16
  311. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/README.md +0 -220
  312. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/build.md +0 -21
  313. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/check.md +0 -35
  314. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/create.md +0 -70
  315. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/fmt.md +0 -20
  316. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/index.md +0 -35
  317. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/lint.md +0 -26
  318. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/pack.md +0 -17
  319. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/run.md +0 -364
  320. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/staged.md +0 -15
  321. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/test.md +0 -18
  322. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/automatic-data-tracking.md +0 -145
  323. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/build.md +0 -40
  324. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/cache.md +0 -107
  325. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/check.md +0 -60
  326. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/ci.md +0 -62
  327. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/commit-hooks.md +0 -60
  328. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/create.md +0 -341
  329. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/dev.md +0 -24
  330. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/docker.md +0 -175
  331. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/env.md +0 -167
  332. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/fmt.md +0 -41
  333. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/github-actions-cache.md +0 -165
  334. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/ide-integration.md +0 -101
  335. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/implode.md +0 -23
  336. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/index.md +0 -134
  337. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/install.md +0 -199
  338. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/lint.md +0 -50
  339. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/migrate-rules.md +0 -347
  340. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/migrate.md +0 -197
  341. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/monorepo.md +0 -176
  342. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/pack.md +0 -69
  343. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/run.md +0 -356
  344. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/test.md +0 -35
  345. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/troubleshooting.md +0 -108
  346. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/upgrade.md +0 -101
  347. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/vpx.md +0 -66
  348. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/why.md +0 -39
  349. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/index.md +0 -12
  350. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/team.md +0 -35
  351. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/templates/generator/README.md +0 -35
  352. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/templates/monorepo/README.md +0 -29
  353. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vue/README.md +0 -58
  354. package/skills/void/docs/node_modules/void/node_modules/arktype/README.md +0 -165
  355. package/skills/void/docs/node_modules/void/node_modules/better-auth/LICENSE.md +0 -20
  356. package/skills/void/docs/node_modules/void/node_modules/better-auth/README.md +0 -32
  357. package/skills/void/docs/node_modules/void/node_modules/better-sqlite3/README.md +0 -99
  358. package/skills/void/docs/node_modules/void/node_modules/blake3-jit/README.md +0 -108
  359. package/skills/void/docs/node_modules/void/node_modules/drizzle-arktype/README.md +0 -51
  360. package/skills/void/docs/node_modules/void/node_modules/drizzle-kit/README.md +0 -79
  361. package/skills/void/docs/node_modules/void/node_modules/drizzle-orm/README.md +0 -44
  362. package/skills/void/docs/node_modules/void/node_modules/drizzle-valibot/README.md +0 -51
  363. package/skills/void/docs/node_modules/void/node_modules/drizzle-zod/README.md +0 -65
  364. package/skills/void/docs/node_modules/void/node_modules/es-module-lexer/README.md +0 -403
  365. package/skills/void/docs/node_modules/void/node_modules/estree-walker/README.md +0 -48
  366. package/skills/void/docs/node_modules/void/node_modules/hono/README.md +0 -85
  367. package/skills/void/docs/node_modules/void/node_modules/ignore/README.md +0 -452
  368. package/skills/void/docs/node_modules/void/node_modules/jsonc-parser/CHANGELOG.md +0 -76
  369. package/skills/void/docs/node_modules/void/node_modules/jsonc-parser/LICENSE.md +0 -21
  370. package/skills/void/docs/node_modules/void/node_modules/jsonc-parser/README.md +0 -364
  371. package/skills/void/docs/node_modules/void/node_modules/jsonc-parser/SECURITY.md +0 -41
  372. package/skills/void/docs/node_modules/void/node_modules/magic-string/README.md +0 -325
  373. package/skills/void/docs/node_modules/void/node_modules/ofetch/README.md +0 -398
  374. package/skills/void/docs/node_modules/void/node_modules/pathslash/README.md +0 -64
  375. package/skills/void/docs/node_modules/void/node_modules/pg/README.md +0 -96
  376. package/skills/void/docs/node_modules/void/node_modules/pglite-server/README.md +0 -135
  377. package/skills/void/docs/node_modules/void/node_modules/picocolors/README.md +0 -21
  378. package/skills/void/docs/node_modules/void/node_modules/proper-lockfile/CHANGELOG.md +0 -108
  379. package/skills/void/docs/node_modules/void/node_modules/proper-lockfile/README.md +0 -183
  380. package/skills/void/docs/node_modules/void/node_modules/tinyglobby/README.md +0 -25
  381. package/skills/void/docs/node_modules/void/node_modules/valibot/LICENSE.md +0 -9
  382. package/skills/void/docs/node_modules/void/node_modules/valibot/README.md +0 -94
  383. package/skills/void/docs/node_modules/void/node_modules/vite-plus/AGENTS.md +0 -16
  384. package/skills/void/docs/node_modules/void/node_modules/vite-plus/README.md +0 -220
  385. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/build.md +0 -21
  386. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/check.md +0 -35
  387. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/create.md +0 -70
  388. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/fmt.md +0 -20
  389. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/index.md +0 -35
  390. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/lint.md +0 -26
  391. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/pack.md +0 -17
  392. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/run.md +0 -364
  393. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/staged.md +0 -15
  394. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/test.md +0 -18
  395. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/automatic-data-tracking.md +0 -145
  396. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/build.md +0 -40
  397. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/cache.md +0 -107
  398. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/check.md +0 -60
  399. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/ci.md +0 -62
  400. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/commit-hooks.md +0 -60
  401. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/create.md +0 -341
  402. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/dev.md +0 -24
  403. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/docker.md +0 -175
  404. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/env.md +0 -167
  405. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/fmt.md +0 -41
  406. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/github-actions-cache.md +0 -165
  407. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/ide-integration.md +0 -101
  408. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/implode.md +0 -23
  409. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/index.md +0 -134
  410. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/install.md +0 -199
  411. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/lint.md +0 -50
  412. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/migrate-rules.md +0 -347
  413. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/migrate.md +0 -197
  414. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/monorepo.md +0 -176
  415. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/pack.md +0 -69
  416. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/run.md +0 -356
  417. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/test.md +0 -35
  418. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/troubleshooting.md +0 -108
  419. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/upgrade.md +0 -101
  420. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/vpx.md +0 -66
  421. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/why.md +0 -39
  422. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/index.md +0 -12
  423. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/team.md +0 -35
  424. package/skills/void/docs/node_modules/void/node_modules/vite-plus/templates/generator/README.md +0 -35
  425. package/skills/void/docs/node_modules/void/node_modules/vite-plus/templates/monorepo/README.md +0 -29
  426. package/skills/void/docs/node_modules/void/node_modules/wrangler/README.md +0 -63
  427. package/skills/void/docs/node_modules/void/node_modules/zod/README.md +0 -191
  428. package/skills/void/docs/node_modules/void/skills/migrate-vite-cloudflare-to-void/SKILL.md +0 -175
  429. package/skills/void/docs/node_modules/void/skills/void/SKILL.md +0 -76
  430. package/skills/void/docs/node_modules/void/skills/void/command/void.md +0 -7
  431. package/skills/void/docs/node_modules/void/test/e2e/README.md +0 -85
@@ -0,0 +1,1216 @@
1
+ import { validate } from "email-validator";
2
+ //#region src/runtime/email/mime.ts
3
+ /**
4
+ * Minimal RFC 5322 / 2047 / 2045 MIME builder for the void/email runtime.
5
+ *
6
+ * Scope is intentionally narrow — text/plain, text/html, multipart/alternative,
7
+ * and multipart/mixed for attachments — covering >95% of transactional sends.
8
+ * Inline images use multipart/related when both attachments and html are present.
9
+ */
10
+ /** Cloudflare Email Workers reject messages larger than ~10 MB after base64 expansion. */
11
+ const MAX_MESSAGE_BYTES = 10485760;
12
+ /**
13
+ * Upper bound on the TOTAL attachment bytes `normalizeEmail` will materialize.
14
+ *
15
+ * Base64 expands by `ceil(n / 3) * 4`, so an attachment set larger than three
16
+ * quarters of `MAX_MESSAGE_BYTES` cannot fit in a message that passes the
17
+ * check at the end of `buildMime` no matter how small everything else is.
18
+ * `buildMime` remains the authority on the final size — this bound only stops
19
+ * `normalizeEmail` from materializing, and then expanding, bytes already known
20
+ * not to fit.
21
+ *
22
+ * It has to be charged here because that final check comes far too late to
23
+ * help. Measured peak live memory is about 10x the attachment bytes (8 MiB in
24
+ * gives 83 MiB live) once the normalized copy, the base64 binary string, the
25
+ * 76-column slices, the joins, `rawText` and its `TextEncoder.encode` are all
26
+ * resident at once. Against a 128 MB isolate ceiling, a set over roughly
27
+ * 12 MiB terminates the isolate rather than returning the `MIME_ERROR` the
28
+ * docs promise for an oversize payload.
29
+ *
30
+ * This is a developer-experience guardrail, NOT the security boundary.
31
+ * Attachments come from the app's own code, never from a remote sender, and
32
+ * the platform proxy already caps the base64 length before decoding and the
33
+ * raw length after. What this buys is a precise, catchable failure at the call
34
+ * that went wrong, instead of a dead isolate with nothing a caller could act
35
+ * on.
36
+ */
37
+ const MAX_ATTACHMENT_BYTES_TOTAL = Math.floor(MAX_MESSAGE_BYTES * 3 / 4);
38
+ const CRLF = "\r\n";
39
+ const ASCII_PRINTABLE = /^[\x20-\x7e]*$/;
40
+ const HEADER_NAME = /^[!-9;-~]+$/;
41
+ function rejectCRLF(field, value) {
42
+ for (let i = 0; i < value.length; i++) {
43
+ const c = value.charCodeAt(i);
44
+ if (c === 13 || c === 10 || c === 0) throw new MimeError("MIME_ERROR", `${field}: must not contain CR, LF, or NUL.`);
45
+ }
46
+ }
47
+ /**
48
+ * Refuse any other C0 control or DEL in a value that is rendered VERBATIM
49
+ * into a header — an attachment's filename, media type and Content-ID.
50
+ * `rejectCRLF` is enough for a value `encodeWord` renders (the subject, a
51
+ * custom header value, a display name): anything outside printable ASCII
52
+ * sends the whole value into base64, so a control never reaches the wire.
53
+ * These three go through `quoteParam` or straight into the line, so
54
+ * `invoice\x01.pdf` was written raw into `name="…"` and `filename="…"`,
55
+ * captured as a success under `void dev` and by the test harness, and left
56
+ * for the receiving MTA to refuse.
57
+ *
58
+ * RFC 5322 §3.2.4 `qtext` is printable US-ASCII less `"` and `\`; a
59
+ * control inside a quoted-string is `obs-qtext`, which §4 says MUST NOT be
60
+ * generated. HTAB is deliberately NOT refused: `quoted-string` is
61
+ * `DQUOTE *([FWS] qcontent) [FWS] DQUOTE` and `FWS` is built from `WSP`
62
+ * (SP / HTAB), so a tab between characters is folding white space exactly
63
+ * as a space is — and `obs-NO-WS-CTL`, the RFC's own list of the controls
64
+ * that used to be `qtext`, leaves %d9 out. The same set is illegal in an
65
+ * RFC 2045 §5.1 `token` (no CTLs) and an RFC 5322 §3.6.4 `msg-id`
66
+ * (`dot-atom-text`), so one rule serves the media type and the Content-ID.
67
+ *
68
+ * The message names the code point and never echoes the value, as
69
+ * `validateAddressParts` does. Runs AFTER the filename's length bound, so
70
+ * the scan is over a value already known to fit a line.
71
+ */
72
+ function rejectControls(field, value) {
73
+ for (let i = 0; i < value.length; i++) {
74
+ const c = value.charCodeAt(i);
75
+ if (c < 32 && c !== 9 || c === 127) throw new MimeError("MIME_ERROR", `${field} contains a control character (U+${c.toString(16).toUpperCase().padStart(4, "0")}).`);
76
+ }
77
+ }
78
+ /** RFC 2045 §5.1 `tspecials`: what a `token` may not contain besides SPACE and CTLs. */
79
+ const TSPECIALS = "()<>@,;:\\\"/[]?=";
80
+ /** RFC 2045 §5.1 `token` character: printable US-ASCII less SPACE and `tspecials`. */
81
+ function isTokenChar(c) {
82
+ return c > 32 && c < 127 && !TSPECIALS.includes(String.fromCharCode(c));
83
+ }
84
+ /**
85
+ * Refuse an attachment media type that does not parse as RFC 2045 §5.1
86
+ * `type "/" subtype *(";" parameter)`.
87
+ *
88
+ * `rejectControls` answers only for CTLs, and `buildAttachmentPart` writes
89
+ * the value verbatim ahead of its own `; name="…"`, so `text/plain; x="`
90
+ * rendered `Content-Type: text/plain; x="; name="a.txt"` — captured as a
91
+ * success under `void dev` and by the harness, while Python's `email`
92
+ * reports `end of header inside quoted string` on that line and reads the
93
+ * builder's `name` parameter as the VALUE of `x`. `text` (no subtype),
94
+ * `text/plain; charset` (no value) and `text/plain;;` went the same way.
95
+ *
96
+ * The grammar is the RFC's, read as a generator: a `token` is printable
97
+ * US-ASCII less SPACE and `tspecials`; a `value` is a `token` or a
98
+ * quoted-string, whose content is anything but a bare `"` or `\` (the same
99
+ * rule `quoteParam` renders `name="…"` by, so a caller's quoted value may
100
+ * carry what the builder's own already does). Optional WSP is admitted on
101
+ * either side of `;` — the RFC 822 implicit LWSP every parser accepts and
102
+ * the form HTTP's `Content-Type` writes — and nowhere else: `text/ plain`,
103
+ * a trailing `;`, and an RFC 822 comment are refused rather than repaired.
104
+ * Two parameters are also refused on their meaning: a `name`, because the
105
+ * builder appends `name="<filename>"` and RFC 6838 §4.3 makes it "an error
106
+ * for a specific parameter to be specified more than once"; and by that
107
+ * same sentence any attribute given twice, compared case-insensitively as
108
+ * §5.1 says attributes match. Type and subtype are kept as given — matching
109
+ * is case-insensitive, so `TEXT/PLAIN` is legal and renders unchanged.
110
+ *
111
+ * One pass, no backtracking, and the only allocation is one lower-cased
112
+ * attribute per parameter for the duplicate check, which is why this runs
113
+ * AFTER the value's length bound. The message names the rule and never
114
+ * echoes the value.
115
+ */
116
+ function validateMediaType(field, value) {
117
+ const syntax = () => new MimeError("MIME_ERROR", `${field} is not a valid media type (RFC 2045 §5.1: type/subtype, then ; attribute=value parameters).`);
118
+ const n = value.length;
119
+ let i = 0;
120
+ /** Advance over a `token`; the number of characters consumed. */
121
+ const token = () => {
122
+ const start = i;
123
+ while (i < n && isTokenChar(value.charCodeAt(i))) i++;
124
+ return i - start;
125
+ };
126
+ const wsp = () => {
127
+ while (i < n && (value[i] === " " || value[i] === " ")) i++;
128
+ };
129
+ if (token() === 0 || value[i] !== "/") throw syntax();
130
+ i++;
131
+ if (token() === 0) throw syntax();
132
+ const seen = /* @__PURE__ */ new Set();
133
+ while (i < n) {
134
+ wsp();
135
+ if (value[i] !== ";") throw syntax();
136
+ i++;
137
+ wsp();
138
+ const start = i;
139
+ if (token() === 0 || value[i] !== "=") throw syntax();
140
+ const attribute = value.slice(start, i).toLowerCase();
141
+ i++;
142
+ if (value[i] === "\"") {
143
+ i++;
144
+ for (;;) {
145
+ if (i >= n) throw syntax();
146
+ const ch = value[i++];
147
+ if (ch === "\"") break;
148
+ if (ch === "\\") {
149
+ if (i >= n) throw syntax();
150
+ i++;
151
+ }
152
+ }
153
+ } else if (token() === 0) throw syntax();
154
+ if (attribute === "name") throw new MimeError("MIME_ERROR", `${field} must not carry a name= parameter; the builder adds name="<filename>" itself.`);
155
+ if (seen.has(attribute)) throw new MimeError("MIME_ERROR", `${field} gives the same parameter twice; attribute names are case-insensitive and each may appear once (RFC 6838 §4.3).`);
156
+ seen.add(attribute);
157
+ }
158
+ }
159
+ /** RFC 5322 §3.2.3 `atext` besides ALPHA and DIGIT. */
160
+ const ATEXT_SYMBOLS = "!#$%&'*+-/=?^_`{|}~";
161
+ /** RFC 5322 §3.2.3 `atext` character. */
162
+ function isAtext(c) {
163
+ return c >= 48 && c <= 57 || c >= 65 && c <= 90 || c >= 97 && c <= 122 || ATEXT_SYMBOLS.includes(String.fromCharCode(c));
164
+ }
165
+ /**
166
+ * Refuse an attachment Content-ID that would not render as one RFC 5322
167
+ * §3.6.4 `msg-id`.
168
+ *
169
+ * `rejectControls` answers only for CTLs, and `buildAttachmentPart` writes
170
+ * the value verbatim — adding `<` and `>` unless the value already starts
171
+ * with `<` — so `logo other` rendered `Content-ID: <logo other>`, `<logo`
172
+ * rendered `Content-ID: <logo` and `logo>` rendered `Content-ID: <logo>>`:
173
+ * captured as a success under `void dev` and by the harness, and left for
174
+ * the receiver to refuse or to fail the `cid:` reference against.
175
+ *
176
+ * The grammar is the RFC's, read as a generator, with two deliberate
177
+ * differences. An `msg-id` is `"<" id-left "@" id-right ">"`, each side a
178
+ * `dot-atom-text` (§3.2.3: `1*atext *("." 1*atext)`, so no leading,
179
+ * trailing or doubled dot — the strict form; `obs-id-left`/`obs-id-right`
180
+ * are §4 obsolete syntax, which MUST NOT be generated); `no-fold-literal`
181
+ * (`[…]` for `id-right`) is left out. First difference: the `@` and its
182
+ * right side are OPTIONAL. RFC 2392 §2 resolves a `cid:` URL by wrapping
183
+ * its body in `<` `>` and matching that against `Content-ID`, so an id
184
+ * without `@` — the documented `contentId: 'logo'` + `cid:logo` — is what
185
+ * every client already resolves, and the domain that would make it a
186
+ * proper `msg-id` adds nothing a reference could use. Second: the angle
187
+ * brackets are optional too, because the builder has always added them
188
+ * around a bare id; given, they must be one pair around the whole value.
189
+ *
190
+ * Refused, then: whitespace of any kind, a second `@`, `<` or `>` anywhere
191
+ * but as that one pair, `"`, `(`, `)`, `,`, `;`, `:`, `\`, `[`, `]`,
192
+ * anything non-ASCII, and the empty string (which used to be validated
193
+ * and then silently NOT rendered). Everything accepted renders exactly as
194
+ * it did.
195
+ *
196
+ * One pass, no allocation, which is why this runs AFTER the value's length
197
+ * bound. The message names the rule and never echoes the value.
198
+ */
199
+ function validateContentId(field, value) {
200
+ const syntax = () => new MimeError("MIME_ERROR", `${field} is not a valid Content-ID (RFC 5322 §3.6.4 msg-id: an identifier of atext, "." and "@", optionally in one pair of angle brackets).`);
201
+ let i = 0;
202
+ let end = value.length;
203
+ if (value[0] === "<") {
204
+ if (end < 2 || value[end - 1] !== ">") throw syntax();
205
+ i = 1;
206
+ end--;
207
+ }
208
+ /** Advance over a `dot-atom-text`; false when there is none, or a dot is misplaced. */
209
+ const dotAtomText = () => {
210
+ let run = 0;
211
+ for (; i < end; i++) {
212
+ const c = value.charCodeAt(i);
213
+ if (isAtext(c)) run++;
214
+ else if (c === 46 && run > 0) run = 0;
215
+ else break;
216
+ }
217
+ return run > 0;
218
+ };
219
+ if (!dotAtomText()) throw syntax();
220
+ if (i < end) {
221
+ if (value[i] !== "@") throw syntax();
222
+ i++;
223
+ if (!dotAtomText()) throw syntax();
224
+ }
225
+ if (i !== end) throw syntax();
226
+ }
227
+ const COMMON_TYPES = {
228
+ txt: "text/plain",
229
+ html: "text/html",
230
+ htm: "text/html",
231
+ css: "text/css",
232
+ csv: "text/csv",
233
+ pdf: "application/pdf",
234
+ json: "application/json",
235
+ xml: "application/xml",
236
+ zip: "application/zip",
237
+ png: "image/png",
238
+ jpg: "image/jpeg",
239
+ jpeg: "image/jpeg",
240
+ gif: "image/gif",
241
+ webp: "image/webp",
242
+ svg: "image/svg+xml",
243
+ ico: "image/x-icon",
244
+ ics: "text/calendar",
245
+ eml: "message/rfc822"
246
+ };
247
+ function inferContentType(filename) {
248
+ const ext = filename.split(".").pop()?.toLowerCase() ?? "";
249
+ return COMMON_TYPES[ext] ?? "application/octet-stream";
250
+ }
251
+ var MimeError = class extends Error {
252
+ code;
253
+ constructor(code, message) {
254
+ super(message);
255
+ this.name = "MimeError";
256
+ this.code = code;
257
+ }
258
+ };
259
+ function fieldErrorCode(field) {
260
+ if (field === "from") return "INVALID_FROM";
261
+ if (field === "to") return "INVALID_TO";
262
+ return "MIME_ERROR";
263
+ }
264
+ /**
265
+ * Validate one parsed address (and its display name, when there is one),
266
+ * throwing a `MimeError` for `field` on anything undeliverable. Exported so
267
+ * the inbound proxy's `forward(to)` and hand-built `reply()` refuse a bare
268
+ * address by the same rule `sendEmail` applies to its `to`.
269
+ *
270
+ * The address rule is `email-validator`, and nothing else. It is also the
271
+ * platform router's rule (`isValidEmailAddress` in the proxy's
272
+ * `email-router.ts`), so there is exactly ONE address syntax in the tree:
273
+ * an action the framework records is never dropped by the router for its
274
+ * shape, and a test harness never captures as a success what the managed
275
+ * deployment would lose. The hand-written predecessor grew one character
276
+ * per review round — the exactly-one-`@` rule, then every C0 control and
277
+ * DEL, then `:`, then a parity test against the router's own regex, which
278
+ * had drifted on the dot in the domain — and still admitted `a..b@x.com`,
279
+ * `.a@x.com` and `a@x..com`, which no dot-atom allows and every MTA
280
+ * refuses. One maintained rule on both sides ends that series.
281
+ *
282
+ * What it enforces: at most 254 characters in total; an ASCII dot-atom
283
+ * local part of at most 64 characters (RFC 5322 §3.2.3 `atext`, dots only
284
+ * between atoms — no quoted local part, no whitespace, no control, no
285
+ * special); a dotted domain of ASCII labels of at most 63 characters each,
286
+ * with a TLD that starts with a letter and has at least two characters; so
287
+ * `user@localhost`, `x@y` and a raw IDN or SMTPUTF8 address are refused
288
+ * (an IDN domain is accepted as punycode, `xn--…`). ASCII-only makes
289
+ * octets and characters the same thing, so the RFC 5321 §4.5.3.1 octet
290
+ * limits are the package's length checks; the 64 is also what caps the
291
+ * default `<slug>+noreply@` sender (`VALID_SLUG` in `runtime/email.ts`,
292
+ * `MAX_SLUG_LENGTH` = `64 - 8` in `cli/project-slug.ts` and the API). One
293
+ * known quirk, pinned in the tests rather than worked around: the FIRST
294
+ * character of the local part may not be a backtick, while any later one
295
+ * may.
296
+ *
297
+ * `MAX_ADDRESS_LENGTH` still runs first, on the raw string, so the scan is
298
+ * bounded before anything reads the address; the package's own 254 check
299
+ * runs before its regex for the same reason. The message never echoes the
300
+ * address: it may hold a control character, and naming the rule is enough.
301
+ */
302
+ function validateAddressParts(field, email, name) {
303
+ if (email.length > 320) throw new MimeError(fieldErrorCode(field), `${field}: address is ${email.length} characters, over the 320-character limit.`);
304
+ if (!validate(email)) throw new MimeError(fieldErrorCode(field), `${field}: address is not a valid email address (ASCII local part and dotted domain, RFC 5322 dot-atom; at most 64 characters before the '@' and 254 in total).`);
305
+ if (name !== void 0) {
306
+ rejectCRLF(`${field} display name`, name);
307
+ if (ASCII_PRINTABLE.test(name) && RFC5322_SPECIALS.test(name) && name.length + QUOTED_NAME_LINE_OVERHEAD > MAX_LINE_OCTETS) throw new MimeError(fieldErrorCode(field), `${field}: display name is ${name.length} characters and must be quoted, which cannot be folded, so its line would exceed the ${MAX_LINE_OCTETS}-octet limit in RFC 5322 §2.1.1. Shorten it.`);
308
+ if (name.includes("@")) throw new MimeError(fieldErrorCode(field), `${field}: display name '${name}' must not contain '@' (likely a malformed address).`);
309
+ }
310
+ }
311
+ /**
312
+ * Undo the quoted-pairs in the content of a quoted display name: `\x` →
313
+ * `x` for any `x` (RFC 5322 §3.2.4 — `qtext` excludes `"` and `\`, so a
314
+ * quoted-string carries either only as a quoted-pair). The parsed name is
315
+ * the RAW text `formatAddress` will quote and escape again, so leaving the
316
+ * pairs in place rendered `"Smith \"Bob\"" <a@x.dev>` as
317
+ * `"Smith \\\"Bob\\\""` and the recipient saw `Smith \"Bob\"`, while the
318
+ * object form of the same name rendered correctly. Only the quoted branch
319
+ * of `parseAddress` calls this; a backslash in an unquoted name is a
320
+ * character like any other, as it is in the object form.
321
+ *
322
+ * NOT `text.replace(/\\(.)/g, '$1')`: a global-regex replacement holds one
323
+ * part per match before it builds the result, about 60 bytes each, and
324
+ * nothing bounds this name before it runs — the quoted-string line guard
325
+ * in `validateAddressParts` needs the DECODED name, and a non-ASCII name is
326
+ * never bounded at all. Measured, 10 MiB of `\"` pairs peaked at 307 MB of
327
+ * heap that way and terminated a 128 MB isolate; this shape peaks at 49 MB.
328
+ * Same discipline as `to7bitBody`: one counting pass, a buffer of exactly
329
+ * the output size, one fill, and the 32K-chunk conversion `base64` uses.
330
+ * The common case — no backslash at all — is one scan and no allocation.
331
+ * The regex that admits the text guarantees every backslash is followed by
332
+ * a character, so the fill never reads past the end.
333
+ */
334
+ function unquotePairs(text) {
335
+ const first = text.indexOf("\\");
336
+ if (first === -1) return text;
337
+ let pairs = 0;
338
+ for (let i = first; i < text.length; i++) if (text.charCodeAt(i) === 92) {
339
+ pairs++;
340
+ i++;
341
+ }
342
+ const out = new Uint16Array(text.length - pairs);
343
+ let n = 0;
344
+ for (let i = 0; i < text.length; i++) {
345
+ let c = text.charCodeAt(i);
346
+ if (c === 92) c = text.charCodeAt(++i);
347
+ out[n++] = c;
348
+ }
349
+ let s = "";
350
+ const chunk = 32768;
351
+ for (let i = 0; i < n; i += chunk) s += String.fromCharCode(...out.subarray(i, i + chunk));
352
+ return s;
353
+ }
354
+ /**
355
+ * Parse one address — a bare `email@domain` or `Display Name <email@domain>`
356
+ * — into its parts, throwing a `MimeError` for `field` on anything else.
357
+ * Exported so `void.json` `email.from` is validated by the same rules the
358
+ * runtime applies to `from` at send time.
359
+ */
360
+ function parseAddress(value, field) {
361
+ if (typeof value === "string") {
362
+ rejectCRLF(`${field} address`, value);
363
+ const quoted = value.match(/^\s*"((?:[^"\\]|\\.)*)"\s*<\s*([^>\s]+)\s*>\s*$/);
364
+ const angle = quoted ?? value.match(/^\s*(?:([^"<\s](?:[^"<]*[^"<\s])?)\s*)?<\s*([^>\s]+)\s*>\s*$/);
365
+ if (angle) {
366
+ const name = (quoted ? unquotePairs(angle[1] ?? "") : angle[1])?.trim();
367
+ const email = angle[2];
368
+ validateAddressParts(field, email, name);
369
+ return name ? {
370
+ email,
371
+ name
372
+ } : { email };
373
+ }
374
+ const trimmed = value.trim();
375
+ validateAddressParts(field, trimmed);
376
+ return { email: trimmed };
377
+ }
378
+ validateAddressParts(field, value.email ?? "", value.name);
379
+ return value.name ? {
380
+ email: value.email,
381
+ name: value.name
382
+ } : { email: value.email };
383
+ }
384
+ function parseAddresses(value, field) {
385
+ return (Array.isArray(value) ? value : [value]).map((entry) => parseAddress(entry, field));
386
+ }
387
+ /** Entries in one recipient field, counted the way `parseAddresses` will see them. */
388
+ function countRecipients(value) {
389
+ if (!value) return 0;
390
+ return Array.isArray(value) ? value.length : 1;
391
+ }
392
+ /**
393
+ * Keeps the `exceeds Cloudflare limit of <N> bytes` tail that `buildMime`'s
394
+ * own size error already uses, so the documented shape — and any caller
395
+ * matching on it — holds for both paths.
396
+ */
397
+ function attachmentsTooLargeError(bytes) {
398
+ return new MimeError("MIME_ERROR", `email: attachment size ${bytes} bytes exceeds Cloudflare limit of ${MAX_ATTACHMENT_BYTES_TOTAL} bytes.`);
399
+ }
400
+ /**
401
+ * Size of `content` WITHOUT materializing it.
402
+ *
403
+ * A string reports its UTF-16 `length`, which is a sound LOWER bound on its
404
+ * UTF-8 byte length — every code unit encodes to at least one byte — so this
405
+ * can never reject a string that would have fit; the exact length is charged
406
+ * after the encode. Anything else reports 0 and is left to `toBytes` to reject
407
+ * by type.
408
+ */
409
+ function declaredSize(content) {
410
+ if (content instanceof Uint8Array) return content.byteLength;
411
+ if (content instanceof ArrayBuffer) return content.byteLength;
412
+ if (typeof Blob !== "undefined" && content instanceof Blob) return content.size;
413
+ if (typeof content === "string") return content.length;
414
+ return 0;
415
+ }
416
+ async function toBytes(content) {
417
+ if (content instanceof Uint8Array) return content;
418
+ if (content instanceof ArrayBuffer) return new Uint8Array(content);
419
+ if (typeof Blob !== "undefined" && content instanceof Blob) return new Uint8Array(await content.arrayBuffer());
420
+ if (typeof content === "string") return new TextEncoder().encode(content);
421
+ throw new MimeError("MIME_ERROR", "attachment content must be string, Uint8Array, ArrayBuffer, or Blob.");
422
+ }
423
+ async function normalizeAttachment(att) {
424
+ if (!att.filename) throw new MimeError("MIME_ERROR", "attachment: missing filename.");
425
+ rejectCRLF("attachment filename", att.filename);
426
+ if (att.filename.length + QUOTED_FILENAME_LINE_OVERHEAD > MAX_LINE_OCTETS) throw new MimeError("MIME_ERROR", `attachment filename is ${att.filename.length} characters; a quoted \`filename=\` parameter cannot be folded, so its line would exceed the ${MAX_LINE_OCTETS}-octet limit in RFC 5322 §2.1.1. Shorten the filename.`);
427
+ rejectControls("attachment filename", att.filename);
428
+ if (att.contentType !== void 0) {
429
+ rejectCRLF("attachment contentType", att.contentType);
430
+ if (att.contentType.length + CONTENT_TYPE_LINE_OVERHEAD > MAX_LINE_OCTETS) throw new MimeError("MIME_ERROR", `attachment contentType is ${att.contentType.length} characters; at most ${MAX_LINE_OCTETS - CONTENT_TYPE_LINE_OVERHEAD} fit on the Content-Type line under the ${MAX_LINE_OCTETS}-octet limit in RFC 5322 §2.1.1. Shorten it.`);
431
+ rejectControls("attachment contentType", att.contentType);
432
+ validateMediaType("attachment contentType", att.contentType);
433
+ }
434
+ if (att.contentId !== void 0) {
435
+ rejectCRLF("attachment contentId", att.contentId);
436
+ const brackets = att.contentId.startsWith("<") ? 0 : 2;
437
+ if (att.contentId.length + brackets + CONTENT_ID_LINE_OVERHEAD > MAX_LINE_OCTETS) throw new MimeError("MIME_ERROR", `attachment contentId is ${att.contentId.length} characters; at most ${MAX_LINE_OCTETS - CONTENT_ID_LINE_OVERHEAD - brackets} fit on the Content-ID line under the ${MAX_LINE_OCTETS}-octet limit in RFC 5322 §2.1.1. Shorten it.`);
438
+ rejectControls("attachment contentId", att.contentId);
439
+ validateContentId("attachment contentId", att.contentId);
440
+ }
441
+ if (att.disposition !== void 0 && att.disposition !== "attachment" && att.disposition !== "inline") throw new MimeError("MIME_ERROR", "attachment disposition must be 'attachment' or 'inline'.");
442
+ const bytes = await toBytes(att.content);
443
+ return {
444
+ filename: att.filename,
445
+ contentType: att.contentType ?? inferContentType(att.filename),
446
+ bytes,
447
+ contentId: att.contentId,
448
+ disposition: att.disposition ?? (att.contentId ? "inline" : "attachment")
449
+ };
450
+ }
451
+ /**
452
+ * Validate inputs and produce a normalized email shape that can be handed to
453
+ * the MIME builder, the dev inbox, or the test harness without re-parsing.
454
+ */
455
+ async function normalizeEmail(opts) {
456
+ if (!opts.text && !opts.html) throw new MimeError("MIME_ERROR", "email: at least one of 'text' or 'html' is required.");
457
+ if (opts.subject !== void 0) rejectCRLF("subject", opts.subject);
458
+ if (opts.headers) for (const [name, value] of Object.entries(opts.headers)) {
459
+ if (!HEADER_NAME.test(name)) throw new MimeError("MIME_ERROR", `headers: '${name}' is not a valid RFC 5322 header name.`);
460
+ if (name.length + HEADER_NAME_LINE_OVERHEAD > MAX_LINE_OCTETS) throw new MimeError("MIME_ERROR", `headers: a header name is ${name.length} characters; a field name cannot be folded, so its line would exceed the ${MAX_LINE_OCTETS}-octet limit in RFC 5322 §2.1.1. Shorten the name.`);
461
+ rejectCRLF(`header '${name}'`, value);
462
+ }
463
+ const recipientCount = countRecipients(opts.to) + countRecipients(opts.cc) + countRecipients(opts.bcc);
464
+ if (recipientCount > 100) throw new MimeError("INVALID_TO", `email: too many recipients (max 100, got ${recipientCount}) across 'to', 'cc' and 'bcc'.`);
465
+ const from = parseAddress(opts.from, "from");
466
+ const to = parseAddresses(opts.to, "to");
467
+ if (to.length === 0) throw new MimeError("INVALID_TO", "email: 'to' must include at least one recipient.");
468
+ const cc = opts.cc ? parseAddresses(opts.cc, "cc") : [];
469
+ const bcc = opts.bcc ? parseAddresses(opts.bcc, "bcc") : [];
470
+ const replyTo = opts.replyTo ? parseAddress(opts.replyTo, "replyTo") : void 0;
471
+ const attachmentList = opts.attachments ?? [];
472
+ if (!Array.isArray(attachmentList)) throw new MimeError("MIME_ERROR", "email: attachments must be an array.");
473
+ if (attachmentList.length > MAX_ATTACHMENTS) throw new MimeError("MIME_ERROR", `email: too many attachments (max ${MAX_ATTACHMENTS}, got ${attachmentList.length}).`);
474
+ const attachments = [];
475
+ let attachmentBytes = 0;
476
+ for (const att of attachmentList) {
477
+ const declared = declaredSize(att.content);
478
+ if (attachmentBytes + declared > MAX_ATTACHMENT_BYTES_TOTAL) throw attachmentsTooLargeError(attachmentBytes + declared);
479
+ const normalized = await normalizeAttachment(att);
480
+ attachmentBytes += normalized.bytes.byteLength;
481
+ if (attachmentBytes > MAX_ATTACHMENT_BYTES_TOTAL) throw attachmentsTooLargeError(attachmentBytes);
482
+ attachments.push(normalized);
483
+ }
484
+ if (opts.threading?.inReplyTo) rejectCRLF("threading.inReplyTo", opts.threading.inReplyTo);
485
+ if (opts.threading?.references) rejectCRLF("threading.references", opts.threading.references);
486
+ return {
487
+ from,
488
+ to,
489
+ cc,
490
+ bcc,
491
+ replyTo,
492
+ subject: opts.subject ?? "",
493
+ text: opts.text,
494
+ html: opts.html,
495
+ headers: opts.headers ? { ...opts.headers } : {},
496
+ attachments,
497
+ threading: opts.threading
498
+ };
499
+ }
500
+ /**
501
+ * RFC 2047 §2 caps an encoded-word at 75 characters *including* the
502
+ * `=?UTF-8?B?` prefix (10) and the `?=` suffix (2). That leaves 63
503
+ * characters of base64, and base64 spends 4 characters per 3 input
504
+ * bytes, so 45 source bytes is the largest payload that fits — 48 would
505
+ * need 64.
506
+ */
507
+ const ENCODED_WORD_PAYLOAD_BYTES = 45;
508
+ /**
509
+ * RFC 2047 encoded-word for non-ASCII text in headers (UTF-8, base64).
510
+ *
511
+ * Text longer than one word's payload becomes several encoded-words
512
+ * separated by a space, which `foldHeader` turns into CRLF + SPACE
513
+ * wherever a line would otherwise overrun (RFC 2047 §6.2: whitespace
514
+ * between adjacent encoded-words is dropped on decode either way). A
515
+ * single unbounded word breached both §2's 75-character ceiling and —
516
+ * for a long subject or display name — RFC 5322 §2.1.1's 998-octet hard
517
+ * line limit, which nothing downstream repairs.
518
+ */
519
+ function encodeWord(text) {
520
+ if (ASCII_PRINTABLE.test(text)) return text;
521
+ const encoder = new TextEncoder();
522
+ const pending = /* @__PURE__ */ new Uint8Array(49);
523
+ let length = 0;
524
+ const words = [];
525
+ const flush = () => {
526
+ if (length > 0) {
527
+ words.push(`=?UTF-8?B?${base64(pending.subarray(0, length))}?=`);
528
+ length = 0;
529
+ }
530
+ };
531
+ for (const char of text) {
532
+ const bytes = encoder.encode(char);
533
+ if (length > 0 && length + bytes.length > ENCODED_WORD_PAYLOAD_BYTES) flush();
534
+ pending.set(bytes, length);
535
+ length += bytes.length;
536
+ }
537
+ flush();
538
+ return words.join(" ");
539
+ }
540
+ /**
541
+ * RFC 5322 §3.2.3, verbatim:
542
+ *
543
+ * specials = "(" / ")" / "<" / ">" / "[" / "]" / ":" / ";" / "@" /
544
+ * "\" / "," / "." / DQUOTE
545
+ *
546
+ * A display name is a §3.2.5 `phrase = 1*word`, `word = atom /
547
+ * quoted-string`, and `atext` is everything printable EXCEPT these, so an
548
+ * ASCII name containing any of them must be rendered as one quoted-string.
549
+ * The class below lists the thirteen in the RFC's order so it can be checked
550
+ * against the production by eye. This check has been wrong twice: it once
551
+ * sat inside a branch that had already excluded every special, so it was
552
+ * unreachable and a comma in a name split the address list; then the list
553
+ * itself lacked `.`, so `Dr. Smith <a@x.dev>` went out unquoted. That is
554
+ * `obs-phrase` (§4.1, `word *(word / "." / CFWS)`), which §4 says MUST NOT
555
+ * be generated, and a strict receiver flags it as obsolete syntax (Python's
556
+ * `email` parser: "period in 'phrase'"). Every conforming generator —
557
+ * Python's `email.headerregistry.Address`, for one — emits `"Dr. Smith"`.
558
+ */
559
+ const RFC5322_SPECIALS = /[()<>[\]:;@\\,."]/;
560
+ function formatAddress(addr) {
561
+ if (!addr.name) return addr.email;
562
+ if (!ASCII_PRINTABLE.test(addr.name)) return `${encodeWord(addr.name)} <${addr.email}>`;
563
+ if (RFC5322_SPECIALS.test(addr.name)) return `"${addr.name.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}" <${addr.email}>`;
564
+ return `${addr.name} <${addr.email}>`;
565
+ }
566
+ function formatAddressList(addrs) {
567
+ return addrs.map(formatAddress).join(", ");
568
+ }
569
+ /**
570
+ * Soft-fold a long header line. Fold points, strongest first: address-list
571
+ * separators (a `, ` outside any double-quoted string), then any remaining
572
+ * unquoted whitespace inside a part. A naive `split(/, /)` would split
573
+ * inside `"Smith, John"`, corrupting quoted display names, and folding only
574
+ * between whole parts is not enough — one recipient carrying a long encoded
575
+ * display name, or a `References:` run of message IDs, is a single part
576
+ * that can blow past RFC 5322 §2.1.1's 998-octet hard limit on its own.
577
+ *
578
+ * RFC 2047 §2 limits any header line carrying an encoded-word to 76
579
+ * characters; RFC 5322 §2.1.1 makes 78 a SHOULD and 998 a MUST. Folding at
580
+ * 76 satisfies all three. A comma always stays on the line it terminates.
581
+ * A token longer than the budget still gets its own line, which is the
582
+ * best a WSP-only fold rule allows (§3.2.2 permits folding only at WSP).
583
+ *
584
+ * One pass, and the only thing kept per input character is the folded
585
+ * output itself: each finished line is one flat string in `lines`. The
586
+ * earlier shape — tokenize the whole value into an array of slices, wrap
587
+ * each in a `{ text, comma }` object, then grow the result with `out +=`
588
+ * per token — held about 130 bytes of heap per token for the length of the
589
+ * fold (a slice, an object and a cons-string node each), so a legal 3 MB
590
+ * subject of one-letter words terminated a 128 MB isolate. That was a
591
+ * per-TOKEN cost, invisible to any size projection: the message fit.
592
+ */
593
+ function foldHeader(name, value) {
594
+ const line = `${name}: ${value}`;
595
+ if (line.length <= 78) return line;
596
+ const MAX_LINE = 76;
597
+ const lines = [];
598
+ let current = [`${name}:`];
599
+ let lineLen = name.length + 1;
600
+ let chunks = 0;
601
+ const emit = (text, comma) => {
602
+ chunks++;
603
+ const piece = comma ? `${text},` : text;
604
+ if (piece.length > 0 && lineLen + 1 + piece.length > MAX_LINE) {
605
+ lines.push(current.join(" "));
606
+ current = ["", piece];
607
+ lineLen = 1 + piece.length;
608
+ } else {
609
+ current.push(piece);
610
+ lineLen += 1 + piece.length;
611
+ }
612
+ };
613
+ let tokenStart = -1;
614
+ let heldStart = -1;
615
+ let heldEnd = -1;
616
+ const endToken = (i) => {
617
+ if (tokenStart < 0) return;
618
+ if (heldStart >= 0) emit(value.slice(heldStart, heldEnd), false);
619
+ heldStart = tokenStart;
620
+ heldEnd = i;
621
+ tokenStart = -1;
622
+ };
623
+ const endPart = (isLast) => {
624
+ if (heldStart >= 0) {
625
+ emit(value.slice(heldStart, heldEnd), !isLast);
626
+ heldStart = -1;
627
+ } else emit("", !isLast);
628
+ };
629
+ let inQuotes = false;
630
+ let escape = false;
631
+ for (let i = 0; i < value.length; i++) {
632
+ const ch = value[i];
633
+ if (escape) {
634
+ escape = false;
635
+ if (tokenStart < 0) tokenStart = i;
636
+ continue;
637
+ }
638
+ if (ch === "\\" && inQuotes) {
639
+ escape = true;
640
+ if (tokenStart < 0) tokenStart = i;
641
+ continue;
642
+ }
643
+ if (ch === "\"") {
644
+ inQuotes = !inQuotes;
645
+ if (tokenStart < 0) tokenStart = i;
646
+ continue;
647
+ }
648
+ if (!inQuotes && ch === "," && value[i + 1] === " ") {
649
+ endToken(i);
650
+ endPart(false);
651
+ i++;
652
+ continue;
653
+ }
654
+ if (!inQuotes && (ch === " " || ch === " ")) {
655
+ endToken(i);
656
+ continue;
657
+ }
658
+ if (tokenStart < 0) tokenStart = i;
659
+ }
660
+ endToken(value.length);
661
+ endPart(true);
662
+ if (chunks <= 1) return line;
663
+ lines.push(current.join(" "));
664
+ return lines.join(CRLF);
665
+ }
666
+ /** RFC 5322 §2.1.1: each line MUST be at most 998 octets, excluding CRLF. */
667
+ const MAX_LINE_OCTETS = 998;
668
+ /** ` filename="` + `"`: the shortest line a quoted filename parameter can fold onto. */
669
+ const QUOTED_FILENAME_LINE_OVERHEAD = 12;
670
+ /**
671
+ * ` ` + `;`: the shortest line an attachment's media type can fold onto,
672
+ * the whole value taken as one token. `foldHeader` would split a value
673
+ * of several parameters at their spaces, so this bound is exact for the
674
+ * one-token media type every attachment carries in practice and
675
+ * conservative for a run of parameters that together pass the line
676
+ * limit; that is accepted, because the alternative — find the fold points
677
+ * first — is the scan this bound exists to run ahead of.
678
+ */
679
+ const CONTENT_TYPE_LINE_OVERHEAD = 2;
680
+ /**
681
+ * `Content-ID: `: the only line an attachment's Content-ID can sit on. The
682
+ * value is one token — `<id>` — so `foldHeader` cannot fold it onto a
683
+ * continuation line the way it can a media type, and the field name is
684
+ * part of what the id must fit beside. Exact, not conservative: the `<`
685
+ * and `>` the builder adds around a bare id are charged at the call.
686
+ */
687
+ const CONTENT_ID_LINE_OVERHEAD = 12;
688
+ /** ` "` + `"`: the shortest line a quoted display name can fold onto. */
689
+ const QUOTED_NAME_LINE_OVERHEAD = 3;
690
+ /** `:` alone: the shortest line a header name can sit on, its value folded onto the next. */
691
+ const HEADER_NAME_LINE_OVERHEAD = 1;
692
+ /**
693
+ * First line of `text` that exceeds the octet limit, if any.
694
+ *
695
+ * Every line is scanned, not just the top header block: a multipart part's
696
+ * own headers (`Content-Disposition: …; filename="…"`) live inside the body
697
+ * and are just as unfoldable. Body content cannot trip this — text is
698
+ * quoted-printable re-encoded above 990 octets and base64 is emitted at a
699
+ * fixed width — so a hit is always a header.
700
+ *
701
+ * One pass over the string, counting UTF-8 octets per CRLF-delimited line,
702
+ * with NO allocation. The earlier `text.split(CRLF)` held one array slot and
703
+ * one substring per line for the whole scan, and a legal body of millions of
704
+ * short lines — pasted logs, say — is millions of lines. Only a CRLF pair
705
+ * ends a line, exactly as the split did; a bare CR or LF is content.
706
+ */
707
+ function findOverlongLine(text) {
708
+ let index = 0;
709
+ let octets = 0;
710
+ for (let i = 0; i < text.length; i++) {
711
+ const c = text.charCodeAt(i);
712
+ if (c === 13 && text.charCodeAt(i + 1) === 10) {
713
+ if (octets > MAX_LINE_OCTETS) return {
714
+ index,
715
+ octets
716
+ };
717
+ index++;
718
+ octets = 0;
719
+ i++;
720
+ } else if (c < 128) octets += 1;
721
+ else if (c < 2048) octets += 2;
722
+ else if (c >= 55296 && c <= 56319 && i + 1 < text.length) {
723
+ const d = text.charCodeAt(i + 1);
724
+ if (d >= 56320 && d <= 57343) {
725
+ octets += 4;
726
+ i++;
727
+ } else octets += 3;
728
+ } else octets += 3;
729
+ }
730
+ return octets > MAX_LINE_OCTETS ? {
731
+ index,
732
+ octets
733
+ } : void 0;
734
+ }
735
+ /**
736
+ * Escape a value for a quoted-string parameter (RFC 2045 §5.1 value, whose
737
+ * quoted-string comes from RFC 5322 §3.2.4).
738
+ *
739
+ * `qtext` excludes both `\\` and `"`, so each may appear only as a
740
+ * quoted-pair. Deleting the quotes and leaving backslashes raw — what this
741
+ * did before — is wrong in two ways: a TRAILING backslash escapes the
742
+ * closing quote and leaves the parameter unterminated, and every embedded
743
+ * backslash is silently eaten by a conformant parser, so a Windows path
744
+ * `C:\\Users\\bob\\report.pdf` arrived as `C:Usersbobreport.pdf`. Measured,
745
+ * Python's `email.policy.default` and postal-mime both report
746
+ * `InvalidHeaderDefect: end of header inside quoted string` on the first
747
+ * case and disagree with a naive parser on the second.
748
+ *
749
+ * Backslash first, then quote — the other order would double-escape.
750
+ *
751
+ * Note this file's own `foldHeader` already tracks quoted-pairs when
752
+ * folding, so the emitter was contradicting the folder.
753
+ */
754
+ function quoteParam(value) {
755
+ return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
756
+ }
757
+ function base64(bytes) {
758
+ let binary = "";
759
+ const chunk = 32768;
760
+ for (let i = 0; i < bytes.length; i += chunk) binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
761
+ return btoa(binary);
762
+ }
763
+ function base64Wrapped(bytes) {
764
+ const encoded = base64(bytes);
765
+ const lines = [];
766
+ for (let i = 0; i < encoded.length; i += 76) lines.push(encoded.slice(i, i + 76));
767
+ return lines.join(CRLF);
768
+ }
769
+ function quotedPrintable(text) {
770
+ const bytes = new TextEncoder().encode(text);
771
+ const out = new Uint8Array(Math.ceil(bytes.length * 3 * (76 / 73)) + 8);
772
+ let n = 0;
773
+ let lineLen = 0;
774
+ const HEX = "0123456789ABCDEF";
775
+ const put = (c) => {
776
+ out[n++] = c;
777
+ };
778
+ const flush = (c0, c1, c2) => {
779
+ const width = c2 !== void 0 ? 3 : c1 !== void 0 ? 2 : 1;
780
+ if (lineLen + width > 75) {
781
+ put(61);
782
+ put(13);
783
+ put(10);
784
+ lineLen = 0;
785
+ }
786
+ put(c0);
787
+ if (c1 !== void 0) put(c1);
788
+ if (c2 !== void 0) put(c2);
789
+ lineLen += width;
790
+ };
791
+ const quote = (b) => flush(61, HEX.charCodeAt(b >> 4), HEX.charCodeAt(b & 15));
792
+ for (let i = 0; i < bytes.length; i++) {
793
+ const b = bytes[i];
794
+ if (b === 10) {
795
+ put(13);
796
+ put(10);
797
+ lineLen = 0;
798
+ continue;
799
+ }
800
+ if (b === 13) {
801
+ if (bytes[i + 1] === 10) continue;
802
+ put(13);
803
+ put(10);
804
+ lineLen = 0;
805
+ continue;
806
+ }
807
+ if (b === 32 || b === 9) {
808
+ if (i === bytes.length - 1 || bytes[i + 1] === 10 || bytes[i + 1] === 13) quote(b);
809
+ else flush(b);
810
+ continue;
811
+ }
812
+ if (b === 61 || b < 32 || b > 126) {
813
+ quote(b);
814
+ continue;
815
+ }
816
+ flush(b);
817
+ }
818
+ return bytesToAsciiString(out.subarray(0, n));
819
+ }
820
+ /** Pure-ASCII bytes to a string, in the same 32K chunks `base64` uses. */
821
+ function bytesToAsciiString(bytes) {
822
+ let s = "";
823
+ const chunk = 32768;
824
+ for (let i = 0; i < bytes.length; i += chunk) s += String.fromCharCode(...bytes.subarray(i, i + chunk));
825
+ return s;
826
+ }
827
+ /**
828
+ * Whether a body must be quoted-printable encoded: anything outside
829
+ * printable ASCII, or any line longer than 990 characters (RFC 5322 §2.1.1
830
+ * makes 998 octets the hard limit). Only LF and CRLF end a line; a bare CR
831
+ * is a control character like any other and forces the encoding.
832
+ *
833
+ * One pass over the string with NO allocation. The earlier shape — strip
834
+ * every line break with a global `replace`, test the copy, then `split` the
835
+ * text into an array of lines — was linear in the message but held a copy
836
+ * of the body plus one array slot and one substring per line, and a legal
837
+ * 6 MB body of millions of two-character lines needed 64 MB for the strip
838
+ * and another 24-40 MB for the split. Those are per-LINE costs, invisible
839
+ * to any size projection: the message fit.
840
+ */
841
+ function needsQuotedPrintable(text) {
842
+ let lineLen = 0;
843
+ for (let i = 0; i < text.length; i++) {
844
+ const c = text.charCodeAt(i);
845
+ if (c === 10) {
846
+ lineLen = 0;
847
+ continue;
848
+ }
849
+ if (c === 13) {
850
+ if (text.charCodeAt(i + 1) === 10) continue;
851
+ return true;
852
+ }
853
+ if (c < 32 || c > 126) return true;
854
+ lineLen++;
855
+ if (lineLen > 990) return true;
856
+ }
857
+ return false;
858
+ }
859
+ /**
860
+ * The 7bit body: `text` with every bare LF widened to CRLF, and every CRLF
861
+ * kept. Only called once `needsQuotedPrintable` has returned false, so every
862
+ * character is printable ASCII, LF, or the CR of a CRLF — one byte each —
863
+ * and a byte buffer is exact.
864
+ *
865
+ * NOT `text.replace(/\r?\n/g, CRLF)`: V8 builds a global-regex replacement
866
+ * from a parts array that holds two or three slots per match, grown by
867
+ * doubling, so that one line cost 144 MB of heap on a legal 6 MB body of
868
+ * 3M lines and terminated a 128 MB isolate — the largest single allocation
869
+ * in the whole build, for a body that renders to 9 MB. The buffer here is
870
+ * the output size, known up front from one counting pass. A body that is
871
+ * already CRLF-only (or has no line breaks) is returned as-is.
872
+ */
873
+ function to7bitBody(text) {
874
+ let bare = 0;
875
+ for (let i = 0; i < text.length; i++) if (text.charCodeAt(i) === 10 && (i === 0 || text.charCodeAt(i - 1) !== 13)) bare++;
876
+ if (bare === 0) return text;
877
+ const out = new Uint8Array(text.length + bare);
878
+ let n = 0;
879
+ for (let i = 0; i < text.length; i++) {
880
+ const c = text.charCodeAt(i);
881
+ if (c === 10 && (i === 0 || text.charCodeAt(i - 1) !== 13)) out[n++] = 13;
882
+ out[n++] = c;
883
+ }
884
+ return bytesToAsciiString(out);
885
+ }
886
+ /**
887
+ * Lower bound on the octets `text` will occupy in the rendered message, in
888
+ * one pass over the string and with NO allocation. Every UTF-8 byte yields at
889
+ * least one octet on either body path — the 7bit path copies bytes and only
890
+ * ever widens a bare LF to CRLF; the quoted-printable path emits each
891
+ * non-ASCII byte as `=XX` (3 octets) and every other byte as itself, and its
892
+ * soft breaks only add. So `ascii + 3 * nonAscii` can never overshoot the
893
+ * real size, which keeps a false rejection impossible; `buildMime`'s final
894
+ * check remains the authority. `TextEncoder.encode` would give the same count
895
+ * but at the price of a full copy, which is exactly the allocation this
896
+ * bound exists to avoid.
897
+ */
898
+ function minBodyOctets(text) {
899
+ let octets = 0;
900
+ for (let i = 0; i < text.length; i++) {
901
+ const c = text.charCodeAt(i);
902
+ if (c < 128) octets += 1;
903
+ else if (c < 2048) octets += 6;
904
+ else if (c >= 55296 && c <= 56319 && i + 1 < text.length) {
905
+ const d = text.charCodeAt(i + 1);
906
+ if (d >= 56320 && d <= 57343) {
907
+ octets += 12;
908
+ i++;
909
+ continue;
910
+ }
911
+ octets += 9;
912
+ } else octets += 9;
913
+ }
914
+ return octets;
915
+ }
916
+ /** Octets a base64 body of `n` bytes occupies at 76 columns, excluding CRLF. */
917
+ function base64Octets(n) {
918
+ return Math.ceil(n / 3) * 4;
919
+ }
920
+ function renderPart(part) {
921
+ return part.headers.map(([k, v]) => foldHeader(k, v)).join(CRLF) + "\r\n\r\n" + part.body;
922
+ }
923
+ function buildBodyPart(contentType, body) {
924
+ const useQp = needsQuotedPrintable(body);
925
+ return {
926
+ headers: [["Content-Type", `${contentType}; charset=UTF-8`], ["Content-Transfer-Encoding", useQp ? "quoted-printable" : "7bit"]],
927
+ body: useQp ? quotedPrintable(body) : to7bitBody(body)
928
+ };
929
+ }
930
+ function buildAttachmentPart(att) {
931
+ const headers = [
932
+ ["Content-Type", `${att.contentType}; name="${quoteParam(att.filename)}"`],
933
+ ["Content-Transfer-Encoding", "base64"],
934
+ ["Content-Disposition", `${att.disposition}; filename="${quoteParam(att.filename)}"`]
935
+ ];
936
+ if (att.contentId) {
937
+ const cid = att.contentId.startsWith("<") ? att.contentId : `<${att.contentId}>`;
938
+ headers.push(["Content-ID", cid]);
939
+ }
940
+ return {
941
+ headers,
942
+ body: base64Wrapped(att.bytes)
943
+ };
944
+ }
945
+ function makeBoundary(prefix) {
946
+ const bytes = /* @__PURE__ */ new Uint8Array(16);
947
+ crypto.getRandomValues(bytes);
948
+ return `=_void_${prefix}_${base64(bytes).replace(/[+/=]/g, "")}`;
949
+ }
950
+ function wrapMultipart(type, parts, extra = {}) {
951
+ const boundary = makeBoundary(type);
952
+ const headerEntries = [["Content-Type", `multipart/${type}; boundary="${boundary}"`]];
953
+ for (const [k, v] of Object.entries(extra)) headerEntries.push([k, v]);
954
+ return {
955
+ headers: headerEntries,
956
+ body: parts.map((p) => `--${boundary}${CRLF}${renderPart(p)}`).join(CRLF) + `${CRLF}--${boundary}--${CRLF}`
957
+ };
958
+ }
959
+ const RESERVED_HEADERS = new Set([
960
+ "from",
961
+ "to",
962
+ "cc",
963
+ "bcc",
964
+ "subject",
965
+ "reply-to",
966
+ "date",
967
+ "message-id",
968
+ "in-reply-to",
969
+ "references",
970
+ "mime-version",
971
+ "content-type",
972
+ "content-transfer-encoding",
973
+ "content-disposition"
974
+ ].map((h) => h.toLowerCase()));
975
+ /**
976
+ * Lower bound on the octets one header VALUE occupies once `encodeWord` (or
977
+ * `formatAddress`, for a display name) and `foldHeader` have rendered it, in
978
+ * one pass over the string and with NO allocation.
979
+ *
980
+ * `encodeWord` leaves a value alone when every character is printable ASCII
981
+ * and otherwise B-encodes ALL of it, so the two paths need different terms:
982
+ *
983
+ * - Plain: every character is at least one octet, EXCEPT whitespace.
984
+ * `foldHeader` re-joins a long value from its non-WSP tokens, so a run of
985
+ * spaces or tabs collapses to one, and a bound that charged them could
986
+ * reject a message that fits. Only the non-WSP characters count.
987
+ * - Encoded: each RFC 2047 word carries at most `ENCODED_WORD_PAYLOAD_BYTES`
988
+ * (45) UTF-8 bytes as base64 — `ceil(n / 3) * 4 >= 4n / 3` octets — inside
989
+ * 12 octets of `=?UTF-8?B?` and `?=`, so there are at least `floor(n / 45)`
990
+ * words' worth of framing. Whitespace inside the value is base64 like any
991
+ * other byte; only the single separators between words fold.
992
+ *
993
+ * Folding (`foldHeader`) can only ADD octets beyond that — a `CRLF SP` per
994
+ * line — and quoting a display name (`formatAddress`) adds the quotes and
995
+ * escapes, so neither path can render fewer octets than this returns.
996
+ * Measured, the encoded bound is 96% of the rendered size for CJK, where the
997
+ * real cost is 5 octets per character; a UTF-16 `length` would have counted
998
+ * 1 and let the isolate die on a header five times over the limit.
999
+ */
1000
+ function minHeaderValueOctets(value) {
1001
+ let utf8 = 0;
1002
+ let visible = 0;
1003
+ let encoded = false;
1004
+ for (let i = 0; i < value.length; i++) {
1005
+ const c = value.charCodeAt(i);
1006
+ if (c < 128) {
1007
+ utf8 += 1;
1008
+ if (c === 32 || c === 9) continue;
1009
+ if (c < 32 || c === 127) encoded = true;
1010
+ visible += 1;
1011
+ } else {
1012
+ encoded = true;
1013
+ if (c < 2048) utf8 += 2;
1014
+ else if (c >= 55296 && c <= 56319 && i + 1 < value.length) {
1015
+ const d = value.charCodeAt(i + 1);
1016
+ if (d >= 56320 && d <= 57343) {
1017
+ utf8 += 4;
1018
+ i++;
1019
+ continue;
1020
+ }
1021
+ utf8 += 3;
1022
+ } else utf8 += 3;
1023
+ }
1024
+ }
1025
+ if (!encoded) return visible;
1026
+ return Math.floor(utf8 * 4 / 3) + 12 * Math.floor(utf8 / ENCODED_WORD_PAYLOAD_BYTES);
1027
+ }
1028
+ /** Lower bound on one rendered address: its display name, plus the address itself verbatim. */
1029
+ function minAddressOctets(addr) {
1030
+ return addr.email.length + (addr.name ? minHeaderValueOctets(addr.name) : 0);
1031
+ }
1032
+ /**
1033
+ * Lower bound on the octets of a value rendered VERBATIM — never through
1034
+ * `encodeWord` — into a header `foldHeader` may still fold: `In-Reply-To`,
1035
+ * `References`, and an attachment part's `Content-Type` media type and
1036
+ * `Content-ID`. Every non-WSP UTF-16 code unit is at least one octet of
1037
+ * UTF-8; a WSP run may collapse to a single space when the line folds, so
1038
+ * none is charged. The `<>` around a Content-ID and the fold's `CRLF SP`
1039
+ * only add.
1040
+ *
1041
+ * `minHeaderValueOctets` cannot stand in here: a non-ASCII value sends it
1042
+ * down the encoded-word formula (4.8 octets per CJK character) while these
1043
+ * values render as raw UTF-8 (3), so it would overshoot and could reject a
1044
+ * message that fits.
1045
+ */
1046
+ function minVerbatimOctets(value) {
1047
+ let octets = 0;
1048
+ for (let i = 0; i < value.length; i++) {
1049
+ const c = value.charCodeAt(i);
1050
+ if (c !== 32 && c !== 9) octets += 1;
1051
+ }
1052
+ return octets;
1053
+ }
1054
+ /**
1055
+ * Octets every attachment part costs before any of its values: what
1056
+ * `wrapMultipart`, `renderPart` and `buildAttachmentPart` emit for a part
1057
+ * whose filename, media type, disposition and content are all empty. Each
1058
+ * term is emitted verbatim on every path — `foldHeader` returns a short
1059
+ * line as-is and re-joins a long one from its tokens with at least one
1060
+ * space between them, so the space after `Name:` and the one after `;` are
1061
+ * floors, not counts — and a fold's `CRLF SP` only adds. The boundary's
1062
+ * random suffix is left out: `makeBoundary` strips `+`, `/` and `=`, so it
1063
+ * has no provable minimum, and `mixed` is the shortest prefix an attachment
1064
+ * part can sit under.
1065
+ */
1066
+ const ATTACHMENT_PART_MIN_OCTETS = 117;
1067
+ /**
1068
+ * Cap on the attachment COUNT, checked against the raw list in
1069
+ * `normalizeEmail` before a single entry is normalized.
1070
+ *
1071
+ * Every part costs at least `ATTACHMENT_PART_MIN_OCTETS` of framing plus
1072
+ * its filename twice, and `normalizeAttachment` refuses an empty filename,
1073
+ * so the size projection in `buildMime` charges a part at least 119 octets
1074
+ * and refuses one more part than this whatever else the message holds.
1075
+ * That projection runs only after `normalizeEmail` has normalized EVERY
1076
+ * entry, and `MAX_ATTACHMENT_BYTES_TOTAL` cannot bound that loop for a
1077
+ * zero-byte attachment — its running total never advances — while the
1078
+ * normalized copy (the object, a fresh `Uint8Array`, the `TextEncoder`
1079
+ * that made it) costs about 275 bytes of live heap per entry. Measured, 2M
1080
+ * empty attachments terminated a 128 MB isolate in 0.3 s instead of
1081
+ * returning `MIME_ERROR`; this many normalize in 24 MB and are then refused
1082
+ * by the post-render size check under the same ceiling.
1083
+ *
1084
+ * Derived, not chosen: it is exactly the largest count the projection can
1085
+ * admit, so this cannot reject a message that would have been accepted, and
1086
+ * `buildMime` remains the authority on the final size. Same class as
1087
+ * `MAX_ATTACHMENT_BYTES_TOTAL` — a developer-experience guardrail on the
1088
+ * app's own input, not the security boundary.
1089
+ */
1090
+ const MAX_ATTACHMENTS = Math.floor(MAX_MESSAGE_BYTES / 119);
1091
+ /**
1092
+ * Octets every custom header costs around its value: the `: ` after the
1093
+ * name and the CRLF that ends its line. Exact, not a floor, once the name
1094
+ * is counted: `buildMime` renders each non-reserved custom header through
1095
+ * `foldHeader`, which emits the name verbatim, then `: ` — or, when the
1096
+ * value's first token does not fit beside the name, `:` CRLF SP, which is
1097
+ * more — and `join(CRLF)` ends every header line, a custom header never
1098
+ * being the last of the block (the body part's own headers follow it).
1099
+ */
1100
+ const CUSTOM_HEADER_FRAMING_OCTETS = 4;
1101
+ /**
1102
+ * Lower bound on the octets of every header `buildMime` renders from
1103
+ * caller-supplied values: `Subject`, the custom headers, the addresses in
1104
+ * `From`, `To`, `Cc` and `Reply-To`, the `In-Reply-To` / `References` pair
1105
+ * `replyEmail` threads through (remote-sender controlled, up to the whole
1106
+ * inbound message), and each attachment part's `Content-Type`, `Content-ID`
1107
+ * and — twice, once per header it is rendered into — filename. `Bcc` is
1108
+ * never rendered and a reserved custom header is dropped, so neither is
1109
+ * counted — charging either could reject a message that fits. The fixed
1110
+ * `Name: ` and `CRLF` of the headers `buildMime` itself names — `Subject`,
1111
+ * the address lists, the threading pair — are omitted for the same reason
1112
+ * a lower bound may omit any term. They are NOT omitted where the caller
1113
+ * controls the name or the count, because there they can be the whole
1114
+ * message:
1115
+ *
1116
+ * - A custom header's name is rendered verbatim, and `normalizeEmail`
1117
+ * bounds ONE name at under a kilobyte, not the sum, nor the count. With
1118
+ * empty values the names and the `: ` CRLF around each are the entire
1119
+ * size, and the framing is exact once the name is counted (see
1120
+ * `CUSTOM_HEADER_FRAMING_OCTETS`), so charging both cannot reject a
1121
+ * message that fits. Measured before the name was charged, 60k empty
1122
+ * headers with 500-character names — a 30 MB message — projected to
1123
+ * zero, rendered in full and peaked at 121 MB of a 128 MB isolate; 100k
1124
+ * of them (50 MB) went to 169 MB.
1125
+ * - An attachment part: `normalizeAttachment` bounds ONE filename at under
1126
+ * a kilobyte, not the sum, and a message is free to carry any number of
1127
+ * parts, so the filename and the framing every part is wrapped in are
1128
+ * the whole size of a message of empty attachments. Measured, 50k
1129
+ * zero-byte attachments with legal 986-character filenames rendered to
1130
+ * 106 MB — held three to four times over between the parts, their join,
1131
+ * `rawText` and `raw` — and terminated a 128 MB isolate before the final
1132
+ * size check could return `MIME_ERROR`.
1133
+ *
1134
+ * The header COUNT is deliberately not bounded here. Each header now costs
1135
+ * at least five octets (a one-character name, `: `, CRLF), so the count
1136
+ * the projection can admit is finite, and past a few hundred thousand keys
1137
+ * the caller's own object — measured at about 74 bytes per key, before
1138
+ * `sendEmail` is called — is the dominant cost, which no check inside this
1139
+ * module can pre-empt. Rendering does add live heap per header (about
1140
+ * 150 bytes: two `Object.entries`, the `topHeaders` pair, the folded line),
1141
+ * so a legal message of 500k four-character headers still does not fit a
1142
+ * 128 MB isolate while one of 400k does; a bound on that would be the
1143
+ * framework's own number — the platform proxy bounds bytes, not headers —
1144
+ * and is left as a product decision rather than invented here.
1145
+ */
1146
+ function minHeaderOctets(email) {
1147
+ let octets = minHeaderValueOctets(email.subject);
1148
+ for (const [name, value] of Object.entries(email.headers)) if (!RESERVED_HEADERS.has(name.toLowerCase())) octets += name.length + CUSTOM_HEADER_FRAMING_OCTETS + minHeaderValueOctets(value);
1149
+ octets += minAddressOctets(email.from);
1150
+ for (const addr of email.to) octets += minAddressOctets(addr);
1151
+ for (const addr of email.cc) octets += minAddressOctets(addr);
1152
+ if (email.replyTo) octets += minAddressOctets(email.replyTo);
1153
+ if (email.threading?.inReplyTo) octets += minVerbatimOctets(email.threading.inReplyTo);
1154
+ if (email.threading?.references) octets += minVerbatimOctets(email.threading.references);
1155
+ for (const att of email.attachments) {
1156
+ octets += ATTACHMENT_PART_MIN_OCTETS + 2 * att.filename.length;
1157
+ octets += minVerbatimOctets(att.contentType);
1158
+ if (att.contentId) octets += minVerbatimOctets(att.contentId);
1159
+ }
1160
+ return octets;
1161
+ }
1162
+ function generateMessageId(fromEmail) {
1163
+ const domain = fromEmail.split("@")[1] ?? "localhost";
1164
+ const bytes = /* @__PURE__ */ new Uint8Array(12);
1165
+ crypto.getRandomValues(bytes);
1166
+ return `<${base64(bytes).replace(/[+/=]/g, "")}@${domain}>`;
1167
+ }
1168
+ /**
1169
+ * Build the raw RFC 5322 message body. Returns the message bytes plus the
1170
+ * generated Message-ID header so callers can surface it in the result.
1171
+ */
1172
+ function buildMime(email) {
1173
+ const projected = minHeaderOctets(email) + (email.text ? minBodyOctets(email.text) : 0) + (email.html ? minBodyOctets(email.html) : 0) + email.attachments.reduce((sum, a) => sum + base64Octets(a.bytes.byteLength), 0);
1174
+ if (projected > 10485760) throw new MimeError("MIME_ERROR", `email: message is at least ${projected} bytes once encoded, which exceeds Cloudflare limit of ${MAX_MESSAGE_BYTES} bytes.`);
1175
+ const messageId = generateMessageId(email.from.email);
1176
+ const topHeaders = [
1177
+ ["Date", (/* @__PURE__ */ new Date()).toUTCString().replace("GMT", "+0000")],
1178
+ ["From", formatAddress(email.from)],
1179
+ ["To", formatAddressList(email.to)]
1180
+ ];
1181
+ if (email.cc.length > 0) topHeaders.push(["Cc", formatAddressList(email.cc)]);
1182
+ if (email.replyTo) topHeaders.push(["Reply-To", formatAddress(email.replyTo)]);
1183
+ topHeaders.push(["Subject", encodeWord(email.subject)]);
1184
+ topHeaders.push(["Message-ID", messageId]);
1185
+ if (email.threading?.inReplyTo) topHeaders.push(["In-Reply-To", email.threading.inReplyTo]);
1186
+ if (email.threading?.references) topHeaders.push(["References", email.threading.references]);
1187
+ topHeaders.push(["MIME-Version", "1.0"]);
1188
+ for (const [name, value] of Object.entries(email.headers)) {
1189
+ if (RESERVED_HEADERS.has(name.toLowerCase())) continue;
1190
+ topHeaders.push([name, encodeWord(value)]);
1191
+ }
1192
+ const textPart = email.text ? buildBodyPart("text/plain", email.text) : void 0;
1193
+ const htmlPart = email.html ? buildBodyPart("text/html", email.html) : void 0;
1194
+ let bodyPart;
1195
+ if (textPart && htmlPart) bodyPart = wrapMultipart("alternative", [textPart, htmlPart]);
1196
+ else bodyPart = textPart ?? htmlPart;
1197
+ if (email.attachments.length > 0) {
1198
+ const inlineAttachments = email.attachments.filter((a) => a.disposition === "inline");
1199
+ const fileAttachments = email.attachments.filter((a) => a.disposition !== "inline");
1200
+ if (inlineAttachments.length > 0 && htmlPart) bodyPart = wrapMultipart("related", [bodyPart, ...inlineAttachments.map(buildAttachmentPart)]);
1201
+ else fileAttachments.push(...inlineAttachments);
1202
+ if (fileAttachments.length > 0) bodyPart = wrapMultipart("mixed", [bodyPart, ...fileAttachments.map(buildAttachmentPart)]);
1203
+ }
1204
+ for (const [k, v] of bodyPart.headers) topHeaders.push([k, v]);
1205
+ const rawText = topHeaders.map(([k, v]) => foldHeader(k, v)).join(CRLF) + "\r\n\r\n" + bodyPart.body;
1206
+ const raw = new TextEncoder().encode(rawText);
1207
+ const overlong = findOverlongLine(rawText);
1208
+ if (overlong !== void 0) throw new MimeError("MIME_ERROR", `email: header line ${overlong.index + 1} is ${overlong.octets} octets, over the ${MAX_LINE_OCTETS}-octet limit in RFC 5322 §2.1.1. A value with no whitespace cannot be folded — shorten it, or move it into the body.`);
1209
+ if (raw.byteLength > 10485760) throw new MimeError("MIME_ERROR", `email: message size ${raw.byteLength} bytes exceeds Cloudflare limit of ${MAX_MESSAGE_BYTES} bytes.`);
1210
+ return {
1211
+ raw,
1212
+ messageId
1213
+ };
1214
+ }
1215
+ //#endregion
1216
+ export { parseAddress as a, normalizeEmail as i, MimeError as n, validateAddressParts as o, buildMime as r, MAX_MESSAGE_BYTES as t };