void 1.0.3 → 1.1.11

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 (194) hide show
  1. package/dist/{account-cmd-BTcUzv9L.mjs → account-cmd-R35a4Oa8.mjs} +3 -3
  2. package/dist/{application-analysis-AB_YYWgH.mjs → application-analysis-D7nNmNJb.mjs} +6 -6
  3. package/dist/{application-routing-L7V-TFhZ.mjs → application-routing-CZH66gXn.mjs} +1 -1
  4. package/dist/{auth-link-DUJuvRx5.mjs → auth-link-VHmiKZmP.mjs} +3 -3
  5. package/dist/{auth-migrations-Cgs0k6lS.mjs → auth-migrations-BO27GDNr.mjs} +38 -2
  6. package/dist/{auth-router-CIxiMG7i.mjs → auth-router-O_Wrs5kg.mjs} +5 -4
  7. package/dist/{better-auth-shared-B74NJIus.d.mts → better-auth-shared-Bv18bB-l.d.mts} +2 -0
  8. package/dist/{better-auth-shared-CMXlZG7W.mjs → better-auth-shared-D-6z0q0f.mjs} +28 -1
  9. package/dist/{build-cmd-BkIv25PA.mjs → build-cmd-CMZiMn46.mjs} +1 -1
  10. package/dist/{cache-Ctzfig-W.mjs → cache-TZ5Ab3d7.mjs} +1 -1
  11. package/dist/{cancel-deploy-CD9o-SsC.mjs → cancel-deploy-DXoR6oC3.mjs} +1 -1
  12. package/dist/cli/cli.mjs +69 -50
  13. package/dist/cli/env-schema-probe.mjs +3 -3
  14. package/dist/client-DrdeJgHR.mjs +1 -0
  15. package/dist/{client-DM9Kcf4e.mjs → client-GuSO3v_x.mjs} +1 -1
  16. package/dist/{cloudflare-auth-CqFBDo52.mjs → cloudflare-auth-DSvBc1Gu.mjs} +1 -1
  17. package/dist/{cloudflare-cmd-BIAF_BIY.mjs → cloudflare-cmd-ZSErUl6Y.mjs} +3 -3
  18. package/dist/{cloudflare-config-DV60XFFK.mjs → cloudflare-config-rdFOys4X.mjs} +2 -2
  19. package/dist/{cloudflare-connect-WjwnVOJA.mjs → cloudflare-connect-BN-k2kD-.mjs} +2 -2
  20. package/dist/{cloudflare-operations-CrSUFRzJ.mjs → cloudflare-operations-Cm8lVx7W.mjs} +1 -1
  21. package/dist/{cloudflare-operations-B42EZeJy.mjs → cloudflare-operations-DH3GedbV.mjs} +6 -6
  22. package/dist/{cloudflare-process-CeDWJRhj.mjs → cloudflare-process-CZJ4viYl.mjs} +2 -2
  23. package/dist/collect-BWhkgtAn.mjs +1 -0
  24. package/dist/{collect-xjffdCdk.mjs → collect-Dew86qce.mjs} +4 -3
  25. package/dist/{config-D0GY5ItR.mjs → config-CyUJzi3q.mjs} +1 -1
  26. package/dist/{config-Bx_1SpnO.mjs → config-D26zcdKy.mjs} +2 -2
  27. package/dist/{config-BEm9vRvS.mjs → config-DC5llZaI.mjs} +2 -2
  28. package/dist/{config-QrJWu0IX.mjs → config-DYJV8aEH.mjs} +1 -1
  29. package/dist/{connect-CKtk-EZe.mjs → connect-yQuLbuiJ.mjs} +4 -4
  30. package/dist/{create-project-B-ewF_Af.mjs → create-project-CGOo_OL_.mjs} +1 -1
  31. package/dist/{create-project-yInGo72M.mjs → create-project-DEEWJX_M.mjs} +1 -1
  32. package/dist/database-provider.d.mts +6 -0
  33. package/dist/{db-7I_gfdVq.mjs → db-COI-yTJE.mjs} +255 -110
  34. package/dist/{db-types-Bb4WBWIs.mjs → db-types-NGxPwnlt.mjs} +1 -1
  35. package/dist/{delete-Cy-M1u-r.mjs → delete-PgN6L_9N.mjs} +1 -1
  36. package/dist/deploy-CxNgEYr4.mjs +2 -0
  37. package/dist/{deploy-BYYlnsiy.mjs → deploy-DsW-PrRQ.mjs} +38 -36
  38. package/dist/{deploy-managed-static-Ca4TFdmx.mjs → deploy-managed-static-Dqz06S2u.mjs} +20 -20
  39. package/dist/{deploy-transport-DWlWBe6M.mjs → deploy-transport-nY3vLXPL.mjs} +2 -2
  40. package/dist/deploy.mjs +5 -5
  41. package/dist/discover-D4TPKo57.mjs +1 -0
  42. package/dist/{discover-D2PGT63u.mjs → discover-LaV0tbIu.mjs} +1 -1
  43. package/dist/{dispatch-DrTTk8Xg.mjs → dispatch-BAbbn3jg.mjs} +6 -6
  44. package/dist/{domain-CZGxPv9u.mjs → domain-DKlNI0SR.mjs} +2 -2
  45. package/dist/{drizzle-CF84FOVM.mjs → drizzle-BQfyvQKX.mjs} +13 -2
  46. package/dist/{email-DzyNVuoU.mjs → email-C0s3OvJk.mjs} +10 -11
  47. package/dist/{email-cloudflare-DfG6UEMm.mjs → email-cloudflare-etpKiITL.mjs} +7 -10
  48. package/dist/{entry-ehmBvs5w.mjs → entry-B_0IhV64.mjs} +1 -1
  49. package/dist/{env-Nx3yf50r.mjs → env-BX2JFsgF.mjs} +4 -4
  50. package/dist/{env-types-DS2_y4X8.mjs → env-types-GRchm5dw.mjs} +1 -1
  51. package/dist/{env-validation-BT7z-qpQ.mjs → env-validation-CuGfWRJd.mjs} +1 -1
  52. package/dist/{env-validation-DYxjz_Wo.mjs → env-validation-D_KyMdab.mjs} +2 -2
  53. package/dist/{fetch-BPCYVi3H.d.mts → fetch-DHLOuPyk.d.mts} +2 -2
  54. package/dist/{fetch-stream-obvZ7fvO.d.mts → fetch-stream-CNxOrBdP.d.mts} +1 -1
  55. package/dist/{gen-CufHEf42.mjs → gen-Bj6ZkOcI.mjs} +25 -19
  56. package/dist/gen-DsCukhuw.mjs +1 -0
  57. package/dist/{generate-Dat0UTry.mjs → generate-B1m4Cm4n.mjs} +1 -1
  58. package/dist/{generate-ClI89brV.mjs → generate-DkEb9rL-.mjs} +1 -1
  59. package/dist/{github-cmd-FU8EjFyU.mjs → github-cmd-DQDryS-5.mjs} +1 -1
  60. package/dist/{headers-Di1FsD3e.mjs → headers-Bi4bOAwp.mjs} +1 -1
  61. package/dist/{help-BT2hovNI.mjs → help-D49SL_lN.mjs} +16 -12
  62. package/dist/help-DOFygYi4.mjs +1 -0
  63. package/dist/{inbound-brj2-TM5.d.mts → inbound-Da7XsjH3.d.mts} +6 -12
  64. package/dist/index.mjs +103 -57
  65. package/dist/{info-l249eUdH.mjs → info-Bxunp14X.mjs} +4 -4
  66. package/dist/{init-D0A6M64Q.mjs → init-DyTEY04s.mjs} +50 -27
  67. package/dist/{link-WMoJFf4i.mjs → link-CgOVn9EC.mjs} +2 -2
  68. package/dist/{list-CQ2EQEHL.mjs → list-CUfD9RSb.mjs} +2 -2
  69. package/dist/{local-d1-BxvQYEFi.mjs → local-d1-CpaPbd_L.mjs} +5 -5
  70. package/dist/{login-C2T1oVG-.mjs → login-BJvGW5Eu.mjs} +2 -2
  71. package/dist/login-alAlxw4S.mjs +1 -0
  72. package/dist/{logs-DnC1d7xB.mjs → logs-DeizBI4j.mjs} +2 -2
  73. package/dist/{migrate-BDIprOYd.mjs → migrate-8rk56vV8.mjs} +4 -4
  74. package/dist/migrate-D2odrAmh.mjs +1 -0
  75. package/dist/{neon-n74ta1Pr.mjs → neon-DTZr7Bu7.mjs} +0 -1
  76. package/dist/nitro.mjs +3 -3
  77. package/dist/{node-cPadMgUO.mjs → node-uN8_zxJr.mjs} +15 -9
  78. package/dist/{operator-cmd-DHieWtFp.mjs → operator-cmd-CA5QQyvE.mjs} +3 -3
  79. package/dist/operator-cmd-D7toMPJ9.mjs +1 -0
  80. package/dist/pages/client.d.mts +1 -1
  81. package/dist/pages/index.d.mts +1 -1
  82. package/dist/pages/index.mjs +4 -4
  83. package/dist/pages/islands-plugin.d.mts +1 -1
  84. package/dist/pages/islands-plugin.mjs +1 -1
  85. package/dist/pages/protocol.d.mts +1 -1
  86. package/dist/{platform-args-CaCL_1Ch.mjs → platform-args-B7ke06up.mjs} +1 -4
  87. package/dist/platform-args-B_LrqEZB.mjs +1 -0
  88. package/dist/{platform-auth-config-Z16-I8nT.mjs → platform-auth-config-CZaY812e.mjs} +3 -3
  89. package/dist/{platform-auth-protection-BXwZiJSF.mjs → platform-auth-protection-U9HCqoXv.mjs} +2 -2
  90. package/dist/{platform-auth-recovery-CXjfqyOv.mjs → platform-auth-recovery-B0BV2hfV.mjs} +2 -2
  91. package/dist/{platform-cmd-DaAOiWTl.mjs → platform-cmd-CXrjk4fb.mjs} +4 -4
  92. package/dist/{platform-cmd-PT4YwMLM.mjs → platform-cmd-D9lwFpcl.mjs} +1 -1
  93. package/dist/{platform-domain-CxpvUeo0.mjs → platform-domain-CXyq-68_.mjs} +2 -2
  94. package/dist/{platform-lifecycle-DBiS0cJd.mjs → platform-lifecycle-D0TdyXbN.mjs} +20 -11
  95. package/dist/{platform-lifecycle-u9pMOvxC.mjs → platform-lifecycle-Dz2cJsLX.mjs} +1 -1
  96. package/dist/{platform-management-DvBS83rC.mjs → platform-management-BvgbusqL.mjs} +1 -1
  97. package/dist/{platform-management-DhEFdQAf.mjs → platform-management-CnXhgxTM.mjs} +1 -3
  98. package/dist/{platform-recovery-BUGOB4RC.mjs → platform-recovery-CH_fThW6.mjs} +2 -2
  99. package/dist/{plugin-inference-DsARdQKE.mjs → plugin-inference-Bekzjuqw.mjs} +2 -2
  100. package/dist/{prepare-PQCgsny0.mjs → prepare-2XD6kh77.mjs} +1 -1
  101. package/dist/{prepare-DEB5SeVJ.mjs → prepare-DepAGBcV.mjs} +9 -9
  102. package/dist/prepare-EVEbQ9n2.mjs +1 -0
  103. package/dist/{project-cmd-DVatD8Ev.mjs → project-cmd-CpLYflCH.mjs} +16 -16
  104. package/dist/{project-paths-DMvEeSau.mjs → project-paths-DGu_QZ_Q.mjs} +1 -1
  105. package/dist/{project-team-DJrWEAG1.mjs → project-team-CTSY7W7Q.mjs} +1 -1
  106. package/dist/{project-token-CBgLBuL6.mjs → project-token-Cybtlfbl.mjs} +1 -1
  107. package/dist/{project-tsconfig-bveqhlYA.mjs → project-tsconfig-Itv1f4WC.mjs} +1 -1
  108. package/dist/{project-zero-trust-92gLtZ25.mjs → project-zero-trust-DKOtqyvu.mjs} +1 -1
  109. package/dist/{protocol-4H7V_3Os.d.mts → protocol-B8sI8dRD.d.mts} +1 -1
  110. package/dist/{providers-BXuhmuub.mjs → providers-BUHwJlmM.mjs} +10 -1
  111. package/dist/{provision-BnSSkiED.mjs → provision-BuifYvLc.mjs} +63 -64
  112. package/dist/provision-UcYjpTRh.mjs +1 -0
  113. package/dist/r2-conditions-B8xUgJRc.mjs +61 -0
  114. package/dist/{requests-7W1Pygt2.mjs → requests-Ryiazz9i.mjs} +1 -1
  115. package/dist/{rollback-BcASVnwP.mjs → rollback-Or0EV3rP.mjs} +2 -2
  116. package/dist/{runner-CabsQ-iM.mjs → runner-3uGC-oLF.mjs} +9 -7
  117. package/dist/{runner-So9mYfcm.mjs → runner-WCdfeI0q.mjs} +1 -1
  118. package/dist/{runner-mysql-BhRaONkO.mjs → runner-mysql-BWmbk5IE.mjs} +1 -1
  119. package/dist/{runner-mysql-DWwE8jKi.mjs → runner-mysql-BcHnLUuM.mjs} +1 -1
  120. package/dist/{runner-pg-_Xncr3H5.mjs → runner-pg-Cgp-cr_p.mjs} +1 -1
  121. package/dist/{runner-pg-BRemq_GX.mjs → runner-pg-Cr3qrqi4.mjs} +1 -1
  122. package/dist/runtime/ai.d.mts +4 -0
  123. package/dist/runtime/ai.mjs +2 -37
  124. package/dist/runtime/better-auth-mysql.d.mts +1 -1
  125. package/dist/runtime/better-auth-mysql.mjs +2 -2
  126. package/dist/runtime/better-auth-pg.d.mts +1 -1
  127. package/dist/runtime/better-auth-pg.mjs +2 -2
  128. package/dist/runtime/better-auth.d.mts +1 -1
  129. package/dist/runtime/better-auth.mjs +2 -2
  130. package/dist/runtime/client-react.d.mts +2 -2
  131. package/dist/runtime/client-solid.d.mts +2 -2
  132. package/dist/runtime/client-svelte.d.mts +2 -2
  133. package/dist/runtime/client-vue.d.mts +2 -2
  134. package/dist/runtime/client.d.mts +2 -2
  135. package/dist/runtime/email/testing.d.mts +1 -1
  136. package/dist/runtime/email.d.mts +2 -3
  137. package/dist/runtime/email.mjs +2 -3
  138. package/dist/runtime/fetch-stream.d.mts +2 -2
  139. package/dist/runtime/fetch.d.mts +1 -1
  140. package/dist/runtime/migration-handler-mysql.mjs +4 -4
  141. package/dist/runtime/migration-handler-pg.mjs +9 -1
  142. package/dist/runtime/remote/binding-handler.mjs +25 -25
  143. package/dist/runtime/remote/index.d.mts +3 -0
  144. package/dist/runtime/remote/index.mjs +86 -23
  145. package/dist/runtime/routing.d.mts +2 -2
  146. package/dist/runtime/sse-client.d.mts +4 -0
  147. package/dist/runtime/ws-server.d.mts +2 -2
  148. package/dist/runtime/ws.d.mts +1 -1
  149. package/dist/{scan-C0BMdmEd.mjs → scan-D2-FcLHs.mjs} +13 -5
  150. package/dist/{secret-bg73f1Ge.mjs → secret-CClHaFxm.mjs} +3 -3
  151. package/dist/{types-BAp5AEBU.d.mts → types-7AIz0OZu.d.mts} +1 -1
  152. package/dist/{validate-CheZGRE7.mjs → validate-CnZPMq4q.mjs} +22 -17
  153. package/dist/validate-D7Wn8PUX.mjs +1 -0
  154. package/dist/{version-metadata-DIrqEHQo.mjs → version-metadata-B1Bm2wSq.mjs} +1 -1
  155. package/dist/{wrangler-BYs07Rxy.mjs → wrangler-C9XSzrju.mjs} +3 -3
  156. package/dist/wrangler-DAdxHEzh.mjs +1 -0
  157. package/dist/{wrapper-CwhpNWtH.mjs → wrapper-BOigE1xJ.mjs} +4 -4
  158. package/dist/{ws-aKVYB-fA.d.mts → ws-CsBNxv86.d.mts} +5 -0
  159. package/docs/guide/auth.md +1 -1
  160. package/docs/guide/database/d1.md +2 -2
  161. package/docs/guide/database/mysql.md +1 -1
  162. package/docs/guide/edge/headers.md +10 -2
  163. package/docs/guide/edge/revalidation.md +2 -0
  164. package/docs/guide/email/receiving.md +1 -1
  165. package/docs/guide/email/sending.md +1 -1
  166. package/docs/guide/pages-routing/islands.md +2 -2
  167. package/docs/guide/platform/administration/connected-apps.md +18 -2
  168. package/docs/guide/remote-dev.md +2 -0
  169. package/docs/guide/websockets.md +2 -2
  170. package/docs/index.md +78 -22
  171. package/docs/reference/api/pages.md +7 -5
  172. package/docs/reference/api.md +2 -0
  173. package/docs/reference/cli/database.md +7 -5
  174. package/docs/reference/cli/deploy.md +12 -10
  175. package/docs/reference/cli/email.md +1 -1
  176. package/docs/reference/cli/generate.md +1 -1
  177. package/docs/reference/cli/platform-installation.md +2 -2
  178. package/docs/reference/cli/project.md +4 -2
  179. package/docs/reference/cli.md +1 -1
  180. package/docs/reference/config.md +11 -1
  181. package/package.json +7 -7
  182. package/dist/client-B60PwCC4.mjs +0 -1
  183. package/dist/collect-Cae8A7Zr.mjs +0 -1
  184. package/dist/deploy-9lgfhrXS.mjs +0 -2
  185. package/dist/discover-ClzFSwns.mjs +0 -1
  186. package/dist/gen-sXyp5DFO.mjs +0 -1
  187. package/dist/help-i2YGGnTH.mjs +0 -1
  188. package/dist/login-XD844Xs-.mjs +0 -1
  189. package/dist/migrate-BRwU-blY.mjs +0 -1
  190. package/dist/operator-cmd-D6gnZsnK.mjs +0 -1
  191. package/dist/platform-args-B0HeTh0C.mjs +0 -1
  192. package/dist/prepare-CYT6tAzD.mjs +0 -1
  193. package/dist/r2-conditions-D1Wk8i7b.mjs +0 -30
  194. package/dist/validate-BQFdf0Pi.mjs +0 -1
@@ -1,6 +1,6 @@
1
1
  import { n as bytesToBase64 } from "../../base64-DVrOndmI.mjs";
2
2
  import { t as cfAccessHeaders } from "../../cf-access-BM9G0lhO.mjs";
3
- import { n as serializeR2Conditional } from "../../r2-conditions-D1Wk8i7b.mjs";
3
+ import { a as serializeR2HttpMetadata, i as serializeR2Conditional, t as R2_OBJECT_HEADER } from "../../r2-conditions-B8xUgJRc.mjs";
4
4
  //#region src/runtime/remote/client.ts
5
5
  function readableMessage(value) {
6
6
  if (typeof value !== "string") return;
@@ -252,6 +252,17 @@ var ProxyKVNamespace = class {
252
252
  //#endregion
253
253
  //#region src/runtime/remote/r2.ts
254
254
  /**
255
+ * HTTP metadata that the REST fallback and older deployments do not store.
256
+ * Every path stores `contentType`.
257
+ */
258
+ const EXTRA_HTTP_METADATA = [
259
+ ["contentLanguage", "content-language"],
260
+ ["contentDisposition", "content-disposition"],
261
+ ["contentEncoding", "content-encoding"],
262
+ ["cacheControl", "cache-control"],
263
+ ["cacheExpiry", "expires"]
264
+ ];
265
+ /**
255
266
  * R2Bucket implementation backed by the remote proxy.
256
267
  * Used during local development when `remote: true` is set in void.config.ts.
257
268
  */
@@ -270,7 +281,8 @@ var ProxyR2Bucket = class {
270
281
  if (!data) return null;
271
282
  return makeR2Object(data);
272
283
  }
273
- async get(key) {
284
+ async get(key, options) {
285
+ if (requestsRange(options?.range) || requestsCondition(options?.onlyIf)) throw new Error("remote: R2 get() with range or onlyIf is not supported in remote mode.");
274
286
  const res = await this.client.fetch("/r2/get", {
275
287
  binding: this.binding,
276
288
  key
@@ -296,6 +308,7 @@ var ProxyR2Bucket = class {
296
308
  else body.value = textValue;
297
309
  if (options?.httpMetadata) {
298
310
  const meta = options.httpMetadata;
311
+ body.httpMetadata = serializeR2HttpMetadata(meta);
299
312
  if (meta instanceof Headers) body.contentType = meta.get("content-type") ?? void 0;
300
313
  else body.contentType = meta.contentType;
301
314
  }
@@ -304,8 +317,11 @@ var ProxyR2Bucket = class {
304
317
  if (conditional) body.onlyIf = serializeR2Conditional(options.onlyIf);
305
318
  const result = await (await this.client.fetch(conditional ? "/r2/put-conditional" : "/r2/put", body)).json();
306
319
  if (result === null) return null;
307
- if ("key" in result) return makeR2Object(result);
308
- if (conditional) throw new Error("remote: R2 conditional put requires a compatible Void platform and deployment.");
320
+ const stored = "key" in result ? result : void 0;
321
+ if (!stored && conditional) throw new Error("remote: R2 conditional put requires a compatible Void platform and deployment.");
322
+ const missing = missingMetadata(options, stored);
323
+ if (missing.length > 0) throw new Error(`remote: R2 put() stored "${key}" without ${missing.join(", ")}. Remote R2 metadata needs a project deployment with the current Void version. Deploy the project, then put the object again.`);
324
+ if (stored) return makeR2Object(stored);
309
325
  return makeR2Object({
310
326
  key,
311
327
  size: 0,
@@ -326,19 +342,19 @@ var ProxyR2Bucket = class {
326
342
  prefix: options?.prefix,
327
343
  limit: options?.limit,
328
344
  cursor: options?.cursor,
329
- delimiter: options?.delimiter
345
+ delimiter: options?.delimiter,
346
+ startAfter: options?.startAfter,
347
+ include: options?.include
330
348
  })).json();
349
+ if (!Array.isArray(data.delimitedPrefixes) && (options?.delimiter || options?.startAfter || options?.include)) throw new Error("remote: R2 list() with delimiter, startAfter or include got no answer from a current project deployment. The deployment uses an older Void version, or the platform could not reach it. Retry. If the error continues, deploy the project with the current Void version.");
331
350
  return {
332
351
  objects: data.objects.map((o) => makeR2Object({
333
- key: o.key,
334
- size: o.size,
335
- etag: "",
336
- lastModified: o.lastModified,
337
- contentType: "application/octet-stream"
352
+ contentType: "application/octet-stream",
353
+ ...o
338
354
  })),
339
355
  truncated: data.truncated,
340
356
  cursor: data.cursor ?? void 0,
341
- delimitedPrefixes: []
357
+ delimitedPrefixes: data.delimitedPrefixes ?? []
342
358
  };
343
359
  }
344
360
  createMultipartUpload() {
@@ -348,33 +364,77 @@ var ProxyR2Bucket = class {
348
364
  throw new Error("resumeMultipartUpload() is not supported with remote bindings");
349
365
  }
350
366
  };
367
+ /**
368
+ * Remote mode ignores a `Headers` value, such as `request.headers`, and returns the whole
369
+ * object, so a reload with If-None-Match or a Range request for media still gets a body.
370
+ * Native R2 ignores fields that are undefined.
371
+ */
372
+ function requestsRange(range) {
373
+ if (range instanceof Headers) return false;
374
+ return Object.values(range ?? {}).some((value) => value !== void 0);
375
+ }
376
+ /** Same rules as `requestsRange`. `secondsGranularity` alone asks for no condition. */
377
+ function requestsCondition(onlyIf) {
378
+ if (onlyIf instanceof Headers) return false;
379
+ return Object.entries(onlyIf ?? {}).some(([name, value]) => name !== "secondsGranularity" && value !== void 0);
380
+ }
381
+ /** Metadata in `options` that the stored object in the put response does not have. */
382
+ function missingMetadata(options, stored) {
383
+ const meta = options?.httpMetadata;
384
+ const missing = EXTRA_HTTP_METADATA.filter(([field, header]) => (meta instanceof Headers ? meta.has(header) : meta?.[field] !== void 0) && stored?.httpMetadata?.[field] === void 0).map(([field]) => field);
385
+ if (Object.keys(options?.customMetadata ?? {}).some((name) => stored?.customMetadata?.[name] === void 0)) missing.push("customMetadata");
386
+ return missing;
387
+ }
388
+ /** R2 `etag` is unquoted. HTTP headers and older handlers send the quoted form. */
389
+ function normalizeEtag(etag) {
390
+ const value = etag.startsWith("W/") ? etag.slice(2) : etag;
391
+ return value.length >= 2 && value.startsWith("\"") && value.endsWith("\"") ? value.slice(1, -1) : value;
392
+ }
393
+ function readHttpMetadata(data) {
394
+ if (!data.httpMetadata) return { contentType: data.contentType };
395
+ const { cacheExpiry, ...metadata } = data.httpMetadata;
396
+ return cacheExpiry ? {
397
+ ...metadata,
398
+ cacheExpiry: new Date(cacheExpiry)
399
+ } : metadata;
400
+ }
351
401
  function makeR2Object(data) {
402
+ const etag = normalizeEtag(data.etag ?? "");
403
+ const httpMetadata = readHttpMetadata(data);
352
404
  return {
353
405
  key: data.key,
354
406
  version: data.version ?? "",
355
407
  size: data.size,
356
- etag: data.etag,
357
- httpEtag: data.etag ? `"${data.etag}"` : "",
408
+ etag,
409
+ httpEtag: etag ? `"${etag}"` : "",
358
410
  checksums: { toJSON: () => ({}) },
359
411
  uploaded: new Date(data.lastModified || Date.now()),
360
- httpMetadata: data.httpMetadata ?? { contentType: data.contentType },
412
+ httpMetadata,
361
413
  customMetadata: data.customMetadata ?? {},
362
414
  storageClass: "Standard",
363
415
  range: void 0,
364
- writeHttpMetadata(_headers) {}
416
+ writeHttpMetadata(headers) {
417
+ const fields = [
418
+ ["content-type", httpMetadata.contentType],
419
+ ["content-language", httpMetadata.contentLanguage],
420
+ ["content-disposition", httpMetadata.contentDisposition],
421
+ ["content-encoding", httpMetadata.contentEncoding],
422
+ ["cache-control", httpMetadata.cacheControl],
423
+ ["expires", httpMetadata.cacheExpiry?.toUTCString()]
424
+ ];
425
+ for (const [name, value] of fields) if (value !== void 0) headers.set(name, value);
426
+ }
365
427
  };
366
428
  }
367
429
  function makeR2ObjectBody(key, res) {
368
- const size = parseInt(res.headers.get("content-length") ?? "0");
369
- const etag = res.headers.get("etag") ?? "";
370
- const contentType = res.headers.get("content-type") ?? "application/octet-stream";
430
+ const meta = res.headers.get(R2_OBJECT_HEADER);
371
431
  return {
372
- ...makeR2Object({
432
+ ...makeR2Object(meta ? JSON.parse(decodeURIComponent(meta)) : {
373
433
  key,
374
- size,
375
- etag,
376
- lastModified: res.headers.get("last-modified") ?? (/* @__PURE__ */ new Date()).toISOString(),
377
- contentType
434
+ size: parseInt(res.headers.get("content-length") ?? "0"),
435
+ etag: res.headers.get("etag") ?? "",
436
+ lastModified: res.headers.get("last-modified") ?? "",
437
+ contentType: res.headers.get("content-type") ?? "application/octet-stream"
378
438
  }),
379
439
  body: res.body,
380
440
  get bodyUsed() {
@@ -392,6 +452,9 @@ function makeR2ObjectBody(key, res) {
392
452
  /**
393
453
  * Replace local D1/KV/R2 bindings with proxy-backed versions.
394
454
  * Called from the generated worker entry point when `__VOID_REMOTE` is set.
455
+ *
456
+ * @deprecated Internal to Void. The generated worker calls this in remote mode.
457
+ * `void/remote` is not a supported import. Removed in the next major version.
395
458
  */
396
459
  function createRemoteEnv(env, bindingNames) {
397
460
  const client = new ProxyClient(env.__VOID_PROXY_URL, env.__VOID_TOKEN, env.__VOID_PROJECT_ID, env);
@@ -1,5 +1,5 @@
1
- import { t as DevCssAsset } from "../types-BAp5AEBU.mjs";
2
- import { a as handlePageMutation, i as handlePageGet } from "../protocol-4H7V_3Os.mjs";
1
+ import { t as DevCssAsset } from "../types-7AIz0OZu.mjs";
2
+ import { a as handlePageMutation, i as handlePageGet } from "../protocol-B8sI8dRD.mjs";
3
3
  import { AsyncLocalStorage } from "node:async_hooks";
4
4
  import { Context, ExecutionContext, Hono, MiddlewareHandler } from "hono";
5
5
  //#region src/runtime/routing-errors.d.ts
@@ -14,6 +14,10 @@ export type SseClient<Events extends SseEventMap = SseEventMap> = {
14
14
  };
15
15
  export type ConnectEventStreamOptions = {
16
16
  withCredentials?: boolean;
17
+ /**
18
+ * @deprecated Has no effect. Register each event with `client.on(name, handler)`.
19
+ * Removed in the next major version.
20
+ */
17
21
  events?: Array<string>;
18
22
  parse?: "json" | "text" | ((data: string) => unknown);
19
23
  };
@@ -1,6 +1,6 @@
1
1
  import { n as AuthState } from "../auth-DkcFflXV.mjs";
2
- import { r as VoidAuthRuntimeOptions, t as VoidAuthEnv } from "../better-auth-shared-B74NJIus.mjs";
3
- import { f as WebSocketInstanceKey, t as AnyWebSocketDefinition } from "../ws-aKVYB-fA.mjs";
2
+ import { r as VoidAuthRuntimeOptions, t as VoidAuthEnv } from "../better-auth-shared-Bv18bB-l.mjs";
3
+ import { f as WebSocketInstanceKey, t as AnyWebSocketDefinition } from "../ws-CsBNxv86.mjs";
4
4
  //#region src/runtime/ws-server.d.ts
5
5
  export type WebSocketRouteMeta = {
6
6
  name?: string;
@@ -1,2 +1,2 @@
1
- import { a as RoomDefinition, c as VoidSocket, d as WebSocketConnection, f as WebSocketInstanceKey, h as defineWebSocket, i as RoomController, l as VoidSocketCloseEvent, m as defineRoom, n as ConnectCommonOptions, o as SocketContext, p as connect, r as RoomContext, s as SocketDefinition, t as AnyWebSocketDefinition, u as WebSocketBaseContext } from "../ws-aKVYB-fA.mjs";
1
+ import { a as RoomDefinition, c as VoidSocket, d as WebSocketConnection, f as WebSocketInstanceKey, h as defineWebSocket, i as RoomController, l as VoidSocketCloseEvent, m as defineRoom, n as ConnectCommonOptions, o as SocketContext, p as connect, r as RoomContext, s as SocketDefinition, t as AnyWebSocketDefinition, u as WebSocketBaseContext } from "../ws-CsBNxv86.mjs";
2
2
  export { AnyWebSocketDefinition, ConnectCommonOptions, RoomContext, RoomController, RoomDefinition, SocketContext, SocketDefinition, VoidSocket, VoidSocketCloseEvent, WebSocketBaseContext, WebSocketConnection, WebSocketInstanceKey, connect, defineRoom, defineWebSocket };
@@ -1,8 +1,9 @@
1
1
  import { a as join, n as dirname, r as extname, t as basename } from "./dist-ipQi0Q8y.mjs";
2
- import { n as readProjectPaths } from "./project-paths-DMvEeSau.mjs";
2
+ import { n as readProjectPaths } from "./project-paths-DGu_QZ_Q.mjs";
3
3
  import { t as glob } from "./dist-Bn8Kodjp.mjs";
4
4
  import { n as isIdentifier, r as isLiteral } from "./ast-CPGPjb0V.mjs";
5
5
  import { a as staticLiteral, i as staticExportInitializers, n as parseRouteFilename } from "./parse-filename-CUbj-1MP.mjs";
6
+ import { i as voidWarn } from "./log-vqY3pqwI.mjs";
6
7
  import { t as MagicString } from "./dist-Cegxq8gs.mjs";
7
8
  import { existsSync, readFileSync } from "node:fs";
8
9
  import { parseSync } from "vite";
@@ -129,9 +130,7 @@ function resolveEffectiveLayoutEntries(page, layouts, namedLayouts) {
129
130
  }));
130
131
  if (page.layout === void 0) return defaultChain;
131
132
  const isExclusive = page.layout.startsWith("!");
132
- const name = isExclusive ? page.layout.slice(1) : page.layout;
133
- if (name.startsWith(".") || name.startsWith("/")) return defaultChain;
134
- const resolved = resolveNamedLayout(name, page.componentId, namedLayouts, page.componentPath);
133
+ const resolved = resolveNamedLayout(isExclusive ? page.layout.slice(1) : page.layout, page.componentId, namedLayouts, page.componentPath);
135
134
  if (!resolved) return defaultChain;
136
135
  const namedEntry = {
137
136
  id: toLayoutId(resolved),
@@ -171,6 +170,8 @@ const PAGE_METHODS = /* @__PURE__ */ new Set([
171
170
  "actions"
172
171
  ]);
173
172
  const MAX_REVALIDATE = 31536e3;
173
+ /** Layout paths already reported; a dev server rescans pages on every file change. */
174
+ const warnedLayoutPaths = /* @__PURE__ */ new Set();
174
175
  function componentLayout(source, filename) {
175
176
  if (!source.includes("layout")) return;
176
177
  for (const script of scriptBlocks(source, filename)) {
@@ -375,7 +376,14 @@ async function scanPages(root, options) {
375
376
  for (const page of pages) {
376
377
  if (page.layout === void 0 || page.layout === false) continue;
377
378
  const name = page.layout.startsWith("!") ? page.layout.slice(1) : page.layout;
378
- if (name.startsWith(".") || name.startsWith("/")) continue;
379
+ if (name.startsWith(".") || name.startsWith("/")) {
380
+ const key = `${pagesDir}\0${page.componentPath}\0${page.layout}`;
381
+ if (!warnedLayoutPaths.has(key)) {
382
+ warnedLayoutPaths.add(key);
383
+ voidWarn(`pages: Page '${page.componentPath}' specifies layout '${page.layout}'. Layout paths are not supported, so the default layouts are used. Use the name of a file in a _layouts/ directory, such as 'landing'.`);
384
+ }
385
+ continue;
386
+ }
379
387
  if (!resolveNamedLayout(name, page.componentId, namedLayouts, page.componentPath)) throw new Error(`pages: Page '${page.componentPath}' specifies layout '${page.layout}', but _layouts/${name}.vue was not found in any ancestor directory.`);
380
388
  }
381
389
  return {
@@ -2,9 +2,9 @@ import { n as readDeploymentPlatform, r as readProjectConfig } from "./project-r
2
2
  import { c as getToken } from "./auth-B6iOCjKI.mjs";
3
3
  import { A as confirm, H as outro, L as log, O as cancel, U as password, tt as isCancel } from "./dist-BVdsO7Py.mjs";
4
4
  import { m as printSuccess, o as formatCount, p as note } from "./output-Ba6ZbrzY.mjs";
5
- import { r as PlatformClient } from "./client-DM9Kcf4e.mjs";
5
+ import { r as PlatformClient } from "./client-GuSO3v_x.mjs";
6
6
  import { a as resolveProjectBySlug, t as getRequestedProjectSlug } from "./resolve-project-WpwkCB_c.mjs";
7
- import { _ as validateSecretValues } from "./env-validation-DYxjz_Wo.mjs";
7
+ import { _ as validateSecretValues } from "./env-validation-D_KyMdab.mjs";
8
8
  import { readFileSync } from "node:fs";
9
9
  //#region src/cli/secret.ts
10
10
  async function runSecretCommand(root, args) {
@@ -93,7 +93,7 @@ async function resolveSecretBackend(root, args) {
93
93
  }
94
94
  async function resolveCloudflareSecretBackend(root, args) {
95
95
  if (args.projectName) throw new Error("secret: --project selects a Void platform project and is not used for direct Cloudflare apps.");
96
- const { deleteCloudflareSecret, listCloudflareSecrets, putCloudflareSecret, resolveCloudflareOperationContext, syncCloudflareSecrets } = await import("./cloudflare-operations-CrSUFRzJ.mjs");
96
+ const { deleteCloudflareSecret, listCloudflareSecrets, putCloudflareSecret, resolveCloudflareOperationContext, syncCloudflareSecrets } = await import("./cloudflare-operations-Cm8lVx7W.mjs");
97
97
  const context = resolveCloudflareOperationContext(root);
98
98
  return {
99
99
  target: "Cloudflare Worker",
@@ -41,7 +41,7 @@ type PageDefinition = {
41
41
  title?: string;
42
42
  description?: string;
43
43
  };
44
- /** Named layout selection: "name", "!name", false, or relative path */
44
+ /** Named layout selection: "name", "!name", or false */
45
45
  layout?: string | false;
46
46
  };
47
47
  type LayoutDefinition = {
@@ -1,5 +1,5 @@
1
1
  import { a as join } from "./dist-ipQi0Q8y.mjs";
2
- import { n as readJournal, r as stripSqlExt } from "./collect-xjffdCdk.mjs";
2
+ import { n as readJournal, r as stripSqlExt } from "./collect-Dew86qce.mjs";
3
3
  import { i as getExecutableSql, t as DESTRUCTIVE_PRAGMA } from "./plan-BhLO2p4w.mjs";
4
4
  import { existsSync, readdirSync } from "node:fs";
5
5
  import { hasStandalonePragma } from "@void/deploy-core";
@@ -74,19 +74,20 @@ function validateMigrations(files) {
74
74
  /**
75
75
  * Enumerate drizzle-kit metadata artifacts in a migrations directory.
76
76
  * Returns repo-relative paths like `db/migrations/meta/0000_snapshot.json`
77
- * and `db/migrations/migrations.js`. Empty when none are present.
77
+ * and `db/migrations/migrations.js`, passed through `sourceRelative` so they
78
+ * include the configured `sourceDir`. Empty when none are present.
78
79
  *
79
80
  * Shared across `assertJournalCoherence`, `void gen migration`, and
80
81
  * `void db rename-migrations` so all three agree on what counts as a
81
82
  * "partial drizzle-kit state" (journal gone but other metadata lingering).
82
83
  */
83
- function listDrizzleArtifacts(migrationsDir) {
84
+ function listDrizzleArtifacts(migrationsDir, sourceRelative = (path) => path) {
84
85
  const metaDir = join(migrationsDir, "meta");
85
86
  const snapshotFiles = existsSync(metaDir) ? readdirSync(metaDir).filter((f) => f.endsWith("_snapshot.json")) : [];
86
87
  const bundledPath = join(migrationsDir, "migrations.js");
87
88
  const out = [];
88
- for (const f of snapshotFiles) out.push(`db/migrations/meta/${f}`);
89
- if (existsSync(bundledPath)) out.push("db/migrations/migrations.js");
89
+ for (const f of snapshotFiles) out.push(sourceRelative(`db/migrations/meta/${f}`));
90
+ if (existsSync(bundledPath)) out.push(sourceRelative("db/migrations/migrations.js"));
90
91
  return out;
91
92
  }
92
93
  /**
@@ -100,28 +101,32 @@ function listDrizzleArtifacts(migrationsDir) {
100
101
  * hand-written `.sql` projects without `db/schema.ts` are supported and are
101
102
  * tracked by filename in `_void_migrations`. They cannot run
102
103
  * `void db generate`, so the check is skipped for them.
104
+ *
105
+ * `sourceRelative` (`ProjectPaths.sourceRelative`) adds the configured
106
+ * `sourceDir` to the paths in error messages.
103
107
  */
104
- function assertJournalCoherence(migrationsDir, collected) {
105
- const journal = readJournal(migrationsDir);
106
- const drizzleArtifacts = listDrizzleArtifacts(migrationsDir);
108
+ function assertJournalCoherence(migrationsDir, collected, sourceRelative = (path) => path) {
109
+ const dir = sourceRelative("db/migrations");
110
+ const journal = readJournal(migrationsDir, sourceRelative);
111
+ const drizzleArtifacts = listDrizzleArtifacts(migrationsDir, sourceRelative);
107
112
  if (journal === void 0) {
108
113
  if (drizzleArtifacts.length > 0) throw new Error([
109
- "db/migrations/meta/_journal.json is missing, but other drizzle-kit metadata is still present:",
114
+ `${dir}/meta/_journal.json is missing, but other drizzle-kit metadata is still present:`,
110
115
  ...drizzleArtifacts.map((p) => ` - ${p}`),
111
116
  "",
112
117
  "This is a partial drizzle-kit state. Restore _journal.json from version control, or delete the orphaned metadata files above if you intended to revert to hand-written migrations."
113
118
  ].join("\n"));
114
119
  return;
115
120
  }
116
- if (journal === null || typeof journal !== "object" || Array.isArray(journal) || typeof journal.version !== "string" || typeof journal.dialect !== "string" || !Array.isArray(journal.entries)) throw new Error("db/migrations/meta/_journal.json is malformed: expected an object with string `version`, string `dialect`, and an `entries` array. Restore the file from version control, or delete it if you intended to revert to hand-written migrations.");
121
+ if (journal === null || typeof journal !== "object" || Array.isArray(journal) || typeof journal.version !== "string" || typeof journal.dialect !== "string" || !Array.isArray(journal.entries)) throw new Error(`${dir}/meta/_journal.json is malformed: expected an object with string \`version\`, string \`dialect\`, and an \`entries\` array. Restore the file from version control, or delete it if you intended to revert to hand-written migrations.`);
117
122
  for (let i = 0; i < journal.entries.length; i++) {
118
123
  const entry = journal.entries[i];
119
- if (entry === null || typeof entry !== "object" || typeof entry.idx !== "number" || typeof entry.version !== "string" || typeof entry.tag !== "string" || typeof entry.breakpoints !== "boolean") throw new Error(`db/migrations/meta/_journal.json is malformed: entry at index ${i} is missing required fields (\`idx\`, \`version\`, \`tag\`, \`breakpoints\`). Restore the file from version control, or delete it if you intended to revert to hand-written migrations.`);
124
+ if (entry === null || typeof entry !== "object" || typeof entry.idx !== "number" || typeof entry.version !== "string" || typeof entry.tag !== "string" || typeof entry.breakpoints !== "boolean") throw new Error(`${dir}/meta/_journal.json is malformed: entry at index ${i} is missing required fields (\`idx\`, \`version\`, \`tag\`, \`breakpoints\`). Restore the file from version control, or delete it if you intended to revert to hand-written migrations.`);
120
125
  }
121
126
  if (journal.entries.length === 0) {
122
127
  if (drizzleArtifacts.length === 0) return;
123
128
  throw new Error([
124
- "db/migrations/meta/_journal.json has no entries, but drizzle-kit metadata is still present:",
129
+ `${dir}/meta/_journal.json has no entries, but drizzle-kit metadata is still present:`,
125
130
  ...drizzleArtifacts.map((p) => ` - ${p}`),
126
131
  "",
127
132
  "This is a partial drizzle-kit state. Restore _journal.json from version control (or run `void db generate`), or delete the orphaned metadata files above if you intended to revert to hand-written migrations."
@@ -131,7 +136,7 @@ function assertJournalCoherence(migrationsDir, collected) {
131
136
  for (const entry of journal.entries) tagCounts.set(entry.tag, (tagCounts.get(entry.tag) ?? 0) + 1);
132
137
  const duplicateTags = [...tagCounts.entries()].filter(([, n]) => n > 1).map(([tag]) => tag);
133
138
  if (duplicateTags.length > 0) throw new Error([
134
- "db/migrations/meta/_journal.json has duplicate migration entries:",
139
+ `${dir}/meta/_journal.json has duplicate migration entries:`,
135
140
  ...duplicateTags.map((t) => ` - ${t} (appears ${tagCounts.get(t)} times)`),
136
141
  "",
137
142
  "Each migration must have a unique tag. Restore _journal.json from version control, or hand-edit to remove the duplicates."
@@ -141,15 +146,15 @@ function assertJournalCoherence(migrationsDir, collected) {
141
146
  const orphans = collected.map((m) => m.name).filter((name) => !journalTags.has(stripSqlExt(name)));
142
147
  const missing = journal.entries.filter((e) => !collectedTags.has(e.tag)).map((e) => `${e.tag}.sql`);
143
148
  if (orphans.length === 0 && missing.length === 0) return;
144
- const lines = ["Migration drift detected between db/migrations/ and db/migrations/meta/_journal.json."];
149
+ const lines = [`Migration drift detected between ${dir}/ and ${dir}/meta/_journal.json.`];
145
150
  if (orphans.length > 0) {
146
151
  lines.push("", "These files exist on disk but are not tracked in _journal.json:");
147
- for (const f of orphans) lines.push(` - db/migrations/${f}`);
148
- lines.push("", "Delete the orphan file(s), or move their contents into `db/schema.ts` and re-run `void db generate`.");
152
+ for (const f of orphans) lines.push(` - ${dir}/${f}`);
153
+ lines.push("", `Delete the orphan file(s), or move their contents into \`${sourceRelative("db/schema.ts")}\` and re-run \`void db generate\`.`);
149
154
  }
150
155
  if (missing.length > 0) {
151
156
  lines.push("", "These entries are in _journal.json but the matching file is missing:");
152
- for (const f of missing) lines.push(` - db/migrations/${f}`);
157
+ for (const f of missing) lines.push(` - ${dir}/${f}`);
153
158
  lines.push("", "Restore the missing file(s) from version control, or regenerate migrations.");
154
159
  }
155
160
  throw new Error(lines.join("\n"));
@@ -0,0 +1 @@
1
+ export { i as validateMigrations } from "./validate-CnZPMq4q.mjs";
@@ -1,5 +1,5 @@
1
1
  import { a as join } from "./dist-ipQi0Q8y.mjs";
2
- import { l as bindingNamesForField, p as findBindingNameOwner } from "./wrangler-BYs07Rxy.mjs";
2
+ import { l as bindingNamesForField, p as findBindingNameOwner } from "./wrangler-C9XSzrju.mjs";
3
3
  //#region src/shared/untrusted-env.ts
4
4
  /** Authentication inputs that Wrangler may consume from the environment. */
5
5
  const WRANGLER_AUTH_CREDENTIAL_ENV_VARS = [
@@ -1,7 +1,7 @@
1
1
  import { a as join, n as dirname, o as normalize, t as basename } from "./dist-ipQi0Q8y.mjs";
2
- import "./project-paths-DMvEeSau.mjs";
3
- import { d as readConfig } from "./config-Bx_1SpnO.mjs";
4
- import { i as persistCloudflareConfig, r as materializeCloudflareConfig } from "./cloudflare-config-DV60XFFK.mjs";
2
+ import "./project-paths-DGu_QZ_Q.mjs";
3
+ import { d as readConfig } from "./config-D26zcdKy.mjs";
4
+ import { i as persistCloudflareConfig, r as materializeCloudflareConfig } from "./cloudflare-config-rdFOys4X.mjs";
5
5
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
6
6
  import { appendSqliteClassMigration, isRecord } from "@void/deploy-core";
7
7
  import { createHash } from "node:crypto";
@@ -0,0 +1 @@
1
+ export { b as readRootWranglerConfig } from "./wrangler-C9XSzrju.mjs";
@@ -1,9 +1,9 @@
1
1
  import { a as join, l as toSlash, n as dirname, r as extname } from "./dist-ipQi0Q8y.mjs";
2
- import { n as readProjectPaths } from "./project-paths-DMvEeSau.mjs";
3
- import { d as readConfig, s as getDatabaseDialect } from "./config-Bx_1SpnO.mjs";
2
+ import { n as readProjectPaths } from "./project-paths-DGu_QZ_Q.mjs";
3
+ import { d as readConfig, s as getDatabaseDialect } from "./config-D26zcdKy.mjs";
4
4
  import { n as isIdentifier, r as isLiteral } from "./ast-CPGPjb0V.mjs";
5
5
  import { t as MagicString } from "./dist-Cegxq8gs.mjs";
6
- import { r as voidDbBundlerPlugin, t as generateQueueResolverSource } from "./dispatch-DrTTk8Xg.mjs";
6
+ import { r as voidDbBundlerPlugin, t as generateQueueResolverSource } from "./dispatch-BAbbn3jg.mjs";
7
7
  import { createRequire } from "node:module";
8
8
  import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
9
9
  import { fileURLToPath } from "node:url";
@@ -150,7 +150,7 @@ async function generateWrapper(workerDir, workerMain, root, features) {
150
150
  let migrations;
151
151
  let migrationPlanBundle;
152
152
  if (features.hasMigrations) {
153
- const { collectMigrations } = await import("./collect-Cae8A7Zr.mjs");
153
+ const { collectMigrations } = await import("./collect-BWhkgtAn.mjs");
154
154
  const { buildMigrationPlanBundle } = await import("./plan-CKX_jWnD.mjs");
155
155
  const migrationsDir = readProjectPaths(root).migrationsDir;
156
156
  migrations = collectMigrations(migrationsDir);
@@ -35,6 +35,11 @@ type WebSocketBaseContext = {
35
35
  readonly id: string;
36
36
  readonly params: ParamsRecord;
37
37
  readonly user: AuthUser | null;
38
+ /**
39
+ * The HTTP request in `onBeforeConnect`, `onConnect` and `onRequest`. In `onMessage` and
40
+ * `onClose` the connection can resume after hibernation, so this is a placeholder without
41
+ * headers, cookies or query. Use `params`, `user` or the connection `state` there.
42
+ */
38
43
  readonly request: Request;
39
44
  readonly env: CloudEnv["Bindings"];
40
45
  readonly storage: DurableObjectStorage;
@@ -92,7 +92,7 @@ Email/password is enabled by default. To add social providers:
92
92
  }
93
93
  ```
94
94
 
95
- Provider credentials come from `AUTH_<PROVIDER>_CLIENT_ID` and `AUTH_<PROVIDER>_CLIENT_SECRET`.
95
+ Provider credentials come from `AUTH_<PROVIDER>_CLIENT_ID` and `AUTH_<PROVIDER>_CLIENT_SECRET`. `cognito` also needs a domain, region, and user pool ID: set `AUTH_COGNITO_DOMAIN`, `AUTH_COGNITO_REGION`, and `AUTH_COGNITO_USER_POOL_ID`, or set them in `auth.ts`.
96
96
 
97
97
  See the full provider list in the [config reference](../reference/config.md#auth).
98
98
 
@@ -8,9 +8,9 @@ outline: deep
8
8
 
9
9
  ## Local Database
10
10
 
11
- `void dev` and local `void db` commands share the same database. If `cloudflare.d1_databases` in `void.config.ts` declares the D1 binding, Void uses its ID. A `preview_database_id` takes precedence during local development.
11
+ The dev server and local `void db` commands share the same database. If `cloudflare.d1_databases` in `void.config.ts` declares the D1 binding, Void uses its ID. A `preview_database_id` takes precedence during local development.
12
12
 
13
- To store local data outside `.void`, set `voidPlugin({ persistTo: 'local-state' })` in your Vite config. Start `void dev` once to record that directory before running database commands. After changing the Vite config, start `void dev` again to refresh it. Frameworks that manage their own Cloudflare runtime use `.wrangler/state`.
13
+ To store local data outside `.void`, set `voidPlugin({ persistTo: 'local-state' })` in your Vite config. Start the dev server once to record that directory before running database commands. After changing the Vite config, start it again to refresh it. Frameworks that manage their own Cloudflare runtime use `.wrangler/state`.
14
14
 
15
15
  ## Migrations & Prototyping
16
16
 
@@ -22,7 +22,7 @@ Add a direct connection for local development:
22
22
  DATABASE_URL=mysql://user:password@host:3306/app
23
23
  ```
24
24
 
25
- Void reads `.env` for `void dev` and `void db` commands. In production, the Worker connects through the inferred `HYPERDRIVE` binding.
25
+ Void reads `.env` for the dev server and `void db` commands. In production, the Worker connects through the inferred `HYPERDRIVE` binding.
26
26
 
27
27
  ## Define a schema
28
28
 
@@ -33,9 +33,17 @@ For hashed assets, you can add headers, but the platform keeps control of cachin
33
33
 
34
34
  Header rules do not apply to ISR cache responses.
35
35
 
36
- ### Blocked headers
36
+ ### Cookie headers
37
37
 
38
- `Set-Cookie` and `Clear-Site-Data` are ignored in header rules. On a shared domain such as `*.apps.example.com`, a rule could otherwise affect cookies or data belonging to another project's subdomain.
38
+ On a [Void platform](../deployment.md#deploy-to-a-team-platform), apps usually share an application domain, such as `apps.example.com`. Header rules on a platform deploy change two headers on all hostnames of the app, including custom domains:
39
+
40
+ - Void ignores `Set-Cookie` in header rules.
41
+ - Void removes the `"cookies"` directive from `Clear-Site-Data` in header rules. Other directives, such as `"cache"` and `"storage"`, still apply.
42
+ - Void ignores the full value of a `Clear-Site-Data` rule that includes `"*"`. The response then keeps the `Clear-Site-Data` value from your app's response, without `"cookies"`. If that value also includes `"*"`, the response has no `Clear-Site-Data`.
43
+
44
+ This stops a header rule from setting or clearing cookies for other apps on the same domain. These limits apply only to header rules. Your app's own response can set cookies and send `Clear-Site-Data`, and on a shared domain those can affect other apps. See [Domains and Resources](../platform/installation/domains.md#adding-a-domain).
45
+
46
+ In `void dev`, with `void deploy --platform cloudflare`, and on [other runtimes](../deployment.md#other-runtimes), header rules apply both headers as written.
39
47
 
40
48
  ## Framework `_headers` files
41
49
 
@@ -63,6 +63,8 @@ export const loader = defineHandler(async (c) => {
63
63
 
64
64
  Per-page values take precedence over `void.config.ts` patterns.
65
65
 
66
+ A page's `revalidate` export also applies to `routes/` handlers whose URLs match the page pattern. For example, if `pages/[...slug].server.ts` exports `revalidate`, Void can also cache the `GET` response of `routes/now.ts`. Void never caches paths under `/api`. To keep a route handler's response out of the cache, set `x-revalidate: 0` on it. See [Per-response TTL override](#per-response-ttl-override).
67
+
66
68
  ## Precedence
67
69
 
68
70
  When multiple sources set a revalidate TTL, the most specific wins:
@@ -96,7 +96,7 @@ to check current provider routing and any remaining setup steps.
96
96
 
97
97
  For direct deployments, follow [Your own Cloudflare account](./domains.md#your-own-cloudflare-account) to configure inbound addresses.
98
98
 
99
- There is no local inbound trigger yet — `void dev` serves the outbound dev inbox only, so test inbound handlers with `createInboundTestHarness` below.
99
+ `void dev` has no local inbound trigger. It serves only the outbound dev inbox. Test inbound handlers with `createInboundTestHarness` below.
100
100
 
101
101
  ## Testing inbound handlers {#testing-inbound-handlers}
102
102
 
@@ -195,7 +195,7 @@ The inbox keeps the most recent 100 messages through HMR and clears on a full se
195
195
 
196
196
  The dev inbox works in native Void apps, TanStack Start, and React Router. It is unavailable in SvelteKit, Nuxt, Analog, and Astro; sends from those apps return `BINDING_MISSING`. Use `createEmailTestHarness` from `void/email/testing` to capture sends in tests. If a configured inbox cannot be reached, `sendEmail` returns `UPSTREAM_ERROR` without sending.
197
197
 
198
- `sendInDev: true` skips the inbox, but returns `BINDING_MISSING` during `void dev`. Deploy the app to test real delivery.
198
+ `sendInDev` is deprecated. During `void dev` it skips the inbox and returns `BINDING_MISSING`, so it never delivers. Remove it, and deploy the app to test real delivery.
199
199
 
200
200
  ## Testing {#testing}
201
201
 
@@ -209,7 +209,7 @@ After a successful action, an island page reloads or redirects through `window.l
209
209
 
210
210
  ## Forms
211
211
 
212
- Island pages cannot use `useForm` because it depends on the Void Router. Use `useIslandForm` instead. It has the same API, but it uses `fetch()` directly:
212
+ Island pages cannot use `useForm` because it depends on the Void Router. Use `useIslandForm` instead. It has the same form state, but its submit helpers take the action URL and use `fetch()` directly:
213
213
 
214
214
  ::: code-group
215
215
 
@@ -344,7 +344,7 @@ export default function PostForm() {
344
344
 
345
345
  :::
346
346
 
347
- `useIslandForm` provides the same state and methods as [`useForm`](./actions-and-forms#useform). Use `post`, `put`, `patch`, or `delete` with the action URL to submit.
347
+ `useIslandForm` provides the same form state as [`useForm`](./actions-and-forms#useform). Pass the action URL to `post`, `put`, `patch`, or `delete` to submit.
348
348
 
349
349
  Successful submissions reload the page; redirects navigate to their destination. A `422` response populates `errors` from `{ errors: { field: "message" } }`. Submit helpers return `Promise<void>`.
350
350
 
@@ -4,7 +4,7 @@ A connected app is a separate application registered as an OAuth client on your
4
4
 
5
5
  ## Register an application
6
6
 
7
- Open **Admin → Connected apps**. Enter the app name, its operator, and each exact HTTPS callback URL on a separate line. Wildcards and callback query strings are not supported. Enable individual publish approvals if the app builds and deploys projects.
7
+ Open **Admin → Connected apps**. Enter the app name, its operator, and each exact HTTPS callback URL on a separate line. Wildcards and callback query strings are not supported. Enable individual publish approvals if the app builds and deploys projects. Enable private previews to run checkpoints before publishing. Existing clients have an **Enable private previews** button; users must connect again to consent to that permission.
8
8
 
9
9
  Save the returned client ID and secret on the app's server. Void shows the secret once. The client secret authenticates that application; it does not grant access to users' accounts. Register a new client if you need replacement credentials, then disable the old one.
10
10
 
@@ -18,6 +18,22 @@ The app sends users to Void's authorization page, where Void displays its regist
18
18
 
19
19
  Users can disconnect applications at `<platform API URL>/oauth/connections`. Grants also expire after an hour and become invalid when their platform login session is revoked or their account is suspended. Applications must request a new sign-in when access expires.
20
20
 
21
+ ## Private previews
22
+
23
+ Sign-in can request `scope=profile preview:deploy` when the administrator has enabled it. After user consent, the app can build private previews without approval for each checkpoint. Production publishing still requires individual approval.
24
+
25
+ Use the granted bearer token to:
26
+
27
+ 1. POST `{ workspace: externalWorkspaceId }` to `/oauth/previews`. The response contains a stable `project_id` and preview `url` for that client, owner and workspace.
28
+ 2. POST `{ project_id, checkpoint, artifact: prepared.artifact }` to `/oauth/preview-deployment-requests`. Use its `access_token` and `approval_id` with the same asset upload and deployment protocol as publishing.
29
+ 3. Once deployment finishes, POST to `/oauth/previews/:id/launch` and open the returned URL. Launch URLs expire after one minute and can be used once. Reload the preview's origin after opening it.
30
+
31
+ Each workspace has a separate preview origin and resources, with no production secrets. Previews support HTTP apps with isolated D1, KV, R2, AI and native Durable Objects. Authentication, external databases, email, schedules, queues and containers require production publishing. Viewing requires the current user grant; disconnecting or disabling the app denies subsequent requests. Preview writes must originate from that preview's own origin.
32
+
33
+ Account Access policies must allow requests to generated preview origins to reach Void. A policy that blocks new Workers hostnames also blocks previews. Void still requires a valid preview grant when the hostname is reachable.
34
+
35
+ Each owner can have ten preview workspaces. Each workspace permits sixty builds per hour and shares the owner's platform resource and usage limits. Inactive previews expire after seven days and Void removes their resources. Opening the workspace through `/oauth/previews` renews that expiry. Disabling preview permission expires its previews.
36
+
21
37
  ## Approve a publish
22
38
 
23
39
  The app builds first, then sends the user to Void with the prepared artifact. Void displays the project name, checkpoint label, resources, manifest, and artifact digest. The user must explicitly approve on Void.
@@ -30,7 +46,7 @@ Disabling the app or disconnecting its grant stops authorization for subsequent
30
46
 
31
47
  ## Build an OAuth client
32
48
 
33
- Discover endpoints at `/.well-known/oauth-authorization-server` on the platform API origin. Use authorization code flow with `scope=profile`, a random `state`, and S256 PKCE. Authenticate token exchanges using HTTP Basic client authentication. Send access tokens as Bearer credentials to `/oauth/userinfo`; its response contains `sub` and `preferred_username`.
49
+ Discover endpoints at `/.well-known/oauth-authorization-server` on the platform API origin. Use authorization code flow with `scope=profile`, a random `state`, and S256 PKCE. Authenticate token exchanges using HTTP Basic client authentication. Send access tokens as Bearer credentials to `/oauth/userinfo`; its response contains `sub`, `preferred_username`, and the consented `scope`.
34
50
 
35
51
  For publishing, package the built app with [`prepareDeployArtifact`](/reference/api/deploy). POST to `/oauth/deployment-requests` using the profile token:
36
52