From 0ebaeb4464a9e3bcc26cfc77ee2f84efcc7d9e16 Mon Sep 17 00:00:00 2001 From: Yangshun Date: Sun, 8 Nov 2026 14:57:33 +0800 Subject: [PATCH] feat(routes): support bracket patterns in typed routing APIs --- docs/guide/pages-routing/actions-and-forms.md | 12 +- docs/guide/pages-routing/head.md | 2 +- docs/guide/pages-routing/overview.md | 18 +-- docs/guide/ssg.md | 2 +- docs/guide/type-safety.md | 61 +++++----- docs/guide/typed-fetch.md | 8 +- docs/guide/websockets.md | 10 +- docs/reference/api/client.md | 8 +- docs/reference/api/handlers.md | 2 +- docs/reference/api/pages.md | 8 +- docs/reference/api/rewrites.md | 6 +- docs/reference/api/types.md | 6 +- packages/void/AGENTS.md | 5 + packages/void/skills/void/SKILL.md | 3 + packages/void/src/codegen/route-types.ts | 61 ++++++++-- packages/void/src/routes-stub.ts | 4 +- packages/void/src/runtime/fetch.ts | 2 +- packages/void/src/runtime/route-url.ts | 18 +-- .../integration/route-url-builder.test.ts | 107 +++++++++++++++++- packages/void/test/unit/action-result.test.ts | 38 ++++--- packages/void/test/unit/fetch-stream.test.ts | 14 ++- packages/void/test/unit/fetch.test.ts | 26 +++-- packages/void/test/unit/route-types.test.ts | 36 ++++-- .../void/test/unit/route-url-builder.test.ts | 15 +++ packages/void/test/unit/route-url.test.ts | 38 +++++++ packages/void/test/unit/ws.test.ts | 16 ++- 26 files changed, 392 insertions(+), 134 deletions(-) diff --git a/docs/guide/pages-routing/actions-and-forms.md b/docs/guide/pages-routing/actions-and-forms.md index 6de8cba6c..adb9619bf 100644 --- a/docs/guide/pages-routing/actions-and-forms.md +++ b/docs/guide/pages-routing/actions-and-forms.md @@ -173,7 +173,7 @@ For dynamic routes, pass `params` in the options: ```ts // pages/users/[id].server.ts has an action -const form = useForm('/users/:id', { name: '' }, { params: { id: '42' } }); +const form = useForm('/users/[id]', { name: '' }, { params: { id: '42' } });
{/* submits to /users/42 */}
``` @@ -209,7 +209,7 @@ Actions and forms keep the current scroll position when updating the page in pla Pass `preserveScroll: false` to reset scroll after an in-place update, or `true` to keep it even after a redirect: ```ts -await action('/users/:id?update', { +await action('/users/[id]?update', { params: { id: '42' }, data: { name: 'Alice' }, preserveScroll: false, @@ -233,7 +233,7 @@ export const actions = { update: defineHandler.withValidator({ body: updateUserSchema, })(async (c, { body }) => { - const { id } = c.req.param() as PageParams<'/users/:id'>; + const { id } = c.req.param() as PageParams<'/users/[id]'>; await db .update(users) .set(body) @@ -241,7 +241,7 @@ export const actions = { }), delete: defineHandler(async (c) => { - const { id } = c.req.param() as PageParams<'/users/:id'>; + const { id } = c.req.param() as PageParams<'/users/[id]'>; await db.delete(users).where(eq(users.id, Number(id))); }), }; @@ -269,7 +269,7 @@ export const actions = { When a page exports named actions, append `?actionName` to the URL: ```ts -const form = useForm('/users/:id?update', { name: '' }, { params: { id } }); +const form = useForm('/users/[id]?update', { name: '' }, { params: { id } });
{/* submits to /users/42?update */}
``` @@ -282,7 +282,7 @@ Use `action()` for a button or other mutation that does not need form state. It ```ts import { action } from '@void/react'; // or "@void/vue", "@void/svelte", "@void/solid" -const result = await action('/users/:id?delete', { +const result = await action('/users/[id]?delete', { params: { id: '42' }, method: 'DELETE', }); diff --git a/docs/guide/pages-routing/head.md b/docs/guide/pages-routing/head.md index 53119bd81..e2c174a48 100644 --- a/docs/guide/pages-routing/head.md +++ b/docs/guide/pages-routing/head.md @@ -20,7 +20,7 @@ export interface Props { } export const loader = defineHandler(async (c) => { - const { slug } = c.req.param() as PageParams<'/posts/:slug'>; + const { slug } = c.req.param() as PageParams<'/posts/[slug]'>; const post = await getPost(slug); return { post }; }); diff --git a/docs/guide/pages-routing/overview.md b/docs/guide/pages-routing/overview.md index d660037ca..17bca5553 100644 --- a/docs/guide/pages-routing/overview.md +++ b/docs/guide/pages-routing/overview.md @@ -164,7 +164,7 @@ import { Link } from "@void/react"; import { routeUrl } from 'void/routes'; Users -View +View ``` ```vue [Vue] @@ -175,7 +175,7 @@ import { routeUrl } from 'void/routes'; ``` @@ -186,7 +186,7 @@ import { routeUrl } from 'void/routes'; Users -View +View ``` ```tsx [Solid] @@ -194,7 +194,7 @@ import { Link } from "@void/solid"; import { routeUrl } from 'void/routes'; Users -View +View ``` ::: @@ -313,7 +313,7 @@ import type { RouteDestination } from 'void/routes'; const router = useRouter(); const profile = { - pathname: '/users/:id', + pathname: '/users/[id]', params: { id: 42 }, query: { tab: 'activity' }, } satisfies RouteDestination; @@ -338,7 +338,7 @@ For dynamic route params, use `useParams()` from the same adapter package. Pass import { useParams } from '@void/react'; export default function PostPage() { - const { id } = useParams<'/posts/:id'>(); // { id: string } + const { id } = useParams<'/posts/[id]'>(); // { id: string } return

Post {id}

; } ``` @@ -348,7 +348,7 @@ export default function PostPage() {