void 1.3.0 → 1.4.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 (152) hide show
  1. package/dist/{account-cmd-B4i71m7k.mjs → account-cmd-HP8bixYo.mjs} +3 -3
  2. package/dist/{application-analysis-DYdVoMAY.mjs → application-analysis-DDOQwiXk.mjs} +11 -7
  3. package/dist/{application-routing-C8EqRDfv.mjs → application-routing-4719u_kf.mjs} +1 -1
  4. package/dist/auth-g2TQbDLD.d.mts +70 -0
  5. package/dist/{auth-link-BKwHPbdv.mjs → auth-link-CedYpDCy.mjs} +3 -3
  6. package/dist/{auth-router-DqLx8E0F.mjs → auth-router-vafo7x8w.mjs} +11 -6
  7. package/dist/{better-auth-shared-DVk2DkTW.d.mts → better-auth-shared-BHB8kiTE.d.mts} +1 -1
  8. package/dist/{build-cmd-Dy4psnt1.mjs → build-cmd-9vUAQ1_V.mjs} +1 -1
  9. package/dist/{cache-DV37S6dl.mjs → cache-jvwLNb0h.mjs} +1 -1
  10. package/dist/{cancel-deploy-DMPyu1Vt.mjs → cancel-deploy-Dw0UmWKO.mjs} +1 -1
  11. package/dist/{cf-build-output-Bapzc_8Z.mjs → cf-build-output-XpNR9zoh.mjs} +20 -4
  12. package/dist/cf-build-output-bd8Uwv1P.mjs +1 -0
  13. package/dist/{cf-worker-settings-BECvvTVd.mjs → cf-worker-settings-Dg27RzyT.mjs} +11 -5
  14. package/dist/cli/cli.mjs +25 -25
  15. package/dist/cli/cloudflare-operation-process.mjs +9 -3
  16. package/dist/cli/env-schema-probe.mjs +2 -2
  17. package/dist/{client-BEMtySaV.mjs → client-B3LnB_bE.mjs} +1 -1
  18. package/dist/client-BZb9j66F.mjs +1 -0
  19. package/dist/{cloudflare-auth-Byo_0_N9.mjs → cloudflare-auth-DyD_kVFr.mjs} +1 -1
  20. package/dist/{cloudflare-cmd-BaqIigwI.mjs → cloudflare-cmd-D-b_u3T4.mjs} +2 -2
  21. package/dist/{cloudflare-config-DchHxPTo.mjs → cloudflare-config-CH1AylZT.mjs} +14 -3
  22. package/dist/{cloudflare-connect-pNLQmr_e.mjs → cloudflare-connect-zTJh8UnN.mjs} +2 -2
  23. package/dist/{cloudflare-operations-vP3b5CQf.mjs → cloudflare-operations-Bc-7fN44.mjs} +4 -4
  24. package/dist/{cloudflare-operations-PCM4wucv.mjs → cloudflare-operations-BtulCNOg.mjs} +1 -1
  25. package/dist/{config-BU6hMu1W.mjs → config-BV2ASlpn.mjs} +1 -1
  26. package/dist/{config-Du88-7Y8.d.mts → config-CAdZVTLN.d.mts} +2 -0
  27. package/dist/config-D-6q7EBH.mjs +87 -0
  28. package/dist/{config-DHgSEWT7.mjs → config-OFOAimzt.mjs} +5 -3
  29. package/dist/config-entry.d.mts +1 -1
  30. package/dist/{config-BIaXIkkv.mjs → config-tqMM6DwY.mjs} +1 -1
  31. package/dist/{connect-DwVsBvVh.mjs → connect-BOM6zvFX.mjs} +4 -4
  32. package/dist/{create-project-CjzbBlTv.mjs → create-project-Bxd02EHN.mjs} +1 -1
  33. package/dist/{create-project-CgrnvzyA.mjs → create-project-C2K0DN2O.mjs} +1 -1
  34. package/dist/{db-rvo64snS.mjs → db-Cdvr3zwG.mjs} +16 -16
  35. package/dist/{db-types-NGxPwnlt.mjs → db-types-0eCiqYb1.mjs} +1 -1
  36. package/dist/{delete-DWX6dP4M.mjs → delete-lETE6Tpt.mjs} +1 -1
  37. package/dist/deploy-CX94CmKa.mjs +2 -0
  38. package/dist/{deploy-hRPvw8ci.mjs → deploy-CdM-LAxX.mjs} +260 -95
  39. package/dist/{deploy-managed-static-CDYA2p3L.mjs → deploy-managed-static-DjeBFG2c.mjs} +24 -17
  40. package/dist/{deploy-transport-JCxw7QDi.mjs → deploy-transport-DIq_gFTP.mjs} +13 -12
  41. package/dist/deploy.d.mts +2 -0
  42. package/dist/deploy.mjs +7 -5
  43. package/dist/{dispatch-OVLeWW8K.mjs → dispatch-CLa2LThw.mjs} +5 -5
  44. package/dist/{domain-CH5iBD9Y.mjs → domain-DyhNtdYf.mjs} +2 -2
  45. package/dist/{email-CbTO1l78.mjs → email-DM2wDcBN.mjs} +6 -6
  46. package/dist/{email-cloudflare-B8-pq_-W.mjs → email-cloudflare-PMbAev9Q.mjs} +20 -7
  47. package/dist/{env-BBdw236G.mjs → env-78hbN-oI.mjs} +3 -3
  48. package/dist/{env-validation-XcukUlFM.mjs → env-validation-B3VobvXm.mjs} +1 -0
  49. package/dist/{env-validation-BfBVvBNe.mjs → env-validation-BUfFZ6bt.mjs} +1 -1
  50. package/dist/error-pages-CLIJ7aYq.mjs +37 -0
  51. package/dist/gen-BhWBT4iu.mjs +1 -0
  52. package/dist/{gen-CCx3t2Ju.mjs → gen-CFmsUsW1.mjs} +3 -3
  53. package/dist/{github-cmd-BzkRLkbg.mjs → github-cmd-De-_IWbM.mjs} +1 -1
  54. package/dist/{handler-CQv5YDS0.d.mts → handler-BHktmuTL.d.mts} +3 -1
  55. package/dist/{headers-D522zd0A.mjs → headers--vaK_chu.mjs} +1 -1
  56. package/dist/index.d.mts +2 -2
  57. package/dist/index.mjs +68 -33
  58. package/dist/{info-oBm8yK8J.mjs → info-Yg0-V5Rs.mjs} +3 -3
  59. package/dist/{init-CBp_uMve.mjs → init-CDiJQycf.mjs} +31 -16
  60. package/dist/{link-Dt_QXmM4.mjs → link-Dllip08S.mjs} +2 -2
  61. package/dist/{list-DF6asTdt.mjs → list-BYRP6am0.mjs} +2 -2
  62. package/dist/{live-CXJ9yC98.d.mts → live-BllfDuzA.d.mts} +2 -2
  63. package/dist/{local-d1-CUhH3qtj.mjs → local-d1-CAwAs9GH.mjs} +1 -1
  64. package/dist/login-BAPmYnb7.mjs +1 -0
  65. package/dist/{login-BjSyhXlT.mjs → login-PcFy5mlC.mjs} +2 -2
  66. package/dist/{logs-DmvBl6xA.mjs → logs-DmJTBhed.mjs} +2 -2
  67. package/dist/{migrate-BXRHpYwR.mjs → migrate-B8S2xTcb.mjs} +5 -66
  68. package/dist/migrate-BIYTPQE6.mjs +1 -0
  69. package/dist/nitro.mjs +4 -4
  70. package/dist/{node-DtD7ZiPf.mjs → node-CDOPrGSl.mjs} +3 -3
  71. package/dist/operator-cmd-CjazwOyb.mjs +1 -0
  72. package/dist/{operator-cmd-DjZKGGgN.mjs → operator-cmd-DmkmS2wL.mjs} +2 -2
  73. package/dist/pages/client.d.mts +17 -2
  74. package/dist/pages/client.mjs +26 -1
  75. package/dist/pages/index.d.mts +17 -1
  76. package/dist/pages/index.mjs +95 -4
  77. package/dist/pages/protocol.d.mts +2 -2
  78. package/dist/{platform-auth-config-BgnuMbml.mjs → platform-auth-config-CImnfohz.mjs} +3 -3
  79. package/dist/{platform-auth-protection-NjRejttU.mjs → platform-auth-protection-BogLhtHi.mjs} +2 -2
  80. package/dist/{platform-auth-recovery-Ug12d9W5.mjs → platform-auth-recovery-B-mEaWEi.mjs} +2 -2
  81. package/dist/{platform-cmd-BYKLMPCn.mjs → platform-cmd-CV8NxIIn.mjs} +1 -1
  82. package/dist/{platform-cmd-HByuZAO7.mjs → platform-cmd-DZK2ixMl.mjs} +3 -3
  83. package/dist/{platform-domain-DLSQ8GF8.mjs → platform-domain-CRvelPGE.mjs} +2 -2
  84. package/dist/{platform-lifecycle-3dwFJmnq.mjs → platform-lifecycle-CPYY7E9B.mjs} +8 -8
  85. package/dist/{platform-lifecycle-BRqcxerG.mjs → platform-lifecycle-DwXq7kZt.mjs} +1 -1
  86. package/dist/{platform-management-ZZ6bQM5j.mjs → platform-management-BOnGbMSM.mjs} +1 -1
  87. package/dist/{platform-management-ZK6XcbCo.mjs → platform-management-lXdgGgAM.mjs} +1 -1
  88. package/dist/{platform-recovery-8vNE5dNN.mjs → platform-recovery-noaALXnE.mjs} +2 -2
  89. package/dist/{prepare-5BR7Splm.mjs → prepare-BuOS18AM.mjs} +1 -1
  90. package/dist/{prepare-Dseww74N.mjs → prepare-D604Yiyr.mjs} +11 -7
  91. package/dist/prepare-DU-tOpS5.mjs +1 -0
  92. package/dist/{project-cmd-CjLNNxwe.mjs → project-cmd-DBYqAIbe.mjs} +13 -13
  93. package/dist/{project-team-DE5drjcl.mjs → project-team-BjCLgTRC.mjs} +1 -1
  94. package/dist/{project-token-XEYEXfiJ.mjs → project-token-BB6PwaA9.mjs} +1 -1
  95. package/dist/{project-tsconfig-Itv1f4WC.mjs → project-tsconfig-CVvk6Z_e.mjs} +24 -2
  96. package/dist/{project-zero-trust-6VPU8zmV.mjs → project-zero-trust-CWDh1P_t.mjs} +1 -1
  97. package/dist/{protocol-BekmFxCI.d.mts → protocol-D5XDySYu.d.mts} +1 -1
  98. package/dist/{provision-Chq0NRAV.mjs → provision-DfxLCg-d.mjs} +1 -1
  99. package/dist/{provision-B7UFrBH7.mjs → provision-DztLJ97X.mjs} +24 -8
  100. package/dist/{requests-CG-Pj8ze.mjs → requests-D_pbzDyz.mjs} +1 -1
  101. package/dist/{rollback-CIrm2txZ.mjs → rollback-CjfIYsJx.mjs} +2 -2
  102. package/dist/{route-types--PAOzHlH.mjs → route-types-DvEGmPif.mjs} +61 -3
  103. package/dist/runtime/auth.d.mts +2 -2
  104. package/dist/runtime/better-auth-mysql.d.mts +1 -1
  105. package/dist/runtime/better-auth-pg.d.mts +1 -1
  106. package/dist/runtime/better-auth.d.mts +1 -1
  107. package/dist/runtime/client-react.d.mts +1 -1
  108. package/dist/runtime/client-solid.d.mts +1 -1
  109. package/dist/runtime/client-svelte.d.mts +1 -1
  110. package/dist/runtime/client-vue.d.mts +1 -1
  111. package/dist/runtime/client.d.mts +1 -1
  112. package/dist/runtime/handler.d.mts +1 -1
  113. package/dist/runtime/live-client.d.mts +1 -1
  114. package/dist/runtime/live.d.mts +1 -1
  115. package/dist/runtime/routing.d.mts +5 -4
  116. package/dist/runtime/routing.mjs +10 -70
  117. package/dist/runtime/validator.d.mts +1 -1
  118. package/dist/runtime/ws-server.d.mts +3 -3
  119. package/dist/runtime/ws.d.mts +1 -1
  120. package/dist/runtime/ws.mjs +4 -2
  121. package/dist/{secret-BL6g7mF5.mjs → secret-3X0PxmuX.mjs} +9 -12
  122. package/dist/{version-metadata-DmaZNTYW.mjs → version-metadata-D2vfMQzv.mjs} +2 -1
  123. package/dist/{wrangler-CDiEBCdS.mjs → wrangler-I-scB_1t.mjs} +80 -12
  124. package/dist/wrangler-UnwdISay.mjs +1 -0
  125. package/dist/{wrapper-CEiDeCqD.mjs → wrapper-BBl1rC32.mjs} +3 -3
  126. package/dist/{ws-D1zWiB_P.d.mts → ws-B3ClMVDh.d.mts} +3 -2
  127. package/docs/guide/auth.md +1 -1
  128. package/docs/guide/edge/redirects.md +22 -0
  129. package/docs/guide/pages-routing/actions-and-forms.md +24 -0
  130. package/docs/guide/platform/installation/maintenance.md +3 -1
  131. package/docs/guide/server-routing.md +2 -0
  132. package/docs/guide/ssg.md +8 -0
  133. package/docs/integrations/cloudflare.md +16 -7
  134. package/docs/reference/api/auth.md +6 -2
  135. package/docs/reference/api/deploy.md +9 -5
  136. package/docs/reference/api/pages.md +2 -0
  137. package/docs/reference/cli/deploy.md +10 -9
  138. package/docs/reference/config.md +11 -1
  139. package/package.json +8 -7
  140. package/schema.json +4 -0
  141. package/skills/void/SKILL.md +1 -1
  142. package/dist/auth-DkcFflXV.d.mts +0 -42
  143. package/dist/cf-build-output-DlBkWdNU.mjs +0 -1
  144. package/dist/client-DyxPQnF_.mjs +0 -1
  145. package/dist/config-BTu1o3Dc.mjs +0 -49
  146. package/dist/deploy-CtHE2A3R.mjs +0 -2
  147. package/dist/gen-TgXy93Rr.mjs +0 -1
  148. package/dist/login-Cdo8Aco1.mjs +0 -1
  149. package/dist/migrate-COPncCGx.mjs +0 -1
  150. package/dist/operator-cmd-DDiX7Y3f.mjs +0 -1
  151. package/dist/prepare-B3VrSF1Z.mjs +0 -1
  152. package/dist/wrangler-DOPr4jyI.mjs +0 -1
@@ -1,12 +1,68 @@
1
1
  import { a as join, c as resolve, n as dirname, o as normalize, t as basename } from "./dist-ipQi0Q8y.mjs";
2
- import "./project-paths-DGu_QZ_Q.mjs";
3
- import { p as readConfig } from "./config-DHgSEWT7.mjs";
4
- import { i as persistCloudflareConfig, r as materializeCloudflareConfig } from "./cloudflare-config-DchHxPTo.mjs";
2
+ import { o as TYPED_CONFIG_FILE, s as loadTypedVoidConfigFile } from "./project-paths-DGu_QZ_Q.mjs";
3
+ import { h as validateConfig, p as readConfig } from "./config-OFOAimzt.mjs";
4
+ import { a as persistCloudflareConfig, i as materializeCloudflareConfig, r as ignoreMaterializedCloudflareConfig } from "./cloudflare-config-CH1AylZT.mjs";
5
5
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
6
+ import { parseSync } from "vite";
6
7
  import { appendSqliteClassMigration, isRecord } from "@void/deploy-core";
7
8
  import { createHash } from "node:crypto";
8
9
  import { isDeepStrictEqual } from "node:util";
9
10
  import { SyntaxKind, applyEdits, createScanner, findNodeAtLocation, modify, parse, parseTree } from "jsonc-parser";
11
+ //#region src/config-write.ts
12
+ const GENERATED_MARKERS = [
13
+ "// Generated by void migrate.",
14
+ "// Generated from void.json by void migrate.",
15
+ "// Generated by void init."
16
+ ];
17
+ function renderVoidConfig(config, marker = GENERATED_MARKERS[0]) {
18
+ return `${marker}\nimport { defineConfig } from 'void/config';\n\nexport default defineConfig(${JSON.stringify(config, null, 2)});\n`;
19
+ }
20
+ function isStaticValue(node) {
21
+ if (!node) return false;
22
+ if (node.type === "Literal") return node.value === null || [
23
+ "string",
24
+ "number",
25
+ "boolean"
26
+ ].includes(typeof node.value);
27
+ if (node.type === "UnaryExpression") return node.operator === "-" && node.argument?.type === "Literal" && typeof node.argument.value === "number";
28
+ if (node.type === "ArrayExpression") return node.elements?.every(isStaticValue) ?? false;
29
+ if (node.type !== "ObjectExpression") return false;
30
+ return node.properties?.every((property) => property.type === "Property" && property.kind === "init" && !property.computed && !property.method && (property.key?.type === "Identifier" || property.key?.type === "Literal") && isStaticValue(property.value)) ?? false;
31
+ }
32
+ function isGeneratedStaticConfig(source) {
33
+ if (!GENERATED_MARKERS.some((marker) => source.startsWith(marker))) return false;
34
+ let program;
35
+ try {
36
+ const parsed = parseSync(TYPED_CONFIG_FILE, source, {
37
+ lang: "js",
38
+ sourceType: "module",
39
+ preserveParens: false,
40
+ showSemanticErrors: true
41
+ });
42
+ if (parsed.errors.length > 0 || parsed.comments.length !== 1 || !GENERATED_MARKERS.includes(`// ${parsed.comments[0]?.value.trim()}`)) return false;
43
+ program = parsed.program;
44
+ } catch {
45
+ return false;
46
+ }
47
+ const [importDeclaration, exportDeclaration] = program.body ?? [];
48
+ const call = exportDeclaration?.declaration;
49
+ return program.body?.length === 2 && importDeclaration?.type === "ImportDeclaration" && importDeclaration.source?.value === "void/config" && importDeclaration.specifiers?.length === 1 && importDeclaration.specifiers[0]?.imported?.name === "defineConfig" && importDeclaration.specifiers[0]?.local?.name === "defineConfig" && exportDeclaration?.type === "ExportDefaultDeclaration" && call?.type === "CallExpression" && call.callee?.name === "defineConfig" && call.arguments?.length === 1 && isStaticValue(call.arguments[0]);
50
+ }
51
+ function canRewriteVoidProjectConfig(root) {
52
+ const path = join(root, TYPED_CONFIG_FILE);
53
+ return !existsSync(path) || isGeneratedStaticConfig(readFileSync(path, "utf8"));
54
+ }
55
+ /** Update CLI-owned static config without silently rewriting authored TypeScript. */
56
+ function writeVoidProjectConfig(root, config) {
57
+ validateConfig(config, TYPED_CONFIG_FILE);
58
+ const path = join(root, TYPED_CONFIG_FILE);
59
+ if (existsSync(path)) {
60
+ if (!isGeneratedStaticConfig(readFileSync(path, "utf8"))) throw new Error(`config: ${TYPED_CONFIG_FILE} contains authored TypeScript or comments. Update it directly and rerun the command.`);
61
+ validateConfig(loadTypedVoidConfigFile(path), TYPED_CONFIG_FILE);
62
+ }
63
+ writeFileSync(path, renderVoidConfig(config, "// Generated by void init."));
64
+ }
65
+ //#endregion
10
66
  //#region src/cli/wrangler.ts
11
67
  const COMPAT_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
12
68
  /** UTF-8 byte-order mark. Node's utf-8 read preserves a leading one. */
@@ -170,10 +226,15 @@ function resolveGeneratedName(base, used, kind) {
170
226
  function readWranglerCompat(root, config, buildOutputDir) {
171
227
  return resolveWranglerCompat(readWranglerCompatFields(root, config, buildOutputDir));
172
228
  }
173
- /** Like `readWranglerCompat()`, but pins the default date when no source sets one. */
174
- function ensureWranglerCompat(root, config, buildOutputDir) {
229
+ /**
230
+ * Like `readWranglerCompat()`, but pins the default date when no source sets one.
231
+ * With `rootWrangler`, a project with no config gets the date in a root wrangler.jsonc
232
+ * instead of a new void.config.ts, so a framework adapter that reads the root file by
233
+ * default still finds it.
234
+ */
235
+ function ensureWranglerCompat(root, config, buildOutputDir, options) {
175
236
  const fields = readWranglerCompatFields(root, config, buildOutputDir);
176
- const defaultDate = fields.voidDate ?? fields.wranglerDate ?? fields.buildDate ? void 0 : ensureVoidCompatibilityDate(root, fields.voidConfig);
237
+ const defaultDate = fields.voidDate ?? fields.wranglerDate ?? fields.buildDate ? void 0 : ensureVoidCompatibilityDate(root, options?.rootWrangler === true);
177
238
  return resolveWranglerCompat(fields, defaultDate ? { defaultDate } : void 0);
178
239
  }
179
240
  function readWranglerCompatFields(root, config, buildOutputDir) {
@@ -205,7 +266,6 @@ function readWranglerCompatFields(root, config, buildOutputDir) {
205
266
  if (Array.isArray(buildParsed.compatibility_flags) && buildParsed.compatibility_flags.every((f) => typeof f === "string")) buildFlags = buildParsed.compatibility_flags;
206
267
  } catch {}
207
268
  return {
208
- voidConfig,
209
269
  voidDate: voidConfig?.worker?.compatibility_date,
210
270
  voidFlags: voidConfig?.worker?.compatibility_flags,
211
271
  wranglerDate,
@@ -226,14 +286,23 @@ function validateCompatDate(value, source) {
226
286
  function readVoidConfigIfExists(root) {
227
287
  return existsSync(join(root, VOID_CONFIG_FILE)) || existsSync(join(root, "void.config.ts")) ? readConfig(root) : void 0;
228
288
  }
229
- function ensureVoidCompatibilityDate(root, config) {
289
+ function ensureVoidCompatibilityDate(root, rootWrangler) {
230
290
  if (existsSync(join(root, "void.config.ts"))) {
231
291
  writeProvisionedWranglerConfig(root, { compatibilityDate: LATEST_KNOWN_COMPAT_DATE });
232
292
  return LATEST_KNOWN_COMPAT_DATE;
233
293
  }
234
294
  const filePath = join(root, VOID_CONFIG_FILE);
235
- let nextConfig = {};
236
- if (existsSync(filePath)) try {
295
+ if (!existsSync(filePath)) {
296
+ if (existsSync(join(root, "wrangler.toml"))) writeFileSync(filePath, `${JSON.stringify({ worker: { compatibility_date: LATEST_KNOWN_COMPAT_DATE } }, null, 2)}\n`);
297
+ else if (rootWrangler || projectWranglerPaths(root).some((path) => existsSync(path))) writeProvisionedWranglerConfig(root, { compatibilityDate: LATEST_KNOWN_COMPAT_DATE });
298
+ else {
299
+ writeVoidProjectConfig(root, { worker: { compatibility_date: LATEST_KNOWN_COMPAT_DATE } });
300
+ ignoreMaterializedCloudflareConfig(root);
301
+ }
302
+ return LATEST_KNOWN_COMPAT_DATE;
303
+ }
304
+ let nextConfig;
305
+ try {
237
306
  const parsed = JSON.parse(readFileSync(filePath, "utf-8"));
238
307
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error(`config: ${VOID_CONFIG_FILE} must be a JSON object.`);
239
308
  nextConfig = parsed;
@@ -241,7 +310,6 @@ function ensureVoidCompatibilityDate(root, config) {
241
310
  if (error instanceof SyntaxError) throw new Error(`config: Invalid JSON in ${VOID_CONFIG_FILE}.`);
242
311
  throw error;
243
312
  }
244
- else if (config) nextConfig = { ...config };
245
313
  nextConfig.worker = {
246
314
  ...nextConfig.worker ?? {},
247
315
  compatibility_date: LATEST_KNOWN_COMPAT_DATE
@@ -1400,4 +1468,4 @@ function normalizeMigrationsPath(p) {
1400
1468
  return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
1401
1469
  }
1402
1470
  //#endregion
1403
- export { syncWranglerWebSockets as A, readWranglerManagedQueueCleanupCandidates as C, sameMigrationsDir as D, resolveLiveBindingConfig as E, writeProvisionedWranglerConfig as M, writeWranglerPublishedTrafficSnapshot as N, syncWranglerBindings as O, readWranglerCustomDomains as S, readWranglerWebSocketTombstones as T, persistMaterializedWranglerConfig as _, LIVE_MIGRATION_TAG as a, readRootWranglerConfig as b, bindingEntriesForField as c, ensureWranglerCompatibilityDate as d, ensureWranglerNodejsAlsFlag as f, normalizeWranglerCompatibilityFlags as g, nextDurableObjectMigrationTag as h, LATEST_KNOWN_COMPAT_DATE as i, updateWranglerCustomDomain as j, syncWranglerDurableObjects as k, bindingNamesForField as l, hasDurableObjectSqliteMigration as m, DEFAULT_LIVE_CLASS as n, acknowledgeWranglerManagedQueueRemovals as o, findBindingNameOwner as p, FRIENDLY_BINDING_NAMES as r, assertNoDuplicateMembers as s, DEFAULT_LIVE_BINDING as t, ensureWranglerCompat as u, readExistingLiveBindingConfig as v, readWranglerPublishedTrafficSnapshot as w, readWranglerCompat as x, readProvisionedBindingEntry as y };
1471
+ export { syncWranglerWebSockets as A, readWranglerManagedQueueCleanupCandidates as C, sameMigrationsDir as D, resolveLiveBindingConfig as E, canRewriteVoidProjectConfig as F, renderVoidConfig as I, writeVoidProjectConfig as L, writeProvisionedWranglerConfig as M, writeWranglerPublishedTrafficSnapshot as N, syncWranglerBindings as O, GENERATED_MARKERS as P, readWranglerCustomDomains as S, readWranglerWebSocketTombstones as T, persistMaterializedWranglerConfig as _, LIVE_MIGRATION_TAG as a, readRootWranglerConfig as b, bindingEntriesForField as c, ensureWranglerCompatibilityDate as d, ensureWranglerNodejsAlsFlag as f, normalizeWranglerCompatibilityFlags as g, nextDurableObjectMigrationTag as h, LATEST_KNOWN_COMPAT_DATE as i, updateWranglerCustomDomain as j, syncWranglerDurableObjects as k, bindingNamesForField as l, hasDurableObjectSqliteMigration as m, DEFAULT_LIVE_CLASS as n, acknowledgeWranglerManagedQueueRemovals as o, findBindingNameOwner as p, FRIENDLY_BINDING_NAMES as r, assertNoDuplicateMembers as s, DEFAULT_LIVE_BINDING as t, ensureWranglerCompat as u, readExistingLiveBindingConfig as v, readWranglerPublishedTrafficSnapshot as w, readWranglerCompat as x, readProvisionedBindingEntry as y };
@@ -0,0 +1 @@
1
+ export { b as readRootWranglerConfig } from "./wrangler-I-scB_1t.mjs";
@@ -1,14 +1,14 @@
1
1
  import { a as join, l as toSlash, n as dirname, r as extname } from "./dist-ipQi0Q8y.mjs";
2
2
  import { n as readProjectPaths } from "./project-paths-DGu_QZ_Q.mjs";
3
- import { l as getDatabaseDialect, p as readConfig } from "./config-DHgSEWT7.mjs";
3
+ import { l as getDatabaseDialect, p as readConfig } from "./config-OFOAimzt.mjs";
4
4
  import { n as isIdentifier, r as isLiteral } from "./ast-CsZ_q_gc.mjs";
5
5
  import { t as MagicString } from "./dist-Cegxq8gs.mjs";
6
- import { c as voidDbBundlerPlugin, r as cloudflareQueueName, t as generateQueueResolverSource } from "./dispatch-OVLeWW8K.mjs";
6
+ import { c as voidDbBundlerPlugin, r as cloudflareQueueName, t as generateQueueResolverSource } from "./dispatch-CLa2LThw.mjs";
7
7
  import { t as WORKER_CONDITIONS } from "./worker-conditions-AWFRcFxR.mjs";
8
8
  import { createRequire } from "node:module";
9
9
  import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
10
- import { fileURLToPath } from "node:url";
11
10
  import { build, parseSync } from "vite";
11
+ import { fileURLToPath } from "node:url";
12
12
  import { createHash } from "node:crypto";
13
13
  //#region src/cli/worker-require-transform.ts
14
14
  function nodes(root) {
@@ -1,6 +1,6 @@
1
1
  import { t as StandardSchemaV1 } from "./standard-schema-Fo_vCAZh.mjs";
2
- import { r as AuthUser } from "./auth-DkcFflXV.mjs";
3
- import { a as CloudEnv } from "./handler-CQv5YDS0.mjs";
2
+ import { r as AuthUser } from "./auth-g2TQbDLD.mjs";
3
+ import { a as CloudEnv } from "./handler-BHktmuTL.mjs";
4
4
  import { WebSocketRouteMap } from "void/routes";
5
5
  //#region src/runtime/ws.d.ts
6
6
  type MaybePromise<T> = T | Promise<T>;
@@ -125,6 +125,7 @@ declare class VoidSocket<P extends keyof WebSocketRouteMap & string> {
125
125
  private readonly buffer;
126
126
  private reconnectAttempts;
127
127
  private closedByUser;
128
+ private reconnectTimer;
128
129
  constructor(url: string, options: ConnectCommonOptions);
129
130
  private emit;
130
131
  private connect;
@@ -119,7 +119,7 @@ import { getSession, getUser, requireAuth } from 'void/auth';
119
119
  - `getSession()` returns `{ user, session } | null`
120
120
  - `requireAuth(c)` returns the authenticated user or throws `401`
121
121
 
122
- Import `AuthUser` and `AuthSession` from `void/auth` for type annotations. See their fields in the [API reference](../reference/api/auth.md#authuser).
122
+ Import `AuthUser` and `AuthSession` from `void/auth` for type annotations. They include the fields your Better Auth plugins and `user.additionalFields` add, such as `role` from `admin()`. See their fields in the [API reference](../reference/api/auth.md#authuser).
123
123
 
124
124
  ## Behavior
125
125
 
@@ -51,6 +51,28 @@ Path-only sources apply to every domain on the project. Host-prefixed sources ap
51
51
 
52
52
  The source must use `https://` and a literal hostname without wildcards or a port. Add the domain to your project with `void domain add` before deploying the rule. A domain awaiting certificate verification starts redirecting once it becomes active.
53
53
 
54
+ ## HTTP to HTTPS
55
+
56
+ Set `routing.alwaysUseHttps` to redirect HTTP requests to HTTPS:
57
+
58
+ ```ts
59
+ import { defineConfig } from 'void/config';
60
+
61
+ export default defineConfig({
62
+ routing: {
63
+ alwaysUseHttps: true,
64
+ },
65
+ });
66
+ ```
67
+
68
+ Void answers with a permanent `308` redirect to the same URL over HTTPS. The path, query string, and request method stay the same. Omitted or `false` means off. `localhost` and loopback addresses such as `127.0.0.1` stay on HTTP, so local development and `vite preview` are unchanged.
69
+
70
+ It works on the Void platform and for Void apps on `void deploy --platform cloudflare`. It needs the Cloudflare target: setting it with `target: 'node'`, `'bun'`, or `'deno'` is a config error.
71
+
72
+ On `void deploy --platform cloudflare`, built files with a content hash in their name, such as `/assets/index-abc123.js`, can be served by Cloudflare before Void runs, so they may still answer over HTTP. Pages always redirect, so browsers load these files over HTTPS. To redirect every request, also turn on [Always Use HTTPS](https://developers.cloudflare.com/ssl/edge-certificates/additional-options/always-use-https/) for your Cloudflare zone.
73
+
74
+ SvelteKit, Nuxt, Astro, Analog, TanStack Start, React Router, and vinext apps run their own Worker, so `void deploy --platform cloudflare` stops with an error when the option is on. Static builds of these frameworks, deployed with `--dir` or `inference.appType`, redirect as usual. For the others, redirect to HTTPS in the framework, or turn on [Always Use HTTPS](https://developers.cloudflare.com/ssl/edge-certificates/additional-options/always-use-https/) for your Cloudflare zone.
75
+
54
76
  ## Framework `_redirects` files
55
77
 
56
78
  Void reads `_redirects` files produced by supported frameworks. You can also place one in Vite's `publicDir`:
@@ -177,6 +177,30 @@ const form = useForm('/users/:id', { name: '' }, { params: { id: '42' } });
177
177
 
178
178
  Create forms often need empty fields again after a successful save. Either pass `resetOnSuccess: true` in the third argument, or call `form.reset({ initial: true })` when `form.wasSuccessful` becomes `true`.
179
179
 
180
+ ### React submission callbacks
181
+
182
+ In React, pass `onSuccess` and `onError` to `useForm()` to respond to a submission:
183
+
184
+ ```tsx
185
+ const form = useForm(
186
+ '/users',
187
+ { name: '', email: '' },
188
+ {
189
+ onSuccess: () => setDialogOpen(false),
190
+ onError: (error) => {
191
+ // A field-error map or VoidActionError for a handled action failure.
192
+ console.error(error);
193
+ },
194
+ },
195
+ );
196
+ ```
197
+
198
+ Each submission uses the callbacks from when it was dispatched. Callbacks run inside the React Action, before the updated UI commits. Use an effect or ref callback to focus an element that appears after submission.
199
+
200
+ Callbacks can return `Promise<void>`. The Action awaits them, and `form.pending` stays true until they complete. Errors thrown or rejected by callbacks reach the nearest React error boundary. Aborted submissions invoke neither callback.
201
+
202
+ Vue, Svelte, and Solid submissions return a promise instead; use `await form.post()` to run work after a submission.
203
+
180
204
  ## Named Actions
181
205
 
182
206
  When a page needs multiple mutations, such as updating and deleting a user on the same page, export `actions` (plural) instead of `action`:
@@ -100,7 +100,9 @@ Repair recreates missing infrastructure that the installer owns. It does not res
100
100
 
101
101
  ### Upgrade the platform
102
102
 
103
- Keep the platform runtime current when updating the Void CLI.
103
+ Keep the platform runtime current when updating the Void CLI. Upgrade the platform before your team updates its CLI: a newer CLI refuses to work with an older platform and asks for `void platform upgrade`. Install the new CLI on the administrator's machine, run the upgrade below, then have the team update.
104
+
105
+ If the platform will deploy apps that use `void/sandbox`, its runtime token needs **Account → Containers → Edit** and **Account → Cloudchamber → Edit**. A token made from an older setup link may lack them, and an upgrade does not add them. Edit the token in the Cloudflare dashboard, or set `VOID_PLATFORM_RUNTIME_CLOUDFLARE_API_TOKEN` to a new token when you upgrade. See [Cloudflare API Tokens](/guide/platform/installation/credentials#runtime-token-permissions).
104
106
 
105
107
  Use the installed CLI's packaged runtime to upgrade:
106
108
 
@@ -61,6 +61,8 @@ export const POST = defineHandler(async (c) => {
61
61
  });
62
62
  ```
63
63
 
64
+ `GET` also answers `HEAD` requests, so route files can't export `HEAD`.
65
+
64
66
  See [Database](./database.md) for queries with D1, PostgreSQL, or MySQL.
65
67
 
66
68
  ### JSX handlers
package/docs/guide/ssg.md CHANGED
@@ -37,6 +37,14 @@ export async function getPrerenderPaths() {
37
37
  }
38
38
  ```
39
39
 
40
+ ## 404 pages
41
+
42
+ Static builds that prerender at least one page include a default `404.html` in `dist/client/`. To provide your own, add `public/404.html` or prerender a page at `/404`.
43
+
44
+ Fully static Void deployments serve this page for missing URLs with an HTTP 404 status by default. You can override this with [`routing.notFound`](../reference/config.md#routing-notfound). Hybrid deployments handle missing URLs in the Worker.
45
+
46
+ When hosting `dist/client/` yourself, configure your host to serve `404.html` with a 404 status for missing pages.
47
+
40
48
  ## Comparison with edge prerendering
41
49
 
42
50
  | `output` value | Default prerender | Per-page override | Prerender timing |
@@ -133,13 +133,14 @@ Apps using email need `email.from` in `void.config.ts`. Void reviews the domain'
133
133
 
134
134
  Set these CI secrets and variables as needed:
135
135
 
136
- | Name | When needed |
137
- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- |
138
- | `CLOUDFLARE_API_TOKEN` | Required. Workers Scripts: Edit, read access to bound resources, and edit access to products Void provisions. |
139
- | `CLOUDFLARE_ACCOUNT_ID` | When the token can access several accounts. |
140
- | `DATABASE_URL` | PostgreSQL and MySQL provisioning and migrations. |
141
- | `CLOUDFLARE_WORKERS_SUBDOMAIN` | When the Worker has no version preview URL. Use `my-team` or `my-team.workers.dev`. |
142
- | `CF_ACCESS_CLIENT_ID`, `CF_ACCESS_CLIENT_SECRET` | When Cloudflare Access protects the readiness URL. |
136
+ | Name | When needed |
137
+ | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
138
+ | `CLOUDFLARE_API_TOKEN` | Required. Workers Scripts: Edit, read access to bound resources, and edit access to products Void provisions. |
139
+ | `CLOUDFLARE_ACCOUNT_ID` | When the token can access several accounts. |
140
+ | `DATABASE_URL` | PostgreSQL and MySQL provisioning and migrations. |
141
+ | `CLOUDFLARE_WORKERS_SUBDOMAIN` | When the Worker has no version preview URL. Use `my-team` or `my-team.workers.dev`. |
142
+ | `CF_ACCESS_CLIENT_ID`, `CF_ACCESS_CLIENT_SECRET` | When Cloudflare Access protects the readiness URL. |
143
+ | `CF_ACCESS_READINESS_ORIGIN` | When Access protects a route or Custom Domain and `workers_dev` is `false`. See [Cloudflare Access](#cloudflare-access). |
143
144
 
144
145
  For email, run `void email setup --platform cloudflare` locally, commit `void.lock.json`, and pass `--require-email` in CI. The token also needs Email Routing Edit and Email Sending Edit.
145
146
 
@@ -153,6 +154,14 @@ export CF_ACCESS_TOKEN="$(cloudflared access token --app=https://<worker>.<accou
153
154
 
154
155
  Void uses Access credentials for readiness checks without saving them. If checks are denied, the deployment fails. A first-deploy bootstrap can already be active behind Access when this happens.
155
156
 
157
+ By default, Void sends Access credentials only to `workers.dev` URLs. If `workers_dev` is `false`, Void can check readiness through your first route or Custom Domain. To send the credentials there, set `CF_ACCESS_READINESS_ORIGIN` to that hostname's origin:
158
+
159
+ ```sh
160
+ export CF_ACCESS_READINESS_ORIGIN="https://app.example.com"
161
+ ```
162
+
163
+ Use one exact `https:` origin, without a wildcard or a path. Set the variable in the shell or CI environment that runs `void deploy`. Void ignores a value that `void.config.ts` sets, for example through a dotenv import. Set it only for a hostname that you control and that serves this Worker, because Void sends your Access credentials to it.
164
+
156
165
  ### Databases and secrets
157
166
 
158
167
  For PostgreSQL and MySQL, supply `DATABASE_URL` to the deploy process. Void uses it for Hyperdrive and migrations without writing it to config. MySQL schema changes can partially apply before an error; see [failed MySQL migrations](../guide/database/mysql.md#failed-migrations).
@@ -79,7 +79,7 @@ function requireAuth(c: CloudContext): AuthUser;
79
79
 
80
80
  ## `AuthUser` {#authuser}
81
81
 
82
- Better Auth user shape re-exported by Void.
82
+ Better Auth user shape re-exported by Void. It also includes the fields that Better Auth plugins and `user.additionalFields` in `auth.ts` add, for example `role` from `admin()` or a custom `plan` field. Your tsconfig needs the generated Void types for this; see [Type Safety setup](../../guide/type-safety.md#setup).
83
83
 
84
84
  ```ts
85
85
  interface AuthUser {
@@ -95,7 +95,7 @@ interface AuthUser {
95
95
 
96
96
  ## `AuthSession` {#authsession}
97
97
 
98
- Better Auth session shape re-exported by Void.
98
+ Better Auth session shape re-exported by Void. It also includes the session fields that Better Auth plugins in `auth.ts` add, for example `impersonatedBy` from `admin()`.
99
99
 
100
100
  ```ts
101
101
  interface AuthSession {
@@ -110,6 +110,10 @@ interface AuthSession {
110
110
  }
111
111
  ```
112
112
 
113
+ ::: tip Types inside `auth.ts`
114
+ `AuthUser` and `AuthSession` are built from `auth.ts`. Inside `auth.ts` and the files it imports, don't use them, or values from `getUser()`, `getSession()` and `requireAuth()`, to type a hook's parameters or returned data. TypeScript reports a circular reference and the extra fields are lost. Let Better Auth infer those types, or use its own `User` and `Session` types. Better Auth callbacks such as `sendResetPassword` pass its base `User`, without plugin fields.
115
+ :::
116
+
113
117
  ## `AuthState` {#authstate}
114
118
 
115
119
  Combined authenticated request state:
@@ -33,7 +33,10 @@ Use `prepareCfBuildOutput` to convert an existing Cloudflare Worker build into a
33
33
  ```ts
34
34
  import { prepareCfBuildOutput } from 'void/deploy';
35
35
 
36
- const prepared = await prepareCfBuildOutput('/path/to/app/dist/server', { mode: 'staging' });
36
+ const prepared = await prepareCfBuildOutput('/path/to/app/dist/server', {
37
+ projectRoot: '/path/to/app',
38
+ mode: 'staging',
39
+ });
37
40
  if (!prepared) {
38
41
  throw new Error('The Worker build could not be prepared.');
39
42
  }
@@ -47,9 +50,10 @@ try {
47
50
 
48
51
  Pass the Worker output directory that contains a generated `wrangler.json`, such as `dist/ssr`, `dist/rsc`, or `dist/server`. When Cloudflare's build tooling built the app, pass the project root that contains its native Build Output at `.cloudflare/output/v0` instead. Preparation copies modules and assets without changing that input. Native output keeps its module types, auxiliary Workers, Container declarations, and build metadata. Preparation does not provision resources, upload a Worker, or activate a deployment. Container images built from a local Dockerfile exist only in the Docker environment that built them, so publish from that environment.
49
52
 
50
- | Option | Description |
51
- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
52
- | `accountId` | Cloudflare account the build must target. A different account in the build returns `null`. When omitted, the build's account is kept or left unset. |
53
- | `mode` | Vite build mode. Native output keeps the mode it was built with, and a different value returns `null`. For other output it defaults to `production`. |
53
+ | Option | Description |
54
+ | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
55
+ | `accountId` | Cloudflare account the build must target. A different account in the build returns `null`. When omitted, the build's account is kept or left unset. |
56
+ | `mode` | Vite build mode. Native output keeps the mode it was built with, and a different value returns `null`. For other output it defaults to `production`. |
57
+ | `projectRoot` | App directory containing `package.json` and its installed dependencies. Defaults to the input directory. |
54
58
 
55
59
  The result contains `directory`, an optional `assetsDirectory` pointing to the input assets, and an asynchronous `dispose()` method. Always dispose the result after use. Unsupported build configuration returns `null`. A directory without a Worker build, a missing or invalid Worker module, or an asset symlink outside the asset directory throws an error.
@@ -109,6 +109,8 @@ Imported from `"@void/react"`.
109
109
 
110
110
  React `Link` GET `data` is merged into the rendered `href` query string. Primitive values are serialized with `String(value)`, arrays become repeated keys, `null` and `undefined` are omitted, and nested objects throw. `prefetch` and `reloadDocument` are GET-only and throw for mutation links.
111
111
 
112
+ React `useForm()` options accept `onSuccess?: () => void | Promise<void>` and `onError?: (error: Record<string, string> | VoidActionError) => void | Promise<void>`. Each submission captures its callbacks, runs them before the updated UI commits, and awaits any returned promise. See [submission callbacks](../../guide/pages-routing/actions-and-forms.md#react-submission-callbacks).
113
+
112
114
  React deferred props returned from `defer()` are Suspense resources. Read them
113
115
  with React's `use()` inside a `<Suspense>` boundary. React Pages requires React
114
116
  19 and uses streaming SSR for the initial deferred shell. Rejections throw from
@@ -86,15 +86,16 @@ void deploy --platform cloudflare --require-email # fail instead of deploying
86
86
 
87
87
  Void signs you in through your browser when needed and saves the selected account. In CI, set `CLOUDFLARE_API_TOKEN` and, if the token can access several accounts, `CLOUDFLARE_ACCOUNT_ID`.
88
88
 
89
- | Option or setting | Cloudflare behavior |
90
- | ------------------------------ | -------------------------------------------------------------------------------------- |
91
- | `--dir`, `--spa` | Deploy static output through a small Worker and Workers Assets |
92
- | `--skip-build` | Reuse existing static, SPA, or SSG output; unavailable for Worker apps |
93
- | `--project` | Unavailable; the Worker and account come from `void.config.ts` and `void.lock.json` |
94
- | Named environments | Unavailable; use the top-level `cloudflare` config |
95
- | `CLOUDFLARE_WORKERS_SUBDOMAIN` | Needed in fresh CI when versions have no preview URL; cached locally after a deploy |
96
- | `--require-email` | Fail instead of deploying without email when the email step cannot run, as in CI |
97
- | `DATABASE_URL` | Required in the deploy environment for PostgreSQL or MySQL provisioning and migrations |
89
+ | Option or setting | Cloudflare behavior |
90
+ | ------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
91
+ | `--dir`, `--spa` | Deploy static output through a small Worker and Workers Assets |
92
+ | `--skip-build` | Reuse existing static, SPA, or SSG output; unavailable for Worker apps |
93
+ | `--project` | Unavailable; the Worker and account come from `void.config.ts` and `void.lock.json` |
94
+ | Named environments | Unavailable; use the top-level `cloudflare` config |
95
+ | `CLOUDFLARE_WORKERS_SUBDOMAIN` | Needed in fresh CI when versions have no preview URL; cached locally after a deploy |
96
+ | `CF_ACCESS_READINESS_ORIGIN` | Lets Access credentials reach the readiness check on a route or Custom Domain, for example `https://app.example.com` |
97
+ | `--require-email` | Fail instead of deploying without email when the email step cannot run, as in CI |
98
+ | `DATABASE_URL` | Required in the deploy environment for PostgreSQL or MySQL provisioning and migrations |
98
99
 
99
100
  The token needs Workers Scripts: Edit, read access to bound resources, and edit access to products Void provisions. Commit `void.lock.json` after the first deploy before using other machines or CI.
100
101
 
@@ -297,7 +297,7 @@ Void-managed WebSocket route files (`*.ws.ts`) are Cloudflare-only because they
297
297
 
298
298
  ### `worker`
299
299
 
300
- Curated Cloudflare Workers configuration. `void init` pins `compatibility_date` here. Binding arrays such as `d1_databases`, `kv_namespaces`, and `r2_buckets` go in [`cloudflare`](#cloudflare), where Void preserves custom settings and resolved IDs during deployment.
300
+ Curated Cloudflare Workers configuration. `void init` pins `compatibility_date` here. If a project has no config file and no date, the first dev server or build creates `void.config.ts` with only this date and adds `.void-wrangler.jsonc` to `.gitignore`. Astro, SvelteKit, Nuxt, and Analog apps get the date in a root `wrangler.jsonc` instead, which their adapter reads by default. A project with a `wrangler.toml` gets it in `void.json`. Binding arrays such as `d1_databases`, `kv_namespaces`, and `r2_buckets` go in [`cloudflare`](#cloudflare), where Void preserves custom settings and resolved IDs during deployment.
301
301
 
302
302
  | Field | Type | Description |
303
303
  | --------------------- | ---------- | -------------------------------------- |
@@ -366,6 +366,16 @@ This choice applies to subsequent `void deploy` runs. Atomic deployment sends tr
366
366
 
367
367
  Routing and edge configuration for headers, redirects, rewrites, and caching.
368
368
 
369
+ #### `routing.alwaysUseHttps`
370
+
371
+ Redirect HTTP requests to HTTPS with a permanent `308` redirect. Omitted or `false` means off. Requires the Cloudflare target. `localhost` stays on HTTP. See [HTTP to HTTPS](../guide/edge/redirects#http-to-https) for where it works.
372
+
373
+ ```ts
374
+ routing: {
375
+ alwaysUseHttps: true,
376
+ }
377
+ ```
378
+
369
379
  #### `routing.headers`
370
380
 
371
381
  Custom response headers. Keys are URL patterns, values are arrays of `"Name: value"` strings. See [Custom Headers](../guide/edge/headers) for details.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "void",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Full-stack framework and deployment CLI for Cloudflare Workers, powered by Vite.",
5
5
  "homepage": "https://void.cloud",
6
6
  "bugs": {
@@ -348,12 +348,13 @@
348
348
  "@cloudflare/vite-plugin": "^1.64.0",
349
349
  "@cloudflare/workers-auth": "^0.14.1",
350
350
  "@cloudflare/workers-types": "^5.20261019.1",
351
+ "@cloudflare/workers-utils": "^0.47.3",
351
352
  "@napi-rs/keyring": "^2.1.2",
352
- "@void/deploy-cloudflare": "1.3.0",
353
- "@void/deploy-core": "1.3.0",
354
- "@void/edge": "1.3.0",
355
- "@void/isr": "1.3.0",
356
- "@void/platform": "1.3.0",
353
+ "@void/deploy-cloudflare": "1.4.0",
354
+ "@void/deploy-core": "1.4.0",
355
+ "@void/edge": "1.4.0",
356
+ "@void/isr": "1.4.0",
357
+ "@void/platform": "1.4.0",
357
358
  "better-auth": "^1.7.11",
358
359
  "better-sqlite3": "^13.0.3",
359
360
  "blake3-jit": "^1.1.0",
@@ -402,7 +403,7 @@
402
403
  "zod": "^4.6.7"
403
404
  },
404
405
  "peerDependencies": {
405
- "@void/md": "1.3.0",
406
+ "@void/md": "1.4.0",
406
407
  "arktype": ">=2.0.0",
407
408
  "valibot": ">=1.0.0-beta.7",
408
409
  "vite": "^8.0.0",
package/schema.json CHANGED
@@ -144,6 +144,10 @@
144
144
  "type": "object",
145
145
  "description": "Routing configuration — headers, redirects, rewrites, fallbacks, revalidation, prerendering.",
146
146
  "properties": {
147
+ "alwaysUseHttps": {
148
+ "type": "boolean",
149
+ "description": "Redirect HTTP requests to HTTPS with a permanent 308 redirect. Omitted or false means off. Requires the Cloudflare target; localhost stays on HTTP."
150
+ },
147
151
  "isr": {
148
152
  "type": "boolean",
149
153
  "description": "Enable inferred ISR caching. Set false to disable ISR and edge prerendering, including per-page prerender and revalidate policies. Does not disable output: static build-time prerendering."
@@ -49,7 +49,7 @@ the app's existing saved destination, including when the target project is new.
49
49
  | Email | `docs/guide/email.md`, then the matching guide under `docs/guide/email/` |
50
50
  | KV / storage / queues / cron jobs | `docs/guide/kv.md`, `docs/guide/storage.md`, `docs/guide/queues.md`, `docs/guide/jobs.md` |
51
51
  | SSR and caching | `docs/guide/ssr.md`, `docs/guide/edge/*.md` |
52
- | Rewrites, redirects, fallbacks | `docs/guide/edge/rewrites.md`, `docs/guide/edge/redirects.md`, `docs/reference/config.md` |
52
+ | HTTPS, rewrites, redirects, fallbacks | `docs/guide/edge/rewrites.md`, `docs/guide/edge/redirects.md`, `docs/reference/config.md` |
53
53
  | Static site generation | `docs/guide/ssg.md` |
54
54
  | Connected apps and artifact preparation | `docs/guide/platform/administration/connected-apps.md`, `docs/reference/api/deploy.md` |
55
55
  | Deployment and CI | `docs/guide/deployment.md`, `docs/reference/cli/deploy.md` |
@@ -1,42 +0,0 @@
1
- import { AsyncLocalStorage } from "node:async_hooks";
2
- import { BetterAuthOptions, Session, User } from "better-auth";
3
- import { Context, Env } from "hono";
4
- //#region src/runtime/auth.d.ts
5
- type AuthUser = User;
6
- type AuthSession = Session;
7
- type AuthState = {
8
- session: AuthSession;
9
- user: AuthUser;
10
- };
11
- type VoidAuthConfigContext = {
12
- defaults: BetterAuthOptions;
13
- env: Record<string, unknown>;
14
- dialect: "sqlite" | "postgresql" | "mysql";
15
- request: Request;
16
- };
17
- type VoidAuthConfig = BetterAuthOptions | ((ctx: VoidAuthConfigContext) => BetterAuthOptions);
18
- /**
19
- * Shared AsyncLocalStorage that holds the authenticated Better Auth state for the
20
- * current request.
21
- *
22
- * Stored on `globalThis` under a `Symbol.for` key so duplicate module loads
23
- * (e.g. once by void's bundled runtime and once by Vite's SSR module runner
24
- * when user code imports from `void/auth`) share a single ALS instance. Without
25
- * this, the middleware writes to one instance and `getUser()` reads another,
26
- * always returning null.
27
- */
28
- type AuthContextStore = {
29
- user: AuthUser | null;
30
- state: AuthState | null;
31
- };
32
- declare const authContext: AsyncLocalStorage<AuthContextStore>;
33
- declare function defineAuth<T extends VoidAuthConfig>(config: T): T;
34
- declare function getUser(): AuthUser | null;
35
- declare function getSession(): AuthState | null;
36
- declare function requireAuth<E extends Env & {
37
- Variables: {
38
- user: AuthUser | null;
39
- };
40
- }>(c: Context<E>): AuthUser;
41
- //#endregion
42
- export { VoidAuthConfigContext as a, getSession as c, VoidAuthConfig as i, getUser as l, AuthState as n, authContext as o, AuthUser as r, defineAuth as s, AuthSession as t, requireAuth as u };
@@ -1 +0,0 @@
1
- export { t as prepareCfBuildOutput } from "./cf-build-output-Bapzc_8Z.mjs";
@@ -1 +0,0 @@
1
- export { r as PlatformClient } from "./client-BEMtySaV.mjs";
@@ -1,49 +0,0 @@
1
- import { n as readProjectPaths } from "./project-paths-DGu_QZ_Q.mjs";
2
- import { existsSync, readFileSync } from "node:fs";
3
- import { pathToFileURL } from "node:url";
4
- import { isRunnableDevEnvironment, parseSync } from "vite";
5
- //#region src/auth/config.ts
6
- function isVoidAuthConfigSource(code, filename) {
7
- if (!code.includes("void/auth")) return false;
8
- let program;
9
- try {
10
- program = parseSync(filename, code).program;
11
- } catch {
12
- return false;
13
- }
14
- return (program.body ?? []).some((node) => {
15
- if (node.type !== "ImportDeclaration" && node.type !== "ExportNamedDeclaration" && node.type !== "ExportAllDeclaration") return false;
16
- return node.source?.value === "void/auth";
17
- });
18
- }
19
- function findVoidAuthConfig(root, paths = readProjectPaths(root)) {
20
- for (const absPath of paths.authConfigCandidates) {
21
- if (!existsSync(absPath)) continue;
22
- if (isVoidAuthConfigSource(readFileSync(absPath, "utf-8"), absPath)) return absPath;
23
- }
24
- return null;
25
- }
26
- /**
27
- * The dev server evaluates the auth config in this Node environment. A native
28
- * `import()` would keep the first evaluation for the life of the process; the
29
- * module runner reads the config again once its files are invalidated, and the
30
- * module graph lists the local files the config imports.
31
- */
32
- const AUTH_CONFIG_ENVIRONMENT = "void_auth_config";
33
- /** The dev server's importer for the auth config, when it has the environment. */
34
- function devAuthConfigImporter(server) {
35
- const environment = server.environments[AUTH_CONFIG_ENVIRONMENT];
36
- return environment && isRunnableDevEnvironment(environment) ? (path) => environment.runner.import(path) : void 0;
37
- }
38
- /**
39
- * Evaluate the auth config. A native `import()` keeps its first evaluation for
40
- * the life of the process, which suits one-off commands; the dev server passes
41
- * its module runner.
42
- */
43
- async function loadVoidAuthConfig(configPath, importModule = (path) => import(pathToFileURL(path).href)) {
44
- if (!configPath) return;
45
- const mod = await importModule(configPath);
46
- return mod.default ?? mod.auth ?? mod.config;
47
- }
48
- //#endregion
49
- export { loadVoidAuthConfig as i, devAuthConfigImporter as n, findVoidAuthConfig as r, AUTH_CONFIG_ENVIRONMENT as t };
@@ -1,2 +0,0 @@
1
- import "./deploy-transport-JCxw7QDi.mjs";
2
- export { i as checkWranglerMigrationApplySet, t as parseRootWranglerConfig, r as runDeploy } from "./deploy-hRPvw8ci.mjs";
@@ -1 +0,0 @@
1
- export { r as runGenCommand } from "./gen-CCx3t2Ju.mjs";