@rangojs/router 0.16.0 → 0.18.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 +255 -89
- package/dist/testing/vitest.js +252 -0
- package/dist/types/browser/cookie-name.d.ts +6 -0
- package/dist/types/browser/history-state.d.ts +16 -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-key-utils.d.ts +17 -2
- package/dist/types/cache/cache-policy.d.ts +17 -0
- package/dist/types/cache/cache-scope.d.ts +132 -19
- package/dist/types/cache/cache-tag.d.ts +92 -7
- package/dist/types/cache/cf/cf-base64.d.ts +6 -0
- package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
- package/dist/types/cache/cf/cf-cache-store.d.ts +185 -44
- package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
- package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
- package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +34 -0
- package/dist/types/cache/handle-capture.d.ts +41 -3
- package/dist/types/cache/handle-snapshot.d.ts +54 -13
- package/dist/types/cache/index.d.ts +1 -0
- package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
- package/dist/types/cache/segment-codec.d.ts +10 -3
- package/dist/types/cache/shell-memo.d.ts +178 -0
- package/dist/types/cache/shell-snapshot.d.ts +110 -21
- package/dist/types/cache/tag-invalidation.d.ts +30 -3
- package/dist/types/cache/taint.d.ts +1 -1
- package/dist/types/cache/types.d.ts +220 -44
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +111 -13
- 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/prerender/build-shell-capture.d.ts +11 -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/instrument.d.ts +8 -0
- package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
- package/dist/types/router/middleware-types.d.ts +9 -3
- 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 +66 -1
- package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +23 -12
- package/dist/types/router/segment-resolution/mask-nested.d.ts +67 -26
- 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/full-payload.d.ts +14 -0
- package/dist/types/rsc/handler.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-build-manifest.d.ts +8 -0
- package/dist/types/rsc/shell-capture-constants.d.ts +61 -6
- package/dist/types/rsc/shell-capture.d.ts +151 -19
- package/dist/types/rsc/shell-serve.d.ts +140 -17
- package/dist/types/rsc/ssr-setup.d.ts +6 -6
- package/dist/types/rsc/types.d.ts +17 -0
- package/dist/types/server/context.d.ts +139 -15
- package/dist/types/server/cookie-store.d.ts +21 -0
- package/dist/types/server/handle-store.d.ts +26 -2
- package/dist/types/server/request-context.d.ts +182 -53
- package/dist/types/ssr/index.d.ts +24 -0
- package/dist/types/ssr/ssr-root.d.ts +9 -8
- package/dist/types/static-handler.d.ts +4 -1
- package/dist/types/testing/flight.entry.d.ts +7 -0
- 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/run-loader.d.ts +18 -3
- package/dist/types/testing/serve-shell-request.d.ts +132 -0
- package/dist/types/testing/shell-status.d.ts +23 -10
- 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 +28 -1
- package/dist/types/types/loader-types.d.ts +8 -6
- package/dist/types/urls/pattern-types.d.ts +22 -18
- 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/types/vite/router-discovery.d.ts +29 -2
- package/dist/vite/index.js +1420 -1348
- package/package.json +2 -2
- package/skills/api-client/SKILL.md +22 -10
- package/skills/breadcrumbs/SKILL.md +75 -22
- package/skills/bundle-analysis/SKILL.md +22 -22
- package/skills/cache-guide/SKILL.md +306 -124
- package/skills/caching/SKILL.md +530 -98
- package/skills/catalog.json +16 -16
- package/skills/client-urls/SKILL.md +65 -38
- package/skills/cloudflare/SKILL.md +87 -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 +75 -57
- 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 +52 -31
- package/skills/deployment-caching/SKILL.md +11 -3
- package/skills/document-cache/SKILL.md +98 -28
- package/skills/fonts/SKILL.md +16 -8
- package/skills/handler-use/SKILL.md +55 -17
- package/skills/hooks/SKILL.md +28 -20
- package/skills/hooks/data.md +23 -3
- package/skills/hooks/handle-and-actions.md +34 -17
- package/skills/hooks/navigation.md +39 -10
- package/skills/hooks/outlets.md +13 -6
- package/skills/hooks/state.md +39 -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 +386 -128
- package/skills/middleware/SKILL.md +129 -26
- package/skills/migrate-nextjs/SKILL.md +156 -70
- package/skills/migrate-nextjs/backend-host-swap.md +5 -2
- package/skills/migrate-react-router/SKILL.md +51 -39
- package/skills/migrate-react-router/component-migration.md +28 -7
- package/skills/migrate-react-router/data-and-actions.md +21 -6
- package/skills/migrate-react-router/route-mapping.md +19 -10
- package/skills/mime-routes/SKILL.md +12 -7
- package/skills/observability/SKILL.md +92 -14
- package/skills/parallel/SKILL.md +88 -47
- package/skills/ppr/SKILL.md +866 -379
- package/skills/prerender/SKILL.md +210 -108
- package/skills/rango/SKILL.md +115 -65
- package/skills/react-compiler/SKILL.md +6 -5
- package/skills/response-routes/SKILL.md +133 -25
- package/skills/route/SKILL.md +170 -60
- package/skills/router-setup/SKILL.md +420 -240
- package/skills/scripts/SKILL.md +39 -21
- package/skills/server-actions/SKILL.md +231 -59
- package/skills/shell-manifest/SKILL.md +28 -14
- package/skills/streams-and-websockets/SKILL.md +32 -14
- package/skills/tailwind/SKILL.md +37 -33
- package/skills/testing/SKILL.md +73 -33
- package/skills/testing/cache-prerender.md +60 -33
- 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 +26 -6
- 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/serve-shell-request.md +188 -0
- 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 +155 -30
- package/skills/typesafety/SKILL.md +20 -9
- package/skills/typesafety/env-and-bindings.md +137 -50
- 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 +224 -83
- package/skills/vercel/SKILL.md +35 -13
- package/skills/view-transitions/SKILL.md +42 -21
- package/src/__augment-tests__/augmented.check.ts +26 -1
- package/src/browser/cookie-name.ts +23 -0
- package/src/browser/history-state.ts +23 -0
- 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 +6 -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 +23 -8
- package/src/cache/cache-policy.ts +22 -0
- package/src/cache/cache-runtime.ts +264 -68
- package/src/cache/cache-scope.ts +419 -43
- package/src/cache/cache-tag.ts +375 -11
- package/src/cache/cf/cf-base64.ts +36 -4
- package/src/cache/cf/cf-cache-constants.ts +12 -0
- package/src/cache/cf/cf-cache-store.ts +990 -348
- package/src/cache/cf/cf-cache-types.ts +32 -4
- package/src/cache/cf/cf-shell-frame.ts +258 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +143 -39
- package/src/cache/document-cache.ts +15 -0
- package/src/cache/handle-capture.ts +90 -10
- package/src/cache/handle-snapshot.ts +133 -28
- package/src/cache/index.ts +2 -0
- package/src/cache/isolate-tag-memo.ts +308 -0
- package/src/cache/segment-codec.ts +26 -7
- package/src/cache/shell-memo.ts +341 -0
- package/src/cache/shell-snapshot.ts +217 -27
- package/src/cache/tag-invalidation.ts +108 -22
- package/src/cache/taint.ts +1 -1
- package/src/cache/types.ts +225 -44
- package/src/cache/vercel/vercel-cache-store.ts +545 -61
- 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 +73 -22
- package/src/reverse.ts +44 -4
- package/src/route-definition/dsl-helpers.ts +121 -33
- 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 +71 -28
- package/src/router/instrument.ts +54 -2
- package/src/router/intercept-resolution.ts +141 -43
- package/src/router/loader-resolution.ts +115 -39
- package/src/router/match-api.ts +40 -26
- package/src/router/match-handlers.ts +5 -0
- package/src/router/match-middleware/background-revalidation.ts +161 -96
- package/src/router/match-middleware/cache-lookup.ts +110 -10
- package/src/router/match-middleware/cache-store.ts +48 -101
- package/src/router/middleware-types.ts +9 -6
- package/src/router/middleware.ts +75 -13
- package/src/router/prerender-match.ts +59 -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 +62 -21
- package/src/router/segment-resolution/helpers.ts +4 -2
- package/src/router/segment-resolution/loader-cache.ts +407 -76
- package/src/router/segment-resolution/loader-mask.ts +12 -22
- package/src/router/segment-resolution/loader-snapshot.ts +208 -56
- package/src/router/segment-resolution/mask-nested.ts +138 -44
- package/src/router/segment-resolution/revalidation.ts +61 -16
- package/src/router/segment-wrappers.ts +4 -0
- package/src/rsc/full-payload.ts +25 -1
- package/src/rsc/handler.ts +14 -2
- package/src/rsc/progressive-enhancement.ts +3 -2
- package/src/rsc/render-pipeline.ts +3 -0
- package/src/rsc/response-cache-serve.ts +9 -6
- package/src/rsc/response-route-handler.ts +11 -3
- package/src/rsc/rsc-rendering.ts +608 -225
- package/src/rsc/shell-build-manifest.ts +27 -9
- package/src/rsc/shell-capture-constants.ts +73 -6
- package/src/rsc/shell-capture.ts +1068 -296
- package/src/rsc/shell-serve.ts +316 -34
- package/src/rsc/ssr-setup.ts +14 -8
- package/src/rsc/types.ts +18 -0
- package/src/server/context.ts +300 -32
- package/src/server/cookie-store.ts +87 -61
- package/src/server/handle-store.ts +232 -7
- package/src/server/request-context.ts +297 -67
- package/src/ssr/index.tsx +136 -44
- package/src/ssr/ssr-root.tsx +9 -8
- package/src/static-handler.ts +4 -1
- package/src/testing/flight-runtime.d.ts +21 -0
- package/src/testing/flight.entry.ts +14 -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 +68 -6
- package/src/testing/serve-shell-request.ts +378 -0
- package/src/testing/shell-status.ts +34 -12
- package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
- package/src/testing/vitest.ts +62 -1
- package/src/theme/ThemeProvider.tsx +7 -6
- package/src/theme/ThemeScript.tsx +3 -3
- package/src/types/cache-types.ts +14 -10
- package/src/types/handler-context.ts +28 -1
- package/src/types/loader-types.ts +8 -6
- package/src/urls/pattern-types.ts +22 -18
- package/src/urls/response-types.ts +3 -23
- package/src/use-loader.tsx +12 -3
- package/src/vite/discovery/shell-prerender-phase.ts +9 -0
- package/src/vite/plugins/use-cache-transform.ts +118 -74
- package/src/vite/rango.ts +7 -3
- package/src/vite/router-discovery.ts +14 -4
|
@@ -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
|
|
|
@@ -103,8 +103,12 @@ interface PaginationData {
|
|
|
103
103
|
}
|
|
104
104
|
export const Pagination = createVar<PaginationData>();
|
|
105
105
|
|
|
106
|
-
// Non-cacheable var — reading inside cache()
|
|
107
|
-
|
|
106
|
+
// Non-cacheable var — reading inside cache(), "use cache", or a PPR shell
|
|
107
|
+
// capture throws at runtime
|
|
108
|
+
interface SessionData {
|
|
109
|
+
userId: string;
|
|
110
|
+
}
|
|
111
|
+
export const Session = createVar<SessionData>({ cache: false });
|
|
108
112
|
```
|
|
109
113
|
|
|
110
114
|
`createVar` accepts an optional options object. The `cache` option (default
|
|
@@ -112,11 +116,25 @@ const Session = createVar<SessionData>({ cache: false });
|
|
|
112
116
|
Write-level escalation is also supported: `ctx.set(Var, value, { cache: false })`
|
|
113
117
|
marks a specific write as non-cacheable even if the var itself is cacheable.
|
|
114
118
|
"Least cacheable wins" — if either says `cache: false`, the value throws on
|
|
115
|
-
read inside `cache()` or `"use cache"`.
|
|
116
|
-
|
|
117
|
-
|
|
119
|
+
read inside `cache()` or `"use cache"`. On a `ppr` route the read also throws
|
|
120
|
+
anywhere the shell capture waits for it (a handler, a promise it passes or
|
|
121
|
+
pushes, an async server component, an `ssr: false` loader, a loader a handler
|
|
122
|
+
awaits) and refuses the capture: the route keeps rendering without a shell,
|
|
123
|
+
and a warning fires once per key. A loader without `ssr: false` that no
|
|
124
|
+
handler awaits may read it, unless it is bound with its own `cache()` and no
|
|
125
|
+
`key()`: that miss fails, since the entry is shared (`/loader` → "Cache
|
|
126
|
+
Key").
|
|
127
|
+
|
|
128
|
+
### Producer (handler, layout, or middleware)
|
|
129
|
+
|
|
130
|
+
Write with `ctx.set()` from middleware, route handlers, or layout handlers;
|
|
131
|
+
read with `ctx.get()` in the segments they wrap. A route handler runs before
|
|
132
|
+
the layouts and parallels declared in its own use callback (handler-first), so
|
|
133
|
+
those see the value; an outer layout wrapping the route renders first and
|
|
134
|
+
does not. See `/route` for the full ordering.
|
|
118
135
|
|
|
119
136
|
```typescript
|
|
137
|
+
import type { Handler } from "@rangojs/router";
|
|
120
138
|
import { Pagination } from "../vars/pagination.js";
|
|
121
139
|
|
|
122
140
|
const ArticleList: Handler<"articles.list"> = async (ctx) => {
|
|
@@ -132,15 +150,30 @@ const ArticleList: Handler<"articles.list"> = async (ctx) => {
|
|
|
132
150
|
### Consumer (layout, parallel, or any context with get)
|
|
133
151
|
|
|
134
152
|
```typescript
|
|
153
|
+
import type { Handler } from "@rangojs/router";
|
|
154
|
+
import { Outlet } from "@rangojs/router/client";
|
|
135
155
|
import { Pagination } from "../vars/pagination.js";
|
|
136
156
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
157
|
+
// Mounted inside the producer's use callback:
|
|
158
|
+
// path("/articles", ArticleList, { name: "articles.list" }, () => [layout(PaginationLayout)])
|
|
159
|
+
export const PaginationLayout: Handler = (ctx) => {
|
|
160
|
+
const pagination = ctx.get(Pagination); // typed as PaginationData | undefined
|
|
161
|
+
return (
|
|
162
|
+
<>
|
|
163
|
+
<Outlet />
|
|
164
|
+
{pagination && (
|
|
165
|
+
<nav>
|
|
166
|
+
Page {pagination.current} of {pagination.total}
|
|
167
|
+
</nav>
|
|
168
|
+
)}
|
|
169
|
+
</>
|
|
170
|
+
);
|
|
171
|
+
};
|
|
142
172
|
```
|
|
143
173
|
|
|
174
|
+
Type the layout (`Handler`) rather than annotating `ctx: any`: with `any`,
|
|
175
|
+
`ctx.get(Pagination)` is `any` too and the token contract is lost.
|
|
176
|
+
|
|
144
177
|
### Why not just use Rango.Vars?
|
|
145
178
|
|
|
146
179
|
`Rango.Vars` (via global namespace augmentation) provides app-global typing for
|
|
@@ -154,7 +187,9 @@ Both approaches coexist: `ctx.get("user")` (global via Vars) and
|
|
|
154
187
|
|
|
155
188
|
## Handle Type Safety
|
|
156
189
|
|
|
157
|
-
|
|
190
|
+
A handle is typed by two parameters: `Handle<TData, TAccumulated>`. `TData` is
|
|
191
|
+
what each segment pushes; `TAccumulated` is what readers get after the collect
|
|
192
|
+
function runs (default `TData[][]`, one array per segment).
|
|
158
193
|
|
|
159
194
|
```typescript
|
|
160
195
|
// Built-in Breadcrumbs handle — import from "@rangojs/router"
|
|
@@ -168,18 +203,33 @@ path("/shop/product/:slug", (ctx) => {
|
|
|
168
203
|
breadcrumb({ label: "Products", href: "/shop/products" });
|
|
169
204
|
return <ProductPage />;
|
|
170
205
|
}, { name: "product" });
|
|
206
|
+
```
|
|
171
207
|
|
|
208
|
+
```tsx
|
|
172
209
|
// In client — typed array
|
|
210
|
+
"use client";
|
|
173
211
|
import { useHandle, Breadcrumbs } from "@rangojs/router/client";
|
|
212
|
+
|
|
174
213
|
function BreadcrumbNav() {
|
|
175
|
-
const crumbs = useHandle(Breadcrumbs);
|
|
176
|
-
|
|
214
|
+
const crumbs = useHandle(Breadcrumbs); // crumbs: BreadcrumbItem[]
|
|
215
|
+
return (
|
|
216
|
+
<nav>
|
|
217
|
+
{crumbs.map((c) => (
|
|
218
|
+
<a key={c.href} href={c.href}>
|
|
219
|
+
{c.label}
|
|
220
|
+
</a>
|
|
221
|
+
))}
|
|
222
|
+
</nav>
|
|
223
|
+
);
|
|
177
224
|
}
|
|
225
|
+
```
|
|
178
226
|
|
|
179
|
-
|
|
227
|
+
```typescript
|
|
228
|
+
// Custom handle: give TAccumulated whenever collect changes the shape
|
|
180
229
|
import { createHandle } from "@rangojs/router";
|
|
230
|
+
|
|
181
231
|
export const PageTitle = createHandle<string, string>(
|
|
182
|
-
(segments) => segments.flat().at(-1) ?? "Default Title"
|
|
232
|
+
(segments) => segments.flat().at(-1) ?? "Default Title",
|
|
183
233
|
);
|
|
184
234
|
```
|
|
185
235
|
|
|
@@ -190,11 +240,16 @@ Use `typeof` to get the full typed definition without manually specifying generi
|
|
|
190
240
|
|
|
191
241
|
```typescript
|
|
192
242
|
// loaders.ts
|
|
243
|
+
import { createLoader } from "@rangojs/router";
|
|
244
|
+
|
|
193
245
|
export const ProductLoader = createLoader(async (ctx) => {
|
|
194
246
|
return { product: await fetchProduct(ctx.params.slug) };
|
|
195
247
|
});
|
|
196
248
|
|
|
197
|
-
//
|
|
249
|
+
// Server: pass the definitions as props
|
|
250
|
+
path("/product/:slug", () => (
|
|
251
|
+
<MyComponent loader={ProductLoader} handle={Breadcrumbs} />
|
|
252
|
+
), { name: "product" }, () => [loader(ProductLoader)]);
|
|
198
253
|
```
|
|
199
254
|
|
|
200
255
|
```tsx
|
|
@@ -208,20 +263,27 @@ function MyComponent({
|
|
|
208
263
|
handle,
|
|
209
264
|
}: {
|
|
210
265
|
loader: typeof ProductLoader; // LoaderDefinition<{ product: Product }>
|
|
211
|
-
handle: typeof Breadcrumbs; // Handle<
|
|
266
|
+
handle: typeof Breadcrumbs; // Handle<BreadcrumbItem, BreadcrumbItem[]>
|
|
212
267
|
}) {
|
|
213
268
|
const { data } = useLoader(loader); // data is typed
|
|
214
|
-
const crumbs = useHandle(handle); // crumbs
|
|
269
|
+
const crumbs = useHandle(handle); // crumbs: BreadcrumbItem[]
|
|
215
270
|
// ...
|
|
216
271
|
}
|
|
217
272
|
```
|
|
218
273
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
274
|
+
Loader and handle definitions are plain `{ __brand, $$id }` objects — the
|
|
275
|
+
loader function and the handle's collect function live in module-level
|
|
276
|
+
registries keyed by `$$id` — so they cross the RSC boundary as-is and the
|
|
277
|
+
hooks look the rest up by id. `useLoader()` still needs the loader registered
|
|
278
|
+
on the route with `loader()` (or use `useFetchLoader()` for on-demand fetching).
|
|
222
279
|
|
|
223
280
|
## Location State Type Safety
|
|
224
281
|
|
|
282
|
+
`createLocationState<T>()` returns a typed definition; the Vite plugin injects
|
|
283
|
+
its key, so it must be exported from a module. Values are stored in
|
|
284
|
+
`history.state`, so `T` must be structured-cloneable: functions, symbols,
|
|
285
|
+
class constructors, React elements, and `unknown` are compile errors.
|
|
286
|
+
|
|
225
287
|
```typescript
|
|
226
288
|
// location-states.ts
|
|
227
289
|
import { createLocationState } from "@rangojs/router";
|
|
@@ -233,13 +295,28 @@ export const ProductPreview = createLocationState<{
|
|
|
233
295
|
image: string;
|
|
234
296
|
}>();
|
|
235
297
|
|
|
298
|
+
// Flash state: cleared after the first read
|
|
299
|
+
export const FlashMessage = createLocationState<{ text: string }>({
|
|
300
|
+
flash: true,
|
|
301
|
+
});
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
```tsx
|
|
305
|
+
"use client";
|
|
306
|
+
import { Link, href, useLocationState } from "@rangojs/router/client";
|
|
307
|
+
import { ProductPreview } from "./location-states";
|
|
308
|
+
|
|
236
309
|
// Passing state through Link
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
310
|
+
function ProductCard() {
|
|
311
|
+
return (
|
|
312
|
+
<Link
|
|
313
|
+
to={href("/product/widget")}
|
|
314
|
+
state={[ProductPreview({ name: "Widget", price: 99, image: "/img.jpg" })]}
|
|
315
|
+
>
|
|
316
|
+
View Product
|
|
317
|
+
</Link>
|
|
318
|
+
);
|
|
319
|
+
}
|
|
243
320
|
|
|
244
321
|
// Reading state in component
|
|
245
322
|
function ProductHeader() {
|
|
@@ -247,8 +324,18 @@ function ProductHeader() {
|
|
|
247
324
|
// preview: { name: string; price: number; image: string } | undefined
|
|
248
325
|
|
|
249
326
|
if (preview) {
|
|
250
|
-
return
|
|
327
|
+
return (
|
|
328
|
+
<h1>
|
|
329
|
+
{preview.name} - ${preview.price}
|
|
330
|
+
</h1>
|
|
331
|
+
);
|
|
251
332
|
}
|
|
252
333
|
return <h1>Loading...</h1>;
|
|
253
334
|
}
|
|
254
335
|
```
|
|
336
|
+
|
|
337
|
+
Other entry points: pass a getter (`ProductPreview(() => ({ ... }))`) to compute
|
|
338
|
+
the value at click time; `ctx.setLocationState(entry)` attaches state from a
|
|
339
|
+
handler; `redirect(url, { state })` carries it through a redirect; and
|
|
340
|
+
`ProductPreview.read()` / `.write(value)` / `.delete()` access the current
|
|
341
|
+
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()`.
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
# Route Types
|
|
2
2
|
|
|
3
|
+
Route names come from the `name` option on `path()`. Names are typed in two
|
|
4
|
+
places: the `urls()` value itself (local names, patterns and response payloads
|
|
5
|
+
as phantom types) and the generated `router.named-routes.gen.ts` (the global
|
|
6
|
+
name map behind `Handler<"name">`, `ctx.reverse()` and `href()`). See
|
|
7
|
+
[`./generated-files-and-cli.md`](./generated-files-and-cli.md) for which surface
|
|
8
|
+
feeds which API.
|
|
9
|
+
|
|
3
10
|
## Route Definition with Type-Safe Names
|
|
4
11
|
|
|
5
12
|
```typescript
|
|
6
13
|
// urls.tsx
|
|
7
14
|
import { urls } from "@rangojs/router";
|
|
8
15
|
|
|
9
|
-
export const urlpatterns = urls(({ path
|
|
16
|
+
export const urlpatterns = urls(({ path }) => [
|
|
10
17
|
path("/", HomePage, { name: "home" }),
|
|
11
18
|
path("/products", ProductsPage, { name: "products" }),
|
|
12
19
|
path("/product/:slug", ProductPage, { name: "product" }),
|
|
@@ -17,7 +24,7 @@ export const urlpatterns = urls(({ path, layout }) => [
|
|
|
17
24
|
// Route names are inferred from the { name } option
|
|
18
25
|
```
|
|
19
26
|
|
|
20
|
-
## Type-Safe
|
|
27
|
+
## Type-Safe URL Generation
|
|
21
28
|
|
|
22
29
|
### Server: ctx.reverse with route names
|
|
23
30
|
|
|
@@ -36,8 +43,15 @@ export const ProductHandler: Handler<"shop.product"> = (ctx) => {
|
|
|
36
43
|
};
|
|
37
44
|
```
|
|
38
45
|
|
|
39
|
-
|
|
40
|
-
|
|
46
|
+
Without a local route map, `.name` calls accept any string (the include scope
|
|
47
|
+
is only known at runtime). Middleware and response-route handlers are the
|
|
48
|
+
exception: their `ctx.reverse` (`GlobalReverseFunction`) has no include scope,
|
|
49
|
+
so it takes global names only and a `.name` call there is a type error. Global names are checked against the generated
|
|
50
|
+
map once it exists. For type-safe local names, generate a route types file with
|
|
51
|
+
`npx rango generate urls/shop.tsx` and pass its `routes` type as the second
|
|
52
|
+
generic to `Handler` or `Prerender`. With a local map, local names also
|
|
53
|
+
require their params (mount params such as `:tenantId` may still be passed as
|
|
54
|
+
extras):
|
|
41
55
|
|
|
42
56
|
```typescript
|
|
43
57
|
import type { Handler } from "@rangojs/router";
|
|
@@ -61,6 +75,7 @@ import { href, useHref, Link } from "@rangojs/router/client";
|
|
|
61
75
|
// href() validates absolute paths via PatternToPath types
|
|
62
76
|
href("/about"); // Valid path
|
|
63
77
|
href("/blog/hello"); // Matches /blog/:slug
|
|
78
|
+
href("/blog/hello?page=2#top"); // Query and hash suffixes are allowed
|
|
64
79
|
|
|
65
80
|
// useHref() auto-prefixes with include() mount
|
|
66
81
|
function ShopNav() {
|
|
@@ -69,6 +84,10 @@ function ShopNav() {
|
|
|
69
84
|
}
|
|
70
85
|
```
|
|
71
86
|
|
|
87
|
+
`href()` is compile-time only: at runtime it returns the path unchanged. It
|
|
88
|
+
does not resolve route names. `useHref()` prepends the mount but its argument
|
|
89
|
+
is typed as any `/${string}`, not checked against routes.
|
|
90
|
+
|
|
72
91
|
`href()` and the `Rango.Path` type read from `RegisteredRoutes` when you augment
|
|
73
92
|
it, otherwise from the auto-generated `GeneratedRouteMap` — so `rango generate`
|
|
74
93
|
alone type-checks `href()` paths with no manual augmentation. The augmentation
|
|
@@ -76,6 +95,7 @@ below is only needed for **`Rango.PathResponse`** (response-payload inference),
|
|
|
76
95
|
`GeneratedRouteMap` cannot provide:
|
|
77
96
|
|
|
78
97
|
```typescript
|
|
98
|
+
// router.tsx, below `export const router = createRouter(...).routes(urlpatterns)`
|
|
79
99
|
// The alias is required: an interface heritage clause cannot take a `typeof`
|
|
80
100
|
// type query directly (TS1109).
|
|
81
101
|
type AppRoutes = typeof router.routeMap;
|
|
@@ -200,15 +220,31 @@ a rename is a type error in one place instead of silent drift:
|
|
|
200
220
|
```ts
|
|
201
221
|
import { addToCart, removeFromCart } from "./actions/cart";
|
|
202
222
|
import * as CartActions from "./actions/cart";
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
revalidate((ctx) => ctx.isAction(
|
|
223
|
+
import * as OrderActions from "./actions/order";
|
|
224
|
+
|
|
225
|
+
// On a loader: after an action, re-run only for cart actions; on navigation,
|
|
226
|
+
// keep the default.
|
|
227
|
+
revalidate((ctx) => (ctx.isAction() ? ctx.isAction(CartActions) : undefined));
|
|
228
|
+
|
|
229
|
+
// Matcher forms (each returns a boolean):
|
|
230
|
+
ctx.isAction(); // any action at all
|
|
231
|
+
ctx.isAction(addToCart); // one action
|
|
232
|
+
ctx.isAction(addToCart, removeFromCart); // several
|
|
233
|
+
ctx.isAction(CartActions); // any action in the module
|
|
234
|
+
ctx.isAction({ addToCart, removeFromCart }); // object form
|
|
235
|
+
ctx.isAction({ Cart: CartActions, Order: OrderActions }); // grouped namespaces
|
|
208
236
|
```
|
|
209
237
|
|
|
210
|
-
`ctx.isAction()` (only available on the revalidate predicate's
|
|
211
|
-
raw boolean
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
238
|
+
`ctx.isAction()` (only available on the revalidate predicate's argument)
|
|
239
|
+
returns a raw boolean and is `false` on plain navigation. Wrap it by what the
|
|
240
|
+
segment's default already does: `|| undefined` adds a re-render on a parent
|
|
241
|
+
layout or layout-level parallel (skipped after actions by default); on a loader
|
|
242
|
+
or route segment, which already re-run after every action, `|| undefined`
|
|
243
|
+
changes nothing, so narrow with the ternary above. Avoid a bare
|
|
244
|
+
`ctx.isAction(X)`: it is a hard `false` on navigation too. See `/loader` →
|
|
245
|
+
"`revalidate()` return shapes". It resolves
|
|
246
|
+
the reference the same way the router derives `actionId` (`$id ?? $$id`: the
|
|
247
|
+
file-path `$id` injected in a production RSC build, otherwise React's `$$id`),
|
|
248
|
+
so matching works in dev and production. `actionId` stays available for
|
|
249
|
+
advanced cases. In `clientUrls()` groups the predicate runs in the browser,
|
|
250
|
+
where `actionId` is the hashed form (see `/client-urls`).
|