From 507262bfbc26341c57fb0c0e4a47407a6763af6e Mon Sep 17 00:00:00 2001 From: Yangshun Date: Fri, 6 Nov 2026 18:46:41 +0800 Subject: [PATCH] feat(pages): check useIslandForm URLs when typedRoutes is on Island form submissions take RouteUrl, so the URL is checked against the app's routes only when routing.typedRoutes is enabled. Co-authored-by: Cursor --- docs/guide/pages-routing/islands.md | 2 +- docs/guide/type-safety.md | 6 +-- docs/reference/api/pages.md | 8 ++-- docs/reference/config.md | 2 +- packages/react/src/runtime/use-island-form.ts | 9 ++-- packages/solid/src/runtime/use-island-form.ts | 9 ++-- .../src/runtime/use-island-form.svelte.ts | 13 +++--- packages/void/src/pages/form-presentation.ts | 10 +++-- .../integration/route-url-builder.test.ts | 41 +++++++++++++++++++ packages/vue/src/runtime/use-island-form.ts | 13 +++--- 10 files changed, 80 insertions(+), 33 deletions(-) diff --git a/docs/guide/pages-routing/islands.md b/docs/guide/pages-routing/islands.md index abb8a166f..eb60f5139 100644 --- a/docs/guide/pages-routing/islands.md +++ b/docs/guide/pages-routing/islands.md @@ -344,7 +344,7 @@ export default function PostForm() { ::: -`useIslandForm` provides the same form state as [`useForm`](./actions-and-forms#useform). Pass the action URL to `post`, `put`, `patch`, or `delete` 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. With [`routing.typedRoutes`](../type-safety.md#checked-url-strings), that URL is a [`RouteUrl`](../../reference/api/types.md#route-types). Cast a path built at runtime with `as RouteUrl`. 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/type-safety.md b/docs/guide/type-safety.md index 561daab29..bb4528d8f 100644 --- a/docs/guide/type-safety.md +++ b/docs/guide/type-safety.md @@ -257,7 +257,7 @@ await revalidate({ paths: ['/', '/blog/hello-world'] satisfies RouteUrl[] }); ### Checked URL strings -[`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: +[`routing.typedRoutes`](../reference/config.md#routing-typedroutes) checks plain URL strings in `Link`'s `href`, `router.visit()`, `router.prefetch()`, `router.flush()`, `c.rewrite()`, and `useIslandForm()`'s `post()`, `put()`, `patch()`, `delete()`, and `submit()`. Apps created with `void init` have it on. To turn it on in an existing app: ```ts // void.config.ts @@ -336,7 +336,7 @@ c.redirect('https://example.com/login'); `void/routes` exports two types for these strings: -- `RouteUrl`: the URL of one of your routes. `routeUrl()` returns it, and `c.rewrite()` accepts it. Annotate a `c.redirect()` path or a prerender or revalidate path list with it. +- `RouteUrl`: the URL of one of your routes. `routeUrl()` returns it, and `c.rewrite()` and `useIslandForm()` accept it. Annotate a `c.redirect()` path or a prerender or revalidate path list with it. - `Href`: anything a link can point to: a `RouteUrl`, a same-page query or hash, or an external URL. Cast a string that TypeScript can't check, such as a path from your CMS or a file in `public/`: @@ -401,7 +401,7 @@ Route types update with the dev server. For CI or standalone typechecking, run ` | Handlers | `c.env` bindings, validator input, return type | | Fetch client | Route paths, HTTP methods, `body`/`query`/`params`, response type | | Navigation | Route patterns and required parameter names and values in `routeUrl()` and destination objects | -| Links | With `routing.typedRoutes`, URL strings in `Link`, router methods, and `c.rewrite()` | +| Links | With `routing.typedRoutes`, URL strings in `Link`, router methods, `c.rewrite()`, and `useIslandForm()` | | `c.redirect` | An internal path built with `routeUrl()`, or a full path annotated with `satisfies RouteUrl` | | `useParams` | Page patterns and the names of the params they return | | `c.req.param` | Page param names, when the result is annotated with `PageParams` | diff --git a/docs/reference/api/pages.md b/docs/reference/api/pages.md index 2d8eb00bb..29da7f21a 100644 --- a/docs/reference/api/pages.md +++ b/docs/reference/api/pages.md @@ -89,7 +89,7 @@ Imported from `"@void/vue"`. | `useParams()` | Returns dynamic route params for the page currently being rendered. Pass the page's pattern as the type argument, such as `useParams<'/users/:id'>()`, to type them. | | `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, setError, clearErrors, clearError }`. | -| `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()`. | +| `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()`. With [`routing.typedRoutes`](../config.md#routing-typedroutes), the URL is a [`RouteUrl`](./types.md#route-types). | | `action(url, options?)` | Awaitable one-shot page action helper. Uses `POST` by default and accepts `{ data, method, params, preserveScroll }`, where `method` can be `"PUT"`, `"PATCH"`, or `"DELETE"`. Returns an `ActionResult`. | | `isVoidActionError(error)` | Returns `true` when `error` is a `VoidActionError` at any status, such as a thrown `401`, `403`, or `500`. Narrows `error` to `VoidActionError`. See [handling thrown errors](../../guide/pages-routing/actions-and-forms.md#validation-errors). | | `isCallSiteActionError(error)` | Returns `true` only for the expected `VoidActionError` statuses that `action()` returns, such as `409` or `422`. | @@ -144,7 +144,7 @@ Imported from `"@void/react"`. | `useParams()` | Returns dynamic route params for the page currently being rendered. Pass the page's pattern as the type argument, such as `useParams<'/users/:id'>()`, to type them. | | `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, setError, clearErrors, clearError }`. | -| `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()`. | +| `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()`. With [`routing.typedRoutes`](../config.md#routing-typedroutes), the URL is a [`RouteUrl`](./types.md#route-types). | | `action(url, options?)` | Awaitable one-shot page action helper. Uses `POST` by default and accepts `{ data, method, params, preserveScroll }`, where `method` can be `"PUT"`, `"PATCH"`, or `"DELETE"`. Returns an `ActionResult`. | | `isVoidActionError(error)` | Returns `true` when `error` is a `VoidActionError` at any status, such as a thrown `401`, `403`, or `500`. Narrows `error` to `VoidActionError`. See [handling thrown errors](../../guide/pages-routing/actions-and-forms.md#validation-errors). | | `isCallSiteActionError(error)` | Returns `true` only for the expected `VoidActionError` statuses that `action()` returns, such as `409` or `422`. | @@ -223,7 +223,7 @@ Imported from `"@void/svelte"`. | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Link` | Svelte component for SPA navigation. Renders `` for GET, `