diff --git a/docs/guide/auth.md b/docs/guide/auth.md index 2e16fa3af..1b8829fe6 100644 --- a/docs/guide/auth.md +++ b/docs/guide/auth.md @@ -92,7 +92,7 @@ Email/password is enabled by default. To add social providers: } ``` -Provider credentials come from `AUTH__CLIENT_ID` and `AUTH__CLIENT_SECRET`. +Provider credentials come from `AUTH__CLIENT_ID` and `AUTH__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`. See the full provider list in the [config reference](../reference/config.md#auth). diff --git a/docs/guide/edge/headers.md b/docs/guide/edge/headers.md index 719c6bb39..33229478c 100644 --- a/docs/guide/edge/headers.md +++ b/docs/guide/edge/headers.md @@ -33,9 +33,17 @@ For hashed assets, you can add headers, but the platform keeps control of cachin Header rules do not apply to ISR cache responses. -### Blocked headers +### Cookie headers -`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. +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: + +- Void ignores `Set-Cookie` in header rules. +- Void removes the `"cookies"` directive from `Clear-Site-Data` in header rules. Other directives, such as `"cache"` and `"storage"`, still apply. +- 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`. + +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). + +In `void dev`, with `void deploy --platform cloudflare`, and on [other runtimes](../deployment.md#other-runtimes), header rules apply both headers as written. ## Framework `_headers` files diff --git a/docs/guide/edge/revalidation.md b/docs/guide/edge/revalidation.md index e5f152987..ff89a9014 100644 --- a/docs/guide/edge/revalidation.md +++ b/docs/guide/edge/revalidation.md @@ -63,6 +63,8 @@ export const loader = defineHandler(async (c) => { Per-page values take precedence over `void.config.ts` patterns. +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). + ## Precedence When multiple sources set a revalidate TTL, the most specific wins: diff --git a/docs/guide/email/receiving.md b/docs/guide/email/receiving.md index 0054a9720..b79bba642 100644 --- a/docs/guide/email/receiving.md +++ b/docs/guide/email/receiving.md @@ -96,7 +96,7 @@ to check current provider routing and any remaining setup steps. For direct deployments, follow [Your own Cloudflare account](./domains.md#your-own-cloudflare-account) to configure inbound addresses. -There is no local inbound trigger yet — `void dev` serves the outbound dev inbox only, so test inbound handlers with `createInboundTestHarness` below. +`void dev` has no local inbound trigger. It serves only the outbound dev inbox. Test inbound handlers with `createInboundTestHarness` below. ## Testing inbound handlers {#testing-inbound-handlers} diff --git a/docs/guide/email/sending.md b/docs/guide/email/sending.md index e2df02aaf..25176090d 100644 --- a/docs/guide/email/sending.md +++ b/docs/guide/email/sending.md @@ -195,7 +195,7 @@ The inbox keeps the most recent 100 messages through HMR and clears on a full se 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. -`sendInDev: true` skips the inbox, but returns `BINDING_MISSING` during `void dev`. Deploy the app to test real delivery. +`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. ## Testing {#testing} diff --git a/docs/guide/pages-routing/islands.md b/docs/guide/pages-routing/islands.md index 4a6a320f9..8e7bcaee8 100644 --- a/docs/guide/pages-routing/islands.md +++ b/docs/guide/pages-routing/islands.md @@ -209,7 +209,7 @@ After a successful action, an island page reloads or redirects through `window.l ## Forms -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: +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: ::: code-group @@ -344,7 +344,7 @@ export default function PostForm() { ::: -`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. +`useIslandForm` provides the same form state as [`useForm`](./actions-and-forms#useform). Pass the action URL to `post`, `put`, `patch`, or `delete` to submit. Successful submissions reload the page; redirects navigate to their destination. A `422` response populates `errors` from `{ errors: { field: "message" } }`. Submit helpers return `Promise`. diff --git a/docs/guide/remote-dev.md b/docs/guide/remote-dev.md index f97fe3228..15bba099b 100644 --- a/docs/guide/remote-dev.md +++ b/docs/guide/remote-dev.md @@ -59,6 +59,8 @@ When the dev server starts with remote mode active, it prints: - **Network latency:** each binding call makes a network request, so responses may be slower than local development. - **R2 multipart uploads:** `createMultipartUpload()` and `resumeMultipartUpload()` are not supported in remote mode. +- **R2 range and conditional reads:** in remote mode, `get()` ignores request headers passed as `range` or `onlyIf` and returns the whole object. For example, `get(key, { range: request.headers, onlyIf: request.headers })` returns the whole object when the request has a `Range` or `If-None-Match` header. `get()` throws when `range` or `onlyIf` is an object that asks for a byte range or a condition, such as `{ offset: 0, length: 100 }` or `{ etagMatches: etag }`. - **R2 conditional writes:** `put(..., { onlyIf })` requires an updated platform and project deployment. Failed preconditions return `null`. +- **R2 metadata and list options:** custom metadata, HTTP metadata, and `list()` with `delimiter`, `startAfter`, or `include` require an updated project deployment. Without one, `list()` with these options and `put()` with metadata other than `contentType` throw an error. - **D1 dump:** `db.dump()` is not supported in remote mode. - **D1 batches:** `db.batch()` requires an updated platform and project deployment. diff --git a/docs/guide/websockets.md b/docs/guide/websockets.md index c33249960..0c37b3b22 100644 --- a/docs/guide/websockets.md +++ b/docs/guide/websockets.md @@ -151,7 +151,7 @@ Messages are JSON events. Raw string and binary messages are not supported. When Void auth is enabled, every WebSocket hook receives the current user as `ctx.user`, or `null` for an anonymous connection. Use `onBeforeConnect` to reject unauthenticated clients. -Connections from another origin are anonymous unless that origin is in your auth `trustedOrigins`. If you read your own cookies from `ctx.request`, check its `Origin` header yourself. +Connections from another origin are anonymous unless that origin is in your auth `trustedOrigins`. If you read your own cookies from `ctx.request` in `onBeforeConnect`, check its `Origin` header yourself. `ctx.user` is resolved when the socket connects and stays the same for that connection, even if the user later signs out or the session is revoked. For actions that must respect a ban or role change, re-check your own records in `onMessage` and close the connection with `ctx.connection.close()` in rooms or `ctx.socket.close()` in `defineWebSocket()`. @@ -173,7 +173,7 @@ Every hook receives a context with: - `ctx.id`: deterministic route instance id - `ctx.params`: matched route params - `ctx.user`: resolved auth user or `null` -- `ctx.request` +- `ctx.request`: the HTTP request in `onBeforeConnect`, `onConnect`, and `onRequest`. In `onMessage` and `onClose`, it is a placeholder without headers, cookies, or query. To keep request data, save it in `onConnect` with `ctx.connection.setState()` in rooms or `ctx.socket.setState()` in `defineWebSocket()`. Read it later from `ctx.connection.state` or `ctx.socket.state`. - `ctx.env` - `ctx.storage` diff --git a/docs/reference/api.md b/docs/reference/api.md index a3899c162..3347e2cd6 100644 --- a/docs/reference/api.md +++ b/docs/reference/api.md @@ -66,6 +66,8 @@ Import Void's app-facing APIs from these paths: | `void/log` | `logger` structured logging helper | | `void/routes` | `RouteMap` and `WebSocketRouteMap` stubs (types only) | | `void/queues` | `queues` proxy for sending messages to typed queues | +| `void/email` | `sendEmail`, `createEmail`, `defineEmail`, `parseEmail`, `replyEmail`, `MimeError`, `ParseEmailError`, and address, attachment, result, and handler types. See [Email](../guide/email.md). | +| `void/email/testing` | `createEmailTestHarness` and `createInboundTestHarness` to capture sends and deliver test messages to `email/` handlers. | | `void/sandbox` | `getSandbox`, `sandbox`, `SandboxLimitError`, `SandboxUnavailableError`, and the SDK 1.0 file, backup, and mount helpers. `getSandbox(id)` resolves a client. Commands, files, port requests, and process completion return promises; limits reject with `SandboxLimitError`, which extends `UsageLimit`. Container state lasts until inactivity (default ten minutes), a crash, or a restart. See [Sandboxes](../guide/sandboxes.md) for the API and deployment requirements. | | `void/env` | `defineEnv`, `env`, schema helpers (`string`, `number`, `boolean`, `url`, `email`, `oneOf`, `json`), and global Cloudflare env types | | `void/nitro` | Nitro module for Nuxt and Analog. Add `'void/nitro'` to the Nitro `modules` so server routes get the app's `void/db` and `@schema` | diff --git a/docs/reference/api/pages.md b/docs/reference/api/pages.md index c0c72bd5c..8f5fb8e1e 100644 --- a/docs/reference/api/pages.md +++ b/docs/reference/api/pages.md @@ -50,7 +50,7 @@ Imported from `"@void/vue"`. | `useParams()` | Returns dynamic route params for the page currently being rendered. | | `useNavigation()` | Returns pending navigation state: `{ state, location, method }`, where `state` is `"idle"`, `"loading"`, or `"submitting"` and `location` is the pending destination. | | `useForm(url, defaults, options?)` | Typed reactive form helper bound to a page action URL. Returns `{ data, post, put, patch, delete, pending, errors, error, hasChanges, wasSuccessful, recentlySuccessful, reset, clearErrors, clearError }`. | -| `useIslandForm(defaults)` | Form helper for island components where the action URL is inferred from the current island request. Returns the same form state and submit helpers as `useForm()`. | +| `useIslandForm(defaults)` | Form helper for island components. Same form state as `useForm()`, plus `submit(url, method)`. `post(url)`, `put(url)`, `patch(url)`, and `delete(url)` take the action URL and submit with `fetch()`. | | `action(url, options?)` | Awaitable one-shot page action helper. Uses `POST` by default and accepts `{ data, method, params }`, where `method` can be `"PUT"`, `"PATCH"`, or `"DELETE"`. Returns an `ActionResult`. | | `useShared()` | Returns shared data injected by middleware via `c.set("shared", {...})`. | @@ -102,7 +102,7 @@ Imported from `"@void/react"`. | `useParams()` | Returns dynamic route params for the page currently being rendered. | | `useNavigation()` | Returns pending navigation state: `{ state, location, method }`, where `state` is `"idle"`, `"loading"`, or `"submitting"` and `location` is the pending destination. | | `useForm(url, defaults, options?)` | Form helper hook. Returns `{ data, setData, post, put, patch, delete, pending, errors, error, hasChanges, wasSuccessful, recentlySuccessful, reset, clearErrors, clearError }`. | -| `useIslandForm(defaults)` | Form helper hook for island components where the action URL is inferred from the current island request. Returns the same form state and submit helpers as `useForm()`. | +| `useIslandForm(defaults)` | Form helper hook for island components. Same form state as `useForm()`. `post(url)`, `put(url)`, `patch(url)`, and `delete(url)` take the action URL and submit with `fetch()`. | | `action(url, options?)` | Awaitable one-shot page action helper. Uses `POST` by default and accepts `{ data, method, params }`, where `method` can be `"PUT"`, `"PATCH"`, or `"DELETE"`. Returns an `ActionResult`. | | `useShared()` | Returns shared data injected by middleware via `c.set("shared", {...})`. | | `Deferred` | React-only prop type for `defer()` results. It is `Promise` and is consumed with React `use()`. | @@ -159,7 +159,7 @@ Imported from `"@void/svelte"`. | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Link` | Svelte component for SPA navigation. Renders `` for GET, `