@rangojs/router 0.16.0 → 0.17.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.
- package/README.md +254 -89
- package/dist/testing/vitest.js +252 -0
- package/dist/types/browser/navigation-transaction.d.ts +6 -0
- package/dist/types/browser/partial-update.d.ts +11 -0
- package/dist/types/browser/react/ScrollRestoration.d.ts +6 -6
- package/dist/types/browser/rsc-router.d.ts +1 -1
- package/dist/types/browser/scroll-restoration.d.ts +7 -0
- package/dist/types/cache/cache-exec-scope.d.ts +18 -1
- package/dist/types/cache/cache-scope.d.ts +34 -5
- package/dist/types/cache/handle-capture.d.ts +41 -3
- package/dist/types/cache/handle-snapshot.d.ts +53 -13
- package/dist/types/cache/segment-codec.d.ts +10 -3
- package/dist/types/cache/taint.d.ts +1 -1
- package/dist/types/cache/types.d.ts +15 -3
- package/dist/types/client.d.ts +4 -4
- package/dist/types/client.rsc.d.ts +4 -5
- package/dist/types/components/DefaultDocument.d.ts +5 -2
- package/dist/types/context-var.d.ts +6 -5
- package/dist/types/escape-script.d.ts +1 -1
- package/dist/types/handles/MetaTags.d.ts +5 -4
- package/dist/types/handles/Scripts.d.ts +8 -7
- package/dist/types/handles/meta.d.ts +2 -2
- package/dist/types/handles/script.d.ts +17 -16
- package/dist/types/href-client.d.ts +1 -1
- package/dist/types/html.d.ts +12 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.rsc.d.ts +3 -2
- package/dist/types/loader-store.d.ts +7 -4
- package/dist/types/reverse.d.ts +36 -4
- package/dist/types/route-definition/dsl-helpers.d.ts +3 -3
- package/dist/types/route-definition/helpers-types.d.ts +33 -9
- package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
- package/dist/types/route-types.d.ts +1 -1
- package/dist/types/router/debug-manifest.d.ts +3 -2
- package/dist/types/router/error-handling.d.ts +20 -0
- package/dist/types/router/handler-context.d.ts +6 -0
- package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
- package/dist/types/router/middleware-types.d.ts +3 -2
- package/dist/types/router/router-context.d.ts +3 -1
- package/dist/types/router/router-interfaces.d.ts +24 -2
- package/dist/types/router/router-options.d.ts +8 -5
- package/dist/types/router/segment-resolution/fresh.d.ts +10 -0
- package/dist/types/router/segment-resolution/loader-cache.d.ts +37 -1
- package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +3 -3
- package/dist/types/router/segment-resolution/mask-nested.d.ts +7 -7
- package/dist/types/router/segment-resolution/revalidation.d.ts +6 -2
- package/dist/types/router/segment-wrappers.d.ts +1 -1
- package/dist/types/rsc/response-route-handler.d.ts +1 -1
- package/dist/types/rsc/rsc-rendering.d.ts +1 -2
- package/dist/types/rsc/shell-capture-constants.d.ts +8 -0
- package/dist/types/rsc/shell-capture.d.ts +5 -4
- package/dist/types/rsc/ssr-setup.d.ts +6 -6
- package/dist/types/rsc/types.d.ts +10 -0
- package/dist/types/server/context.d.ts +33 -3
- package/dist/types/server/handle-store.d.ts +26 -2
- package/dist/types/server/request-context.d.ts +24 -21
- package/dist/types/ssr/index.d.ts +15 -0
- package/dist/types/static-handler.d.ts +4 -1
- package/dist/types/testing/generated-routes.d.ts +2 -2
- package/dist/types/testing/internal/context.d.ts +1 -1
- package/dist/types/testing/render-route.d.ts +32 -6
- package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +28 -5
- package/dist/types/testing/vitest.d.ts +35 -1
- package/dist/types/theme/ThemeScript.d.ts +3 -3
- package/dist/types/types/cache-types.d.ts +14 -10
- package/dist/types/types/handler-context.d.ts +14 -0
- package/dist/types/types/loader-types.d.ts +8 -6
- package/dist/types/urls/response-types.d.ts +3 -12
- package/dist/types/use-loader.d.ts +12 -3
- package/dist/types/vite/plugins/use-cache-transform.d.ts +33 -2
- package/dist/types/vite/rango.d.ts +1 -1
- package/dist/vite/index.js +1381 -1346
- package/package.json +2 -2
- package/skills/api-client/SKILL.md +22 -10
- package/skills/breadcrumbs/SKILL.md +65 -22
- package/skills/bundle-analysis/SKILL.md +22 -22
- package/skills/cache-guide/SKILL.md +207 -97
- package/skills/caching/SKILL.md +295 -93
- package/skills/catalog.json +16 -16
- package/skills/client-urls/SKILL.md +53 -31
- package/skills/cloudflare/SKILL.md +72 -20
- package/skills/cloudflare/references/d1-and-local-dev.md +15 -2
- package/skills/cloudflare/references/streaming-and-deploy.md +10 -3
- package/skills/cloudflare/references/webhooks-and-crypto.md +9 -2
- package/skills/comparison/SKILL.md +5 -0
- package/skills/comparison/references/framework-comparison.md +39 -33
- package/skills/composability/SKILL.md +38 -29
- package/skills/css/SKILL.md +10 -13
- package/skills/debug-manifest/SKILL.md +74 -31
- package/skills/defer-hydration/SKILL.md +2 -1
- package/skills/deployment-caching/SKILL.md +6 -1
- package/skills/document-cache/SKILL.md +92 -28
- package/skills/fonts/SKILL.md +16 -8
- package/skills/handler-use/SKILL.md +53 -17
- package/skills/hooks/SKILL.md +28 -20
- package/skills/hooks/data.md +23 -3
- package/skills/hooks/handle-and-actions.md +31 -16
- package/skills/hooks/navigation.md +39 -10
- package/skills/hooks/outlets.md +13 -6
- package/skills/hooks/state.md +37 -15
- package/skills/hooks/urls.md +7 -5
- package/skills/host-router/SKILL.md +64 -12
- package/skills/i18n/SKILL.md +60 -34
- package/skills/intercept/SKILL.md +161 -26
- package/skills/layout/SKILL.md +106 -50
- package/skills/links/SKILL.md +51 -19
- package/skills/loader/SKILL.md +242 -106
- package/skills/middleware/SKILL.md +119 -23
- package/skills/migrate-nextjs/SKILL.md +100 -45
- package/skills/migrate-nextjs/backend-host-swap.md +5 -2
- package/skills/migrate-react-router/SKILL.md +49 -38
- package/skills/migrate-react-router/component-migration.md +25 -6
- package/skills/migrate-react-router/data-and-actions.md +18 -5
- package/skills/migrate-react-router/route-mapping.md +12 -6
- package/skills/mime-routes/SKILL.md +12 -7
- package/skills/observability/SKILL.md +28 -14
- package/skills/parallel/SKILL.md +75 -39
- package/skills/ppr/SKILL.md +345 -150
- package/skills/prerender/SKILL.md +189 -100
- package/skills/rango/SKILL.md +65 -34
- package/skills/react-compiler/SKILL.md +6 -5
- package/skills/response-routes/SKILL.md +133 -25
- package/skills/route/SKILL.md +165 -60
- package/skills/router-setup/SKILL.md +418 -240
- package/skills/scripts/SKILL.md +31 -20
- package/skills/server-actions/SKILL.md +220 -59
- package/skills/shell-manifest/SKILL.md +12 -7
- package/skills/streams-and-websockets/SKILL.md +32 -14
- package/skills/tailwind/SKILL.md +37 -33
- package/skills/testing/SKILL.md +68 -32
- package/skills/testing/cache-prerender.md +55 -29
- package/skills/testing/client-components.md +59 -6
- package/skills/testing/e2e-parity.md +12 -2
- package/skills/testing/flight.md +19 -11
- package/skills/testing/handles.md +8 -5
- package/skills/testing/loader.md +8 -5
- package/skills/testing/middleware.md +8 -3
- package/skills/testing/predicates.md +120 -0
- package/skills/testing/render-handler.md +70 -22
- package/skills/testing/response-routes.md +11 -4
- package/skills/testing/reverse-and-types.md +20 -14
- package/skills/testing/server-actions.md +12 -3
- package/skills/testing/server-tree.md +9 -0
- package/skills/testing/setup.md +9 -5
- package/skills/theme/SKILL.md +106 -29
- package/skills/typesafety/SKILL.md +20 -9
- package/skills/typesafety/env-and-bindings.md +126 -47
- package/skills/typesafety/generated-files-and-cli.md +26 -14
- package/skills/typesafety/params-and-search.md +44 -19
- package/skills/typesafety/route-types.md +50 -14
- package/skills/use-cache/SKILL.md +200 -82
- package/skills/vercel/SKILL.md +21 -11
- package/skills/view-transitions/SKILL.md +41 -20
- package/src/__augment-tests__/augmented.check.ts +26 -1
- package/src/browser/index.ts +2 -2
- package/src/browser/navigation-bridge.ts +3 -4
- package/src/browser/navigation-transaction.ts +22 -2
- package/src/browser/partial-update.ts +35 -25
- package/src/browser/react/ScrollRestoration.tsx +6 -6
- package/src/browser/rsc-router.tsx +1 -1
- package/src/browser/scroll-restoration.ts +53 -6
- package/src/cache/cache-exec-scope.ts +37 -4
- package/src/cache/cache-key-utils.ts +1 -6
- package/src/cache/cache-runtime.ts +230 -61
- package/src/cache/cache-scope.ts +76 -12
- package/src/cache/document-cache.ts +15 -0
- package/src/cache/handle-capture.ts +90 -10
- package/src/cache/handle-snapshot.ts +132 -28
- package/src/cache/segment-codec.ts +26 -7
- package/src/cache/tag-invalidation.ts +4 -3
- package/src/cache/taint.ts +1 -1
- package/src/cache/types.ts +16 -3
- package/src/client.rsc.tsx +10 -9
- package/src/client.tsx +5 -4
- package/src/components/DefaultDocument.tsx +7 -2
- package/src/context-var.ts +6 -5
- package/src/escape-script.ts +1 -1
- package/src/handles/MetaTags.tsx +13 -11
- package/src/handles/Scripts.tsx +11 -10
- package/src/handles/meta.ts +2 -2
- package/src/handles/script.ts +17 -16
- package/src/href-client.ts +1 -1
- package/src/html.ts +12 -0
- package/src/index.rsc.ts +5 -0
- package/src/index.ts +1 -0
- package/src/loader-store.ts +7 -4
- package/src/prerender/build-shell-capture.ts +8 -11
- package/src/reverse.ts +44 -4
- package/src/route-definition/dsl-helpers.ts +117 -30
- package/src/route-definition/helpers-types.ts +33 -9
- package/src/route-definition/resolve-handler-use.ts +121 -6
- package/src/route-types.ts +0 -3
- package/src/router/debug-manifest.ts +10 -2
- package/src/router/error-handling.ts +51 -15
- package/src/router/handler-context.ts +34 -25
- package/src/router/intercept-resolution.ts +125 -39
- package/src/router/loader-resolution.ts +56 -11
- package/src/router/match-api.ts +12 -17
- package/src/router/match-middleware/background-revalidation.ts +161 -96
- package/src/router/match-middleware/cache-lookup.ts +93 -10
- package/src/router/match-middleware/cache-store.ts +17 -90
- package/src/router/middleware-types.ts +3 -5
- package/src/router/middleware.ts +52 -9
- package/src/router/prerender-match.ts +53 -19
- package/src/router/route-snapshot.ts +11 -1
- package/src/router/router-context.ts +2 -0
- package/src/router/router-interfaces.ts +25 -2
- package/src/router/router-options.ts +8 -5
- package/src/router/segment-resolution/fresh.ts +44 -12
- package/src/router/segment-resolution/loader-cache.ts +205 -45
- package/src/router/segment-resolution/loader-mask.ts +12 -22
- package/src/router/segment-resolution/loader-snapshot.ts +6 -6
- package/src/router/segment-resolution/mask-nested.ts +7 -7
- package/src/router/segment-resolution/revalidation.ts +34 -6
- package/src/router/segment-wrappers.ts +4 -0
- package/src/rsc/render-pipeline.ts +3 -0
- package/src/rsc/response-route-handler.ts +11 -3
- package/src/rsc/rsc-rendering.ts +8 -45
- package/src/rsc/shell-capture-constants.ts +13 -0
- package/src/rsc/shell-capture.ts +111 -90
- package/src/rsc/ssr-setup.ts +14 -8
- package/src/rsc/types.ts +11 -0
- package/src/server/context.ts +88 -11
- package/src/server/cookie-store.ts +5 -4
- package/src/server/handle-store.ts +232 -7
- package/src/server/request-context.ts +35 -30
- package/src/ssr/index.tsx +40 -7
- package/src/static-handler.ts +4 -1
- package/src/testing/flight-runtime.d.ts +21 -0
- package/src/testing/generated-routes.ts +2 -2
- package/src/testing/internal/context.ts +1 -1
- package/src/testing/internal/flight-client-globals.ts +7 -5
- package/src/testing/render-route.tsx +85 -24
- package/src/testing/run-loader.ts +5 -1
- package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
- package/src/testing/vitest.ts +62 -1
- package/src/theme/ThemeScript.tsx +3 -3
- package/src/types/cache-types.ts +14 -10
- package/src/types/handler-context.ts +14 -0
- package/src/types/loader-types.ts +8 -6
- package/src/urls/response-types.ts +3 -23
- package/src/use-loader.tsx +12 -3
- package/src/vite/plugins/use-cache-transform.ts +118 -74
- package/src/vite/rango.ts +7 -3
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: typesafety
|
|
3
|
-
description: Set up type-safe
|
|
3
|
+
description: Set up type-safe route names, params, search params, response payloads, and environment types in @rangojs/router. Use when route or search params aren't typed, ctx.env or ctx.get() is untyped, TypeScript can't infer a loader's return type, or wiring RegisteredRoutes / the generated route map.
|
|
4
4
|
argument-hint: [setup]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Type Safety Setup
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
environment
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
This skill covers how route names, params, search params, response payloads,
|
|
10
|
+
environment bindings, and context variables get their types. Use it when
|
|
11
|
+
setting up a new app's type surfaces or when one of them resolves to
|
|
12
|
+
`any`/`unknown`/`never` unexpectedly.
|
|
13
|
+
|
|
14
|
+
What you lose without the setup:
|
|
15
|
+
|
|
16
|
+
- `ctx.reverse()` accepts any name and `href()` any `/${string}` path, so a
|
|
17
|
+
typo 404s at runtime instead of failing to compile.
|
|
18
|
+
- `Handler<"name">` is rejected (there is no generated map to look the name
|
|
19
|
+
up in), so `ctx.params`/`ctx.search` can't be inferred from the route.
|
|
20
|
+
- `ctx.env` is `unknown` until `Rango.Env` is registered (every binding access
|
|
21
|
+
is a compile error), and string-key `ctx.get("key")` returns `any` until
|
|
22
|
+
`Rango.Vars` is registered (a typo'd key is silently accepted).
|
|
23
|
+
- `Rango.PathResponse` resolves to `never` until `Rango.RegisteredRoutes` is
|
|
24
|
+
registered.
|
|
15
25
|
|
|
16
26
|
Each topic's full setup, code, and caveats live in a companion file linked
|
|
17
27
|
below. Read the one for your case.
|
|
@@ -48,5 +58,6 @@ below. Read the one for your case.
|
|
|
48
58
|
scoped context tokens, handle typing, passing loaders/handles as typed
|
|
49
59
|
props, and location state typing.
|
|
50
60
|
|
|
51
|
-
|
|
52
|
-
`useReverse`)
|
|
61
|
+
Related skills: `/links` (URL generation: `ctx.reverse`, `href`,
|
|
62
|
+
`useReverse`, per-module `*.gen.ts`), `/response-routes` (response payload
|
|
63
|
+
typing), `/api-client` (typed fetch client over `RouteResponse`).
|
|
@@ -27,6 +27,7 @@ export interface AppVariables {
|
|
|
27
27
|
|
|
28
28
|
```typescript
|
|
29
29
|
// router.tsx
|
|
30
|
+
import { createRouter } from "@rangojs/router";
|
|
30
31
|
import type { AppBindings, AppVariables } from "./env";
|
|
31
32
|
|
|
32
33
|
const router = createRouter<AppBindings>({
|
|
@@ -40,7 +41,9 @@ declare global {
|
|
|
40
41
|
interface Vars extends AppVariables {}
|
|
41
42
|
}
|
|
42
43
|
}
|
|
44
|
+
```
|
|
43
45
|
|
|
46
|
+
```typescript
|
|
44
47
|
// middleware - typed via ctx.set / ctx.get
|
|
45
48
|
import type { Middleware } from "@rangojs/router";
|
|
46
49
|
|
|
@@ -52,38 +55,35 @@ export const authMiddleware: Middleware = async (ctx, next) => {
|
|
|
52
55
|
});
|
|
53
56
|
await next();
|
|
54
57
|
};
|
|
55
|
-
|
|
56
|
-
// loaders - typed context
|
|
57
|
-
export const UserLoader = createLoader(async (ctx) => {
|
|
58
|
-
const db = ctx.env.DB; // D1Database (plain bindings)
|
|
59
|
-
const userId = ctx.get("user")?.id; // from Rango.Vars
|
|
60
|
-
return db.prepare("SELECT * FROM users WHERE id = ?").bind(userId).first();
|
|
61
|
-
});
|
|
62
58
|
```
|
|
63
59
|
|
|
64
60
|
## Global Environment Registration
|
|
65
61
|
|
|
66
|
-
|
|
62
|
+
The `declare global` block above is what types the contexts. `createRouter<TEnv>()`
|
|
63
|
+
types the router instance, but `urls()` modules, `Handler`, middleware, and
|
|
64
|
+
response routes read `Rango.Env` / `Rango.Vars` — they cannot see the router's
|
|
65
|
+
generic. Put the block in a file that is part of every TypeScript program that
|
|
66
|
+
typechecks handlers (usually `router.tsx`, kept in via tsconfig `files`).
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
// router.tsx
|
|
70
|
-
declare global {
|
|
71
|
-
namespace Rango {
|
|
72
|
-
interface Env extends AppBindings {}
|
|
73
|
-
interface Vars extends AppVariables {}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
```
|
|
68
|
+
What each context sees:
|
|
77
69
|
|
|
78
|
-
|
|
70
|
+
| Context | `ctx.env` | `ctx.get("key")` |
|
|
71
|
+
| ------------------------------------- | ----------------------------------------- | -------------------------------------- |
|
|
72
|
+
| Handlers, middleware, response routes | `Rango.Env` (`unknown` if not registered) | `Rango.Vars` (`any` if not registered) |
|
|
73
|
+
| Loaders (`createLoader`) | `any` (not typed from `Rango.Env`) | `Rango.Vars` (`any` if not registered) |
|
|
74
|
+
|
|
75
|
+
An unregistered `Rango.Env` is deliberately `unknown`, so `ctx.env.DB` is a
|
|
76
|
+
compile error rather than a silent `any`. An unregistered `Rango.Vars` falls
|
|
77
|
+
back to `any` so string-key vars work with zero config.
|
|
79
78
|
|
|
80
79
|
```typescript
|
|
81
80
|
// In loaders
|
|
81
|
+
import { createLoader } from "@rangojs/router";
|
|
82
|
+
|
|
82
83
|
export const DashboardLoader = createLoader(async (ctx) => {
|
|
83
|
-
|
|
84
|
-
// ctx.
|
|
85
|
-
|
|
86
|
-
return { user };
|
|
84
|
+
const user = ctx.get("user"); // typed from global Rango.Vars
|
|
85
|
+
const db = ctx.env.DB as D1Database; // ctx.env is `any` in loaders
|
|
86
|
+
return { user, count: await countOrders(db, user?.id) };
|
|
87
87
|
});
|
|
88
88
|
```
|
|
89
89
|
|
|
@@ -104,7 +104,10 @@ interface PaginationData {
|
|
|
104
104
|
export const Pagination = createVar<PaginationData>();
|
|
105
105
|
|
|
106
106
|
// Non-cacheable var — reading inside cache() or "use cache" throws at runtime
|
|
107
|
-
|
|
107
|
+
interface SessionData {
|
|
108
|
+
userId: string;
|
|
109
|
+
}
|
|
110
|
+
export const Session = createVar<SessionData>({ cache: false });
|
|
108
111
|
```
|
|
109
112
|
|
|
110
113
|
`createVar` accepts an optional options object. The `cache` option (default
|
|
@@ -114,9 +117,16 @@ marks a specific write as non-cacheable even if the var itself is cacheable.
|
|
|
114
117
|
"Least cacheable wins" — if either says `cache: false`, the value throws on
|
|
115
118
|
read inside `cache()` or `"use cache"`.
|
|
116
119
|
|
|
117
|
-
### Producer (handler or middleware)
|
|
120
|
+
### Producer (handler, layout, or middleware)
|
|
121
|
+
|
|
122
|
+
Write with `ctx.set()` from middleware, route handlers, or layout handlers;
|
|
123
|
+
read with `ctx.get()` in the segments they wrap. A route handler runs before
|
|
124
|
+
the layouts and parallels declared in its own use callback (handler-first), so
|
|
125
|
+
those see the value; an outer layout wrapping the route renders first and
|
|
126
|
+
does not. See `/route` for the full ordering.
|
|
118
127
|
|
|
119
128
|
```typescript
|
|
129
|
+
import type { Handler } from "@rangojs/router";
|
|
120
130
|
import { Pagination } from "../vars/pagination.js";
|
|
121
131
|
|
|
122
132
|
const ArticleList: Handler<"articles.list"> = async (ctx) => {
|
|
@@ -132,15 +142,30 @@ const ArticleList: Handler<"articles.list"> = async (ctx) => {
|
|
|
132
142
|
### Consumer (layout, parallel, or any context with get)
|
|
133
143
|
|
|
134
144
|
```typescript
|
|
145
|
+
import type { Handler } from "@rangojs/router";
|
|
146
|
+
import { Outlet } from "@rangojs/router/client";
|
|
135
147
|
import { Pagination } from "../vars/pagination.js";
|
|
136
148
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
149
|
+
// Mounted inside the producer's use callback:
|
|
150
|
+
// path("/articles", ArticleList, { name: "articles.list" }, () => [layout(PaginationLayout)])
|
|
151
|
+
export const PaginationLayout: Handler = (ctx) => {
|
|
152
|
+
const pagination = ctx.get(Pagination); // typed as PaginationData | undefined
|
|
153
|
+
return (
|
|
154
|
+
<>
|
|
155
|
+
<Outlet />
|
|
156
|
+
{pagination && (
|
|
157
|
+
<nav>
|
|
158
|
+
Page {pagination.current} of {pagination.total}
|
|
159
|
+
</nav>
|
|
160
|
+
)}
|
|
161
|
+
</>
|
|
162
|
+
);
|
|
163
|
+
};
|
|
142
164
|
```
|
|
143
165
|
|
|
166
|
+
Type the layout (`Handler`) rather than annotating `ctx: any`: with `any`,
|
|
167
|
+
`ctx.get(Pagination)` is `any` too and the token contract is lost.
|
|
168
|
+
|
|
144
169
|
### Why not just use Rango.Vars?
|
|
145
170
|
|
|
146
171
|
`Rango.Vars` (via global namespace augmentation) provides app-global typing for
|
|
@@ -154,7 +179,9 @@ Both approaches coexist: `ctx.get("user")` (global via Vars) and
|
|
|
154
179
|
|
|
155
180
|
## Handle Type Safety
|
|
156
181
|
|
|
157
|
-
|
|
182
|
+
A handle is typed by two parameters: `Handle<TData, TAccumulated>`. `TData` is
|
|
183
|
+
what each segment pushes; `TAccumulated` is what readers get after the collect
|
|
184
|
+
function runs (default `TData[][]`, one array per segment).
|
|
158
185
|
|
|
159
186
|
```typescript
|
|
160
187
|
// Built-in Breadcrumbs handle — import from "@rangojs/router"
|
|
@@ -168,18 +195,33 @@ path("/shop/product/:slug", (ctx) => {
|
|
|
168
195
|
breadcrumb({ label: "Products", href: "/shop/products" });
|
|
169
196
|
return <ProductPage />;
|
|
170
197
|
}, { name: "product" });
|
|
198
|
+
```
|
|
171
199
|
|
|
200
|
+
```tsx
|
|
172
201
|
// In client — typed array
|
|
202
|
+
"use client";
|
|
173
203
|
import { useHandle, Breadcrumbs } from "@rangojs/router/client";
|
|
204
|
+
|
|
174
205
|
function BreadcrumbNav() {
|
|
175
|
-
const crumbs = useHandle(Breadcrumbs);
|
|
176
|
-
|
|
206
|
+
const crumbs = useHandle(Breadcrumbs); // crumbs: BreadcrumbItem[]
|
|
207
|
+
return (
|
|
208
|
+
<nav>
|
|
209
|
+
{crumbs.map((c) => (
|
|
210
|
+
<a key={c.href} href={c.href}>
|
|
211
|
+
{c.label}
|
|
212
|
+
</a>
|
|
213
|
+
))}
|
|
214
|
+
</nav>
|
|
215
|
+
);
|
|
177
216
|
}
|
|
217
|
+
```
|
|
178
218
|
|
|
179
|
-
|
|
219
|
+
```typescript
|
|
220
|
+
// Custom handle: give TAccumulated whenever collect changes the shape
|
|
180
221
|
import { createHandle } from "@rangojs/router";
|
|
222
|
+
|
|
181
223
|
export const PageTitle = createHandle<string, string>(
|
|
182
|
-
(segments) => segments.flat().at(-1) ?? "Default Title"
|
|
224
|
+
(segments) => segments.flat().at(-1) ?? "Default Title",
|
|
183
225
|
);
|
|
184
226
|
```
|
|
185
227
|
|
|
@@ -190,11 +232,16 @@ Use `typeof` to get the full typed definition without manually specifying generi
|
|
|
190
232
|
|
|
191
233
|
```typescript
|
|
192
234
|
// loaders.ts
|
|
235
|
+
import { createLoader } from "@rangojs/router";
|
|
236
|
+
|
|
193
237
|
export const ProductLoader = createLoader(async (ctx) => {
|
|
194
238
|
return { product: await fetchProduct(ctx.params.slug) };
|
|
195
239
|
});
|
|
196
240
|
|
|
197
|
-
//
|
|
241
|
+
// Server: pass the definitions as props
|
|
242
|
+
path("/product/:slug", () => (
|
|
243
|
+
<MyComponent loader={ProductLoader} handle={Breadcrumbs} />
|
|
244
|
+
), { name: "product" }, () => [loader(ProductLoader)]);
|
|
198
245
|
```
|
|
199
246
|
|
|
200
247
|
```tsx
|
|
@@ -208,20 +255,27 @@ function MyComponent({
|
|
|
208
255
|
handle,
|
|
209
256
|
}: {
|
|
210
257
|
loader: typeof ProductLoader; // LoaderDefinition<{ product: Product }>
|
|
211
|
-
handle: typeof Breadcrumbs; // Handle<
|
|
258
|
+
handle: typeof Breadcrumbs; // Handle<BreadcrumbItem, BreadcrumbItem[]>
|
|
212
259
|
}) {
|
|
213
260
|
const { data } = useLoader(loader); // data is typed
|
|
214
|
-
const crumbs = useHandle(handle); // crumbs
|
|
261
|
+
const crumbs = useHandle(handle); // crumbs: BreadcrumbItem[]
|
|
215
262
|
// ...
|
|
216
263
|
}
|
|
217
264
|
```
|
|
218
265
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
266
|
+
Loader and handle definitions are plain `{ __brand, $$id }` objects — the
|
|
267
|
+
loader function and the handle's collect function live in module-level
|
|
268
|
+
registries keyed by `$$id` — so they cross the RSC boundary as-is and the
|
|
269
|
+
hooks look the rest up by id. `useLoader()` still needs the loader registered
|
|
270
|
+
on the route with `loader()` (or use `useFetchLoader()` for on-demand fetching).
|
|
222
271
|
|
|
223
272
|
## Location State Type Safety
|
|
224
273
|
|
|
274
|
+
`createLocationState<T>()` returns a typed definition; the Vite plugin injects
|
|
275
|
+
its key, so it must be exported from a module. Values are stored in
|
|
276
|
+
`history.state`, so `T` must be structured-cloneable: functions, symbols,
|
|
277
|
+
class constructors, React elements, and `unknown` are compile errors.
|
|
278
|
+
|
|
225
279
|
```typescript
|
|
226
280
|
// location-states.ts
|
|
227
281
|
import { createLocationState } from "@rangojs/router";
|
|
@@ -233,13 +287,28 @@ export const ProductPreview = createLocationState<{
|
|
|
233
287
|
image: string;
|
|
234
288
|
}>();
|
|
235
289
|
|
|
290
|
+
// Flash state: cleared after the first read
|
|
291
|
+
export const FlashMessage = createLocationState<{ text: string }>({
|
|
292
|
+
flash: true,
|
|
293
|
+
});
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
```tsx
|
|
297
|
+
"use client";
|
|
298
|
+
import { Link, href, useLocationState } from "@rangojs/router/client";
|
|
299
|
+
import { ProductPreview } from "./location-states";
|
|
300
|
+
|
|
236
301
|
// Passing state through Link
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
302
|
+
function ProductCard() {
|
|
303
|
+
return (
|
|
304
|
+
<Link
|
|
305
|
+
to={href("/product/widget")}
|
|
306
|
+
state={[ProductPreview({ name: "Widget", price: 99, image: "/img.jpg" })]}
|
|
307
|
+
>
|
|
308
|
+
View Product
|
|
309
|
+
</Link>
|
|
310
|
+
);
|
|
311
|
+
}
|
|
243
312
|
|
|
244
313
|
// Reading state in component
|
|
245
314
|
function ProductHeader() {
|
|
@@ -247,8 +316,18 @@ function ProductHeader() {
|
|
|
247
316
|
// preview: { name: string; price: number; image: string } | undefined
|
|
248
317
|
|
|
249
318
|
if (preview) {
|
|
250
|
-
return
|
|
319
|
+
return (
|
|
320
|
+
<h1>
|
|
321
|
+
{preview.name} - ${preview.price}
|
|
322
|
+
</h1>
|
|
323
|
+
);
|
|
251
324
|
}
|
|
252
325
|
return <h1>Loading...</h1>;
|
|
253
326
|
}
|
|
254
327
|
```
|
|
328
|
+
|
|
329
|
+
Other entry points: pass a getter (`ProductPreview(() => ({ ... }))`) to compute
|
|
330
|
+
the value at click time; `ctx.setLocationState(entry)` attaches state from a
|
|
331
|
+
handler; `redirect(url, { state })` carries it through a redirect; and
|
|
332
|
+
`ProductPreview.read()` / `.write(value)` / `.delete()` access the current
|
|
333
|
+
history entry outside React (client-only, non-reactive).
|
|
@@ -5,23 +5,23 @@
|
|
|
5
5
|
```typescript
|
|
6
6
|
// router.tsx
|
|
7
7
|
import { createRouter } from "@rangojs/router";
|
|
8
|
+
import { Document } from "./document";
|
|
8
9
|
import { urlpatterns } from "./urls";
|
|
10
|
+
import type { AppBindings } from "./env";
|
|
9
11
|
|
|
10
|
-
const router = createRouter<AppBindings>({
|
|
12
|
+
export const router = createRouter<AppBindings>({
|
|
11
13
|
document: Document,
|
|
12
14
|
}).routes(urlpatterns);
|
|
13
15
|
|
|
14
16
|
// Server-side named-route reverse (type-safe via routeMap)
|
|
15
17
|
export const reverse = router.reverse;
|
|
16
|
-
|
|
17
|
-
export default router;
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
### Which global type should I use?
|
|
21
21
|
|
|
22
22
|
Use the generated route map by default. Manual `RegisteredRoutes` augmentation
|
|
23
23
|
is only needed when you want the richer `typeof router.routeMap` shape
|
|
24
|
-
available globally.
|
|
24
|
+
available globally — in practice, for `Rango.PathResponse`.
|
|
25
25
|
|
|
26
26
|
- `GeneratedRouteMap` — auto-registered by `router.named-routes.gen.ts`
|
|
27
27
|
Use for `Handler<"name">` (type annotation), `Prerender<"name">(...)` (function
|
|
@@ -95,19 +95,27 @@ instance in the same TypeScript program:
|
|
|
95
95
|
}
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
The Vite plugin writes `src/router.named-routes.gen.ts` (next to the router
|
|
99
|
+
file) automatically: before the dev server starts, again when route files
|
|
100
|
+
change, and during build. To produce it without starting Vite (a fresh clone,
|
|
101
|
+
a CI typecheck step), run the CLI against the router file:
|
|
99
102
|
|
|
100
103
|
```bash
|
|
101
104
|
npx rango generate src/router.tsx
|
|
102
105
|
```
|
|
103
106
|
|
|
104
|
-
|
|
105
|
-
`Rango.GeneratedRouteMap`. Keep that generated file committed with the router
|
|
107
|
+
The file augments `Rango.GeneratedRouteMap`. Keep it committed with the router
|
|
106
108
|
source. The `files` entry keeps `router.tsx` in the program even when nothing
|
|
107
109
|
imports it directly, so `Rango.Env`, `Rango.Vars`, and optional
|
|
108
110
|
`Rango.RegisteredRoutes` augmentation are visible to handlers, loaders, actions,
|
|
109
111
|
and client helpers.
|
|
110
112
|
|
|
113
|
+
CLI flags: the default mode fails (and writes nothing) when an `include()` cannot
|
|
114
|
+
be resolved statically (factory functions, dynamic expressions); `--static`
|
|
115
|
+
accepts partial output with warnings; `--runtime` uses Vite-based discovery for
|
|
116
|
+
full coverage (needs `vite` and `@vitejs/plugin-rsc`, optional
|
|
117
|
+
`--config <path>`). Passing a directory scans it for router and `urls()` files.
|
|
118
|
+
|
|
111
119
|
### Named Routes, `$$routeNames`, And `router.routeMap`
|
|
112
120
|
|
|
113
121
|
There are two runtime/type surfaces with similar names:
|
|
@@ -299,7 +307,9 @@ export const urlpatterns = urls(({ path, layout, loader }) => [
|
|
|
299
307
|
]);
|
|
300
308
|
|
|
301
309
|
// 3. router.tsx - Create router and export reverse
|
|
302
|
-
|
|
310
|
+
import { createRouter } from "@rangojs/router";
|
|
311
|
+
|
|
312
|
+
export const router = createRouter<AppBindings>({
|
|
303
313
|
document: Document,
|
|
304
314
|
}).routes(urlpatterns);
|
|
305
315
|
|
|
@@ -312,10 +322,10 @@ declare global {
|
|
|
312
322
|
}
|
|
313
323
|
|
|
314
324
|
export const reverse = router.reverse;
|
|
315
|
-
export default router;
|
|
316
325
|
|
|
317
|
-
// 4.
|
|
318
|
-
// router.
|
|
326
|
+
// 4. router.named-routes.gen.ts is written by the Vite plugin (dev + build);
|
|
327
|
+
// run `npx rango generate src/router.tsx` to produce it without Vite.
|
|
328
|
+
// It auto-registers GeneratedRouteMap globally.
|
|
319
329
|
// No manual RegisteredRoutes declaration is needed for named-route handlers,
|
|
320
330
|
// ctx.reverse, prerender, href(), or Rango.Path. Add `RegisteredRoutes
|
|
321
331
|
// extends AppRoutes` (an alias of typeof router.routeMap) when global
|
|
@@ -323,10 +333,12 @@ export default router;
|
|
|
323
333
|
// router.routeMap metadata.
|
|
324
334
|
|
|
325
335
|
// 5. loaders/*.ts - Type-safe loaders
|
|
336
|
+
import { createLoader } from "@rangojs/router";
|
|
337
|
+
|
|
326
338
|
export const ProductLoader = createLoader(async (ctx) => {
|
|
327
|
-
// ctx.params
|
|
328
|
-
// ctx.get("user"):
|
|
329
|
-
// ctx.env
|
|
339
|
+
// ctx.params.slug: string | undefined (a loader is not bound to one route)
|
|
340
|
+
// ctx.get("user"): AppVariables["user"] (from Rango.Vars)
|
|
341
|
+
// ctx.env: any (createLoader does not apply Rango.Env; cast if you need types)
|
|
330
342
|
return { product: await fetchProduct(ctx.params.slug) };
|
|
331
343
|
});
|
|
332
344
|
|
|
@@ -22,12 +22,17 @@ global `GeneratedRouteMap` (the gen file). No explicit route map import needed:
|
|
|
22
22
|
import type { Handler } from "@rangojs/router";
|
|
23
23
|
|
|
24
24
|
export const SearchPage: Handler<"search"> = (ctx) => {
|
|
25
|
-
// ctx.search is typed: { q: string; page?: number; sort?: string }
|
|
26
|
-
const { q, page, sort } = ctx.search;
|
|
25
|
+
// ctx.search is typed: { q: string | undefined; page?: number; sort?: string }
|
|
26
|
+
const { q = "", page = 1, sort } = ctx.search;
|
|
27
27
|
return <SearchResults q={q} page={page} sort={sort} />;
|
|
28
28
|
};
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
+
Required keys (no `?`) still resolve to `T | undefined` inside the handler:
|
|
32
|
+
the schema tells callers (`ctx.reverse`, `useReverse`) what to send, but the
|
|
33
|
+
server cannot trust the client to send it, so the handler must handle a
|
|
34
|
+
missing value.
|
|
35
|
+
|
|
31
36
|
This avoids circular references because `Handler` defaults to `GeneratedRouteMap`
|
|
32
37
|
(from `router.named-routes.gen.ts`) instead of `RegisteredRoutes` (which depends on `router.tsx`).
|
|
33
38
|
|
|
@@ -48,8 +53,12 @@ Note the difference: `Handler<"search">` (no dot) resolves against the global
|
|
|
48
53
|
`routes` for param/search inference and only uses it for local `ctx.reverse(".x")`.
|
|
49
54
|
|
|
50
55
|
Supported types: `"string"`, `"number"`, `"boolean"`, with `?` suffix for optional.
|
|
51
|
-
Values are automatically coerced from query string
|
|
52
|
-
|
|
56
|
+
Values are automatically coerced from the query string: numbers accept finite
|
|
57
|
+
decimal values (`"2"` becomes `2`; `"0x10"`, `"Infinity"` or an empty value are
|
|
58
|
+
treated as missing), and a present boolean is `true` for `"true"`/`"1"` and
|
|
59
|
+
`false` otherwise. Absent keys are omitted, required or not. Routes without a
|
|
60
|
+
`search` schema get `ctx.search` as an empty object; read the raw query from
|
|
61
|
+
`ctx.searchParams` (a `URLSearchParams`).
|
|
53
62
|
|
|
54
63
|
### RouteSearchParams and RouteParams utility types
|
|
55
64
|
|
|
@@ -104,12 +113,15 @@ export const NamedRoutes = {
|
|
|
104
113
|
} as const;
|
|
105
114
|
```
|
|
106
115
|
|
|
107
|
-
You never
|
|
108
|
-
|
|
116
|
+
You never edit a `.gen.ts` by hand. Treat the generated types as call-site
|
|
117
|
+
checks, not modules to read:
|
|
109
118
|
|
|
110
|
-
- **
|
|
111
|
-
`Rango.GeneratedRouteMap`. It is the whole-app manifest,
|
|
112
|
-
globally — `Handler<"name">` and `ctx.reverse("name")` already
|
|
119
|
+
- **You rarely need to import `router.named-routes.gen.ts`**, and you don't
|
|
120
|
+
need to reference `Rango.GeneratedRouteMap`. It is the whole-app manifest,
|
|
121
|
+
auto-wired globally — `Handler<"name">` and `ctx.reverse("name")` already
|
|
122
|
+
see it. Importing its `NamedRoutes` value is an escape hatch (global names
|
|
123
|
+
in `useReverse`, an absolute-path map for `/api-client`); on the client it
|
|
124
|
+
ships every route name and pattern in the app.
|
|
113
125
|
- **Per-module `*.gen.ts` imports are fine** — they are the opt-in local-route
|
|
114
126
|
pattern for `useReverse(routes)` and explicit local handler typing
|
|
115
127
|
(`Handler<".name", routes>`). See `/links`.
|
|
@@ -119,35 +131,48 @@ smell — fix the call site (or regenerate), never edit the generated file.
|
|
|
119
131
|
|
|
120
132
|
## Loader Type Safety
|
|
121
133
|
|
|
122
|
-
|
|
134
|
+
A loader's return type is inferred from its function and flows to every
|
|
135
|
+
consumer. `createLoader` is not bound to one route, so `ctx.params` is
|
|
136
|
+
`Record<string, string | undefined>`:
|
|
123
137
|
|
|
124
138
|
```typescript
|
|
125
139
|
// loaders/product.ts
|
|
140
|
+
import { createLoader } from "@rangojs/router";
|
|
141
|
+
|
|
126
142
|
export const ProductLoader = createLoader(async (ctx) => {
|
|
127
143
|
return {
|
|
128
|
-
id: ctx.params.slug,
|
|
144
|
+
id: ctx.params.slug ?? "",
|
|
129
145
|
name: "Widget",
|
|
130
146
|
price: 99,
|
|
131
147
|
};
|
|
132
148
|
});
|
|
149
|
+
```
|
|
133
150
|
|
|
134
|
-
|
|
135
|
-
import { useLoader } from "@rangojs/router/client";
|
|
151
|
+
Server handler (escape hatch — reads the data in the handler itself):
|
|
136
152
|
|
|
137
|
-
|
|
138
|
-
|
|
153
|
+
```typescript
|
|
154
|
+
path("/product/:slug", async (ctx) => {
|
|
155
|
+
const product = await ctx.use(ProductLoader);
|
|
139
156
|
// product: { id: string; name: string; price: number }
|
|
140
157
|
return <h1>{product.name}</h1>;
|
|
141
|
-
}
|
|
158
|
+
}, { name: "product" }, () => [loader(ProductLoader)]);
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Client component (the default consumption site):
|
|
142
162
|
|
|
143
|
-
|
|
163
|
+
```typescript
|
|
144
164
|
"use client";
|
|
145
165
|
import { useLoader } from "@rangojs/router/client";
|
|
166
|
+
import { ProductLoader } from "../loaders/product";
|
|
146
167
|
|
|
147
168
|
function ProductPrice() {
|
|
148
169
|
const { data } = useLoader(ProductLoader);
|
|
149
170
|
// data: { id: string; name: string; price: number }
|
|
150
|
-
|
|
151
|
-
return <span>${product.price}</span>;
|
|
171
|
+
return <span>${data.price}</span>;
|
|
152
172
|
}
|
|
153
173
|
```
|
|
174
|
+
|
|
175
|
+
`useLoader()` is a client hook — never call it in a server component.
|
|
176
|
+
Client-side data is typed through `Rango.FlightSerialize<T>` (the RSC Flight
|
|
177
|
+
boundary), so values Flight preserves (such as `Date`) keep their type. See
|
|
178
|
+
`/loader` for when to use `ctx.use()` versus `useLoader()`.
|