void 0.24.0 → 0.26.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 (199) hide show
  1. package/README.md +19 -106
  2. package/dist/{account-cmd-vJhh5lrb.mjs → account-cmd-CDry3B5V.mjs} +5 -5
  3. package/dist/{application-analysis-BVsVqc11.mjs → application-analysis-DC2yPvzY.mjs} +1 -1
  4. package/dist/{application-routing-B7QEjqSR.mjs → application-routing-C9khgLiS.mjs} +1 -1
  5. package/dist/{auth-DX3JURcX.mjs → auth-B60QDbjg.mjs} +1 -1
  6. package/dist/{auth-CQkSDS50.mjs → auth-D52yG7hl.mjs} +3 -3
  7. package/dist/{auth-link-D75t9wO2.mjs → auth-link-DjeOptYi.mjs} +5 -6
  8. package/dist/{auth-router-ClfhL1J7.mjs → auth-router-BRxIuqDi.mjs} +7 -7
  9. package/dist/{build-cmd-zMi1iFnS.mjs → build-cmd-Ce3C8huo.mjs} +6 -7
  10. package/dist/{cache-ZtQmyxVV.mjs → cache-CS8Yxr5a.mjs} +6 -7
  11. package/dist/{cancel-deploy-Box0kkEV.mjs → cancel-deploy-mZsD88Xa.mjs} +4 -6
  12. package/dist/{cf-access-BGe5sWXF.mjs → cf-access-YTlqvAsk.mjs} +1 -1
  13. package/dist/cf-build-output-ChY6-XWP.mjs +2 -0
  14. package/dist/cli/cli.mjs +45 -45
  15. package/dist/cli/cloudflare-operation-process.mjs +3 -3
  16. package/dist/cli/env-schema-probe.d.mts +1 -1
  17. package/dist/cli/env-schema-probe.mjs +2 -2
  18. package/dist/client-BxVixJBI.mjs +2 -0
  19. package/dist/{client-DKAsFA4X.mjs → client-mC-dFGDD.mjs} +103 -25
  20. package/dist/{cloudflare-cmd-CmY8dGkd.mjs → cloudflare-cmd-bhwCxAN6.mjs} +2 -2
  21. package/dist/{cloudflare-connect-IiR3ZieC.mjs → cloudflare-connect-CuhRgk3k.mjs} +2 -2
  22. package/dist/{cloudflare-operations-DzH-A966.mjs → cloudflare-operations-DYa3coIk.mjs} +2 -2
  23. package/dist/{cloudflare-operations-BCdXSsmo.mjs → cloudflare-operations-DoQrVHAt.mjs} +1 -1
  24. package/dist/{connect-B-usTnlF.mjs → connect-H6EvP62X.mjs} +12 -9
  25. package/dist/{create-project-CYOOAYcn.mjs → create-project-B2kk9Q0o.mjs} +6 -6
  26. package/dist/{create-project-FWbf5Olo.mjs → create-project-Dbb4uiN-.mjs} +1 -1
  27. package/dist/{db-Ck2eZ2iB.mjs → db-DpCKZto0.mjs} +35 -38
  28. package/dist/{delete-sK56qtzZ.mjs → delete-CxlSvsvW.mjs} +6 -7
  29. package/dist/{deploy-gCVaiJbS.mjs → deploy-CeEbnv4b.mjs} +9249 -8925
  30. package/dist/deploy-qmiLE1G1.mjs +2 -0
  31. package/dist/{dev-inbox-P0u4tM8Y.mjs → dev-inbox-D64ZKUod.mjs} +5 -6
  32. package/dist/{domain-DFfQE8jg.mjs → domain-B_9VgOm6.mjs} +8 -8
  33. package/dist/{email-CdnhUHvJ.mjs → email-BokHgjKj.mjs} +7 -7
  34. package/dist/{env-DmGdHi5i.mjs → env-BdTc-K2L.mjs} +7 -8
  35. package/dist/{env-public-BX_r8HR6.d.mts → env-public-DR7ZM6d-.d.mts} +1 -1
  36. package/dist/{env-validation-zZOCcQVZ.mjs → env-validation-cZBvGWaP.mjs} +1 -1
  37. package/dist/{fetch-stream-IvCYKyQL.mjs → fetch-stream-Nm3hkZbT.mjs} +8 -1
  38. package/dist/{gen-CshAkukR.mjs → gen-BhDlTpwW.mjs} +2 -4
  39. package/dist/gen-qCBwH3fZ.mjs +2 -0
  40. package/dist/{github-cmd-DftuEvIt.mjs → github-cmd-hhkTzlGS.mjs} +7 -16
  41. package/dist/{handler-CZ4nAylQ.d.mts → handler-BeK3weD4.d.mts} +1 -1
  42. package/dist/{help-Dotbf6iz.mjs → help-Bv44e2tm.mjs} +13 -9
  43. package/dist/help-Dw5f9Xrg.mjs +2 -0
  44. package/dist/index.d.mts +19 -15
  45. package/dist/index.mjs +2225 -2142
  46. package/dist/{info-B97bTX9N.mjs → info-DhtTKh65.mjs} +2 -4
  47. package/dist/{init-BTQEZZGJ.mjs → init-BMf4T03q.mjs} +17 -18
  48. package/dist/{limits-Bq5LG8Id.d.mts → limits-CFsDKQvU.d.mts} +1 -10
  49. package/dist/{limits-Cjuk2VPm.mjs → limits-D0vD1M9A.mjs} +1 -22
  50. package/dist/{link-B46sYH0K.mjs → link-BqYsSS_I.mjs} +6 -7
  51. package/dist/{list-BnPrIsU_.mjs → list-_kBToO-l.mjs} +10 -10
  52. package/dist/{live-Chw1eIMv.d.mts → live-iAuEH1hd.d.mts} +1 -1
  53. package/dist/{local-d1-2CMnpuW_.mjs → local-d1-fGxYrVD6.mjs} +2 -2
  54. package/dist/login-D7meQg_3.mjs +2 -0
  55. package/dist/{login-BuiMyUrx.mjs → login-DFuo-RWO.mjs} +9 -7
  56. package/dist/{logs-CZG9d-ic.mjs → logs-DYgnh6dH.mjs} +7 -8
  57. package/dist/migrate-CEu2X5DB.mjs +2 -0
  58. package/dist/{migrate-q4_njtaW.mjs → migrate-CV1on-yJ.mjs} +2 -4
  59. package/dist/{node-BkyRWRx8.mjs → node-C6Q6e1Es.mjs} +1 -1
  60. package/dist/{operator-args-B3BTmSoi.mjs → operator-args-DdY54Bgb.mjs} +1 -1
  61. package/dist/{operator-auth-7erYc6Lt.mjs → operator-auth-2dsGlTT5.mjs} +4 -8
  62. package/dist/{operator-client-CpHjTd0t.mjs → operator-client-BFqpp6z0.mjs} +3 -3
  63. package/dist/{operator-cmd-DGqPJUD3.mjs → operator-cmd-CUGHXrQf.mjs} +27 -9
  64. package/dist/operator-cmd-DRPvhpjM.mjs +2 -0
  65. package/dist/{output-CbM85T4k.mjs → output-BvdaehtE.mjs} +28 -4
  66. package/dist/pages/client.d.mts +4 -1
  67. package/dist/pages/client.mjs +120 -29
  68. package/dist/pages/index.mjs +1 -1
  69. package/dist/pages/islands-plugin.mjs +1 -1
  70. package/dist/pages/protocol.d.mts +2 -2
  71. package/dist/{plan-NPpwZ_kc.mjs → plan-CJGEeTEI.mjs} +2 -2
  72. package/dist/plan-COtUvdMA.mjs +2 -0
  73. package/dist/{platform-args-3k8X9-H_.mjs → platform-args-DSV2p4nY.mjs} +4 -2
  74. package/dist/platform-args-Dcy_5b_Y.mjs +2 -0
  75. package/dist/{platform-auth-config-DDrxUy4m.mjs → platform-auth-config-Bosx7U1h.mjs} +20 -19
  76. package/dist/{platform-auth-protection-BLadnnct.mjs → platform-auth-protection-DcbL_LW_.mjs} +8 -8
  77. package/dist/{platform-auth-recovery-CmCzX2Fl.mjs → platform-auth-recovery-WfTZ8K6o.mjs} +7 -7
  78. package/dist/{platform-cmd-DMDEc9uS.mjs → platform-cmd-DbKtQb_4.mjs} +73 -38
  79. package/dist/{platform-cmd-pMTRhvYs.mjs → platform-cmd-dyFUof0t.mjs} +1 -1
  80. package/dist/platform-discovery-BC0HCKYt.mjs +41 -0
  81. package/dist/{platform-domain-Di6EYrQl.mjs → platform-domain-Bsq43AJp.mjs} +5 -4
  82. package/dist/{platform-lifecycle-BFdSi9Qt.mjs → platform-lifecycle--5gWmDoI.mjs} +1 -1
  83. package/dist/{platform-lifecycle-BBoNzojY.mjs → platform-lifecycle-Cfi0o6xM.mjs} +4605 -3527
  84. package/dist/platform-management-Bbwf6-Dq.mjs +2 -0
  85. package/dist/{platform-management-CWOQOWXm.mjs → platform-management-BrF9STZp.mjs} +211 -31
  86. package/dist/{platform-plans-config-Vl9BP3H4.mjs → platform-plans-config-DU-8nC2Y.mjs} +3 -3
  87. package/dist/{platform-recovery-BJnQJEtQ.mjs → platform-recovery-DQAqAQYu.mjs} +3 -3
  88. package/dist/{platform-registry-DkPPXfp_.mjs → platform-registry-P387v42A.mjs} +18 -11
  89. package/dist/prepare-BBknoZI1.mjs +2 -0
  90. package/dist/{prepare-CBLT3CHH.mjs → prepare-BMrRWLwP.mjs} +3 -4
  91. package/dist/{prepare-DnaKBi--.mjs → prepare-WoUubFKL.mjs} +1 -1
  92. package/dist/{project-DsblYifa.mjs → project-BiaPQ7ur.mjs} +1 -1
  93. package/dist/{project-DKG5Ra8p.mjs → project-DQstYTxO.mjs} +1 -1
  94. package/dist/{project-cmd-DxnXstAq.mjs → project-cmd-BbH2LptN.mjs} +21 -22
  95. package/dist/{project-team-BmHcaALJ.mjs → project-team-Yiww3-go.mjs} +8 -9
  96. package/dist/{project-token-GionZlBI.mjs → project-token-BrtT2NkY.mjs} +6 -9
  97. package/dist/{project-zero-trust-JLCjVQA7.mjs → project-zero-trust-nYJ2V2nc.mjs} +5 -6
  98. package/dist/{prompts-BNUoTiaf.mjs → prompts-6OSVA7DJ.mjs} +58 -15
  99. package/dist/{prompts-2TGE6oXx.mjs → prompts-ZAuJlyCj.mjs} +1 -1
  100. package/dist/{protocol-CjF_iI9X.d.mts → protocol-BX8t2jiw.d.mts} +1 -1
  101. package/dist/{provision-CVku1JFU.mjs → provision-DMTPG8Rh.mjs} +1 -1
  102. package/dist/{provision-CaoxO_U7.mjs → provision-VeqAC77A.mjs} +18 -8
  103. package/dist/{requests-BOjIVAeF.mjs → requests-TIz_jtSu.mjs} +5 -6
  104. package/dist/{rollback-CwR3pLj3.mjs → rollback-dBqICPzj.mjs} +9 -10
  105. package/dist/{runner-Ol0TNjk6.mjs → runner--2KF6Kua.mjs} +1 -1
  106. package/dist/{runner-CXA9Fh8h.mjs → runner-voIltZrY.mjs} +1 -1
  107. package/dist/runtime/ai.d.mts +10 -11
  108. package/dist/runtime/ai.mjs +13 -18
  109. package/dist/runtime/client-react.mjs +1 -1
  110. package/dist/runtime/client-solid.mjs +1 -1
  111. package/dist/runtime/client-svelte.mjs +1 -1
  112. package/dist/runtime/client-vue.mjs +1 -1
  113. package/dist/runtime/client.mjs +1 -1
  114. package/dist/runtime/email/testing.mjs +1 -1
  115. package/dist/runtime/email.mjs +35 -17
  116. package/dist/runtime/env-public.d.mts +1 -1
  117. package/dist/runtime/fetch-stream.mjs +1 -1
  118. package/dist/runtime/handler.d.mts +1 -1
  119. package/dist/runtime/limits.d.mts +2 -2
  120. package/dist/runtime/limits.mjs +2 -2
  121. package/dist/runtime/live-client.d.mts +1 -1
  122. package/dist/runtime/live.d.mts +1 -1
  123. package/dist/runtime/migration-handler.mjs +33 -7
  124. package/dist/runtime/routing.d.mts +1 -1
  125. package/dist/runtime/routing.mjs +1 -1
  126. package/dist/runtime/sandbox.d.mts +1 -1
  127. package/dist/runtime/sandbox.mjs +12 -22
  128. package/dist/runtime/validator.d.mts +1 -1
  129. package/dist/runtime/ws-server.d.mts +1 -1
  130. package/dist/runtime/ws.d.mts +1 -1
  131. package/dist/{sandbox-XZAqzFlG.d.mts → sandbox-C5LFoJMf.d.mts} +7 -7
  132. package/dist/{scan-C7okrLyM.mjs → scan-C8dA7iM4.mjs} +10 -6
  133. package/dist/{secret-ByWJWBUG.mjs → secret-AKnNXNP5.mjs} +15 -16
  134. package/dist/{skills-C1PlclPt.mjs → skills-BPQUGClu.mjs} +1 -1
  135. package/dist/{subcommand-prompt-klFCfcor.mjs → subcommand-prompt-B6WS9Aoz.mjs} +2 -2
  136. package/dist/sveltekit.d.mts +1 -1
  137. package/dist/sveltekit.mjs +4 -9
  138. package/dist/validate-pna7R3Pv.mjs +2 -0
  139. package/dist/{validate-ctOrgiS3.mjs → validate-ymhap6M_.mjs} +1 -1
  140. package/dist/{wrangler-7K-bW_DL.mjs → wrangler-DhUerFkW.mjs} +10 -15
  141. package/dist/{ws-CL1w7GXU.d.mts → ws-C79j4aED.d.mts} +1 -1
  142. package/package.json +21 -21
  143. package/sandbox.Dockerfile +1 -1
  144. package/skills/void/SKILL.md +5 -2
  145. package/skills/void/docs/guide/ai.md +78 -137
  146. package/skills/void/docs/guide/app-types.md +1 -1
  147. package/skills/void/docs/guide/auth.md +0 -2
  148. package/skills/void/docs/guide/database/d1.md +7 -7
  149. package/skills/void/docs/guide/database.md +15 -0
  150. package/skills/void/docs/guide/deployment.md +58 -124
  151. package/skills/void/docs/guide/env-vars.md +1 -5
  152. package/skills/void/docs/guide/index.md +19 -18
  153. package/skills/void/docs/guide/kv.md +0 -2
  154. package/skills/void/docs/guide/live.md +2 -0
  155. package/skills/void/docs/guide/pages-routing/actions-and-forms.md +49 -47
  156. package/skills/void/docs/guide/pages-routing/overview.md +2 -2
  157. package/skills/void/docs/guide/platform/administration/email.md +0 -2
  158. package/skills/void/docs/guide/platform/administration/operations.md +11 -22
  159. package/skills/void/docs/guide/platform/administration/plans.md +1 -5
  160. package/skills/void/docs/guide/platform/administration/zero-trust.md +1 -4
  161. package/skills/void/docs/guide/platform/development/schema-ci.md +7 -1
  162. package/skills/void/docs/guide/platform/installation/credentials.md +2 -2
  163. package/skills/void/docs/guide/platform/installation/first-deployment.md +5 -1
  164. package/skills/void/docs/guide/platform/installation/maintenance.md +33 -11
  165. package/skills/void/docs/guide/platform/installation/prerequisites.md +79 -7
  166. package/skills/void/docs/guide/platform/installation/setup.md +1 -1
  167. package/skills/void/docs/guide/platform/installation/uninstall.md +44 -11
  168. package/skills/void/docs/guide/quickstart.md +37 -80
  169. package/skills/void/docs/guide/sandboxes.md +75 -66
  170. package/skills/void/docs/guide/self-hosted-platform.md +20 -0
  171. package/skills/void/docs/guide/websockets.md +0 -2
  172. package/skills/void/docs/integrations/frameworks/overview.md +2 -2
  173. package/skills/void/docs/integrations/frameworks/sveltekit.md +40 -25
  174. package/skills/void/docs/integrations/nodejs-bun-deno.md +7 -2
  175. package/skills/void/docs/reference/api/resources.md +38 -14
  176. package/skills/void/docs/reference/api.md +35 -34
  177. package/skills/void/docs/reference/cli/auth.md +3 -1
  178. package/skills/void/docs/reference/cli/deploy.md +1 -1
  179. package/skills/void/docs/reference/cli/platform-config.md +2 -2
  180. package/skills/void/docs/reference/cli/platform-installation.md +14 -12
  181. package/skills/void/docs/reference/cli/platform-operations.md +4 -8
  182. package/skills/void/docs/reference/cli/platform.md +9 -2
  183. package/skills/void/docs/reference/cli.md +1 -1
  184. package/skills/void/docs/reference/resource-inference.md +12 -8
  185. package/dist/cf-build-output-_0HNWysu.mjs +0 -2
  186. package/dist/client-B9UBI03V.mjs +0 -2
  187. package/dist/deploy-irP5q0-x.mjs +0 -2
  188. package/dist/gen-DvQTWzDU.mjs +0 -2
  189. package/dist/help-mabuIV98.mjs +0 -2
  190. package/dist/login-DIy9Sb-U.mjs +0 -2
  191. package/dist/migrate-BCiJ8w0f.mjs +0 -2
  192. package/dist/plan-D1Q5rf-r.mjs +0 -2
  193. package/dist/platform-args-DdnM6WIf.mjs +0 -2
  194. package/dist/platform-management-DG47jys8.mjs +0 -2
  195. package/dist/prepare-CnjcTV2s.mjs +0 -2
  196. package/dist/validate-Dq_L3s0S.mjs +0 -2
  197. package/skills/void/docs/guide/env-migration.md +0 -109
  198. package/dist/{cf-build-output-BPqOT964.mjs → cf-build-output-U8o1Gl6X.mjs} +1 -1
  199. package/dist/{env-validation-C_8tPdhs.mjs → env-validation-vBSQ8gw5.mjs} +1 -1
@@ -1,5 +1,5 @@
1
- import { $ as wrapTextWithPrefix, G as promptTitle, M as getGuide, V as optionText, b as S_RADIO_ACTIVE, d as S_CHECKBOX_SELECTED, i as S_BAR_END, it as wrapAnsi, j as formatInstructionFooter, n as SELECT_INSTRUCTIONS, r as S_BAR, t as MULTISELECT_INSTRUCTIONS, u as S_CHECKBOX_INACTIVE, w as S_STEP_SUBMIT } from "./prompts-BNUoTiaf.mjs";
2
- import { c as import_picocolors } from "./output-CbM85T4k.mjs";
1
+ import { $ as wrapTextWithPrefix, G as promptTitle, M as getGuide, V as optionText, b as S_RADIO_ACTIVE, d as S_CHECKBOX_SELECTED, i as S_BAR_END, it as wrapAnsi, j as formatInstructionFooter, n as SELECT_INSTRUCTIONS, r as S_BAR, t as MULTISELECT_INSTRUCTIONS, u as S_CHECKBOX_INACTIVE, w as S_STEP_SUBMIT } from "./prompts-6OSVA7DJ.mjs";
2
+ import { c as import_picocolors } from "./output-BvdaehtE.mjs";
3
3
  //#region src/cli/subcommand-prompt.ts
4
4
  function promptSubcommand(command, subcommands) {
5
5
  return new Promise((resolve) => {
@@ -4,7 +4,7 @@ type TsconfigHook = (config: Tsconfig) => Tsconfig | void;
4
4
  export type VoidSvelteKitTsconfigOptions = {
5
5
  /** Project root. Defaults to process.cwd(). */
6
6
  root?: string;
7
- /** SvelteKit output directory. Defaults to ".svelte-kit". */
7
+ /** @deprecated Generated paths no longer depend on SvelteKit's output directory. */
8
8
  outDir?: string;
9
9
  };
10
10
  /**
@@ -1,4 +1,4 @@
1
- import { a as join, i as isAbsolute, l as relative, u as resolve } from "./dist-BVqXeR_h.mjs";
1
+ import { a as join, i as isAbsolute, u as resolve } from "./dist-BVqXeR_h.mjs";
2
2
  import { existsSync, readFileSync } from "node:fs";
3
3
  //#region src/sveltekit.ts
4
4
  /**
@@ -7,18 +7,17 @@ import { existsSync, readFileSync } from "node:fs";
7
7
  * `kit.typescript.config` in `svelte.config.js`.
8
8
  */
9
9
  function mergeVoidSvelteKitTsconfig(config, options = {}) {
10
- const root = options.root ?? process.cwd();
10
+ const root = resolve(options.root ?? process.cwd());
11
11
  const voidDir = join(root, ".void");
12
12
  const voidTsconfigPath = join(voidDir, "tsconfig.json");
13
13
  if (!existsSync(voidTsconfigPath)) return config;
14
14
  const voidConfig = JSON.parse(readFileSync(voidTsconfigPath, "utf-8"));
15
- const outputDir = resolve(root, options.outDir ?? ".svelte-kit");
16
15
  const voidFiles = Array.isArray(voidConfig.files) ? voidConfig.files.filter((entry) => typeof entry === "string") : [];
17
- if (voidFiles.length > 0) config.files = uniqueStrings([...Array.isArray(config.files) ? config.files.filter((entry) => typeof entry === "string") : [], ...voidFiles.map((entry) => relativeFrom(outputDir, resolveTsconfigEntry(voidDir, entry)))]);
16
+ if (voidFiles.length > 0) config.files = uniqueStrings([...Array.isArray(config.files) ? config.files.filter((entry) => typeof entry === "string") : [], ...voidFiles.map((entry) => resolveTsconfigEntry(voidDir, entry))]);
18
17
  const voidPaths = readPathMap(readObject(readObject(voidConfig.compilerOptions)?.paths));
19
18
  if (Object.keys(voidPaths).length > 0) {
20
19
  const paths = ensureObject(ensureObject(config, "compilerOptions"), "paths");
21
- for (const [key, values] of Object.entries(voidPaths)) paths[key] = values.map((entry) => relativeFrom(outputDir, resolveTsconfigEntry(voidDir, entry)));
20
+ for (const [key, values] of Object.entries(voidPaths)) paths[key] = values.map((entry) => resolveTsconfigEntry(voidDir, entry));
22
21
  }
23
22
  return config;
24
23
  }
@@ -51,10 +50,6 @@ function readPathMap(value) {
51
50
  function resolveTsconfigEntry(baseDir, entry) {
52
51
  return isAbsolute(entry) ? entry : resolve(baseDir, entry);
53
52
  }
54
- function relativeFrom(fromDir, target) {
55
- const value = relative(fromDir, target);
56
- return value.startsWith(".") ? value : `./${value}`;
57
- }
58
53
  function uniqueStrings(values) {
59
54
  return Array.from(new Set(values));
60
55
  }
@@ -0,0 +1,2 @@
1
+ import { i as validateMigrations } from "./validate-ymhap6M_.mjs";
2
+ export { validateMigrations };
@@ -1,6 +1,6 @@
1
1
  import { a as join } from "./dist-BVqXeR_h.mjs";
2
2
  import { n as readJournal, r as stripSqlExt } from "./collect-DiTn9yr6.mjs";
3
- import { t as DESTRUCTIVE_PRAGMA } from "./plan-NPpwZ_kc.mjs";
3
+ import { t as DESTRUCTIVE_PRAGMA } from "./plan-CJGEeTEI.mjs";
4
4
  import { existsSync, readdirSync } from "node:fs";
5
5
  import { tokenizeSql } from "@void/deploy-core";
6
6
  //#region src/migrations/validate.ts
@@ -4,7 +4,7 @@ import { m as readConfig } from "./config-Br_JZD6u.mjs";
4
4
  import { a as hasSandboxSqliteMigration, c as resolveSandboxConfig, i as assertSandboxNamespaceConfig, l as sandboxContainerConfig, r as SANDBOX_MIGRATION_TAG } from "./config-C_XRIPx2.mjs";
5
5
  import { i as persistCloudflareConfig, r as materializeCloudflareConfig } from "./cloudflare-config-Bktvwtpf.mjs";
6
6
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
7
- import { appendSqliteClassMigration } from "@void/deploy-core";
7
+ import { appendSqliteClassMigration, isRecord } from "@void/deploy-core";
8
8
  import { createHash } from "node:crypto";
9
9
  import { SyntaxKind, applyEdits, createScanner, findNodeAtLocation, modify, parse, parseTree } from "jsonc-parser";
10
10
  //#region src/cli/wrangler.ts
@@ -686,7 +686,10 @@ function writeProvisionedWranglerConfig(root, patch, options) {
686
686
  const matches = [];
687
687
  for (let i = 0; i < currentList.length; i++) {
688
688
  const entry = currentList[i];
689
- if (isRecord(entry) && entry[identityKey] === binding) matches.push(i);
689
+ if (isRecord(entry) && entry[identityKey] === binding) matches.push({
690
+ index: i,
691
+ entry
692
+ });
690
693
  }
691
694
  if (matches.length > 1) throw new Error(`deploy: cannot update "${configPath}" — binding ${JSON.stringify(binding)} is declared ${matches.length} times in "${field}". Void cannot choose which entry to update, and wrangler rejects duplicate binding names. Remove the duplicate and retry.`);
692
695
  const owners = collectWorkerBindingNames(configWithoutField(currentConfig, field));
@@ -694,8 +697,7 @@ function writeProvisionedWranglerConfig(root, patch, options) {
694
697
  const owner = owners.get(binding);
695
698
  if (owner !== void 0) throw new Error(`deploy: cannot update "${configPath}" — cannot adopt ${JSON.stringify(binding)} into "${field}" because that binding name is already used by a ${owner} binding. Wrangler requires every binding name to be unique across the whole worker, so this would make the config undeployable. Rename the conflicting binding and retry.`);
696
699
  if (matches.length === 1) {
697
- const index = matches[0];
698
- const existing = isRecord(currentList[index]) ? currentList[index] : {};
700
+ const { index, entry: existing } = matches[0];
699
701
  for (const [key, value] of Object.entries(properties)) if (!jsonEqual(existing[key], value)) set([
700
702
  field,
701
703
  index,
@@ -752,20 +754,16 @@ function writeProvisionedWranglerConfig(root, patch, options) {
752
754
  const upsertQueueEntries = (section, entries) => {
753
755
  if (!entries || entries.length === 0) return;
754
756
  const currentQueues = assertObjectContainer(parseJsonc(text).queues, "queues");
755
- if (section === "producers") assertValidQueueProducers(currentQueues?.producers);
756
- const currentList = Array.isArray(currentQueues?.[section]) ? [...currentQueues[section]] : [];
757
+ const currentList = section === "producers" ? assertValidQueueProducers(currentQueues?.producers) : Array.isArray(currentQueues?.consumers) ? [...currentQueues.consumers] : [];
757
758
  for (const entry of entries) {
758
759
  let index;
759
760
  if (section === "producers") {
760
761
  const matches = [];
761
- for (let i = 0; i < currentList.length; i++) {
762
- const existing = currentList[i];
763
- if (isRecord(existing) && existing.binding === entry.binding) matches.push(i);
764
- }
762
+ for (let i = 0; i < currentList.length; i++) if (currentList[i].binding === entry.binding) matches.push(i);
765
763
  if (matches.length > 1) throw new Error(`deploy: cannot update "${configPath}" — queue producer binding ${JSON.stringify(entry.binding)} is declared ${matches.length} times across producers. A producer binding must map to exactly one queue, so wrangler rejects this config. Remove the duplicate producers and retry.`);
766
764
  if (matches.length === 1) {
767
765
  const existing = currentList[matches[0]];
768
- if (isRecord(existing) && existing.queue !== entry.queue) throw new Error(`deploy: cannot update "${configPath}" — queue producer binding ${JSON.stringify(entry.binding)} is already bound to queue ${JSON.stringify(existing.queue)}, but queue ${JSON.stringify(entry.queue)} also requests it. A producer binding must map to exactly one queue. Rename or remove the conflicting producer and retry.`);
766
+ if (existing.queue !== entry.queue) throw new Error(`deploy: cannot update "${configPath}" — queue producer binding ${JSON.stringify(entry.binding)} is already bound to queue ${JSON.stringify(existing.queue)}, but queue ${JSON.stringify(entry.queue)} also requests it. A producer binding must map to exactly one queue. Rename or remove the conflicting producer and retry.`);
769
767
  }
770
768
  index = matches.length === 1 ? matches[0] : -1;
771
769
  workerBindingOwners ??= collectWorkerBindingNames(parseJsonc(text));
@@ -774,7 +772,7 @@ function writeProvisionedWranglerConfig(root, patch, options) {
774
772
  if (owner !== void 0) throw new Error(`deploy: cannot update "${configPath}" — queue producer binding ${JSON.stringify(bindingName)} is already used by a ${owner} binding. Wrangler requires every binding name to be unique across the whole worker, so this producer would make the config undeployable. Rename the conflicting binding (or the queue) and retry.`);
775
773
  } else index = currentList.findIndex((existing) => isRecord(existing) && existing.queue === entry.queue);
776
774
  if (index >= 0) {
777
- const existing = isRecord(currentList[index]) ? currentList[index] : {};
775
+ const existing = currentList[index];
778
776
  for (const [key, value] of Object.entries(entry)) {
779
777
  if (value === void 0) continue;
780
778
  if (!jsonEqual(existing[key], value)) set([
@@ -1243,9 +1241,6 @@ function offsetToLineColumn(text, offset) {
1243
1241
  column
1244
1242
  };
1245
1243
  }
1246
- function isRecord(value) {
1247
- return typeof value === "object" && value !== null && !Array.isArray(value);
1248
- }
1249
1244
  function validateDurableObjectMigrations(value, configPath) {
1250
1245
  if (value === void 0) return [];
1251
1246
  if (!Array.isArray(value)) throw new Error(`deploy: cannot update "${configPath}" — "migrations" must be an array of Durable Object migration steps.`);
@@ -1,6 +1,6 @@
1
1
  import { t as StandardSchemaV1 } from "./standard-schema-Fo_vCAZh.mjs";
2
2
  import { r as AuthUser } from "./auth-DkcFflXV.mjs";
3
- import { a as CloudEnv } from "./handler-CZ4nAylQ.mjs";
3
+ import { a as CloudEnv } from "./handler-BeK3weD4.mjs";
4
4
  import { WebSocketRouteMap } from "void/routes";
5
5
  //#region src/runtime/ws.d.ts
6
6
  type MaybePromise<T> = T | Promise<T>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "void",
3
- "version": "0.24.0",
3
+ "version": "0.26.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/voidzero-dev/void.git",
@@ -331,24 +331,24 @@
331
331
  }
332
332
  },
333
333
  "dependencies": {
334
- "@cloudflare/build-output-utils": "0.8.4",
335
- "@cloudflare/codemods": "0.3.0",
336
- "@cloudflare/config": "0.22.0",
334
+ "@cloudflare/build-output-utils": "0.8.5",
335
+ "@cloudflare/codemods": "0.4.0",
336
+ "@cloudflare/config": "0.23.0",
337
337
  "@cloudflare/sandbox": "1.0.0",
338
- "@cloudflare/vite-plugin": "1.62.2",
339
- "@cloudflare/workers-auth": "0.9.5",
338
+ "@cloudflare/vite-plugin": "1.62.5",
339
+ "@cloudflare/workers-auth": "0.12.0",
340
340
  "@cloudflare/workers-types": "^4.20260702.1",
341
341
  "@napi-rs/keyring": "2.1.0",
342
- "@void/deploy-cloudflare": "0.24.0",
343
- "@void/deploy-core": "0.24.0",
344
- "@void/edge": "0.24.0",
345
- "@void/isr": "0.24.0",
346
- "@void/platform": "0.24.0",
342
+ "@void/deploy-cloudflare": "0.26.0",
343
+ "@void/deploy-core": "0.26.0",
344
+ "@void/edge": "0.26.0",
345
+ "@void/isr": "0.26.0",
346
+ "@void/platform": "0.26.0",
347
347
  "acorn": "^8.18.0",
348
- "better-auth": "^1.7.6",
348
+ "better-auth": "^1.7.7",
349
349
  "better-sqlite3": "^13.0.3",
350
350
  "blake3-jit": "^1.1.0",
351
- "cf": "1.0.0-beta.9",
351
+ "cf": "1.0.0-beta.12",
352
352
  "drizzle-arktype": "^0.1.3",
353
353
  "drizzle-kit": "^0.31.11",
354
354
  "drizzle-orm": "^0.45.3",
@@ -356,27 +356,27 @@
356
356
  "drizzle-zod": "^0.8.3",
357
357
  "email-validator": "^2.0.4",
358
358
  "es-module-lexer": "^3.0.2",
359
- "hono": "^4.13.11",
360
- "ignore": "^7.0.10",
359
+ "hono": "^4.13.13",
360
+ "ignore": "^7.0.12",
361
361
  "jiti": "^2.7.0",
362
362
  "jsonc-parser": "3.3.1",
363
363
  "mime": "4.1.0",
364
- "miniflare": "5.20260926.1-alpha",
364
+ "miniflare": "5.20261001.0-alpha",
365
365
  "minimatch": "^10.2.6",
366
366
  "mysql2": "3.24.5",
367
- "pg": "8.23.0",
368
- "postal-mime": "^4.0.0",
367
+ "pg": "8.23.1",
368
+ "postal-mime": "^4.0.4",
369
369
  "proper-lockfile": "^4.1.2",
370
370
  "undici": "8.11.2"
371
371
  },
372
372
  "devDependencies": {
373
373
  "@electric-sql/pglite": "0.5.8",
374
374
  "@types/better-sqlite3": "^9.6.0",
375
- "@types/node": "^26.6.3",
375
+ "@types/node": "^26.6.4",
376
376
  "@types/pg": "^8.23.1",
377
377
  "@types/proper-lockfile": "^4.1.4",
378
378
  "@voidzero-dev/vite-plus-prompts": "0.0.0",
379
- "arktype": "^2.2.5",
379
+ "arktype": "^2.2.7",
380
380
  "ci-info": "^4.4.0",
381
381
  "estree-walker": "^3.0.3",
382
382
  "fast-wrap-ansi": "^0.2.2",
@@ -393,7 +393,7 @@
393
393
  "zod": "^4.6.5"
394
394
  },
395
395
  "peerDependencies": {
396
- "@void/md": "0.24.0",
396
+ "@void/md": "0.26.0",
397
397
  "arktype": ">=2.0.0",
398
398
  "valibot": ">=1.0.0-beta.7",
399
399
  "vite": "^8.0.0",
@@ -1,4 +1,4 @@
1
- FROM node:24.21.0-trixie-slim
1
+ FROM node:26.10.0-trixie-slim
2
2
  COPY --from=docker.io/cloudflare/sandbox:1.0.0 /usr/local/bin/sandbox-shim /usr/local/bin/sandbox-shim
3
3
  WORKDIR /workspace
4
4
  CMD ["sleep", "infinity"]
@@ -19,6 +19,8 @@ Use `void` and `@void/*` in imports, examples, and package manifests. Follow the
19
19
  project's existing framework and deployment destination. Deploying to a Void
20
20
  platform requires an explicit `void connect <url>` first; never infer a platform
21
21
  from old examples, tokens, or project IDs.
22
+ Platform hostnames passed to `void connect` default to HTTPS; include `http://`
23
+ explicitly when connecting to a local HTTP platform.
22
24
  For a one-time deployment elsewhere, connect to that platform, then set
23
25
  `VOID_API_URL` for the deploy command and pass `--project <slug>`. This preserves
24
26
  the app's existing saved destination, including when the target project is new.
@@ -78,7 +80,8 @@ the app's existing saved destination, including when the target project is new.
78
80
  - Declare application environment keys in `env.ts` and access them through
79
81
  `void/env`; follow `docs/guide/env-vars.md` for local and production values.
80
82
  - For platform setup, follow the pages linked from
81
- `docs/guide/self-hosted-platform.md`. For recovery, use its documented resume
83
+ `docs/guide/self-hosted-platform.md`. Use `void platform list` to find the
84
+ installation slug for maintenance. For recovery, use its documented resume
82
85
  or repair workflow and preserve the installation's credentials and resources.
83
86
  - Use `--plan` to review platform changes. If an operation has an uncertain
84
87
  outcome, inspect its status and events before retrying.
@@ -87,4 +90,4 @@ If the user invokes this skill without a task, inspect the app's local structure
87
90
  configuration, and saved deployment destination, give a brief status, and ask
88
91
  what they want to work on.
89
92
 
90
- For AI and Sandbox operations, use `.match({ ok, limited })` and choose a useful quota fallback. In Pages, prefer the adapter’s `Form` with `useForm()` so action failures remain visible and entered data is preserved. See `docs/guide/ai.md`, `docs/guide/sandboxes.md`, and `docs/guide/pages-routing/actions-and-forms.md`.
93
+ Await AI and Sandbox operations normally. Usage limits reject with `UsageLimit` from `void/limits`; Void routes and Pages actions automatically return HTTP 429. Optionally catch with `instanceof UsageLimit` for a fallback or custom response. In Pages, prefer the adapter’s `Form` with `useForm()` so action failures remain visible and entered data is preserved. See `docs/guide/ai.md`, `docs/guide/sandboxes.md`, and `docs/guide/pages-routing/actions-and-forms.md`.
@@ -4,19 +4,24 @@ outline: deep
4
4
 
5
5
  # AI
6
6
 
7
- Void provides a typed AI client for Workers AI and Cloudflare's [AI Gateway](https://developers.cloudflare.com/ai-gateway/). Import `ai` from `void/ai` and run inference directly from your route handlers. Direct deployments use your Cloudflare account; a team platform routes requests through its installation's proxy and usage controls.
7
+ Run Workers AI models and provider APIs from server code with `ai` from `void/ai`. Provider requests use Cloudflare's [AI Gateway](https://developers.cloudflare.com/ai-gateway/).
8
8
 
9
- ```ts
10
- import { ai } from 'void/ai';
11
- ```
9
+ ## Local Development
12
10
 
13
- ## Handling usage limits
11
+ For a direct Cloudflare project, connect your account and start development:
14
12
 
15
- Every AI operation is lazy and must be executed with `.match({ ok, limited })`. Both handlers are required, including for provider-native requests, model listing and document conversion. Return a useful fallback from `limited`, or use `limit.response({ message: 'AI is temporarily unavailable.' })` to send a structured HTTP 429. Limits expose `resource`, `reason`, and an optional `retryAt`. Other failures still reject.
13
+ ```sh
14
+ void connect --platform cloudflare
15
+ npm run dev
16
+ ```
16
17
 
17
- Do not await the operation itself; await its `.match()` call. TypeScript checks your handlers and the resulting success/fallback union.
18
+ When the app imports `void/ai`, Void enables a remote Workers AI binding during
19
+ development and preview. Requests use your Cloudflare account and its allowance;
20
+ Workers AI has no local simulator. Use your Cloudflare login or
21
+ `CLOUDFLARE_API_TOKEN` in an automated environment.
18
22
 
19
- Streaming responses end with a `void:error` SSE event if generation is interrupted after the response starts. A recognized quota interruption calls your `limited` handler and emits `void:limit`, preserving the message from `limit.response()`. `fetchStream()` throws on either terminal event so your UI can exit its generating state. This also applies to streaming provider responses and `ai.run()` with `stream: true`.
23
+ For a team platform, use `void connect <platform-url>` and link your project.
24
+ Development requests use that platform's account and usage limits.
20
25
 
21
26
  ## Basic Usage
22
27
 
@@ -29,16 +34,15 @@ import { ai } from 'void/ai';
29
34
  export const POST = defineHandler(async (c) => {
30
35
  const { prompt } = await c.req.json();
31
36
 
32
- const result = await ai
33
- .run('@cf/meta/llama-3.3-70b-instruct-fp8-fast', {
34
- messages: [{ role: 'user', content: prompt }],
35
- })
36
- .match({ ok: (result) => result, limited: (limit) => limit.response() });
37
-
37
+ const result = await ai.run('@cf/meta/llama-3.3-70b-instruct-fp8-fast', {
38
+ messages: [{ role: 'user', content: prompt }],
39
+ });
38
40
  return c.json(result);
39
41
  });
40
42
  ```
41
43
 
44
+ AI methods return promises. Await them to receive the result, or return the promise directly from a handler. Usage limits reject with [`UsageLimit`](#usage-limits); Void route handlers and Pages actions automatically send a structured HTTP 429 when it is left uncaught.
45
+
42
46
  You can use any model available through Cloudflare's AI binding, including Workers AI models such as `@cf/meta/llama-3.3-70b-instruct-fp8-fast` and Cloudflare Gateway models such as `google/gemini-2.5-flash` or `openai/gpt-4.1-mini`. The input object must match the selected Cloudflare model's schema. Models that return binary data, such as generated images, are returned as a `Blob` from `ai.run()`.
43
47
 
44
48
  ## Streaming
@@ -52,27 +56,23 @@ import { ai } from 'void/ai';
52
56
  export const POST = defineHandler(async (c) => {
53
57
  const { prompt } = await c.req.json();
54
58
 
55
- return ai
56
- .stream('@cf/meta/llama-3.3-70b-instruct-fp8-fast', {
57
- messages: [{ role: 'user', content: prompt }],
58
- })
59
- .match({ ok: (result) => result, limited: (limit) => limit.response() });
59
+ return ai.stream('@cf/meta/llama-3.3-70b-instruct-fp8-fast', {
60
+ messages: [{ role: 'user', content: prompt }],
61
+ });
60
62
  });
61
63
  ```
62
64
 
65
+ Streaming responses end with a `void:error` SSE event if generation is interrupted after the response starts, or `void:limit` for a usage limit. `fetchStream()` from `void/client` throws `UsageLimit` for limits before or during streaming and `StreamError` for other terminal errors. Catch the error in your UI to stop its generating state, retain partial output, and offer retry. This also applies to streaming provider responses and `ai.run()` with `stream: true`.
66
+
63
67
  ## Listing Models
64
68
 
65
69
  Use `ai.models()` to list available models:
66
70
 
67
71
  ```ts
68
- const models = await ai
69
- .models()
70
- .match({ ok: (result) => result, limited: (limit) => limit.response() });
72
+ const models = await ai.models();
71
73
 
72
74
  // Filter by task
73
- const textModels = await ai
74
- .models({ task: 'Text Generation' })
75
- .match({ ok: (result) => result, limited: (limit) => limit.response() });
75
+ const textModels = await ai.models({ task: 'Text Generation' });
76
76
  ```
77
77
 
78
78
  ## Markdown Conversion
@@ -80,28 +80,9 @@ const textModels = await ai
80
80
  Use `ai.toMarkdown()` to convert documents to markdown:
81
81
 
82
82
  ```ts
83
- const result = await ai
84
- .toMarkdown([{ name: 'document.pdf', blob: pdfBytes }])
85
- .match({ ok: (result) => result, limited: (limit) => limit.response() });
86
- ```
87
-
88
- ## Local Development
89
-
90
- For a direct Cloudflare project, connect your account and start development:
91
-
92
- ```sh
93
- void connect --platform cloudflare
94
- npm run dev
83
+ const result = await ai.toMarkdown([{ name: 'document.pdf', blob: pdfBytes }]);
95
84
  ```
96
85
 
97
- When the app imports `void/ai`, Void enables a remote Workers AI binding during
98
- development and preview. Requests use your Cloudflare account and its allowance;
99
- Workers AI has no local simulator. Use your Cloudflare login or
100
- `CLOUDFLARE_API_TOKEN` in an automated environment.
101
-
102
- For a team platform, use `void connect <platform-url>` and link your project.
103
- Development requests use that platform's account and usage limits.
104
-
105
86
  ## Usage Limits
106
87
 
107
88
  Workers AI usage is measured in [neurons](https://developers.cloudflare.com/workers-ai/platform/pricing/).
@@ -110,51 +91,50 @@ requests also use your provider credentials and their billing terms.
110
91
 
111
92
  A team platform uses its own Cloudflare account and may apply per-user limits. Ask your administrator about the available allowance.
112
93
 
113
- ## Cloudflare Gateway Models
94
+ Usage limits reject the operation's promise with `UsageLimit` from `void/limits`. It includes `resource`, `reason`, and an optional `retryAt` timestamp. In Void route handlers and Pages actions, an uncaught limit produces a structured HTTP 429 with retry information. In queues, scheduled jobs, and other server code, it remains a rejected promise.
114
95
 
115
- `ai.run()` mirrors Cloudflare's `env.AI.run()` model naming and input schemas. Third-party models use Cloudflare model IDs and Cloudflare-managed credentials.
96
+ Handling a limit is optional. Use `instanceof UsageLimit` when you want a fallback or a message tailored to your app:
116
97
 
117
98
  ```ts
118
- const result = await ai
119
- .run('google/gemini-2.5-flash', {
120
- contents: [
121
- {
122
- role: 'user',
123
- parts: [{ text: 'Explain Durable Objects in one paragraph.' }],
124
- },
125
- ],
126
- })
127
- .match({ ok: (result) => result, limited: (limit) => limit.response() });
99
+ import { defineHandler } from 'void';
100
+ import { ai } from 'void/ai';
101
+ import { UsageLimit } from 'void/limits';
102
+
103
+ export const POST = defineHandler(async (c) => {
104
+ const { prompt } = await c.req.json();
105
+ try {
106
+ const result = await ai.run('@cf/meta/llama-3.3-70b-instruct-fp8-fast', {
107
+ messages: [{ role: 'user', content: prompt }],
108
+ });
109
+ return c.json(result);
110
+ } catch (error) {
111
+ if (error instanceof UsageLimit) {
112
+ return error.response({ message: 'Try again shortly. Your prompt has been kept.' });
113
+ }
114
+ throw error;
115
+ }
116
+ });
128
117
  ```
129
118
 
130
- OpenAI-compatible models use OpenAI-style `messages`:
119
+ `error.response()` creates the structured HTTP 429 and accepts an optional custom `message`. Other failures reject normally. When using `fetchStream()`, the same `instanceof UsageLimit` check gives your client access to the message and retry information.
131
120
 
132
- ```ts
133
- const result = await ai
134
- .run('openai/gpt-4.1-mini', {
135
- messages: [{ role: 'user', content: 'Summarize this deploy.' }],
136
- })
137
- .match({ ok: (result) => result, limited: (limit) => limit.response() });
138
- ```
121
+ ## Cloudflare Gateway Models
139
122
 
140
- Pass Cloudflare AI Gateway options as the third argument:
123
+ `ai.run()` mirrors Cloudflare's `env.AI.run()` model naming and input schemas. Third-party models use Cloudflare model IDs and Cloudflare-managed credentials.
141
124
 
142
125
  ```ts
143
- const result = await ai
144
- .run(
145
- 'openai/gpt-4.1-mini',
146
- {
147
- messages: [{ role: 'user', content: 'Summarize this deploy.' }],
148
- },
126
+ const result = await ai.run('google/gemini-2.5-flash', {
127
+ contents: [
149
128
  {
150
- gateway: {
151
- skipCache: true,
152
- },
129
+ role: 'user',
130
+ parts: [{ text: 'Explain Durable Objects in one paragraph.' }],
153
131
  },
154
- )
155
- .match({ ok: (result) => result, limited: (limit) => limit.response() });
132
+ ],
133
+ });
156
134
  ```
157
135
 
136
+ Pass AI Gateway options as the third argument: `ai.run(model, inputs, { gateway: { skipCache: true } })`.
137
+
158
138
  For provider-native requests on a direct deployment, configure [`ai.gateway`](../integrations/cloudflare.md#ai-self-host).
159
139
 
160
140
  ## Provider-Native Requests
@@ -173,19 +153,13 @@ import { ai } from 'void/ai';
173
153
  export const POST = defineHandler(async (c) => {
174
154
  const { prompt } = await c.req.json();
175
155
 
176
- const response = await ai
177
- .provider('openai')
178
- .fetch('/chat/completions', {
179
- body: {
180
- model: 'gpt-4o',
181
- messages: [{ role: 'user', content: prompt }],
182
- max_tokens: 512,
183
- },
184
- })
185
- .match({ ok: (result) => result, limited: (limit) => limit.response() });
186
-
187
- const result = await response.json();
188
- return c.json(result);
156
+ return ai.provider('openai').fetch('/chat/completions', {
157
+ body: {
158
+ model: 'gpt-4o',
159
+ messages: [{ role: 'user', content: prompt }],
160
+ max_tokens: 512,
161
+ },
162
+ });
189
163
  });
190
164
  ```
191
165
 
@@ -203,10 +177,7 @@ const response = await ai
203
177
  },
204
178
  ],
205
179
  },
206
- })
207
- .match({ ok: (result) => result, limited: (limit) => limit.response() });
208
-
209
- const result = await response.json();
180
+ });
210
181
  ```
211
182
 
212
183
  ### Custom Providers
@@ -222,8 +193,7 @@ const response = await ai
222
193
  })
223
194
  .fetch('/v1/respond', {
224
195
  body: { prompt: 'Hello' },
225
- })
226
- .match({ ok: (result) => result, limited: (limit) => limit.response() });
196
+ });
227
197
  ```
228
198
 
229
199
  ### Image Generation
@@ -233,9 +203,7 @@ Use `ai.run()` or `ai.image()` for Cloudflare-native image models:
233
203
  ```ts
234
204
  export const POST = defineHandler(async (c) => {
235
205
  const { prompt } = await c.req.json();
236
- return ai
237
- .image('@cf/black-forest-labs/flux-1-schnell', { prompt })
238
- .match({ ok: (result) => result, limited: (limit) => limit.response() });
206
+ return ai.image('@cf/black-forest-labs/flux-1-schnell', { prompt });
239
207
  });
240
208
  ```
241
209
 
@@ -245,16 +213,13 @@ Use `ai.provider().fetch()` for provider-native image APIs. Match the [provider'
245
213
  export const POST = defineHandler(async (c) => {
246
214
  const { prompt } = await c.req.json();
247
215
 
248
- return ai
249
- .provider('openai')
250
- .fetch('/images/generations', {
251
- body: {
252
- model: 'gpt-image-2.5-sunburst',
253
- prompt,
254
- size: '1024x1024',
255
- },
256
- })
257
- .match({ ok: (result) => result, limited: (limit) => limit.response() });
216
+ return ai.provider('openai').fetch('/images/generations', {
217
+ body: {
218
+ model: 'gpt-image-2.5-sunburst',
219
+ prompt,
220
+ size: '1024x1024',
221
+ },
222
+ });
258
223
  });
259
224
  ```
260
225
 
@@ -268,39 +233,15 @@ export const POST = defineHandler(async (c) => {
268
233
  form.set('prompt', String(body.prompt));
269
234
  form.set('image', body.image as Blob, 'source.png');
270
235
 
271
- return ai
272
- .provider('openai')
273
- .fetch('/images/edits', { body: form })
274
- .match({ ok: (result) => result, limited: (limit) => limit.response() });
236
+ return ai.provider('openai').fetch('/images/edits', { body: form });
275
237
  });
276
238
  ```
277
239
 
278
240
  ### Provider Key Convention
279
241
 
280
- Provider-native requests require an API key set as a project secret. The env var name is automatically derived from the provider name:
281
-
282
- | Provider prefix | Env var |
283
- | ------------------ | --------------------- |
284
- | `openai` | `OPENAI_API_KEY` |
285
- | `anthropic` | `ANTHROPIC_API_KEY` |
286
- | `google-ai-studio` | `GOOGLE_API_KEY` |
287
- | `deepseek` | `DEEPSEEK_API_KEY` |
288
- | `groq` | `GROQ_API_KEY` |
289
- | `mistral` | `MISTRAL_API_KEY` |
290
- | `grok` | `GROK_API_KEY` |
291
- | `openrouter` | `OPENROUTER_API_KEY` |
292
- | `perplexity` | `PERPLEXITY_API_KEY` |
293
- | `cohere` | `COHERE_API_KEY` |
294
- | `cerebras` | `CEREBRAS_API_KEY` |
295
- | `huggingface` | `HUGGINGFACE_API_KEY` |
296
- | `replicate` | `REPLICATE_API_KEY` |
297
- | `baseten` | `BASETEN_API_KEY` |
298
- | `cartesia` | `CARTESIA_API_KEY` |
299
- | `deepgram` | `DEEPGRAM_API_KEY` |
300
- | `elevenlabs` | `ELEVENLABS_API_KEY` |
301
- | `fal` | `FAL_API_KEY` |
302
- | `ideogram` | `IDEOGRAM_API_KEY` |
303
- | `parallel` | `PARALLEL_API_KEY` |
242
+ Provider-native requests use a project secret, such as `OPENAI_API_KEY` for OpenAI or `GOOGLE_API_KEY` for Google AI Studio.
243
+
244
+ See the [provider key reference](../reference/api/resources.md#provider-keys) for supported providers and their secret names.
304
245
 
305
246
  OpenAI-style providers use `Authorization: Bearer <key>`. Google AI Studio uses `x-goog-api-key`. Use `apiKeyHeader` and `apiKeyPrefix` for custom providers.
306
247
 
@@ -45,7 +45,7 @@ Add `voidPlugin()` to the framework's Vite config to get binding inference, type
45
45
 
46
46
  Any project that produces static files, whether that is an SPA, a static site, or a plain directory. Void serves the assets without requiring application code.
47
47
 
48
- **SPAs** (client-side single-page apps) fall back all non-file paths to `index.html` with a 200 status, so client-side routing works out of the box. Detected when `vite` is a dependency and no backend files exist.
48
+ **SPAs** (client-side single-page apps) serve `index.html` with a 200 status for paths that match no file, so client-side routing works out of the box. On a Void platform, missing files under the hashed assets folder (`/assets/`) return 404 instead. Detected when `vite` is a dependency and no backend files exist.
49
49
 
50
50
  **Static sites** (static site generators like VitePress, Docusaurus, or any pre-built directory) produce per-page HTML files. Detected when a known SSG is a dependency, or when using the `--dir` flag.
51
51
 
@@ -155,5 +155,3 @@ void db generate
155
155
  ```
156
156
 
157
157
  Review and commit the SQL before deploying. Void includes your auth configuration and plugin tables; you don't need to duplicate them in `db/schema.ts` or run the Better Auth CLI.
158
-
159
- MySQL stores OAuth access, refresh, and ID tokens as unbounded text. Existing direct-deploy MySQL apps should run `void db generate` once after upgrading to widen earlier `varchar(255)` token columns. Void platform deployments apply the same safe widening automatically.