void 1.5.0 → 1.6.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 (178) hide show
  1. package/dist/{account-cmd-ChVKPZ-w.mjs → account-cmd-DRhmCWcm.mjs} +3 -3
  2. package/dist/{application-analysis-2vGZrKzg.mjs → application-analysis-HRFsZOv3.mjs} +3 -3
  3. package/dist/{application-routing-DnUQoOJD.mjs → application-routing-P4TggvWp.mjs} +1 -1
  4. package/dist/{auth-link-D4uMdOsw.mjs → auth-link-CVXP1G6G.mjs} +3 -3
  5. package/dist/{auth-router-62dna450.mjs → auth-router-D1pGdbvB.mjs} +2 -2
  6. package/dist/{better-auth-shared-CiclgPde.mjs → better-auth-shared-C958aM7W.mjs} +28 -3
  7. package/dist/{build-cmd-D93K_4Sa.mjs → build-cmd-DmjbuT6d.mjs} +1 -1
  8. package/dist/{cache-CiIzEED4.mjs → cache-2khBO1MR.mjs} +1 -1
  9. package/dist/{cancel-deploy-BNoGfn2e.mjs → cancel-deploy-_itbbvY3.mjs} +1 -1
  10. package/dist/cli/cli.mjs +27 -27
  11. package/dist/client-CEtr5UOR.mjs +1 -0
  12. package/dist/{client-Qayy9xEh.mjs → client-CoxPEhJq.mjs} +6 -2
  13. package/dist/{cloudflare-cmd-DiFGaLow.mjs → cloudflare-cmd-fZ6AMSWk.mjs} +1 -1
  14. package/dist/{cloudflare-config-Chet188_.mjs → cloudflare-config-eQunqX6A.mjs} +1 -1
  15. package/dist/{cloudflare-connect-BdgAQBc7.mjs → cloudflare-connect-9OZe4XVQ.mjs} +1 -1
  16. package/dist/{cloudflare-operations-CWA24vCh.mjs → cloudflare-operations-CrskD4_O.mjs} +4 -4
  17. package/dist/{cloudflare-operations-D8MoezuN.mjs → cloudflare-operations-oLvAd9Nd.mjs} +1 -1
  18. package/dist/{config-Bt4rw93x.mjs → config-BBlVXZCU.mjs} +1 -1
  19. package/dist/{config-DkA9jYF2.mjs → config-BITKQ3O6.mjs} +2 -1
  20. package/dist/{config-BszFVhpk.mjs → config-BgysUhnm.mjs} +1 -1
  21. package/dist/{config-CzsNzGVH.d.mts → config-CPxJmt4M.d.mts} +2 -0
  22. package/dist/config-entry.d.mts +1 -1
  23. package/dist/{connect-b9mJbb6X.mjs → connect-CKvmkl2R.mjs} +4 -4
  24. package/dist/{create-project-rnjum4_V.mjs → create-project-BiMmR5wA.mjs} +1 -1
  25. package/dist/{create-project-eomwm0wa.mjs → create-project-Bq0M8MtO.mjs} +1 -1
  26. package/dist/{db-UPWmiSk1.mjs → db-cq5RW4Vy.mjs} +17 -17
  27. package/dist/{delete-BeeYi1WO.mjs → delete-CY4L8fi2.mjs} +1 -1
  28. package/dist/deploy-B029oHlE.mjs +2 -0
  29. package/dist/{deploy-CwmCcTlq.mjs → deploy-D_gEojD2.mjs} +79 -42
  30. package/dist/{deploy-managed-static-gPDxyp3v.mjs → deploy-managed-static-BHVbB_-A.mjs} +11 -11
  31. package/dist/{deploy-transport-L6zzPE9T.mjs → deploy-transport-DEyOUfac.mjs} +2 -2
  32. package/dist/deploy.mjs +5 -4
  33. package/dist/{dev-inbox-CwH5VuZj.mjs → dev-inbox-CUUS6iUg.mjs} +1 -1
  34. package/dist/{dispatch-CiJ1Xrba.mjs → dispatch-BNLB5_Gm.mjs} +1 -1
  35. package/dist/{domain-3xdH9p2L.mjs → domain-BlH90Ods.mjs} +2 -2
  36. package/dist/{email-Du3YRnM5.mjs → email-Bkq8ZL1p.mjs} +6 -6
  37. package/dist/{email-cloudflare-CtD8qE13.mjs → email-cloudflare-MvJIxrwx.mjs} +5 -107
  38. package/dist/{env-Cnvp0pGy.mjs → env-AvuCSeCH.mjs} +2 -2
  39. package/dist/{gen-BvVmpZ5f.mjs → gen-BD-dfjxY.mjs} +3 -3
  40. package/dist/gen-CIHXh9I8.mjs +1 -0
  41. package/dist/{github-cmd-X2GKiLi-.mjs → github-cmd-CA-lBdkn.mjs} +1 -1
  42. package/dist/{handler-Drm_lyrV.d.mts → handler-BR6Tvjkp.d.mts} +8 -3
  43. package/dist/{handler-xmzz0U38.mjs → handler-CKeTtYID.mjs} +2 -12
  44. package/dist/headers-CuTEdP9C.mjs +445 -0
  45. package/dist/{help-EWDNxST-.mjs → help-1OCFDsRr.mjs} +9 -1
  46. package/dist/help-Ba8Dq5-r.mjs +1 -0
  47. package/dist/{inbound-DSKSHDUj.d.mts → inbound-BHt4dIlL.d.mts} +16 -5
  48. package/dist/{inbound-DQfekMtz.mjs → inbound-CdO-qY0w.mjs} +2 -76
  49. package/dist/index.d.mts +1 -1
  50. package/dist/index.mjs +65 -37
  51. package/dist/{info-jW8_d3VI.mjs → info-CJxua_db.mjs} +4 -4
  52. package/dist/{init-jDQ5G9QC.mjs → init-B-4CluYx.mjs} +12 -11
  53. package/dist/{isr-config-DhBkzCdY.mjs → isr-config-Czzb4mIq.mjs} +1 -47
  54. package/dist/{link-DOyxX5Xs.mjs → link-DAXOT-G_.mjs} +2 -2
  55. package/dist/{list-C9pTYDtl.mjs → list-BYSOo9fm.mjs} +2 -2
  56. package/dist/{live-CDBHyYiR.d.mts → live-0xrcVpyq.d.mts} +1 -1
  57. package/dist/{local-d1-CKfek6Z4.mjs → local-d1-BLgsNUj9.mjs} +1 -1
  58. package/dist/login-BpbYeu-n.mjs +1 -0
  59. package/dist/{login-koQbjzPd.mjs → login-CJopTWX4.mjs} +2 -2
  60. package/dist/{logs-cTU5TsRW.mjs → logs-DE0weBsR.mjs} +13 -11
  61. package/dist/{migrate-C62pGTfr.mjs → migrate-ChHHZBTi.mjs} +3 -3
  62. package/dist/migrate-HdA6Kh2p.mjs +1 -0
  63. package/dist/{mime-DKkO7ZtA.mjs → mime-CtP6-qAF.mjs} +143 -15
  64. package/dist/nitro.mjs +3 -3
  65. package/dist/{node-BfAt2rz8.mjs → node-CZ6lGNLn.mjs} +1 -1
  66. package/dist/{operator-cmd-CnAvSqJ5.mjs → operator-cmd-BUqsdk2K.mjs} +28 -10
  67. package/dist/operator-cmd-wBYyp8iQ.mjs +1 -0
  68. package/dist/pages/client.d.mts +143 -11
  69. package/dist/pages/client.mjs +173 -12
  70. package/dist/pages/index.d.mts +22 -3
  71. package/dist/pages/index.mjs +18 -4
  72. package/dist/pages/islands-plugin.d.mts +46 -2
  73. package/dist/pages/islands-plugin.mjs +91 -5
  74. package/dist/pages/prefetch.d.mts +2 -2
  75. package/dist/pages/prefetch.mjs +1 -1
  76. package/dist/pages/protocol.d.mts +2 -2
  77. package/dist/pages/protocol.mjs +2 -2
  78. package/dist/platform-args-C7XUw9mw.mjs +1 -0
  79. package/dist/{platform-args-Bv9hoLEL.mjs → platform-args-Cx1M-7bq.mjs} +18 -4
  80. package/dist/{platform-auth-config-CLj0EvnB.mjs → platform-auth-config-K4m_LE8b.mjs} +4 -4
  81. package/dist/{platform-auth-protection-DQDSxVhr.mjs → platform-auth-protection-KF5Vo-1I.mjs} +2 -2
  82. package/dist/{platform-auth-recovery-DD3oLerq.mjs → platform-auth-recovery-BOdFrl7R.mjs} +2 -2
  83. package/dist/{platform-cmd-WnOmdLzR.mjs → platform-cmd-DpCHawGF.mjs} +1 -1
  84. package/dist/{platform-cmd-PLNLg-2N.mjs → platform-cmd-Ds3EAV5t.mjs} +5 -4
  85. package/dist/{platform-domain-Bnq0vg0q.mjs → platform-domain-rWJshybd.mjs} +2 -2
  86. package/dist/{platform-lifecycle-BQiN9HN5.mjs → platform-lifecycle-BH34I0rh.mjs} +1 -1
  87. package/dist/{platform-lifecycle-BCsFg1Tl.mjs → platform-lifecycle-D9RdGub1.mjs} +66 -31
  88. package/dist/{platform-management-nzCDwtiN.mjs → platform-management-BRefdHGD.mjs} +21 -3
  89. package/dist/{platform-management-CMJR8Zi-.mjs → platform-management-BfLW0Zlo.mjs} +1 -1
  90. package/dist/{platform-recovery-DBNoME3I.mjs → platform-recovery-DZuZGJtt.mjs} +2 -2
  91. package/dist/platform-runtime-logs-CdJGnk3f.mjs +68 -0
  92. package/dist/{platform-setup-VEcHu0Vq.mjs → platform-setup-Vw1AaUN8.mjs} +15 -2
  93. package/dist/{plugin-inference-DmwjC3y2.mjs → plugin-inference-CdLbxZL_.mjs} +1 -1
  94. package/dist/{prefetch-L93_1Jk7.mjs → prefetch-Dx2jIbtg.mjs} +19 -1
  95. package/dist/{prefetch-CG0K6JPd.d.mts → prefetch-NdNeubbH.d.mts} +7 -1
  96. package/dist/{prepare-pfl2LIMy.mjs → prepare-BJW-f1f1.mjs} +4 -4
  97. package/dist/{prepare-BPsfLWaW.mjs → prepare-BMGeKL16.mjs} +1 -1
  98. package/dist/prepare-BdxJJCMv.mjs +1 -0
  99. package/dist/{project-cmd-5Izu4Pg3.mjs → project-cmd-B_bUyZ3J.mjs} +15 -15
  100. package/dist/{project-team-DSUyjZFh.mjs → project-team-MjT5K4rQ.mjs} +1 -1
  101. package/dist/{project-token-wj09YQSB.mjs → project-token-BKHGwY3k.mjs} +1 -1
  102. package/dist/{project-zero-trust-BMHf2YF0.mjs → project-zero-trust-Bzdd05-h.mjs} +1 -1
  103. package/dist/{protocol-0-lp9ZYx.mjs → protocol-COyySMki.mjs} +130 -107
  104. package/dist/{protocol-xdrl-In0.d.mts → protocol-CkwjASep.d.mts} +2 -2
  105. package/dist/{provision-CRiNnlfE.mjs → provision-BbCzpbXs.mjs} +1 -1
  106. package/dist/{provision-CxHIGBIZ.mjs → provision-DQF6Kw-6.mjs} +1 -1
  107. package/dist/{requests-DicgB5jU.mjs → requests-daJmVpjk.mjs} +1 -1
  108. package/dist/{rollback-DrDZOKNE.mjs → rollback-BgL0wY1J.mjs} +2 -2
  109. package/dist/{route-types-irZc1txT.mjs → route-types-CZc1YXjF.mjs} +40 -4
  110. package/dist/route-url-C_YIH_zO.d.mts +37 -0
  111. package/dist/routes-stub.d.mts +6 -2
  112. package/dist/runtime/better-auth-mysql.mjs +1 -1
  113. package/dist/runtime/better-auth-pg.mjs +1 -1
  114. package/dist/runtime/better-auth.mjs +1 -1
  115. package/dist/runtime/email/testing.d.mts +1 -1
  116. package/dist/runtime/email/testing.mjs +3 -3
  117. package/dist/runtime/email.d.mts +1 -1
  118. package/dist/runtime/email.mjs +3 -3
  119. package/dist/runtime/handler.d.mts +1 -1
  120. package/dist/runtime/handler.mjs +2 -2
  121. package/dist/runtime/live-client.d.mts +1 -1
  122. package/dist/runtime/live.d.mts +1 -1
  123. package/dist/runtime/route-url.d.mts +2 -2
  124. package/dist/runtime/routing.d.mts +3 -3
  125. package/dist/runtime/routing.mjs +37 -16
  126. package/dist/runtime/validator.d.mts +1 -1
  127. package/dist/runtime/ws-server.d.mts +1 -1
  128. package/dist/runtime/ws.d.mts +1 -1
  129. package/dist/{scan-DSQtH3oS.mjs → scan-X2rDDRNa.mjs} +18 -10
  130. package/dist/{secret-C_QtTzJG.mjs → secret-CPq-B_vp.mjs} +2 -2
  131. package/dist/{static-asset-marker-NR1XS2z5.mjs → static-asset-marker-DsQKz6lt.mjs} +8 -1
  132. package/dist/validation-error-uAAjlsiW.mjs +82 -0
  133. package/dist/{version-metadata-Blf5H9BQ.mjs → version-metadata-C8hLzQkv.mjs} +1 -1
  134. package/dist/{wrangler-p-kLCJqF.mjs → wrangler-DESSbN8L.mjs} +2 -2
  135. package/dist/wrangler-DUB3MGHV.mjs +1 -0
  136. package/dist/{wrapper-iejSJDwL.mjs → wrapper-C9SUxoj7.mjs} +2 -2
  137. package/dist/{ws-DzBD-NUj.d.mts → ws-Dmjg5NH2.d.mts} +1 -1
  138. package/docs/guide/edge/headers.md +2 -0
  139. package/docs/guide/edge/rewrites.md +1 -1
  140. package/docs/guide/email/receiving.md +1 -1
  141. package/docs/guide/email/sending.md +42 -12
  142. package/docs/guide/pages-routing/actions-and-forms.md +21 -1
  143. package/docs/guide/pages-routing/islands.md +3 -3
  144. package/docs/guide/pages-routing/layouts.md +16 -2
  145. package/docs/guide/pages-routing/loaders.md +2 -2
  146. package/docs/guide/pages-routing/markdown.md +1 -1
  147. package/docs/guide/pages-routing/overview.md +103 -0
  148. package/docs/guide/platform/administration/connected-apps.md +4 -2
  149. package/docs/guide/platform/installation/credentials.md +6 -4
  150. package/docs/guide/platform/installation/maintenance.md +13 -0
  151. package/docs/guide/platform/installation/setup.md +22 -1
  152. package/docs/guide/ssg.md +3 -3
  153. package/docs/guide/type-safety.md +42 -1
  154. package/docs/integrations/frameworks/astro.md +17 -12
  155. package/docs/integrations/frameworks/overview.md +2 -2
  156. package/docs/reference/api/pages.md +101 -50
  157. package/docs/reference/api/rewrites.md +4 -1
  158. package/docs/reference/api/types.md +14 -10
  159. package/docs/reference/api.md +1 -1
  160. package/docs/reference/cli/platform-installation.md +4 -1
  161. package/docs/reference/cli/setup.md +1 -1
  162. package/docs/reference/config.md +10 -0
  163. package/package.json +8 -7
  164. package/schema.json +4 -0
  165. package/skills/void/SKILL.md +5 -0
  166. package/dist/client-DVtAKeUm.mjs +0 -1
  167. package/dist/deploy-CfU7CR-b.mjs +0 -2
  168. package/dist/gen-i61rdQgg.mjs +0 -1
  169. package/dist/headers-DEUBAM6W.mjs +0 -225
  170. package/dist/help-BcZDUcTw.mjs +0 -1
  171. package/dist/login-C0MiQrYs.mjs +0 -1
  172. package/dist/migrate-BG09-KOv.mjs +0 -1
  173. package/dist/operator-cmd-BhHXIL2G.mjs +0 -1
  174. package/dist/platform-args-C1kqYUen.mjs +0 -1
  175. package/dist/prepare-BJMmfq3W.mjs +0 -1
  176. package/dist/route-url-BGOiyKNz.d.mts +0 -24
  177. package/dist/validation-error-rDVbUJet.mjs +0 -47
  178. package/dist/wrangler-CPRX2d9A.mjs +0 -1
@@ -199,6 +199,8 @@ import { routeUrl } from 'void/routes';
199
199
 
200
200
  :::
201
201
 
202
+ With [`routing.typedRoutes`](../type-safety.md#checked-url-strings), which apps created with `void init` have on, `href` and the router methods below reject plain strings such as `"/user"` that don't match a route.
203
+
202
204
  `Link` also supports query data, history replacement, full-page navigation, and cancellation:
203
205
 
204
206
  ```tsx
@@ -263,12 +265,113 @@ export default function PostPage() {
263
265
  }
264
266
  ```
265
267
 
268
+ ### Active links
269
+
270
+ `Link` marks the link to the current page with `aria-current="page"` and `data-void-active`. Use them to style navigation:
271
+
272
+ ```css
273
+ nav a[data-void-active] {
274
+ font-weight: 600;
275
+ }
276
+ ```
277
+
278
+ A link is active only on its own path by default. Set `activeMatch="prefix"` to keep it active on nested pages:
279
+
280
+ ::: code-group
281
+
282
+ ```tsx [React]
283
+ <nav>
284
+ <Link href="/">Home</Link>
285
+ <Link href="/users" activeMatch="prefix">
286
+ Users
287
+ </Link>
288
+ </nav>
289
+ ```
290
+
291
+ ```vue [Vue]
292
+ <nav>
293
+ <Link href="/">Home</Link>
294
+ <Link href="/users" active-match="prefix">Users</Link>
295
+ </nav>
296
+ ```
297
+
298
+ ```svelte [Svelte]
299
+ <nav>
300
+ <Link href="/">Home</Link>
301
+ <Link href="/users" activeMatch="prefix">Users</Link>
302
+ </nav>
303
+ ```
304
+
305
+ ```tsx [Solid]
306
+ <nav>
307
+ <Link href="/">Home</Link>
308
+ <Link href="/users" activeMatch="prefix">
309
+ Users
310
+ </Link>
311
+ </nav>
312
+ ```
313
+
314
+ :::
315
+
316
+ | Current URL | `href="/users"` | `href="/users" activeMatch="prefix"` |
317
+ | --------------- | --------------- | ------------------------------------ |
318
+ | `/users` | both attributes | both attributes |
319
+ | `/users?page=2` | both attributes | both attributes |
320
+ | `/users/42` | none | `data-void-active` |
321
+ | `/users-admin` | none | none |
322
+
323
+ `aria-current="page"` marks only the exact page, so select `[aria-current="page"]` to style it differently from its section. A link to `/` is active only on the home page.
324
+
325
+ Paths match the way pages are routed: letter case and trailing slashes count, while the query string and hash are ignored. Links to other origins and non-GET links are never active. An `aria-current` or `data-void-active` you pass to `Link` replaces the computed value.
326
+
327
+ ### Pending links
328
+
329
+ While a page loads, `Link` adds `data-void-pending` to every link to it. Use it to show that a click is being handled:
330
+
331
+ ```css
332
+ a[data-void-pending] {
333
+ opacity: 0.6;
334
+ /* Skip visits that finish quickly, such as to a prefetched page */
335
+ transition: opacity 0.15s 0.1s;
336
+ }
337
+ ```
338
+
339
+ A link is pending only while its exact destination loads. The path, query and hash must all match, so a link to `/users?page=2` isn't pending while `/users?page=3` loads. A non-GET link, which renders a `<button>`, is pending while a request with its method to its `href` runs. `router.refresh()` reloads the current page, so links to it are pending during a refresh.
340
+
341
+ A `data-void-pending` you pass to `Link` replaces the computed value. For a loading indicator that covers the whole page, use [`useNavigation()`](../../reference/api/pages.md).
342
+
343
+ ### Prefetching
344
+
345
+ Add `prefetch` to a `Link` to load its page before the user clicks, so the visit doesn't wait for the server:
346
+
347
+ ```tsx
348
+ <Link href="/dashboard" prefetch>
349
+ Dashboard
350
+ </Link>
351
+ ```
352
+
353
+ `prefetch` on its own loads the page on hover, keyboard focus, or touch. A `Link` can also prefetch on click, as soon as it renders, or once it stays in view, and `cacheFor` sets how long a prefetched page is reused. See [Prefetching](../../reference/api/pages.md#prefetching) for every option.
354
+
355
+ To give every `Link` a prefetch strategy, set `prefetch.defaultStrategy` in the adapter plugin. A `Link` that sets its own `prefetch` keeps it:
356
+
357
+ ```ts
358
+ // vite.config.ts
359
+ voidReact({ prefetch: { defaultStrategy: 'hover' } });
360
+ ```
361
+
362
+ Prefer `'hover'` or `'click'` as the default. With `'mount'` or `'visible'`, every `Link` loads its page once it renders or stays in view, which adds up in long lists.
363
+
364
+ `Link` doesn't prefetch while the browser reports that data saver is on or the connection is 2G, which only Chromium-based browsers report.
365
+
366
+ Prefetching runs the destination's loader, so keep loaders free of side effects such as counting views.
367
+
266
368
  ## Scroll Restoration
267
369
 
268
370
  Void manages scroll position for Pages automatically:
269
371
 
270
372
  - **Back/forward navigation and reloads** restore the saved scroll position.
271
373
  - **New navigation** scrolls to the top of the page.
374
+ - **Actions and forms** keep scroll when updating the current page. Redirects follow normal navigation behavior.
272
375
  - **Hash links** (`/docs#api`) scroll to the target element. Same-page hash links (`#section`) skip the server fetch entirely.
273
376
 
274
377
  To keep the current scroll position, pass `preserveScroll: true`:
@@ -16,7 +16,7 @@ Local development permits exact HTTP callbacks on localhost when the platform's
16
16
 
17
17
  The app sends users to Void's authorization page, where Void displays its registered name, operator, callback origin, and requested permission. Sign-in permits reading the user's ID and username. It does not provide an account token or project management access.
18
18
 
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.
19
+ Users can disconnect applications at `<platform API URL>/oauth/connections`. Access tokens expire after an hour, and apps renew them with a refresh token. Refreshing does not extend access past the Void sign-in the user approved the app from: access ends 30 days after that sign-in, or earlier if the sign-in is revoked or the account is suspended. Applications must then ask the user to sign in again.
20
20
 
21
21
  ## Private previews
22
22
 
@@ -48,6 +48,8 @@ Disabling the app or disconnecting its grant stops authorization for subsequent
48
48
 
49
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`.
50
50
 
51
+ The code exchange also returns a `refresh_token`. To renew access, POST `grant_type=refresh_token` and `refresh_token` to `/oauth/token` with client Basic authentication. The response has a new access token and a new `refresh_token`; store the new one and discard the old. Each refresh token works once: sending a used one again revokes the connection and the user must reconnect, so send one refresh at a time per connection. Store refresh tokens as securely as access tokens. An optional space-separated `scope` requests fewer permissions than the user approved; the connection keeps the narrower set, and later refreshes cannot widen it. `deploy:approve` publish tokens never come with a refresh token, so each publish still needs its own approval.
52
+
51
53
  For publishing, package the built app with [`prepareDeployArtifact`](/reference/api/deploy). POST to `/oauth/deployment-requests` using the profile token:
52
54
 
53
55
  ```ts
@@ -66,6 +68,6 @@ Redirect the browser to the returned `authorization_url`. Exchange its code with
66
68
 
67
69
  Use the token to request URLs from `POST /projects/:id/deploy/upload-urls`, upload the declared assets, and send the artifact to `POST /projects/:id/deploy`. Set `x-void-deploy-idempotency-key` to `approval_id`. Deployment uses Void's managed deployment protocol and requires the platform deployment Queue. The credential cannot read secrets, mint project tokens, delete projects, or deploy different bytes.
68
70
 
69
- Revoke identity grants with `POST /oauth/revoke`, authenticated as the client. No refresh token or OpenID Connect ID token is issued.
71
+ Revoke a connection with `POST /oauth/revoke`, authenticated as the client, using either its access token or its refresh token. Void does not issue an OpenID Connect ID token.
70
72
 
71
73
  If the API is protected by Cloudflare Access, the application operator must also configure a service identity permitted by that Access application's policy. Server OAuth endpoints and approved deployment requests still validate their own client, grant, and project authority; browser consent requires the user's company identity.
@@ -8,7 +8,7 @@ Keep one password-manager entry for this platform. Paste the saved values into t
8
8
 
9
9
  | Credential | Created by | Why the platform needs it |
10
10
  | -------------------------------------- | -------------------------------- | ---------------------------------------------------------------------------------------- |
11
- | Management API token | You, in Cloudflare | Lets the CLI change DNS and Access for a domain installation or Access setup. |
11
+ | Management API token | You, in Cloudflare | Lets the CLI create a zone, change DNS, and set up Access. |
12
12
  | Runtime API token | You, in Cloudflare | Stored as a Worker secret so the platform can deploy apps after you close your terminal. |
13
13
  | PostgreSQL URL | You, with your database provider | Stores platform users, projects, and deployments. |
14
14
  | Login provider client ID and secret | You, or Void for Access login | Signs people in to the platform. |
@@ -20,11 +20,13 @@ Keep one password-manager entry for this platform. Paste the saved values into t
20
20
 
21
21
  ## Cloudflare API Tokens {#runtime-token-permissions}
22
22
 
23
- For a domain installation or Cloudflare Access setup, the installer guides you through creating separate management and runtime tokens using Cloudflare's [API token setup](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/). Its links preselect the permissions and suggest names based on your platform's display name, but Cloudflare's form cannot preselect a zone: for a domain installation, change the zone policy from **All Domains** to **Specified Domains** and select your zone. If Void will create the zone, keep **All Domains** on both tokens: the zone does not exist yet, and the runtime token needs it to purge cache. Save the tokens in your password manager and paste them into the masked CLI prompts.
23
+ For a domain installation or Cloudflare Access setup, the installer guides you through creating tokens using Cloudflare's [API token setup](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/): a runtime token, and a separate management token when the installation creates the zone or its wildcard DNS record, or sets up Access. Its links preselect the permissions and suggest names based on your platform's display name, but Cloudflare's form cannot preselect a zone: for a domain installation, change the zone policy from **All Domains** to **Specified Domains** and select your zone. If Void will create the zone, keep **All Domains** on both tokens: the zone does not exist yet, and the runtime token needs it to purge cache. Save the tokens in your password manager and paste them into the masked CLI prompts.
24
24
 
25
25
  For workers.dev testing with the default API hostname, browser login can authorize
26
- infrastructure setup. Void also checks account-wide Access requirements; if the
27
- current credential cannot read them, the installer asks for a scoped Access token.
26
+ infrastructure setup. So can a domain installation on an existing zone whose
27
+ `*.<application domain>` record is already proxied. Void also checks account-wide
28
+ Access requirements; if the current credential cannot read them, the installer
29
+ asks for a scoped Access token.
28
30
  Protected apps and public apps under Default-Deny need an ongoing app-management
29
31
  token, stored encrypted. Access setup requires a management token even on
30
32
  workers.dev. Skip zone permissions until you add a domain.
@@ -147,6 +147,19 @@ Let running rotations finish before you roll back. If an older proxy runs next t
147
147
 
148
148
  :::
149
149
 
150
+ ### Change runtime logs {#runtime-logs}
151
+
152
+ Switch an installed platform between [full and minimal runtime logs](/guide/platform/installation/setup#runtime-logs) with an upgrade:
153
+
154
+ ```sh
155
+ void platform upgrade [id] --runtime-logs minimal
156
+ void platform upgrade [id] --runtime-logs full
157
+ ```
158
+
159
+ Later upgrades keep the setting. The upgrade also changes Workers Logs on the projects' existing deployments. If that step fails, the upgrade still finishes and prints what to run next: `void platform repair [id]` after switching to minimal, or the same upgrade again after switching to full. Records stored before the change are not removed; they expire with Cloudflare's retention.
160
+
161
+ While runtime logs are minimal, `void platform rollback` refuses a runtime that does not support the setting. Run `void platform upgrade [id] --runtime-logs full` first.
162
+
150
163
  ### Platform Workers changed outside Void {#workers-changed-outside-void}
151
164
 
152
165
  Use Void commands to manage the platform's Workers. If their configuration was changed in Cloudflare, or Void cannot identify the active version, maintenance asks you to approve replacing it. Preview the affected Workers with `--plan`.
@@ -12,7 +12,7 @@ For workers.dev testing, start the interactive install and choose your login met
12
12
  void platform install --workers-dev
13
13
  ```
14
14
 
15
- For a domain installation or Access setup, Void guides you to create a management API token and paste it into a masked prompt. Browser login can preview DNS, but cannot change it or configure Access. The token-creation link preselects the permissions for your choices. When using an existing domain, change the zone policy from **All Domains** to **Specified Domains** and select your zone, as the CLI prompt describes. No shell environment variable is needed for interactive installation.
15
+ When a domain installation creates the zone or its wildcard DNS record, or you set up Access, Void guides you to create a management API token and paste it into a masked prompt. Browser login can preview DNS and use an existing proxied `*.<application domain>` record, but cannot change DNS or configure Access. The token-creation link preselects the permissions for your choices. When using an existing domain, change the zone policy from **All Domains** to **Specified Domains** and select your zone, as the CLI prompt describes. No shell environment variable is needed for interactive installation.
16
16
 
17
17
  ::: details Use an existing management token from the shell
18
18
 
@@ -93,6 +93,25 @@ Keep the file’s existing `connections` configuration. For `mode: "platform"`,
93
93
  same-account identity and policy selections are reused. Headless setup requires an
94
94
  explicit app-management token; it never silently retains an installer-only token.
95
95
 
96
+ ## Choose what runtime logs record {#runtime-logs}
97
+
98
+ By default, the platform records each request's URL, console output, and exception messages. Project owners see them with `void project logs`, and administrators with `void platform deployment logs`. They are kept in your Cloudflare account, in Workers Logs and in the platform's log history.
99
+
100
+ If your apps can put personal data in URLs, logs, or error messages, install with minimal runtime logs:
101
+
102
+ ```sh
103
+ void platform install --runtime-logs minimal
104
+ ```
105
+
106
+ With minimal runtime logs:
107
+
108
+ - Platform and project Workers do not send anything to Workers Logs.
109
+ - Log history keeps each request's time, method, status, outcome, deployment, log levels, and exception names, but not its URL, log messages, or exception messages.
110
+ - `--level` filters still work. `--filter` can match exception names, but not the messages that are no longer recorded.
111
+ - Log views say that messages and URLs are not recorded on this platform.
112
+
113
+ The setting covers the whole platform. To change it later, see [Change runtime logs](/guide/platform/installation/maintenance#runtime-logs).
114
+
96
115
  ## Finish login configuration
97
116
 
98
117
  For Google, create an OAuth client and register the printed callback URL. You
@@ -262,4 +281,6 @@ Resume restores your selected Hyperdrive. If that selection is missing from the
262
281
 
263
282
  If Cloudflare rejects a saved runtime token, Void opens the token page and asks for a replacement. For a missing [email permission](/guide/platform/installation/credentials#enable-email), update the existing token's permissions and resume. If you supplied a token through an environment variable, update that variable.
264
283
 
284
+ To replace the runtime token, for example after revoking it, resume with the new token in `VOID_PLATFORM_RUNTIME_CLOUDFLARE_API_TOKEN`. Void checks it, saves it, and updates the platform Workers that the interrupted run already deployed.
285
+
265
286
  :::
package/docs/guide/ssg.md CHANGED
@@ -58,8 +58,8 @@ With the default `output: "server"`, use `export const prerender = true` for dep
58
58
 
59
59
  `void deploy` chooses the deployment automatically:
60
60
 
61
- - **Fully static:** if every page is prerendered and there are no API routes, middleware, cron jobs, or queues, Void deploys as a pure static site with no worker.
62
- - **Hybrid:** if any pages are not prerenderable, such as dynamic pages without `getPrerenderPaths()` or pages with `export const prerender = false`, a worker is deployed to handle those routes at runtime. Prerendered pages are still served as static assets.
61
+ - **Fully static:** if every page is prerendered and there are no API routes, middleware, cron jobs, or queues, Void deploys the built files as a static site. With `--platform cloudflare`, pages and files are served without running a Worker. If you use [edge rules](./edge/headers.md) (`routing` headers, redirects, rewrites, fallbacks, or `alwaysUseHttps`) or a `_headers` or `_redirects` file in `public/`, a small Worker runs on every request to apply them.
62
+ - **Hybrid:** if any pages are not prerenderable, such as dynamic pages without `getPrerenderPaths()` or pages with `export const prerender = false`, a Worker is deployed to handle those routes at runtime. With `--platform cloudflare`, prerendered pages without a `.server.ts` file are served as static files without running the Worker. API routes, pages with a `.server.ts` file, and dynamic pages run in the Worker. Every page runs through the Worker when the app uses middleware, auth, live updates, edge rules (including a `_headers` or `_redirects` file in `public/`), a custom Vite `base`, a `routing.notFound` setting other than `"none"`, or a dynamic page at the top level of `pages/`, such as `pages/[slug].vue`.
63
63
 
64
64
  ## Relationship to `inference.appType: "static"`
65
65
 
@@ -68,5 +68,5 @@ The `inference.appType` field describes app type (SPA, static, void), while `out
68
68
  | | `inference.appType: "static"` | `output: "static"` |
69
69
  | ------------ | ----------------------------------------------- | --------------------------------------------- |
70
70
  | **What** | Deploy a pre-built static site (no Void plugin) | Prerender a Void app at build time |
71
- | **Worker** | None (static assets only) | Only if some pages can't be prerendered |
71
+ | **Worker** | None (static assets only) | Only for server code and edge rules |
72
72
  | **Use case** | VitePress, plain HTML, external SSG tools | Void apps with mostly or fully static content |
@@ -65,7 +65,7 @@ export const loader = defineHandler(async (c) => {
65
65
  <!-- pages/users/index.vue -->
66
66
  <script setup lang="ts">
67
67
  import type { Props } from './index.server';
68
- defineProps<Props>(); // ← { users: { id: number; name: string; ... }[] }
68
+ const { users } = defineProps<Props>(); // ← users: { id: number; name: string; ... }[]
69
69
  </script>
70
70
  ```
71
71
 
@@ -141,6 +141,46 @@ routeUrl('/files/:path{.+}', { params: { path: 'docs/get started' } });
141
141
  // /files/docs/get%20started
142
142
  ```
143
143
 
144
+ ### Checked URL strings
145
+
146
+ [`routing.typedRoutes`](../reference/config.md#routing-typedroutes) checks plain URL strings in `Link`'s `href`, `router.visit()`, `router.prefetch()`, `router.flush()`, and `c.rewrite()`. Apps created with `void init` have it on. To turn it on in an existing app:
147
+
148
+ ```ts
149
+ // void.config.ts
150
+ import { defineConfig } from 'void/config';
151
+
152
+ export default defineConfig({
153
+ routing: { typedRoutes: true },
154
+ });
155
+ ```
156
+
157
+ A string must then match one of your routes, optionally followed by a query string or hash:
158
+
159
+ ```tsx
160
+ <Link href="/about">About</Link>
161
+ <Link href={`/users/${user.id}?tab=activity`}>Profile</Link>
162
+ <Link href={routeUrl('/users/:id', { params: { id } })}>Profile</Link>
163
+ <Link href="/abuot">About</Link> // Type error: no route matches
164
+ ```
165
+
166
+ Links can also point to the current page (`?page=2`, `#comments`) and to `http://`, `https://`, `mailto:`, and `tel:` URLs. Relative paths such as `../settings` are rejected; use the absolute path. A fixed part with spaces or non-ASCII characters can be written as is (`/café menu`) or percent-encoded in uppercase, as `routeUrl()` returns it (`/caf%C3%A9%20menu`). A dynamic segment matches any text, including none, so `/users/:id` also accepts `/users/` and `/users/42/edit`. With a root dynamic page such as `pages/[slug].tsx`, a protocol-relative URL like `//example.com` passes too.
167
+
168
+ `void/routes` exports two types for these strings:
169
+
170
+ - `RouteUrl`: the URL of one of your routes. `routeUrl()` returns it, and `c.rewrite()` accepts it.
171
+ - `Href`: anything a link can point to: a `RouteUrl`, a same-page query or hash, or an external URL.
172
+
173
+ Cast a string that TypeScript can't check, such as a path from your CMS or a file in `public/`:
174
+
175
+ ```tsx
176
+ import type { Href, RouteUrl } from 'void/routes';
177
+
178
+ <Link href={post.path as RouteUrl}>{post.title}</Link>
179
+ <Link href={user.website as Href}>Website</Link>
180
+ ```
181
+
182
+ Restart the dev server or run `void prepare` after changing `routing.typedRoutes`.
183
+
144
184
  Route types update with the dev server. For CI or standalone typechecking, run `void prepare` and follow [Setup](#setup). The [API reference](../reference/api/types.md#void-routes) lists the URL helper and route types.
145
185
 
146
186
  ## What Gets Checked
@@ -151,6 +191,7 @@ Route types update with the dev server. For CI or standalone typechecking, run `
151
191
  | Handlers | `c.env` bindings, validator input, return type |
152
192
  | Fetch client | Route paths, HTTP methods, `body`/`query`/`params`, response type |
153
193
  | Navigation | Route patterns and required parameter names and values in `routeUrl()` and destination objects |
194
+ | Links | With `routing.typedRoutes`, URL strings in `Link`, router methods, and `c.rewrite()` |
154
195
  | `useForm` | Action URLs, `data` fields, `errors` keys, `reset()` args (including `{ initial: true }`), `params`, `resetOnSuccess` |
155
196
  | `action()` | Action URLs, `data` payload, `params` |
156
197
 
@@ -6,9 +6,9 @@ outline: deep
6
6
 
7
7
  Use Void with [Astro](https://astro.build/) for resource detection, typed database queries, and migrations. Astro keeps its own build and Cloudflare integration; Void plugs into its Vite configuration.
8
8
 
9
- ::: warning Astro 6 required
9
+ ::: warning Astro 7 required
10
10
 
11
- - [Astro 6+](https://astro.build/blog/astro-6/) is required to use `import { x } from "void/x"` helpers in Astro apps.
11
+ - [Astro 7+](https://astro.build/blog/astro-7/) is required because Void needs Vite 8.
12
12
  - You also need to enable `"nodejs_als"` under `compatibility_flags` in your Cloudflare config.
13
13
  :::
14
14
 
@@ -67,16 +67,15 @@ void deploy
67
67
 
68
68
  ## Using Void Features
69
69
 
70
- Most [Void platform features](../../guide/app-types.md#void-apps) work with Astro in both local dev and production when using Astro 6+ with `nodejs_als`.
70
+ Most [Void platform features](../../guide/app-types.md#void-apps) work with Astro in both local dev and production when using Astro 7+ with `nodejs_als`.
71
71
 
72
72
  Void-managed auth is not supported in framework mode yet. Use Better Auth's official Astro integration directly for now.
73
73
 
74
- Pages that access runtime bindings must opt out of prerendering:
74
+ Pages and API routes that access runtime bindings must opt out of prerendering:
75
75
 
76
76
  ```astro
77
77
  ---
78
78
  export const prerender = false;
79
- // now Astro.locals.runtime is available
80
79
  ---
81
80
  ```
82
81
 
@@ -89,6 +88,8 @@ import { users } from '@schema';
89
88
  import type { APIRoute } from 'astro';
90
89
  import { db } from 'void/db';
91
90
 
91
+ export const prerender = false;
92
+
92
93
  export const GET: APIRoute = async () => {
93
94
  const rows = await db.select().from(users);
94
95
  return Response.json(rows);
@@ -105,18 +106,20 @@ Declare variables in `env.ts` and read them with `import { env } from "void/env"
105
106
 
106
107
  Void always uses `VITE_*` for client-exposed schema keys, including in Astro projects. Astro's separate `PUBLIC_*` convention still applies to direct `import.meta.env` access, but it does not change the `void/env` server/client boundary.
107
108
 
108
- `void/env` replaces [`astro:env`](https://docs.astro.build/en/guides/environment-variables/) and `Astro.locals.runtime.env` for env-var access. Keep `Astro.locals.runtime.env` around when you need raw binding access (D1, KV, R2, etc.).
109
+ `void/env` replaces [`astro:env`](https://docs.astro.build/en/guides/environment-variables/) for env-var access. Use `env` from `cloudflare:workers` when you need raw binding access (D1, KV, R2, etc.).
109
110
 
110
111
  ## Accessing Bindings Directly
111
112
 
112
- Access bindings via `Astro.locals.runtime.env` (works in both dev and production):
113
+ Access bindings with `env` from `cloudflare:workers` (works in both dev and production):
113
114
 
114
115
  ```ts
115
116
  import type { APIRoute } from 'astro';
117
+ import { env } from 'cloudflare:workers';
118
+
119
+ export const prerender = false;
116
120
 
117
- export const GET: APIRoute = async ({ locals }) => {
118
- const { DB } = locals.runtime.env;
119
- const { results } = await DB.prepare('SELECT * FROM users').all();
121
+ export const GET: APIRoute = async () => {
122
+ const { results } = await env.DB.prepare('SELECT * FROM users').all();
120
123
  return Response.json(results);
121
124
  };
122
125
  ```
@@ -125,8 +128,10 @@ In `.astro` pages (remember to set `prerender = false`):
125
128
 
126
129
  ```astro
127
130
  ---
131
+ import { env } from 'cloudflare:workers';
132
+
128
133
  export const prerender = false;
129
- const { DB } = Astro.locals.runtime.env;
130
- const { results } = await DB.prepare("SELECT * FROM users").all();
134
+
135
+ const { results } = await env.DB.prepare("SELECT * FROM users").all();
131
136
  ---
132
137
  ```
@@ -21,7 +21,7 @@ TanStack Start and React Router use the Cloudflare Vite plugin provided by Void.
21
21
  | [SvelteKit](./sveltekit.md) | `@sveltejs/kit` | Vite-based, uses its own Cloudflare adapter |
22
22
  | [Nuxt](./nuxt.md) | `nuxt` | Own CLI and build toolchain |
23
23
  | [Analog](./analog.md) | `@analogjs/platform` | Vite-based, uses Nitro with Cloudflare preset |
24
- | [Astro **v6+**](./astro.md) | `astro` | Own CLI and Cloudflare adapter (v6 required) |
24
+ | [Astro **v7+**](./astro.md) | `astro` | Own CLI and Cloudflare adapter (v7 required) |
25
25
 
26
26
  Detection is automatic. The CLI reads your `package.json` dependencies.
27
27
 
@@ -40,7 +40,7 @@ With `voidPlugin()` added to the framework's Vite config, frameworks get most of
40
40
 
41
41
  For framework apps, Void revalidation caches public HTML document responses with both `void deploy` and `void deploy --platform cloudflare`. Client data requests continue to use the framework's own behavior. A response with `Set-Cookie` or `Cache-Control: private`, `no-store`, or `no-cache` is never shared.
42
42
 
43
- Frameworks can also access bindings directly via the framework's own mechanisms (e.g. `env` from `cloudflare:workers` in SvelteKit 3 and Analog, `platform.env` in SvelteKit 2, `event.context.cloudflare.env` in Nuxt, `Astro.locals.runtime.env` in Astro).
43
+ Frameworks can also access bindings directly via the framework's own mechanisms (e.g. `env` from `cloudflare:workers` in SvelteKit 3, Analog, and Astro, `platform.env` in SvelteKit 2, `event.context.cloudflare.env` in Nuxt).
44
44
 
45
45
  ## Configuration
46
46