void 0.20.2 → 0.21.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 (310) hide show
  1. package/README.md +2 -2
  2. package/dist/agents-gni4eqBu.mjs +111 -0
  3. package/dist/auth-3sGB0zJ1.mjs +22 -0
  4. package/dist/{auth-DPl6kck4.mjs → auth-B9_D6ygs.mjs} +6 -241
  5. package/dist/auth-CAw7zHqj.mjs +2 -0
  6. package/dist/auth-client-BWBT8HLp.mjs +6 -0
  7. package/dist/auth-client-Bawdj38n.d.mts +7 -0
  8. package/dist/auth-client-react-7GFffu8R.d.mts +7 -0
  9. package/dist/auth-client-react-CMLwT2xI.mjs +6 -0
  10. package/dist/auth-client-solid-DyTFL4cq.d.mts +7 -0
  11. package/dist/auth-client-solid-UoreJWZG.mjs +6 -0
  12. package/dist/auth-client-svelte-Cv_WvrRk.mjs +6 -0
  13. package/dist/auth-client-svelte-zetW0KIH.d.mts +7 -0
  14. package/dist/auth-client-vue-CFk7Xbv3.mjs +6 -0
  15. package/dist/auth-client-vue-bChShbQP.d.mts +7 -0
  16. package/dist/{auth-cmd-gniL2fNt.mjs → auth-cmd-MkBG2u1f.mjs} +5 -5
  17. package/dist/{auth-link-NZdjCmSc.mjs → auth-link-Devmv96E.mjs} +5 -5
  18. package/dist/{better-auth-shared-DSCeohOK.d.mts → better-auth-shared-BoVwA5Vm.d.mts} +1 -6
  19. package/dist/{better-auth-shared-CYw1T3k4.mjs → better-auth-shared-syGYYmnY.mjs} +1 -1
  20. package/dist/{build-cmd-sI18tX_O.mjs → build-cmd-DsBGwtfs.mjs} +6 -4
  21. package/dist/{cache-IHn5MwBC.mjs → cache-BMw8eMyF.mjs} +6 -4
  22. package/dist/{cancel-deploy-C5qTOdLi.mjs → cancel-deploy-B3_s9NIN.mjs} +5 -3
  23. package/dist/cf-access-rAZxzMkL.mjs +181 -0
  24. package/dist/cf-build-output-CTdlo6Rt.mjs +520 -0
  25. package/dist/cli/cf-compat.d.mts +1 -0
  26. package/dist/cli/cf-compat.mjs +934 -0
  27. package/dist/cli/cli.mjs +61 -38
  28. package/dist/cli/env-schema-probe.mjs +3 -3
  29. package/dist/client-DAAivdid.mjs +2 -0
  30. package/dist/{client-dHfSJvAN.mjs → client-PdAJ-F0t.mjs} +148 -89
  31. package/dist/{cloudflare-auth-6M5llVPC.mjs → cloudflare-auth-BCmTc1X_.mjs} +13 -44
  32. package/dist/{cloudflare-cmd-4RPGN3KB.mjs → cloudflare-cmd-D6PrKn7o.mjs} +4 -7
  33. package/dist/{cloudflare-connect-t1UU5svD.mjs → cloudflare-connect-DloREDlc.mjs} +6 -6
  34. package/dist/cloudflare-operations-BHNJVDbN.mjs +2 -0
  35. package/dist/{cloudflare-operations-BzWnlC1_.mjs → cloudflare-operations-CPEdHYhM.mjs} +37 -39
  36. package/dist/cloudflare-user-output-Q-k0QoNQ.mjs +20 -0
  37. package/dist/collect-DAUItMDS.mjs +2 -0
  38. package/dist/collect-DXHNWHcT.mjs +48 -0
  39. package/dist/config--T87TD8T.mjs +2 -0
  40. package/dist/{config-NOG_U1aK.mjs → config-BSe70f4T.mjs} +4 -6
  41. package/dist/config-DDIFxQYx.mjs +2 -0
  42. package/dist/{config-uNGuFsI2.mjs → config-Dxr6cTXn.mjs} +22 -37
  43. package/dist/config-DywxBLQC.d.mts +185 -0
  44. package/dist/config-entry.d.mts +6 -0
  45. package/dist/config-entry.mjs +7 -0
  46. package/dist/config-uP7ZDtVZ.mjs +21 -0
  47. package/dist/config-write-y3CBI_ux.mjs +60 -0
  48. package/dist/{connect-Bfk31O_8.mjs → connect-BvC9kolB.mjs} +8 -6
  49. package/dist/{create-project-Bk9Z0-Jg.mjs → create-project-C9lQhRzj.mjs} +9 -25
  50. package/dist/create-project-CI-17RVJ.mjs +2 -0
  51. package/dist/database-provider-Cx525bwX.mjs +6 -0
  52. package/dist/database-provider.mjs +1 -5
  53. package/dist/{db-BkRoptAt.mjs → db-R7IQgOv5.mjs} +48 -44
  54. package/dist/{delete-DouASY9P.mjs → delete-CghI_NXn.mjs} +6 -4
  55. package/dist/deploy-CYPymbtG.mjs +2 -0
  56. package/dist/{deploy-DTaWUS1S.mjs → deploy-H968Lo4T.mjs} +765 -197
  57. package/dist/discover-BBzDZe_o.mjs +2 -0
  58. package/dist/{discover-xvfrgJeo.mjs → discover-C4O6YxVS.mjs} +3 -8
  59. package/dist/{dist-BrsS7cai.mjs → dist-4WkAWhWx.mjs} +1 -15
  60. package/dist/{dist-m40_XgNh.mjs → dist-Bn8Kodjp.mjs} +1 -1
  61. package/dist/dist-C5fND3R0.mjs +2 -0
  62. package/dist/dist-D7-nEOXi.mjs +2 -0
  63. package/dist/{output-B0cfNSx5.mjs → dist-Dn6nn2IU.mjs} +3 -926
  64. package/dist/{domain-1RhhOVrC.mjs → domain-CITt8lC-.mjs} +7 -5
  65. package/dist/{route-types-Da-DpyUp.mjs → drizzle-Beb2Am5S.mjs} +1 -280
  66. package/dist/{email-Ce6SQq-i.mjs → email-C5kaZXzJ.mjs} +2 -2
  67. package/dist/{email-C-lGh51B.mjs → email-_8VmyX7V.mjs} +14 -12
  68. package/dist/{entry-DU3oDoQ3.mjs → entry-DdRFGK0y.mjs} +2 -2
  69. package/dist/{env-DJHsPE7Z.mjs → env-DV4r3nHz.mjs} +9 -7
  70. package/dist/{env-DBKmK4vc.mjs → env-DX_v-Q-v.mjs} +1 -1
  71. package/dist/env-Yz4HcvHs.mjs +78 -0
  72. package/dist/env-helpers--wFmQ_5Q.mjs +136 -0
  73. package/dist/{env-types-BNPhro-M.mjs → env-types-CWDtqHgw.mjs} +6 -2
  74. package/dist/{env-validation-CF6KvTRf.mjs → env-validation-BsFEXps5.mjs} +19 -28
  75. package/dist/env-validation-DIDGM7h4.mjs +2 -0
  76. package/dist/fetch-CXDChK7B.mjs +18 -0
  77. package/dist/fetch-_SeGZao9.d.mts +57 -0
  78. package/dist/fetch-stream-AOByI7Ki.mjs +81 -0
  79. package/dist/fetch-stream-Bjf0hoZb.d.mts +49 -0
  80. package/dist/gen-BBiIZw6g.mjs +2 -0
  81. package/dist/{gen-B_wPnVTK.mjs → gen-CFOEEc-t.mjs} +13 -11
  82. package/dist/{generate-RTK8_kK1.mjs → generate-C0VY6RVf.mjs} +2 -2
  83. package/dist/{github-cmd-PW7ZnWTp.mjs → github-cmd-jYjha2LO.mjs} +18 -22
  84. package/dist/{handler-D1hLsObx.d.mts → handler-BXJTXd02.d.mts} +6 -1
  85. package/dist/handler-DghKr6dU.mjs +150 -0
  86. package/dist/head-D_QRR5Yd.mjs +112 -0
  87. package/dist/head-client-DzmJGN4C.mjs +90 -0
  88. package/dist/{headers-BAHwgHdW.mjs → headers-B_HBMgi0.mjs} +2 -2
  89. package/dist/{help-CwOX-zmI.mjs → help-C46mnlUS.mjs} +17 -12
  90. package/dist/help-CS_nAsWu.mjs +2 -0
  91. package/dist/index.d.mts +2 -20
  92. package/dist/index.mjs +116 -60
  93. package/dist/{init-BWZ7q5Z4.mjs → init-CSXmvxKu.mjs} +49 -50
  94. package/dist/{link-Rmvu2Wl_.mjs → link-DxMOALXk.mjs} +7 -5
  95. package/dist/{list-DEE2S6mY.mjs → list-0wQs0oYg.mjs} +7 -5
  96. package/dist/live-CB1y5IuC.mjs +411 -0
  97. package/dist/live-CKiJilLr.d.mts +105 -0
  98. package/dist/local-d1-DzykTWY8.mjs +104 -0
  99. package/dist/{login-Uvferzmm.mjs → login-CKk5NX4d.mjs} +6 -11
  100. package/dist/login-Ch9cgWRF.mjs +2 -0
  101. package/dist/{logs-27FenuiC.mjs → logs-hxWBSoej.mjs} +6 -4
  102. package/dist/migrate-CLty4mZR.mjs +2 -0
  103. package/dist/migrate-DcW8F2W8.mjs +285 -0
  104. package/dist/migration-handler-DM4clYj5.d.mts +51 -0
  105. package/dist/{neon-DHwd2zvC.mjs → neon-n74ta1Pr.mjs} +1 -1
  106. package/dist/{node-Ez5KW5rn.mjs → node-Cupyf7-s.mjs} +6 -6
  107. package/dist/{operator-auth-B3e08unv.mjs → operator-auth-BkVgJqv-.mjs} +2 -2
  108. package/dist/{operator-client-LUZnlnYk.mjs → operator-client-A0iex2yi.mjs} +2 -1
  109. package/dist/{operator-cmd-CjOTmAYE.mjs → operator-cmd-BEUYhaHB.mjs} +6 -6
  110. package/dist/output-urU86XeT.mjs +146 -0
  111. package/dist/{package-json-CPoWX79C.mjs → package-json-iCbMg5XF.mjs} +1 -1
  112. package/dist/pages/client.d.mts +5 -2
  113. package/dist/pages/client.mjs +5 -3
  114. package/dist/pages/head-client.mjs +1 -89
  115. package/dist/pages/head.mjs +1 -111
  116. package/dist/pages/index.d.mts +2 -3
  117. package/dist/pages/index.mjs +7 -7
  118. package/dist/pages/islands-plugin.mjs +2 -2
  119. package/dist/pages/prefetch.d.mts +2 -30
  120. package/dist/pages/prefetch.mjs +1 -89
  121. package/dist/pages/protocol.d.mts +2 -2
  122. package/dist/pages/protocol.mjs +3 -3
  123. package/dist/pages/serialize.d.mts +2 -9
  124. package/dist/pages/serialize.mjs +1 -13
  125. package/dist/plan-BEZ8VJW0.mjs +256 -0
  126. package/dist/plan-DpuOr14e.mjs +2 -0
  127. package/dist/{platform-auth-config-DrbQXXiW.mjs → platform-auth-config-Df6yVw-e.mjs} +6 -6
  128. package/dist/{platform-auth-protection-Bhtvp0B_.mjs → platform-auth-protection-Jb0yftay.mjs} +7 -5
  129. package/dist/{platform-auth-recovery-CeOKGVeJ.mjs → platform-auth-recovery-DmRyIfW0.mjs} +6 -5
  130. package/dist/platform-cmd-C9Vt7m-d.mjs +2 -0
  131. package/dist/{platform-cmd-BFhieCdV.mjs → platform-cmd-DRxCOTwy.mjs} +8 -5
  132. package/dist/{platform-domain-C74PULqV.mjs → platform-domain-IjiXgrXJ.mjs} +4 -3
  133. package/dist/{platform-lifecycle-BwAIgz-t.mjs → platform-lifecycle-BquAaU-5.mjs} +263 -56
  134. package/dist/platform-lifecycle-CuJIZNvA.mjs +2 -0
  135. package/dist/{platform-management-COogu_Se.mjs → platform-management-CCQKGsq4.mjs} +12 -7
  136. package/dist/platform-management-Cgyed0WY.mjs +2 -0
  137. package/dist/{platform-recovery-ewqLefp1.mjs → platform-recovery-DS8ih1SW.mjs} +3 -3
  138. package/dist/platform-registry-BJbgZLS1.mjs +431 -0
  139. package/dist/{plugin-inference-BDRfZngg.mjs → plugin-inference-DsvtJLll.mjs} +4 -4
  140. package/dist/prefetch-Bsc_Pb6c.mjs +90 -0
  141. package/dist/prefetch-Ce6la4EI.d.mts +31 -0
  142. package/dist/{prepare-blNRQvQl.mjs → prepare-CaUxODOU.mjs} +3 -2
  143. package/dist/prepare-D4CkM3_v.mjs +2 -0
  144. package/dist/{prepare-CtDJjoOj.mjs → prepare-pcWcxSCh.mjs} +13 -11
  145. package/dist/prerender-render-Cf_WDE9W.mjs +111 -0
  146. package/dist/prerender-render.mjs +1 -110
  147. package/dist/{preset-lAy0B0BQ.mjs → preset-Dowh9tTt.mjs} +16 -118
  148. package/dist/project-BEBFDFLz.mjs +2 -0
  149. package/dist/project-CWNIPoXc.mjs +209 -0
  150. package/dist/{project-cmd-DmZK9Hxf.mjs → project-cmd-2lN--YAX.mjs} +18 -16
  151. package/dist/{project-paths-SK8nMHPp.mjs → project-paths-CKQ-Q5JS.mjs} +47 -14
  152. package/dist/project-slug-23TpquG4.mjs +8 -0
  153. package/dist/project-slug-DofjTFd-.mjs +2 -0
  154. package/dist/{project-team-D8jOJMUJ.mjs → project-team-Dapn9HZn.mjs} +9 -5
  155. package/dist/{project-token-DA34bf-C.mjs → project-token-C90v9SJK.mjs} +6 -4
  156. package/dist/{project-tsconfig-Ql2XsSQp.mjs → project-tsconfig-CwfqUnVp.mjs} +2 -2
  157. package/dist/{protocol-C-pqYJjE.d.mts → protocol-ZH3jP4a7.d.mts} +1 -1
  158. package/dist/providers-BNKRacMr.d.mts +7 -0
  159. package/dist/provision-CNgEBkVA.mjs +3 -0
  160. package/dist/{provision-CSJOjjQk.mjs → provision-Cck2m3jJ.mjs} +11 -25
  161. package/dist/queues-BWKt1Xo4.d.mts +7 -0
  162. package/dist/{requests-CUExwGQQ.mjs → requests-CzX46I0i.mjs} +5 -3
  163. package/dist/resolve-project-BTotl8Nn.mjs +2 -0
  164. package/dist/{resolve-project--Vxawf7z.mjs → resolve-project-Xvis70DG.mjs} +2 -8
  165. package/dist/response-Tn7rU0MV.mjs +30 -0
  166. package/dist/{rollback-CDNGU1gr.mjs → rollback-BEeyc73H.mjs} +6 -4
  167. package/dist/{rolldown-runtime-rQ84J-ij.mjs → rolldown-runtime-DXIUcv95.mjs} +1 -10
  168. package/dist/route-types-Id82-veQ.mjs +280 -0
  169. package/dist/{local-d1-D2I6Ox5F.mjs → runner-BGVsGgkb.mjs} +6 -114
  170. package/dist/runner-CQs_cDSG.mjs +2 -0
  171. package/dist/runner-mysql-1o47achK.mjs +2 -0
  172. package/dist/{runner-mysql-7BPUNGmL.mjs → runner-mysql-BhwMk2Bm.mjs} +2 -9
  173. package/dist/{runner-pg-BkEza-dX.mjs → runner-pg-CJ_JeGF6.mjs} +2 -9
  174. package/dist/runner-pg-D7z01mQS.mjs +2 -0
  175. package/dist/runtime/ai.mjs +1 -1
  176. package/dist/runtime/auth-client-react.d.mts +2 -6
  177. package/dist/runtime/auth-client-react.mjs +1 -5
  178. package/dist/runtime/auth-client-solid.d.mts +2 -6
  179. package/dist/runtime/auth-client-solid.mjs +1 -5
  180. package/dist/runtime/auth-client-svelte.d.mts +2 -6
  181. package/dist/runtime/auth-client-svelte.mjs +1 -5
  182. package/dist/runtime/auth-client-vue.d.mts +2 -6
  183. package/dist/runtime/auth-client-vue.mjs +1 -5
  184. package/dist/runtime/auth-client.d.mts +2 -6
  185. package/dist/runtime/auth-client.mjs +1 -5
  186. package/dist/runtime/auth.mjs +1 -21
  187. package/dist/runtime/better-auth-mysql.d.mts +1 -1
  188. package/dist/runtime/better-auth-mysql.mjs +1 -1
  189. package/dist/runtime/better-auth-pg.d.mts +1 -1
  190. package/dist/runtime/better-auth-pg.mjs +1 -1
  191. package/dist/runtime/better-auth.d.mts +1 -1
  192. package/dist/runtime/better-auth.mjs +1 -1
  193. package/dist/runtime/client-react.d.mts +3 -3
  194. package/dist/runtime/client-react.mjs +3 -3
  195. package/dist/runtime/client-solid.d.mts +3 -3
  196. package/dist/runtime/client-solid.mjs +3 -3
  197. package/dist/runtime/client-svelte.d.mts +3 -3
  198. package/dist/runtime/client-svelte.mjs +3 -3
  199. package/dist/runtime/client-vue.d.mts +3 -3
  200. package/dist/runtime/client-vue.mjs +3 -3
  201. package/dist/runtime/client.d.mts +3 -3
  202. package/dist/runtime/client.mjs +3 -3
  203. package/dist/runtime/db.mjs +1 -1
  204. package/dist/runtime/durable.mjs +1 -1
  205. package/dist/runtime/email/testing.mjs +1 -1
  206. package/dist/runtime/env-helpers.mjs +1 -135
  207. package/dist/runtime/env-public-client.mjs +1 -1
  208. package/dist/runtime/env-public.mjs +2 -2
  209. package/dist/runtime/env.mjs +1 -77
  210. package/dist/runtime/fetch-stream.d.mts +2 -49
  211. package/dist/runtime/fetch-stream.mjs +2 -80
  212. package/dist/runtime/fetch.d.mts +2 -57
  213. package/dist/runtime/fetch.mjs +2 -17
  214. package/dist/runtime/handler.d.mts +1 -1
  215. package/dist/runtime/handler.mjs +1 -149
  216. package/dist/runtime/kv.mjs +1 -1
  217. package/dist/runtime/live-client.d.mts +1 -1
  218. package/dist/runtime/live-server.mjs +2 -2
  219. package/dist/runtime/live.d.mts +2 -104
  220. package/dist/runtime/live.mjs +1 -410
  221. package/dist/runtime/migration-handler-mysql.d.mts +1 -1
  222. package/dist/runtime/migration-handler-pg.d.mts +1 -1
  223. package/dist/runtime/migration-handler.d.mts +2 -50
  224. package/dist/runtime/queues.d.mts +2 -6
  225. package/dist/runtime/queues.mjs +1 -1
  226. package/dist/runtime/remote/index.mjs +49 -8
  227. package/dist/runtime/response.mjs +1 -29
  228. package/dist/runtime/sandbox.d.mts +4 -56
  229. package/dist/runtime/sandbox.mjs +82 -221
  230. package/dist/runtime/sse.mjs +1 -171
  231. package/dist/runtime/storage.mjs +1 -1
  232. package/dist/runtime/validator.d.mts +1 -1
  233. package/dist/runtime/validator.mjs +1 -71
  234. package/dist/runtime/ws-server.d.mts +2 -2
  235. package/dist/runtime/ws-server.mjs +2 -2
  236. package/dist/runtime/ws.d.mts +2 -121
  237. package/dist/{scan-CpK-57ug.mjs → scan-DJbooZm2.mjs} +3 -3
  238. package/dist/{scan-BMH4rzlv.mjs → scan-DdDvRCU1.mjs} +8 -26
  239. package/dist/{secret-Bzzi2e9E.mjs → secret-wnTel5Yw.mjs} +8 -6
  240. package/dist/serialize-BPvnNQuA.mjs +14 -0
  241. package/dist/serialize-CfSwWfF2.d.mts +10 -0
  242. package/dist/{skills-C0RvGjeE.mjs → skills-B-690E7h.mjs} +3 -2
  243. package/dist/sse-BaC1jXko.mjs +172 -0
  244. package/dist/{subcommand-prompt-Bmyn5Rlc.mjs → subcommand-prompt-Gj3VzLIh.mjs} +2 -1
  245. package/dist/sveltekit.d.mts +2 -1
  246. package/dist/sveltekit.mjs +3 -2
  247. package/dist/validate-DqJ33oHj.mjs +2 -0
  248. package/dist/validate-qNhV00PD.mjs +180 -0
  249. package/dist/validator-BTOu0fB0.mjs +72 -0
  250. package/dist/{wrangler--imS8n0d.mjs → wrangler-D01qs6VB.mjs} +259 -71
  251. package/dist/ws-BwcqizuH.d.mts +122 -0
  252. package/dist/{yarn-pnp-DxSInkzL.mjs → yarn-pnp-CVEc3gE7.mjs} +1 -1
  253. package/package.json +19 -8
  254. package/skills/void/SKILL.md +7 -5
  255. package/skills/void/docs/guide/ai.md +3 -3
  256. package/skills/void/docs/guide/app-types.md +12 -11
  257. package/skills/void/docs/guide/auth.md +2 -2
  258. package/skills/void/docs/guide/database/d1.md +1 -1
  259. package/skills/void/docs/guide/database/mysql.md +1 -1
  260. package/skills/void/docs/guide/database/postgresql.md +3 -3
  261. package/skills/void/docs/guide/deployment.md +16 -16
  262. package/skills/void/docs/guide/durable-state.md +2 -2
  263. package/skills/void/docs/guide/edge/headers.md +3 -3
  264. package/skills/void/docs/guide/edge/prerendering.md +1 -1
  265. package/skills/void/docs/guide/edge/redirects.md +4 -4
  266. package/skills/void/docs/guide/edge/revalidation.md +6 -6
  267. package/skills/void/docs/guide/edge/rewrites.md +28 -27
  268. package/skills/void/docs/guide/edge/static-assets.md +1 -1
  269. package/skills/void/docs/guide/email.md +27 -25
  270. package/skills/void/docs/guide/env-migration.md +1 -1
  271. package/skills/void/docs/guide/index.md +1 -1
  272. package/skills/void/docs/guide/pages-routing/head.md +1 -1
  273. package/skills/void/docs/guide/platform/administration/access.md +171 -0
  274. package/skills/void/docs/guide/platform/administration/email.md +121 -0
  275. package/skills/void/docs/guide/platform/administration/operations.md +97 -0
  276. package/skills/void/docs/guide/platform/administration/projects.md +56 -0
  277. package/skills/void/docs/guide/platform/development/local.md +119 -0
  278. package/skills/void/docs/guide/platform/development/runtime.md +124 -0
  279. package/skills/void/docs/guide/platform/development/schema-ci.md +95 -0
  280. package/skills/void/docs/guide/platform/installation/ci.md +58 -0
  281. package/skills/void/docs/guide/platform/installation/credentials.md +90 -0
  282. package/skills/void/docs/guide/platform/installation/domains.md +68 -0
  283. package/skills/void/docs/guide/platform/installation/first-deployment.md +82 -0
  284. package/skills/void/docs/guide/platform/installation/maintenance.md +137 -0
  285. package/skills/void/docs/guide/platform/installation/prerequisites.md +88 -0
  286. package/skills/void/docs/guide/platform/installation/setup.md +169 -0
  287. package/skills/void/docs/guide/platform/installation/uninstall.md +54 -0
  288. package/skills/void/docs/guide/platform-administration.md +6 -414
  289. package/skills/void/docs/guide/platform-development.md +5 -316
  290. package/skills/void/docs/guide/project-collaboration.md +1 -1
  291. package/skills/void/docs/guide/remote-dev.md +2 -2
  292. package/skills/void/docs/guide/sandboxes.md +10 -25
  293. package/skills/void/docs/guide/self-hosted-platform.md +11 -694
  294. package/skills/void/docs/guide/ssg.md +1 -1
  295. package/skills/void/docs/guide/websockets.md +1 -1
  296. package/skills/void/docs/integrations/cloudflare.md +86 -84
  297. package/skills/void/docs/integrations/frameworks/analog.md +14 -9
  298. package/skills/void/docs/integrations/frameworks/astro.md +14 -10
  299. package/skills/void/docs/integrations/frameworks/nuxt.md +14 -9
  300. package/skills/void/docs/integrations/frameworks/overview.md +35 -29
  301. package/skills/void/docs/integrations/frameworks/react-router.md +1 -1
  302. package/skills/void/docs/integrations/frameworks/sveltekit.md +33 -30
  303. package/skills/void/docs/integrations/frameworks/tanstack-start.md +1 -1
  304. package/skills/void/docs/integrations/nodejs-bun-deno.md +5 -5
  305. package/skills/void/docs/reference/api.md +55 -38
  306. package/skills/void/docs/reference/cli.md +60 -34
  307. package/skills/void/docs/reference/config.md +57 -34
  308. package/skills/void/docs/reference/resource-inference.md +10 -10
  309. package/skills/void/docs/reference/structure.md +2 -2
  310. package/dist/validate-tBBN_dXH.mjs +0 -505
@@ -10,7 +10,7 @@ Send transactional email from your app via [Cloudflare's `send_email` binding](h
10
10
  import { sendEmail } from 'void/email';
11
11
 
12
12
  const result = await sendEmail({
13
- from: 'Acme <acme+noreply@mail.void.cloud>',
13
+ from: 'Acme <acme+noreply@mail.example.com>', // use your project's sender address
14
14
  to: 'user@example.com',
15
15
  subject: 'Welcome',
16
16
  text: 'Thanks for signing up!',
@@ -36,16 +36,20 @@ hits first, because a recipient you have not verified yet fails per-recipient.
36
36
 
37
37
  ## Setup
38
38
 
39
- Zero config on the Void platform (`void deploy`). Every Void project ships with:
39
+ On a platform with email enabled, your app needs no email configuration before
40
+ `void deploy`. Ask your administrator for the platform's shared mail domain. A
41
+ self-hosted administrator [enables email during installation or upgrade](/guide/platform/installation/credentials#runtime-token-permissions); installations without it do not offer platform email. Void Cloud uses `mail.void.cloud`.
40
42
 
41
- - **Sender** — `<your-slug>+noreply@mail.void.cloud`. Used as the default `from` if you omit it. The platform owns the zone with Email Routing + DKIM + SPF + DMARC set up; you do nothing. Project slugs are capped at 56 characters so this local part fits RFC 5321's 64 octets; a project created before the cap with a longer slug must pass `from` explicitly.
43
+ Each project on an email-enabled platform has:
44
+
45
+ - **Sender** — `<your-slug>+noreply@<mail-domain>`. Used as the default `from` if you omit it. The platform administrator configures the mail zone and its Email Routing, DKIM, SPF, and DMARC records. Project slugs are capped at 56 characters so this local part fits RFC 5321's 64 octets; a project created before the cap with a longer slug must pass `from` explicitly.
42
46
  - **No worker binding to add** — outbound mail is sent by the Void proxy, which holds the
43
47
  platform `send_email` binding. Your worker never gets one, so there is nothing to configure.
44
48
  - **Your own address as a recipient** — the email on your Void account is registered as a recipient when the project is created. It is verified at once when Cloudflare already holds it verified for the platform (you clicked its link for an earlier project of yours); otherwise Cloudflare mails it a verification link, and until you click that link and run `void email destinations` — the listing is what records the click — a send to yourself comes back `ok: false` with a per-recipient `UNVERIFIED_DESTINATION` in `result.deliveries`.
45
49
 
46
50
  That's it for configuration. Delivery is gated separately: outbound mail only reaches verified recipients, so `sendEmail({ to: '...', subject: '...', text: '...' })` works on first deploy for your own address once it is verified, and for anyone else after `void email allow` — see [Adding recipients](#adding-recipients).
47
51
 
48
- Deploying to your own Cloudflare account instead (`void deploy --platform cloudflare`) takes one line of `void.json` and one Enter on the first deploy — see [Your own Cloudflare account](#your-own-cloudflare-account).
52
+ Deploying to your own Cloudflare account instead (`void deploy --platform cloudflare`) takes one line of `void.config.ts` and one Enter on the first deploy — see [Your own Cloudflare account](#your-own-cloudflare-account).
49
53
 
50
54
  ## Adding recipients
51
55
 
@@ -61,7 +65,7 @@ Cloudflare emails the recipient with a verification link. Once they click it and
61
65
  void email destinations
62
66
  ```
63
67
 
64
- The project owner's email (the GitHub address you signed up with) is added automatically when the project is created, so it skips `void email allow` — not the verification. See [Setup](#setup) for when it is verified at once and when there is a link to click.
68
+ The project owner's account email is added automatically when the project is created on an email-enabled platform, so it skips `void email allow` — not the verification. See [Setup](#setup) for when it is verified at once and when there is a link to click.
65
69
 
66
70
  ::: warning When this is the right fit
67
71
  The shared sender is great for: ops alerts to the team, notifications to the project owner, reply-by-email flows on top of inbound, internal/app-internal mail.
@@ -71,7 +75,7 @@ For SaaS sending to arbitrary end-users (every signup gets a welcome email), the
71
75
 
72
76
  ## Your own domain on the platform
73
77
 
74
- The shared sender lives on the platform's `mail.void.cloud` zone. To send — and receive — at a domain you own, register its Cloudflare zone with the project:
78
+ The shared sender uses the platform's configured mail domain. To send — and receive — at a domain you own, register its Cloudflare zone with the project:
75
79
 
76
80
  ```sh
77
81
  void email domain add acme.com
@@ -120,15 +124,14 @@ At most 50 recipients across `to`, `cc` and `bcc` per call. Each address is chec
120
124
 
121
125
  `Address` accepts either a string (`"hello@acme.dev"` or `"Name <hello@acme.dev>"`) or an object (`{ email, name? }`). Display names with non-ASCII characters are RFC 2047 encoded automatically.
122
126
 
123
- On the platform the sender is pinned to your project. `from` must be your project's own platform address — `<project-slug>@mail.void.cloud` or `<project-slug>+<tag>@mail.void.cloud`, optionally with a display name (`Acme <acme+noreply@mail.void.cloud>`) — or any address on a domain registered with `void email domain add` (see [Your own domain on the platform](#your-own-domain-on-the-platform)). Anything else is rejected with `INVALID_FROM`. Omit `from` and Void fills in `<project-slug>+noreply@mail.void.cloud` for you.
127
+ On the platform the sender is pinned to your project. `from` must be your project's own platform address — `<project-slug>@<mail-domain>` or `<project-slug>+<tag>@<mail-domain>`, optionally with a display name — or any address on a domain registered with `void email domain add` (see [Your own domain on the platform](#your-own-domain-on-the-platform)). For example, if your platform's mail domain is `mail.example.com`, you can use `Acme <acme+noreply@mail.example.com>`. Anything else is rejected with `INVALID_FROM`. Omit `from` and Void fills in `<project-slug>+noreply@<mail-domain>` for you.
124
128
 
125
- On your own Cloudflare account, `from` defaults to `email.from` from `void.json` and must be on a domain your account can send from; Cloudflare rejects any other sender and `sendEmail` reports it as `INVALID_FROM`.
129
+ On your own Cloudflare account, `from` defaults to `email.from` from `void.config.ts` and must be on a domain your account can send from; Cloudflare rejects any other sender and `sendEmail` reports it as `INVALID_FROM`.
126
130
 
127
131
  ## Attachments
128
132
 
129
133
  ```ts
130
134
  await sendEmail({
131
- from: 'Acme <acme+noreply@mail.void.cloud>',
132
135
  to: 'user@example.com',
133
136
  subject: 'Your receipt',
134
137
  text: 'Receipt attached.',
@@ -146,7 +149,6 @@ For inline images (e.g. logos referenced from HTML), set `disposition: 'inline'`
146
149
 
147
150
  ```ts
148
151
  await sendEmail({
149
- from: 'Acme <acme+noreply@mail.void.cloud>',
150
152
  to: 'user@example.com',
151
153
  subject: 'Hello',
152
154
  html: '<img src="cid:logo" alt="Acme">',
@@ -237,11 +239,11 @@ Under the hood, your code runs in workerd (a separate process from the Vite dev
237
239
 
238
240
  The dev inbox is **not** available under a Class B or C framework — SvelteKit, Nuxt, Analog, Astro. Those adapters own their own dev server and worker build, so Void never installs the Cloudflare Vite plugin for them and has nowhere to register the inbox. Declaring an `assets` binding does not help: SvelteKit, Nuxt and Analog do not run your code in a workerd instance fronted by Vite at all, so there is no loopback to bind to. Sends from those apps return `BINDING_MISSING`. Use `createEmailTestHarness` from `void/email/testing` instead, which captures in-process and works everywhere. When the inbox is configured but unreachable, `sendEmail` returns `UPSTREAM_ERROR` — nothing is captured and nothing is sent.
239
241
 
240
- `sendInDev: true` bypasses the dev inbox for a single send. `void dev` binds no send transport at all — the platform's `__VOID_PROXY` service binding is added only on a deployed worker, and the own-account `SEND_EMAIL` binding is stripped under `serve` so miniflare cannot write stray `.eml` files or send real mail (only that entry: a `send_email` binding of your own under another name is left exactly as `wrangler.jsonc` declares it). With the inbox skipped there is nothing left to fall through to, so the call returns `BINDING_MISSING`: it proves the inbox was bypassed, it does not deliver. To verify real delivery, deploy and send from the deployed worker.
242
+ `sendInDev: true` bypasses the dev inbox for a single send. `void dev` binds no send transport at all — the platform's `__VOID_PROXY` service binding is added only on a deployed worker, and the own-account `SEND_EMAIL` binding is stripped under `serve` so miniflare cannot write stray `.eml` files or send real mail (only that entry: a `send_email` binding of your own under another name is left exactly as `cloudflare.send_email` in `void.config.ts` declares it). With the inbox skipped there is nothing left to fall through to, so the call returns `BINDING_MISSING`: it proves the inbox was bypassed, it does not deliver. To verify real delivery, deploy and send from the deployed worker.
241
243
 
242
244
  ```ts
243
245
  const result = await sendEmail({
244
- from: 'acme+noreply@mail.void.cloud',
246
+ from: 'acme+noreply@mail.example.com', // replace with your project sender
245
247
  to: 'verified@acme.dev',
246
248
  subject: 'Skips the dev inbox',
247
249
  text: 'Not captured — and not delivered either.',
@@ -264,7 +266,7 @@ describe('signup flow', () => {
264
266
  const inbox = createEmailTestHarness();
265
267
 
266
268
  await sendEmail({
267
- from: 'acme+noreply@mail.void.cloud',
269
+ from: 'acme+noreply@mail.example.com', // use your project sender
268
270
  to: 'user@example.com',
269
271
  subject: 'Welcome',
270
272
  text: 'Hi!',
@@ -406,7 +408,7 @@ deployed `email/` handlers without per-project DNS setup. A
406
408
  handlers once its inbound readiness is `ready`. Use `void email domain status`
407
409
  to check current provider routing and any remaining setup steps.
408
410
 
409
- On your own Cloudflare account there is no shared facility: the deploy derives one Email Routing rule per handler and writes it into `wrangler.jsonc` for you — see [Your own Cloudflare account](#your-own-cloudflare-account).
411
+ On your own Cloudflare account there is no shared facility: the deploy derives one Email Routing rule per handler and records it in `void.lock.json` for you — see [Your own Cloudflare account](#your-own-cloudflare-account).
410
412
 
411
413
  There is no local inbound trigger yet — `void dev` serves the outbound dev inbox only, so test inbound handlers with `createInboundTestHarness` below.
412
414
 
@@ -476,7 +478,7 @@ The harness uses the same precedence rules as the production dispatcher. Reserve
476
478
 
477
479
  ### Two things to type, once
478
480
 
479
- 1. **`email.from` in `void.json`** — the default sender, and the domain Void sets up:
481
+ 1. **`email.from` in `void.config.ts`** — the default sender, and the domain Void sets up:
480
482
 
481
483
  ```json
482
484
  {
@@ -486,17 +488,17 @@ The harness uses the same precedence rules as the production dispatcher. Reserve
486
488
  }
487
489
  ```
488
490
 
489
- The host (`mail.acme.com`) must be a zone in the Cloudflare account your deploy is pinned to, or sit under one. Without `email.from`, a deploy that uses email prints `add "email": { "from": "you@mail.acme.com" } to void.json` and deploys without it. Void does not pick a zone for you — wrangler cannot list them — and it never writes `void.json`.
491
+ The host (`mail.acme.com`) must be a zone in the Cloudflare account your deploy is pinned to, or sit under one. Without `email.from`, a deploy that uses email asks you to set it in `void.config.ts` and deploys without email. Void does not pick a zone for you or write the setting automatically.
490
492
 
491
493
  2. **`void cloudflare login`**, or **`CLOUDFLARE_API_TOKEN`** set to a token with **Email Routing Edit** and **Email Sending Edit** (zone and account) alongside the deploy permissions. A browser session created by older Cloudflare tooling lacks the two email scopes; the deploy tells you to run `void cloudflare logout`, then `void cloudflare login` (one browser Allow) and skips the email step. A token's permissions cannot be listed up front, so a token that lacks one shows up as a row Void could not read (`unknown`), with the permission named. A Global API Key pair (`CLOUDFLARE_API_KEY`) is refused: it has no single bearer for the two calls wrangler has no command for.
492
494
 
493
495
  ### The first deploy
494
496
 
495
- Before any of your project code runs, the deploy **reads** — the session, the zone, public DNS (MX on the mail domain and on the apex, `_dmarc`, `cf-bounce`), Email Routing status, the zone's subaddressing setting, the routing rules on your addresses, Email Sending, and what `wrangler.jsonc` already holds — and prints what it found and what it would change:
497
+ Before any of your project code runs, the deploy **reads** — the session, the zone, public DNS (MX on the mail domain and on the apex, `_dmarc`, `cf-bounce`), Email Routing status, the zone's subaddressing setting, the routing rules on your addresses, Email Sending, and what the resolved Cloudflare config already holds — and prints what it found and what it would change:
496
498
 
497
499
  ```
498
500
  Email in use email/support+[ticket].ts · email/_default.ts · sendEmail() in 2 files
499
- domain mail.acme.com (void.json email.from)
501
+ domain mail.acme.com (void.config.ts email.from)
500
502
  zone acme.com account Acme (f721b8e5…) · session dev@acme.com
501
503
  apex MX aspmx.l.google.com left alone — mail lives on the subdomain
502
504
  routing mail.acme.com not enabled · subaddressing off
@@ -509,7 +511,7 @@ Before any of your project code runs, the deploy **reads** — the session, the
509
511
  + enable Email Routing on mail.acme.com Cloudflare writes and locks 3 MX + 1 SPF record there
510
512
  + turn on subaddressing for acme.com support+anything@ reaches support@
511
513
  + onboard mail.acme.com for Email Sending MX/SPF/DKIM on cf-bounce.mail.acme.com, _dmarc.mail.acme.com (p=reject)
512
- + wrangler.jsonc send_email: [{ name: "SEND_EMAIL" }], addresses: ["support@mail.acme.com"], vars.__VOID_EMAIL_FROM: "noreply@mail.acme.com"
514
+ + Cloudflare config send_email: [{ name: "SEND_EMAIL" }], addresses: ["support@mail.acme.com"], vars.__VOID_EMAIL_FROM: "noreply@mail.acme.com"
513
515
  + routing rule (created on deploy) support@mail.acme.com → acme-support
514
516
  ! email/_default.ts a catch-all exists only on an apex; other @mail.acme.com mail bounces
515
517
 
@@ -528,7 +530,7 @@ On the second deploy every row reads ready: no prompt, no account call, and wran
528
530
 
529
531
  DNS can take a few minutes to become visible to the resolver running the deploy. If `void email setup` has already written the exact `addresses` plan but that resolver still sees no subdomain MX, Void preserves the plan and stops before the build or upload. Run `void email status --platform cloudflare`, then retry the deploy after it sees Cloudflare's MX records.
530
532
 
531
- Two rows live in `wrangler.jsonc` rather than in your account, and a deploy that finds every account row ready reconciles them with a plain file write, no prompt: the `addresses` array is rewritten whenever it is not the current derivation (an entry pruned since, a worker rename, a new handler), and `vars.__VOID_EMAIL_FROM` follows a changed `email.from`. Routing rules are `wrangler deploy`'s own work, so a committed `addresses` entry whose rule does not exist yet — the state right after `void email setup` — still reads ready; the address map marks it `(rule created by this deploy)`.
533
+ Two rows live in the generated Cloudflare config rather than in your account, and a deploy that finds every account row ready reconciles them with a plain file write, no prompt: the `addresses` array is rewritten whenever it is not the current derivation (an entry pruned since, a worker rename, a new handler), and `vars.__VOID_EMAIL_FROM` follows a changed `email.from`. Routing rules are `wrangler deploy`'s own work, so a committed `addresses` entry whose rule does not exist yet — the state right after `void email setup` — still reads ready; the address map marks it `(rule created by this deploy)`.
532
534
 
533
535
  A subdomain is added to a zone that already routes. When Email Routing is **off** on the apex (`Enabled: false`), the subdomain step is not attempted at all — Void never enables routing on the apex from the subdomain path, since that would lock MX records over the apex's live mail — and the checklist prints the dashboard step (`Email → Settings → Subdomains → add mail.acme.com`) instead; `addresses` is withheld until routing on the subdomain reads ready.
534
536
 
@@ -544,7 +546,7 @@ The apex path (`email.from` on `acme.com` itself) is allowed with the same one E
544
546
 
545
547
  **Subaddressing** is a per-zone Cloudflare setting, and it is off by default. Until it is on, a rule for `support@mail.acme.com` does not match `support+T-42@mail.acme.com`. The checklist's `turn on subaddressing for acme.com` row flips it for the whole zone — on the apex and every subdomain — so `email/support+[ticket].ts` works the way it does on the platform.
546
548
 
547
- ### What Void writes into `wrangler.jsonc`
549
+ ### What Void records in `void.lock.json`
548
550
 
549
551
  ```jsonc
550
552
  {
@@ -563,7 +565,7 @@ The apex path (`email.from` on `acme.com` itself) is allowed with the same one E
563
565
  - An address already routed to another worker or to a forwarding rule is **pruned** from the array and reported (`! support@mail.acme.com already routed to …; left alone, not in addresses`). wrangler's plan is never destructive on your account because of something Void derived.
564
566
  - If the array holds entries Void did not derive, the deploy prints which ones and **skips the email step for that deploy** — the deploy itself continues and the array is left untouched. Remove them, or manage `addresses` by hand and leave `email.from` unset.
565
567
 
566
- Deleting a handler leaves its address in `addresses`: the next deploy names it in a `✘` row as an entry Void no longer derives, and skips the email step until you remove that entry from `wrangler.jsonc` by hand (the row says which). Once removed, wrangler's plan drops the rule with its own y/n (default No) in a terminal, an error in CI. Removing the last handler and every `sendEmail()` call leaves the whole setup in `wrangler.jsonc`; the deploy warns which addresses are still routed to a worker with no `email()` export and how to detach them, and deploys as-is.
568
+ Deleting a handler leaves its address in `addresses`: the next deploy names it in a `✘` row as an entry Void no longer derives, and skips the email step until you override `cloudflare.addresses` in `void.config.ts` with the desired list (the row says which). Once removed, Cloudflare's plan drops the rule with its own y/n (default No) in a terminal, an error in CI. Removing the last handler and every `sendEmail()` call leaves the whole setup in `void.lock.json`; the deploy warns which addresses are still routed to a worker with no `email()` export and how to detach them, and deploys as-is. To detach fully, remove `addresses`, `send_email`, and `vars.__VOID_EMAIL_FROM` from `resolved` in `void.lock.json` and remove any authored overrides in `void.config.ts`, then delete the routing rules in Cloudflare.
567
569
 
568
570
  How handlers become addresses, with `email.from` on `mail.acme.com` under the zone `acme.com`:
569
571
 
@@ -591,7 +593,7 @@ Workers Paid needed for arbitrary recipients. Inbound works; sendEmail() to veri
591
593
 
592
594
  Everything else — routing, rules, the binding — still goes through, and the address map ends with `outbound sendEmail() from support@mail.acme.com (verified destinations only)`. Verified destinations are the addresses under **Email Routing → Destination addresses** in your Cloudflare dashboard; a handler that `forward()`s to a new address needs the same verification click.
593
595
 
594
- The refusal is remembered by the `send_email` binding that same run writes into `wrangler.jsonc`: Void writes the binding only after it has attempted the onboarding, so a committed binding next to a domain that is still not onboarded means "tried, refused". Later deploys ask nothing about it, `--require-email` passes, `void email status` reads the domain as set up (its sending row says `not onboarded — verified destinations only` and names the retry), and the address map keeps ending with `(verified destinations only)`. The deploy never retries the onboarding on its own. After upgrading to Workers Paid, run `void email setup --platform cloudflare` once: it asks `Onboard mail.acme.com for Email Sending?` and, on Yes, onboards the domain — from then on the map ends without the marker.
596
+ The refusal is remembered by the `send_email` binding that same run records in `void.lock.json`: Void records the binding only after it has attempted the onboarding, so a committed binding next to a domain that is still not onboarded means "tried, refused". Later deploys ask nothing about it, `--require-email` passes, `void email status` reads the domain as set up (its sending row says `not onboarded — verified destinations only` and names the retry), and the address map keeps ending with `(verified destinations only)`. The deploy never retries the onboarding on its own. After upgrading to Workers Paid, run `void email setup --platform cloudflare` once: it asks `Onboard mail.acme.com for Email Sending?` and, on Yes, onboards the domain — from then on the map ends without the marker.
595
597
 
596
598
  If `_dmarc.mail.acme.com` or `cf-bounce.mail.acme.com` already has a TXT record, sending onboarding is refused — it writes its own `_dmarc` (`p=reject`) and DKIM records and Cloudflare would answer with a conflict. Inbound is unaffected; remove the records or keep sending off.
597
599
 
@@ -601,12 +603,12 @@ The email prompt follows wrangler's own interactivity rule: a CI environment as
601
603
 
602
604
  ```
603
605
  deploy: email on mail.acme.com is not set up, and this shell cannot ask.
604
- Run `void email setup --platform cloudflare` once locally, commit wrangler.jsonc, then redeploy — deploying without email.
606
+ Run `void email setup --platform cloudflare` once locally, commit void.lock.json, then redeploy — deploying without email.
605
607
  ```
606
608
 
607
609
  then deploys **without** email. Pass `--require-email` to fail instead. When setup has already committed the exact subdomain `addresses` plan and only its MX records are not visible yet, every deploy stops and preserves that plan until DNS can be verified. Automatic resource provisioning is not an escape hatch: it creates D1/KV/R2/Queue/Hyperdrive resources, never a mail setup. To read the rows without deploying or being asked anything, run `void email status --platform cloudflare`.
608
610
 
609
- So the CI story is: run `void email setup --platform cloudflare` once on your machine (it runs the same preflight, checklist and prompt as the first deploy, then writes `wrangler.jsonc`, without deploying), commit `wrangler.jsonc`, and let CI run `void deploy --platform cloudflare --require-email`. Once the MX records are visible, the committed binding and `addresses` make every row read ready and nothing is asked — on Workers Free too, where the committed binding is what remembers the refused sending onboarding (see [Sending](#sending)).
611
+ So the CI story is: run `void email setup --platform cloudflare` once on your machine (it runs the same preflight, checklist and prompt as the first deploy, then updates `void.lock.json`, without deploying), commit `void.lock.json`, and let CI run `void deploy --platform cloudflare --require-email`. Once the MX records are visible, the committed binding and `addresses` make every row read ready and nothing is asked — on Workers Free too, where the committed binding is what remembers the refused sending onboarding (see [Sending](#sending)).
610
612
 
611
613
  ### If the subdomain step is refused
612
614
 
@@ -80,7 +80,7 @@ void secret list
80
80
 
81
81
  Remote stores return names only; plaintext values are validated during upload and again at worker startup. If an upload fails or a name is missing, retain the source values and retry before continuing.
82
82
 
83
- For direct Cloudflare deployments, remove the migrated server keys from `vars` in `wrangler.jsonc` after confirming the upload. Void now treats any schema-declared server key in Worker `vars` as an error.
83
+ For direct Cloudflare deployments, remove the migrated server keys from `cloudflare.vars` in `void.config.ts` after confirming the upload. Void now treats any schema-declared server key in Worker `vars` as an error.
84
84
 
85
85
  ## 4. Supply client values at build time
86
86
 
@@ -36,7 +36,7 @@ Types follow your data through the app: from a Drizzle schema to a route handler
36
36
 
37
37
  Void brings these pieces together:
38
38
 
39
- - **Resources from your code:** imports tell Void which supported resources to provision. Use `void.json` or your Cloudflare config when you need to customize them.
39
+ - **Resources from your code:** imports tell Void which supported resources to provision. Use `void.config.ts` and its `cloudflare` field when you need to customize them.
40
40
  - **Types from database to frontend:** your Drizzle schema defines DB types, route handlers infer return types, and the [typed fetch client](./typed-fetch.md) checks calls at the usage site. One [Standard Schema](https://standardschema.dev/) validator can drive both runtime validation and compile-time types.
41
41
  - **Local Cloudflare development:** native Void apps run server code in `workerd`, with local database, KV, and storage.
42
42
  - **Deploy that understands the app:** `void deploy` reads your migrations, provisions the resources you actually use, and ships the result to the edge.
@@ -39,7 +39,7 @@ export const head = defineHead<Props>((c, props) => {
39
39
 
40
40
  ## Config Defaults
41
41
 
42
- Set site-wide head defaults in `void.json`:
42
+ Set site-wide head defaults in `void.config.ts`:
43
43
 
44
44
  ```json
45
45
  {
@@ -0,0 +1,171 @@
1
+ ---
2
+ outline: deep
3
+ ---
4
+
5
+ # Sign-in and Access
6
+
7
+ ## Signing In
8
+
9
+ For the browser admin UI, open `<API URL>/admin` and use the administrator login method selected during setup. On a new installation, follow the printed administrator setup instructions to create the first admin user. You do not need to create an app or sign in through the CLI first.
10
+
11
+ Connect to your platform's API URL, then sign in as an administrator:
12
+
13
+ ```sh
14
+ void connect https://platform.example.com --no-login
15
+ void platform auth login
16
+ ```
17
+
18
+ The first command saves the connection. The second opens your browser to sign in and saves an administrator session in your system keychain. Sessions last for one hour and are stored separately for each platform.
19
+
20
+ You can check which account is signed in at any time:
21
+
22
+ ```sh
23
+ void platform auth status
24
+ ```
25
+
26
+ This shows your account, the session's expiry, and the features available on the platform. To end the session, run `void platform auth logout`.
27
+
28
+ If the platform is protected by Cloudflare Access, `void connect <url>` handles
29
+ the company sign-in before Void login. Interactive Access authentication uses a
30
+ locally installed `cloudflared` and saves its short-lived credential in your
31
+ system keychain for that platform origin. You do not need to copy browser cookies.
32
+
33
+ For CI, Access credentials do not replace Void deployment credentials. A project
34
+ owner creates the latter with `void project token create`; it is independently
35
+ revocable, expires within 90 days, and authorizes only that project's deploy
36
+ workflow. Human and operator tokens cannot be renewed by Access service proof.
37
+
38
+ Store Access credentials in origin-keyed `VOID_ACCESS_CREDENTIALS`. A deploy
39
+ that uses prerendering or remote bindings needs entries for both the exact API
40
+ and proxy HTTPS origins, even when both entries contain the same admitted
41
+ service-token pair. `VOID_ACCESS_ORIGIN` selects only one recipient and therefore
42
+ cannot cover both calls. Keep the JSON value in your secret manager, not in
43
+ application configuration. See [CI deployment setup](/guide/platform/installation/first-deployment)
44
+ for the required shape.
45
+
46
+ ## Choosing a Platform
47
+
48
+ If you manage more than one platform, list your connections and choose a default:
49
+
50
+ ```sh
51
+ void platform list
52
+ void platform use <connection-id>
53
+ ```
54
+
55
+ You can also select a platform for a single command with `--connection`:
56
+
57
+ ```sh
58
+ void platform user list --connection <connection-id>
59
+ ```
60
+
61
+ Use an ID or URL from the connection list. Administrative commands use this selection even when you run them inside an app with a different deployment destination.
62
+
63
+ ## Recovering Administrator Login
64
+
65
+ If no administrator can use the configured identity provider, the installation
66
+ owner can recover an existing administrator with Cloudflare management access,
67
+ direct database access, and the original encrypted recovery credentials:
68
+
69
+ ```sh
70
+ void platform config auth recover company --installation <id> --file recovery.json
71
+ ```
72
+
73
+ The file identifies the existing account, for example
74
+ `{"administratorUserId":"existing-admin-id"}`. Recovery opens a real login test
75
+ and asks you to confirm the exact identity before restoring access. It does not
76
+ create a new administrator. To use a replacement provider, include its nonsecret
77
+ `configuration` with a new connection ID and supply the secret through
78
+ `--client-secret-env <name>`. To rotate an existing provider's secret, keep its
79
+ connection ID and identity configuration.
80
+
81
+ For `--yes`, also pin `expectedIdentity` with the exact `issuer` and `subject` in
82
+ the file. A successful browser login is still required. Use `--plan` to preview
83
+ the affected administrator and connection before starting recovery.
84
+
85
+ ## Giving People Access
86
+
87
+ Open **Settings** in the administrator UI, or run `void platform config auth`,
88
+ to add, test, enable, or disable login methods. Access protection and login
89
+ methods are separate settings. A provider test shows the authenticated account
90
+ before you explicitly link it or enable the configuration.
91
+
92
+ To change the company gate after installation, use the installing workstation
93
+ with its saved recovery credentials:
94
+
95
+ ```sh
96
+ void platform config auth protection show
97
+ void platform config auth protection enable --installation <id>
98
+ void platform config auth protection disable --installation <id>
99
+ ```
100
+
101
+ Enabling offers application creation or connection to an existing application.
102
+ It checks every API, proxy, and configured dashboard origin and requires a company
103
+ user sign-in. Changing protection ends all current human sessions; sign in again
104
+ afterwards. Before removing a gate used for company signup, select another
105
+ verified company rule or restricted signup. Removing protection retains the
106
+ Cloudflare applications for deliberate cleanup. Disabling an Access login method
107
+ does not remove the gate.
108
+
109
+ Users can add another enabled login to their existing account with
110
+ `void auth link <connection-id>` or **Account** in the optional
111
+ dashboard. Sign in again first if prompted, then authenticate with the additional
112
+ provider and confirm the identity shown. Matching email addresses alone do not
113
+ link accounts.
114
+
115
+ In the admin dashboard, open **People → Access** to choose who can join, invite
116
+ people, and manage individual signup grants. Choose **Company-approved or
117
+ individually approved users** to create accounts automatically for people who
118
+ meet your configured company rules while still allowing explicit invitations
119
+ and allowlist grants. Company protection at the platform edge still applies
120
+ before anyone reaches a login page.
121
+
122
+ With invited/allowlisted signup selected, let a teammate join with GitHub by adding their login to the allowlist:
123
+
124
+ ```sh
125
+ void platform signup allow github teammate
126
+ ```
127
+
128
+ Void shows the change and asks you to confirm it. The teammate can then run `void connect` with the platform's URL and sign in.
129
+
130
+ You can allow an email address or a whole email domain in the same way:
131
+
132
+ ```sh
133
+ void platform signup allow email teammate@example.com
134
+ void platform signup allow email '*@example.com'
135
+ ```
136
+
137
+ Email patterns apply across the platform's sign-in providers. Quote a domain pattern so your shell passes the `*` to Void.
138
+
139
+ For an OIDC login without a verified email, allow the identity by its login connection and stable provider subject instead:
140
+
141
+ ```sh
142
+ void platform signup allow identity company-sso 'Employee-42'
143
+ ```
144
+
145
+ Use the connection ID shown by `void platform config auth list` and the exact subject reported by your identity provider. The match is case-sensitive and does not infer an email address or link another account. Any domain or group restrictions configured for that login method still apply, and the account joins as an ordinary user. Remove the grant with `void platform signup disallow identity company-sso 'Employee-42'`.
146
+
147
+ To invite someone else by email, use:
148
+
149
+ ```sh
150
+ void platform invitation send alex@example.org
151
+ ```
152
+
153
+ An invitation grants signup access for that exact email address. The person must
154
+ sign in with a login method that supplies the same verified email address.
155
+ When platform email delivery is configured, Void also sends connection
156
+ instructions. If delivery is unavailable or fails, the grant still works:
157
+ share the platform's `/invite` page or `void connect '<platform URL>'` command
158
+ with the person yourself. The admin dashboard shows the delivery outcome
159
+ separately from the invitation's pending, accepted, or revoked status.
160
+
161
+ Inspect the current access settings and invitations with:
162
+
163
+ ```sh
164
+ void platform signup show
165
+ void platform invitation list
166
+ ```
167
+
168
+ Invitation history remains available after an involved account is removed. The stored actor ID
169
+ remains visible when that account's login no longer exists.
170
+
171
+ `void platform signup open` allows anyone to sign up. Use `void platform signup restrict` to require an allowlist match again. Disallowing an entry affects future signup; it does not suspend an existing account.
@@ -0,0 +1,121 @@
1
+ ---
2
+ outline: deep
3
+ ---
4
+
5
+ # Email
6
+
7
+ ## Managing Email
8
+
9
+ These commands apply to a platform that enables email. Every project can send from and receive at `<slug>+tag@<mail domain>` on the platform's shared mail domain; the [email guide](/guide/email) describes what applications do with that.
10
+
11
+ ### Deciding who mail may reach
12
+
13
+ By default, mail from the shared sender reaches only the recipients each project has verified. For a team whose applications mainly mail colleagues, widen that once for the whole installation:
14
+
15
+ ```sh
16
+ void platform email policy
17
+ void platform email policy-set domains --domains example.com,corp.example.net
18
+ void platform email policy-set any
19
+ void platform email policy-set verified
20
+ ```
21
+
22
+ `domains` lets every project mail any address on the listed domains in addition to its verified recipients; `any` lifts the check. Cloudflare still refuses a recipient it has not verified until your mail domain is onboarded for Email Sending, which needs Workers Paid on the platform's account. Until then such sends come back as `UNVERIFIED_DESTINATION` for that recipient, whatever the policy says. Two things the policy never changes: sends from a project's own custom domain, and mail to any address on the platform's own mail domain — those stay verified-recipient-only, so no project reaches another project's inbox without its consent. A tightening applies to new send admissions as soon as it commits; previously admitted attempts may finish. The same setting is on the admin UI's **Email** page.
23
+
24
+ ### Project caps
25
+
26
+ Each project has 200 recipient submissions per UTC calendar month and 10 in a rolling 60-second window by default. An attempt stays charged once it starts, including a failed or uncertain outcome. Raise or lower a project's caps by id or slug:
27
+
28
+ ```sh
29
+ void platform email limit hr-portal --monthly 2000 --burst 30
30
+ ```
31
+
32
+ The project's page in the admin UI shows the current values and clears an override.
33
+
34
+ ### Recovering an interrupted send
35
+
36
+ If a sender stops after beginning a provider call, its unresolved attempt blocks
37
+ removal of the project or sender domain. Inspect the attempt ID and start time:
38
+
39
+ ```sh
40
+ void platform email attempts --project hr-portal
41
+ ```
42
+
43
+ First confirm that the original execution has ended through your Worker logs or
44
+ incident records. Once the attempt is at least 24 hours old, resolve it with an
45
+ audit reason that contains no recipient address or message content:
46
+
47
+ ```sh
48
+ void platform email attempt-resolve <attempt-id> --ended --reason "Original Worker execution ended; provider outcome could not be verified" --plan
49
+ void platform email attempt-resolve <attempt-id> --ended --reason "Original Worker execution ended; provider outcome could not be verified"
50
+ ```
51
+
52
+ This records the send as `outcome_unknown` and releases its cleanup fence. The
53
+ attempt remains charged and is never retried. If its 30-day delivery record has
54
+ expired, only its recipient-free fence is removed. Do not resolve an attempt
55
+ while its original execution may still be active.
56
+
57
+ Inspect a project's retained receipt and recipient outcomes with:
58
+
59
+ ```sh
60
+ void platform email logs hr-portal --page 1 --limit 50 --json
61
+ ```
62
+
63
+ Logs include operation IDs, recorded outcomes, provider references, and error codes.
64
+ After project deletion, use its project ID until the 30-day metadata retention
65
+ period expires. Message bodies, subjects, attachments, and credentials are not logged.
66
+
67
+ ### Registering email domains for projects
68
+
69
+ Your users may hold no Cloudflare account. Tell the platform that administrators register email domains, so `void email domain add` prints the command to ask you for instead of opening a token-creation page:
70
+
71
+ ```sh
72
+ void platform email settings-set --domains admin
73
+ ```
74
+
75
+ Then register a domain for a project. The zone must be in the platform's Cloudflare account; the platform's own credential does the setup, and no Cloudflare credential is stored per domain:
76
+
77
+ ```sh
78
+ void platform email domain-add mail.example.com --project hr-portal
79
+ void platform email domain-status mail.example.com
80
+ void platform email domain-rotate-secret mail.example.com
81
+ void platform email domains
82
+ ```
83
+
84
+ Name the exact mail domain: a subdomain such as `mail.example.com` when the apex already receives mail, otherwise the apex itself. `domain-status` reports inbound, outbound, and management readiness separately. Follow any required DNS or Cloudflare dashboard step, then use `domain-sync` to reconcile. Use `domain-rotate-secret` when you need to replace the zone ingress credential explicitly. Email Sending onboarding needs Workers Paid; after upgrading, `domain-sync` re-attempts it. For a zone in another Cloudflare account, pipe an API token for that account on standard input:
85
+
86
+ ```sh
87
+ void platform email domain-add mail.other.example --project hr-portal --token-stdin --yes < token.txt
88
+ ```
89
+
90
+ Project owners see administrator-registered domains in `void email domain list` and `status`; `status` names the `void platform email` command for any step they cannot take themselves, and `sync` and `remove` point them at `domain-sync` and `domain-remove`. A domain an owner registered before you switched to `admin` stays theirs to renew, sync and remove. The runtime token needs the email permissions listed in the [self-hosting guide](/guide/platform/installation/credentials#runtime-token-permissions) for this to work.
91
+
92
+ A permission refusal leaves setup blocked. After correcting it, run `domain-sync`
93
+ to resume that same setup operation; repeat `domain-rotate-secret` to resume a
94
+ blocked rotation without replacing its staged secret. If project deletion leaves
95
+ a blocked domain cleanup, correct the permission and run `domain-remove <domain>`
96
+ to finish the retained cleanup. If its token has expired or been revoked, provide
97
+ a replacement scoped to the same account and zone:
98
+
99
+ ```sh
100
+ void platform email domain-remove mail.other.example --token-stdin --yes < token.txt
101
+ ```
102
+
103
+ This recovery is available only after project deletion has begun and no other
104
+ project uses the connection. For a live project, renew its token through
105
+ `domain-add`. A timed-out Cloudflare
106
+ mutation stays stopped because replay may duplicate a provider write. After
107
+ confirming the original execution ended, wait 24 hours, inspect the exact
108
+ routing, Worker, secret, catch-all, or Sending resource named by the preview,
109
+ and use its recovery fence:
110
+
111
+ ```sh
112
+ void platform email operation-resolve <operation-id> --ended --outcome <applied|not-applied> --reason "Provider state verified" --plan
113
+ void platform email operation-resolve <operation-id> --ended --outcome <applied|not-applied> --reason "Provider state verified"
114
+ ```
115
+
116
+ Run recovery with the same platform version that created the persisted intent.
117
+ `applied` continues without replaying the write; `not-applied` permits that exact
118
+ step to retry. Secret rotation keeps its staged generation, and removal resumes
119
+ from the unresolved cleanup step. Age alone never authorizes a retry.
120
+
121
+ Register administrator domains only after a platform upgrade has completed, and remove them before rolling the platform back to a version that predates this feature: an earlier runtime cannot use the platform credential for them and does not distinguish administrator-registered domains from owner-registered ones.
@@ -0,0 +1,97 @@
1
+ ---
2
+ outline: deep
3
+ ---
4
+
5
+ # Operations
6
+
7
+ ## Previewing Changes
8
+
9
+ Commands that change the platform show the affected objects before asking for confirmation. To inspect a change without applying it, add `--plan`:
10
+
11
+ ```sh
12
+ void platform user suspend <user-id> --reason "Investigating unexpected traffic" --plan
13
+ ```
14
+
15
+ The preview shows which account will be suspended and the effect on its projects. Run the command again without `--plan` to confirm interactively, or use `--yes` when the change is ready to apply:
16
+
17
+ ```sh
18
+ void platform user suspend <user-id> --reason "Investigating unexpected traffic" --yes
19
+ ```
20
+
21
+ Scripts must use `--yes` to apply these changes. The `void platform auth` session commands run directly and do not use `--plan` or `--yes`; changes under `void platform config auth` do use previews and confirmation. After a change, `void platform system events` shows the administrator, affected objects, and recorded outcome.
22
+
23
+ Changes can take longer when an account owns many resources. Requests that change the platform allow five minutes by default; use `--timeout` to set a different limit in seconds:
24
+
25
+ ```sh
26
+ void platform user delete <user-id> --timeout 600 --plan
27
+ ```
28
+
29
+ If a request times out or loses its connection, some work may already have finished. Check the affected objects and `system events` before retrying. Void reports partial results when it can and does not automatically repeat the change.
30
+
31
+ ## Following Logs
32
+
33
+ To investigate a deployment, find its ID and follow its runtime logs:
34
+
35
+ ```sh
36
+ void platform deployment list --project <project-id>
37
+ void platform deployment logs <deployment-id> --since 10m --follow
38
+ ```
39
+
40
+ Runtime logs include application messages, exceptions, and HTTP status codes. Without `--follow`, the command reads a page of historical logs. Use `--since` to choose a duration, an ISO date, or a timestamp in milliseconds.
41
+
42
+ Logs can become available after a request finishes. Following checks a five-minute overlap to pick up delayed records without printing them again. Records delayed longer than that may need a later historical query.
43
+
44
+ Build logs use the build's ID:
45
+
46
+ ```sh
47
+ void platform build logs <build-id> --follow
48
+ ```
49
+
50
+ Following waits for the final logs before stopping, including diagnostics written after the build's status changes. For a GitHub Actions build, the command gives you the URL of its logs.
51
+
52
+ ## Checking the Platform
53
+
54
+ Use the overview to see recent activity, or run a health check to test the platform's services and database:
55
+
56
+ ```sh
57
+ void platform system overview
58
+ void platform system health
59
+ ```
60
+
61
+ Health checks use the services configured for the selected platform. An unhealthy result exits with a nonzero status, so the same command can be used in a script.
62
+
63
+ The browser dashboard's **System Status** checks refresh every 30 seconds. Failed checks show an HTTP status or connection error beside the service name. If the dashboard cannot refresh the checks, it reports that status is unavailable instead of displaying stale results.
64
+
65
+ Use `void platform upgrade`, `repair`, `disable`, and `enable` to maintain your platform. See [platform maintenance](/guide/platform/installation/maintenance#resume-repair-recover-and-upgrade).
66
+
67
+ For disaster recovery, keep a matching PostgreSQL backup, provider data backups, installation identity and complete project-encryption keyring, and immutable runtime artifacts. Keep traffic disabled while restoring data, run `discover` to reconstruct verified local metadata, and review `repair --plan` before recreating missing installer-owned infrastructure. Neither command restores backed-up data. See [Prepare for disaster recovery](/guide/platform/installation/maintenance#prepare-for-disaster-recovery) for the full sequence.
68
+
69
+ ## Using Scripts
70
+
71
+ Add `--json` to read a command's result from another program:
72
+
73
+ ```sh
74
+ void platform user list --page 1 --limit 50 --json
75
+ void platform system health --json
76
+ ```
77
+
78
+ Results go to standard output. Command errors go to standard error as JSON and exit with a nonzero status. With `--follow`, each log response is a separate JSON line.
79
+
80
+ Operator tokens expire after one hour. For CI, mint a new operator token at the start of each job from a valid full API login session belonging to an active administrator. Choose the platform explicitly, inject the full session as `VOID_TOKEN` from your secret manager, and capture the exchange output directly into the protected job environment:
81
+
82
+ ```sh
83
+ export VOID_API_URL=https://platform.example.com
84
+ VOID_OPERATOR_TOKEN="$(
85
+ printf '%s' "$VOID_TOKEN" | void platform auth token --token-stdin
86
+ )" || exit 1
87
+ export VOID_OPERATOR_TOKEN
88
+ unset VOID_TOKEN
89
+ void platform system health --json
90
+ unset VOID_OPERATOR_TOKEN
91
+ ```
92
+
93
+ Disable shell tracing for the exchange and do not write either token to logs or plaintext files. A full API login session expires after 30 days and can be revoked sooner; renew it through the normal authenticated login flow and update the protected CI secret. A job that runs longer than one hour must repeat the exchange while its full API session is still valid. An expired operator token cannot refresh itself, and Void does not issue permanent service tokens for administrator automation.
94
+
95
+ `auth login --token-stdin` performs the same elevation and saves the one-hour operator token in the system keychain for interactive use. See [operator authentication](/reference/cli#operator-authentication) for the full command syntax.
96
+
97
+ An email zone has one connection and ingress Worker shared by its exact domain assignments. Removing one assignment preserves resources used by the others. The Email administration page can explicitly rotate that connection secret; ordinary synchronization does not rotate it. Setup and cleanup outcomes include operation IDs, and uncertain provider writes remain recorded until reconciled.