From 6fead3265653f5f94742f14fce7ea8b33a2ef4bf Mon Sep 17 00:00:00 2001 From: LongYinan Date: Thu, 15 Oct 2026 13:20:36 +0800 Subject: [PATCH] fix: finish or correct Void features that were advertised but unfinished Docs, help, types and error messages promised behavior that the code did not have. Each case now either works, or the promise matches the code: - Markdown code-block copy buttons copy the code. - The cognito auth provider reads its domain, region and user pool ID. - Hosted `void db execute --remote --file` runs the file on the configured D1 binding. - `void gen model` writes the database types it promises. - Remote R2 keeps object metadata and list options, and refuses what it cannot honor instead of returning wrong data. - Help, the CLI and API references, and guides describe what Void does. - Public options and exports that never worked are deprecated, not removed, because Void is on 1.x. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_0123r5HnanmW2VUrZ7VWDufH --- docs/guide/auth.md | 2 +- docs/guide/edge/headers.md | 12 +- docs/guide/edge/revalidation.md | 2 + docs/guide/email/receiving.md | 2 +- docs/guide/email/sending.md | 2 +- docs/guide/pages-routing/islands.md | 4 +- docs/guide/remote-dev.md | 2 + docs/guide/websockets.md | 4 +- docs/reference/api.md | 2 + docs/reference/api/pages.md | 12 +- docs/reference/cli.md | 2 +- docs/reference/cli/database.md | 2 +- docs/reference/cli/deploy.md | 22 +- docs/reference/cli/email.md | 2 +- docs/reference/cli/platform-installation.md | 4 +- docs/reference/cli/project.md | 6 +- docs/reference/config.md | 12 +- packages/deploy-core/src/target.ts | 12 +- packages/edge/src/runtime.ts | 5 + packages/isr/src/index.ts | 20 +- packages/md/src/compile.ts | 16 +- packages/md/src/plugins/pre-wrapper.ts | 22 +- packages/md/src/runtime/index.ts | 10 + packages/md/src/theme/code.css | 8 + .../md/test/integration/markdown-blog.test.ts | 38 +++ packages/md/test/unit/plugins.test.ts | 63 +++++ packages/void/AGENTS.md | 3 + packages/void/src/auth/providers.ts | 17 ++ packages/void/src/cli/auth-router.ts | 12 +- packages/void/src/cli/cli.ts | 5 +- packages/void/src/cli/db-args.ts | 6 +- packages/void/src/cli/db.ts | 27 +-- packages/void/src/cli/email-args.ts | 29 +-- packages/void/src/cli/email-cloudflare.ts | 20 +- packages/void/src/cli/email.ts | 10 +- packages/void/src/cli/gen-args.ts | 2 +- packages/void/src/cli/gen.ts | 5 +- packages/void/src/cli/help.ts | 30 ++- packages/void/src/cli/platform-args.ts | 15 +- packages/void/src/cli/platform-management.ts | 8 - packages/void/src/cli/project-args.ts | 2 +- packages/void/src/cli/project-cmd.ts | 2 +- packages/void/src/cli/provision.ts | 219 +++++++++--------- packages/void/src/config-loader.ts | 2 +- packages/void/src/database-provider.ts | 6 + packages/void/src/database-providers/neon.ts | 1 - packages/void/src/pages/layout-resolution.ts | 5 - packages/void/src/pages/scan.ts | 12 +- packages/void/src/pages/types.ts | 2 +- packages/void/src/runtime/ai-types.ts | 4 + packages/void/src/runtime/ai.ts | 70 +----- .../void/src/runtime/better-auth-shared.ts | 56 ++++- packages/void/src/runtime/email.ts | 9 +- packages/void/src/runtime/email/types.ts | 18 +- packages/void/src/runtime/fetch.ts | 4 +- .../src/runtime/remote/binding-handler.ts | 69 ++++-- packages/void/src/runtime/remote/index.ts | 3 + .../void/src/runtime/remote/r2-conditions.ts | 72 +++++- packages/void/src/runtime/remote/r2.ts | 206 ++++++++++++---- packages/void/src/runtime/sse-client.ts | 4 + packages/void/src/runtime/ws.ts | 5 + packages/void/test/unit/ai.test.ts | 32 ++- packages/void/test/unit/auth-router.test.ts | 13 ++ packages/void/test/unit/better-auth.test.ts | 98 ++++++++ packages/void/test/unit/cli-help.test.ts | 23 ++ packages/void/test/unit/db-args.test.ts | 4 +- .../void/test/unit/db-execute-remote.test.ts | 42 +++- .../void/test/unit/db-studio-remote.test.ts | 4 +- .../test/unit/email-cli-cloudflare.test.ts | 16 ++ .../void/test/unit/email-cli-domain.test.ts | 11 - .../void/test/unit/email-cloudflare.test.ts | 10 +- .../void/test/unit/email-domain-args.test.ts | 55 +---- packages/void/test/unit/email-send.test.ts | 15 +- packages/void/test/unit/gen.test.ts | 25 ++ packages/void/test/unit/pages-scan.test.ts | 55 ++++- packages/void/test/unit/platform-args.test.ts | 6 +- .../unit/platform-management-upgrade.test.ts | 1 - .../test/unit/platform-management.test.ts | 21 +- .../void/test/unit/platform-teardown.test.ts | 1 - .../test/unit/provision-resources.test.ts | 74 +++--- .../unit/provision-runner-signals.test.ts | 10 +- .../unit/provision-runner-timeouts.test.ts | 2 +- packages/void/test/unit/remote/r2.test.ts | 198 ++++++++++++++++ packages/void/test/unit/wrangler.test.ts | 12 +- platform/packages/api/src/deploy/cache.ts | 5 +- platform/packages/api/src/isr-legacy-keys.ts | 29 +++ platform/packages/api/src/revalidate-core.ts | 14 +- .../api/test/unit/clear-isr-cache.test.ts | 11 +- .../api/test/unit/isr-legacy-keys.test.ts | 25 ++ .../kitchen-sink/email/support+[ticket].ts | 5 +- 90 files changed, 1455 insertions(+), 610 deletions(-) create mode 100644 platform/packages/api/src/isr-legacy-keys.ts create mode 100644 platform/packages/api/test/unit/isr-legacy-keys.test.ts 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, `