@rangojs/router 0.0.0-experimental.13 → 0.0.0-experimental.13221847
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/AGENTS.md +9 -0
- package/README.md +884 -4
- package/dist/bin/rango.js +1531 -212
- package/dist/vite/index.js +3995 -2489
- package/package.json +57 -52
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +85 -23
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +12 -8
- package/skills/document-cache/SKILL.md +18 -16
- package/skills/fonts/SKILL.md +6 -4
- package/skills/hooks/SKILL.md +328 -70
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +131 -8
- package/skills/layout/SKILL.md +100 -3
- package/skills/links/SKILL.md +62 -15
- package/skills/loader/SKILL.md +368 -42
- package/skills/middleware/SKILL.md +171 -34
- package/skills/mime-routes/SKILL.md +14 -10
- package/skills/parallel/SKILL.md +137 -1
- package/skills/prerender/SKILL.md +366 -28
- package/skills/rango/SKILL.md +85 -21
- package/skills/response-routes/SKILL.md +136 -83
- package/skills/route/SKILL.md +195 -21
- package/skills/router-setup/SKILL.md +123 -30
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +240 -102
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +102 -4
- package/src/bin/rango.ts +312 -15
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/event-controller.ts +92 -64
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +11 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +266 -558
- package/src/browser/navigation-client.ts +132 -75
- package/src/browser/navigation-store.ts +33 -50
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +303 -309
- package/src/browser/prefetch/cache.ts +206 -0
- package/src/browser/prefetch/fetch.ts +144 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +128 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +190 -70
- package/src/browser/react/NavigationProvider.tsx +78 -11
- package/src/browser/react/context.ts +6 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +12 -12
- package/src/browser/react/location-state-shared.ts +95 -53
- package/src/browser/react/location-state.ts +60 -15
- package/src/browser/react/mount-context.ts +6 -1
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +29 -51
- package/src/browser/react/use-client-cache.ts +5 -3
- package/src/browser/react/use-handle.ts +29 -70
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-navigation.ts +22 -63
- package/src/browser/react/use-params.ts +65 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-router.ts +63 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +80 -97
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +188 -57
- package/src/browser/scroll-restoration.ts +117 -44
- package/src/browser/segment-reconciler.ts +221 -0
- package/src/browser/segment-structure-assert.ts +16 -0
- package/src/browser/server-action-bridge.ts +488 -606
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +116 -47
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +63 -21
- package/src/build/generate-route-types.ts +36 -1038
- package/src/build/index.ts +2 -5
- package/src/build/route-trie.ts +38 -12
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +98 -0
- package/src/build/route-types/codegen.ts +102 -0
- package/src/build/route-types/include-resolution.ts +411 -0
- package/src/build/route-types/param-extraction.ts +48 -0
- package/src/build/route-types/per-module-writer.ts +128 -0
- package/src/build/route-types/router-processing.ts +479 -0
- package/src/build/route-types/scan-filter.ts +78 -0
- package/src/build/runtime-discovery.ts +231 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +342 -0
- package/src/cache/cache-scope.ts +122 -303
- package/src/cache/cf/cf-cache-store.ts +571 -17
- package/src/cache/cf/index.ts +13 -3
- package/src/cache/document-cache.ts +116 -77
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +1 -15
- package/src/cache/memory-segment-store.ts +191 -13
- package/src/cache/profile-registry.ts +73 -0
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +256 -0
- package/src/cache/taint.ts +98 -0
- package/src/cache/types.ts +72 -122
- package/src/client.rsc.tsx +3 -1
- package/src/client.tsx +84 -126
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +86 -0
- package/src/debug.ts +19 -9
- package/src/errors.ts +77 -7
- package/src/handle.ts +12 -7
- package/src/handles/MetaTags.tsx +73 -20
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +1 -0
- package/src/handles/meta.ts +30 -13
- package/src/host/cookie-handler.ts +21 -15
- package/src/host/errors.ts +8 -8
- package/src/host/index.ts +4 -7
- package/src/host/pattern-matcher.ts +27 -27
- package/src/host/router.ts +61 -39
- package/src/host/testing.ts +8 -8
- package/src/host/types.ts +15 -7
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +65 -45
- package/src/index.rsc.ts +104 -40
- package/src/index.ts +122 -67
- package/src/internal-debug.ts +9 -3
- package/src/loader.rsc.ts +18 -93
- package/src/loader.ts +26 -9
- package/src/network-error-thrower.tsx +3 -1
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +4 -2
- package/src/prerender/store.ts +121 -17
- package/src/prerender.ts +325 -20
- package/src/reverse.ts +144 -124
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- package/src/route-definition/dsl-helpers.ts +959 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +430 -0
- package/src/route-definition/index.ts +52 -0
- package/src/route-definition/redirect.ts +93 -0
- package/src/route-definition.ts +1 -1450
- package/src/route-map-builder.ts +87 -133
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +41 -6
- package/src/router/content-negotiation.ts +116 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +9 -9
- package/src/router/find-match.ts +160 -0
- package/src/router/handler-context.ts +324 -116
- package/src/router/intercept-resolution.ts +11 -4
- package/src/router/lazy-includes.ts +237 -0
- package/src/router/loader-resolution.ts +179 -133
- package/src/router/logging.ts +112 -6
- package/src/router/manifest.ts +58 -19
- package/src/router/match-api.ts +89 -88
- package/src/router/match-context.ts +4 -2
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +86 -89
- package/src/router/match-middleware/cache-lookup.ts +295 -49
- package/src/router/match-middleware/cache-store.ts +56 -13
- package/src/router/match-middleware/intercept-resolution.ts +45 -22
- package/src/router/match-middleware/segment-resolution.ts +20 -9
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +44 -21
- package/src/router/metrics.ts +240 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +222 -0
- package/src/router/middleware.ts +327 -369
- package/src/router/pattern-matching.ts +169 -31
- package/src/router/prerender-match.ts +402 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +105 -14
- package/src/router/router-context.ts +40 -21
- package/src/router/router-interfaces.ts +452 -0
- package/src/router/router-options.ts +592 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +677 -0
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +199 -0
- package/src/router/segment-resolution/revalidation.ts +1296 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -1354
- package/src/router/segment-wrappers.ts +291 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/trie-matching.ts +96 -29
- package/src/router/types.ts +15 -9
- package/src/router.ts +642 -2366
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +639 -1027
- package/src/rsc/helpers.ts +140 -6
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +209 -0
- package/src/rsc/manifest-init.ts +86 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +379 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +237 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +348 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +38 -11
- package/src/search-params.ts +66 -54
- package/src/segment-system.tsx +165 -17
- package/src/server/context.ts +237 -54
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +11 -6
- package/src/server/handle-store.ts +94 -15
- package/src/server/loader-registry.ts +15 -56
- package/src/server/request-context.ts +438 -71
- package/src/server.ts +26 -164
- package/src/ssr/index.tsx +101 -31
- package/src/static-handler.ts +22 -4
- package/src/theme/ThemeProvider.tsx +21 -15
- package/src/theme/ThemeScript.tsx +5 -5
- package/src/theme/constants.ts +5 -2
- package/src/theme/index.ts +4 -14
- package/src/theme/theme-context.ts +4 -30
- package/src/theme/theme-script.ts +21 -18
- package/src/types/boundaries.ts +158 -0
- package/src/types/cache-types.ts +198 -0
- package/src/types/error-types.ts +192 -0
- package/src/types/global-namespace.ts +100 -0
- package/src/types/handler-context.ts +773 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +183 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +109 -0
- package/src/types/segments.ts +150 -0
- package/src/types.ts +1 -1795
- package/src/urls/include-helper.ts +197 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +339 -0
- package/src/urls/path-helper.ts +329 -0
- package/src/urls/pattern-types.ts +95 -0
- package/src/urls/response-types.ts +106 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -1323
- package/src/use-loader.tsx +85 -77
- package/src/vite/discovery/bundle-postprocess.ts +184 -0
- package/src/vite/discovery/discover-routers.ts +344 -0
- package/src/vite/discovery/prerender-collection.ts +385 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +47 -0
- package/src/vite/discovery/state.ts +108 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +11 -2259
- package/src/vite/plugin-types.ts +48 -0
- package/src/vite/plugins/cjs-to-esm.ts +93 -0
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/client-ref-hashing.ts +105 -0
- package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -47
- package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +8 -43
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +569 -0
- package/src/vite/plugins/refresh-cmd.ts +65 -0
- package/src/vite/plugins/use-cache-transform.ts +323 -0
- package/src/vite/plugins/version-injector.ts +83 -0
- package/src/vite/plugins/version-plugin.ts +266 -0
- package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +445 -0
- package/src/vite/router-discovery.ts +777 -0
- package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +181 -9
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +137 -0
- package/src/vite/utils/manifest-utils.ts +70 -0
- package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
- package/src/vite/utils/prerender-utils.ts +189 -0
- package/src/vite/utils/shared-utils.ts +169 -0
- package/CLAUDE.md +0 -43
- package/dist/vite/index.named-routes.gen.ts +0 -103
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/request-controller.ts +0 -164
- package/src/cache/memory-store.ts +0 -253
- package/src/href-context.ts +0 -33
- package/src/router.gen.ts +0 -6
- package/src/static-handler.gen.ts +0 -5
- package/src/urls.gen.ts +0 -8
- package/src/vite/expose-internal-ids.ts +0 -1167
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -22,43 +22,62 @@ export const urlpatterns = urls(({ path, layout, include }) => [
|
|
|
22
22
|
path("/about", AboutPage, { name: "about" }),
|
|
23
23
|
|
|
24
24
|
// JSON API route (inline, alongside RSC routes)
|
|
25
|
-
path.json(
|
|
26
|
-
status
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
path.json(
|
|
26
|
+
"/api/status",
|
|
27
|
+
(ctx) => ({
|
|
28
|
+
status: "ok",
|
|
29
|
+
timestamp: Date.now(),
|
|
30
|
+
}),
|
|
31
|
+
{ name: "status" },
|
|
32
|
+
),
|
|
29
33
|
|
|
30
34
|
// Text route
|
|
31
|
-
path.text(
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
path.text(
|
|
36
|
+
"/robots.txt",
|
|
37
|
+
(ctx) => {
|
|
38
|
+
return "User-agent: *\nAllow: /\nDisallow: /api/\n";
|
|
39
|
+
},
|
|
40
|
+
{ name: "robots" },
|
|
41
|
+
),
|
|
34
42
|
|
|
35
43
|
// Markdown route
|
|
36
|
-
path.md(
|
|
37
|
-
|
|
38
|
-
|
|
44
|
+
path.md(
|
|
45
|
+
"/docs/:slug.md",
|
|
46
|
+
(ctx) => {
|
|
47
|
+
return `# ${ctx.params.slug}\n\nDocumentation content here.`;
|
|
48
|
+
},
|
|
49
|
+
{ name: "docs" },
|
|
50
|
+
),
|
|
39
51
|
|
|
40
52
|
// Response route (full control, returns Response directly)
|
|
41
|
-
path.image(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
53
|
+
path.image(
|
|
54
|
+
"/og/:slug.png",
|
|
55
|
+
async (ctx) => {
|
|
56
|
+
const image = await generateOgImage(ctx.params.slug);
|
|
57
|
+
return new Response(image, {
|
|
58
|
+
headers: {
|
|
59
|
+
"Content-Type": "image/png",
|
|
60
|
+
"Cache-Control": "public, max-age=86400",
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
{ name: "ogImage" },
|
|
65
|
+
),
|
|
47
66
|
]);
|
|
48
67
|
```
|
|
49
68
|
|
|
50
69
|
## Available Tags
|
|
51
70
|
|
|
52
|
-
| Tag
|
|
53
|
-
|
|
54
|
-
| `json`
|
|
55
|
-
| `text`
|
|
56
|
-
| `html`
|
|
57
|
-
| `xml`
|
|
58
|
-
| `md`
|
|
59
|
-
| `image`
|
|
60
|
-
| `stream` | `path.stream()` | Response
|
|
61
|
-
| `any`
|
|
71
|
+
| Tag | Usage | Handler returns | Auto-wrap |
|
|
72
|
+
| -------- | --------------- | ------------------ | ------------------------ |
|
|
73
|
+
| `json` | `path.json()` | plain object/array | `{ data: T }` envelope |
|
|
74
|
+
| `text` | `path.text()` | string | text/plain Response |
|
|
75
|
+
| `html` | `path.html()` | string | text/html Response |
|
|
76
|
+
| `xml` | `path.xml()` | string | application/xml Response |
|
|
77
|
+
| `md` | `path.md()` | string | text/markdown Response |
|
|
78
|
+
| `image` | `path.image()` | Response | pass-through |
|
|
79
|
+
| `stream` | `path.stream()` | Response | pass-through |
|
|
80
|
+
| `any` | `path.any()` | Response | pass-through |
|
|
62
81
|
|
|
63
82
|
## ResponseHandlerContext
|
|
64
83
|
|
|
@@ -67,14 +86,15 @@ Response route handlers receive a lighter context (no `ctx.use()`, no `ctx.res`)
|
|
|
67
86
|
```typescript
|
|
68
87
|
interface ResponseHandlerContext<TParams, TEnv> {
|
|
69
88
|
request: Request;
|
|
70
|
-
params: TParams;
|
|
71
|
-
env:
|
|
89
|
+
params: TParams; // Typed from URL pattern
|
|
90
|
+
env: TEnv; // Plain bindings (DB, KV, etc.)
|
|
72
91
|
searchParams: URLSearchParams;
|
|
73
92
|
url: URL;
|
|
74
93
|
pathname: string;
|
|
75
|
-
|
|
94
|
+
reverse: (name: string, params?: Record<string, string>) => string;
|
|
95
|
+
get: GetVariableFn; // Read middleware variables
|
|
76
96
|
header: (name: string, value: string) => void;
|
|
77
|
-
|
|
97
|
+
// Use cookies().set(name, value, opts) for cookie mutations (standalone API)
|
|
78
98
|
}
|
|
79
99
|
```
|
|
80
100
|
|
|
@@ -84,31 +104,39 @@ String-returning handlers (json, text, html, xml, md) can set custom headers and
|
|
|
84
104
|
without constructing a full Response:
|
|
85
105
|
|
|
86
106
|
```typescript
|
|
87
|
-
path.md(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
107
|
+
path.md(
|
|
108
|
+
"/docs/:slug.md",
|
|
109
|
+
(ctx) => {
|
|
110
|
+
ctx.header("Cache-Control", "public, max-age=3600");
|
|
111
|
+
cookies().set("last-doc", ctx.params.slug, { path: "/" });
|
|
112
|
+
return `# ${ctx.params.slug}\n\nContent here.`;
|
|
113
|
+
},
|
|
114
|
+
{ name: "docs" },
|
|
115
|
+
);
|
|
92
116
|
```
|
|
93
117
|
|
|
94
|
-
Headers
|
|
118
|
+
Headers set via `ctx.header()` and cookies set via `cookies().set()` are merged into the
|
|
95
119
|
auto-wrapped Response. If the handler returns a `Response` directly, these are ignored
|
|
96
120
|
(use the Response headers instead).
|
|
97
121
|
|
|
98
|
-
### Environment
|
|
122
|
+
### Environment Access
|
|
99
123
|
|
|
100
|
-
`env`
|
|
124
|
+
`ctx.env` is always the plain bindings passed as TEnv to `createRouter<TEnv>()`:
|
|
101
125
|
|
|
102
126
|
```typescript
|
|
103
|
-
|
|
127
|
+
// createRouter<{ DB: D1Database; KV: KVNamespace }>({ ... })
|
|
104
128
|
|
|
105
129
|
// In a response handler:
|
|
106
|
-
path.json(
|
|
107
|
-
|
|
108
|
-
ctx
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
130
|
+
path.json(
|
|
131
|
+
"/api/data",
|
|
132
|
+
(ctx) => {
|
|
133
|
+
ctx.env.DB; // D1Database (plain bindings)
|
|
134
|
+
ctx.env.KV; // KVNamespace
|
|
135
|
+
// Variables are accessed via ctx.get("key") or ctx.get(ContextVar)
|
|
136
|
+
return { data: "ok" };
|
|
137
|
+
},
|
|
138
|
+
{ name: "data" },
|
|
139
|
+
);
|
|
112
140
|
```
|
|
113
141
|
|
|
114
142
|
## JSON Envelope
|
|
@@ -131,16 +159,22 @@ Throw `RouterError` to return structured error envelopes:
|
|
|
131
159
|
```typescript
|
|
132
160
|
import { RouterError } from "@rangojs/router";
|
|
133
161
|
|
|
134
|
-
path.json(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
162
|
+
path.json(
|
|
163
|
+
"/api/users/:id",
|
|
164
|
+
(ctx) => {
|
|
165
|
+
const user = users.get(ctx.params.id);
|
|
166
|
+
if (!user) {
|
|
167
|
+
throw new RouterError("NOT_FOUND", `User ${ctx.params.id} not found`, {
|
|
168
|
+
status: 404,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
if (!hasPermission(ctx)) {
|
|
172
|
+
throw new RouterError("FORBIDDEN", "Access denied", { status: 403 });
|
|
173
|
+
}
|
|
174
|
+
return user;
|
|
175
|
+
},
|
|
176
|
+
{ name: "user" },
|
|
177
|
+
);
|
|
144
178
|
```
|
|
145
179
|
|
|
146
180
|
### Returning Response Directly
|
|
@@ -148,15 +182,19 @@ path.json("/api/users/:id", (ctx) => {
|
|
|
148
182
|
JSON handlers can return `Response` to bypass auto-wrap (custom status, headers, streaming):
|
|
149
183
|
|
|
150
184
|
```typescript
|
|
151
|
-
path.json(
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
},
|
|
185
|
+
path.json(
|
|
186
|
+
"/api/export",
|
|
187
|
+
(ctx) => {
|
|
188
|
+
const csv = generateCsv();
|
|
189
|
+
return new Response(csv, {
|
|
190
|
+
headers: {
|
|
191
|
+
"Content-Type": "text/csv",
|
|
192
|
+
"Content-Disposition": "attachment; filename=export.csv",
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
},
|
|
196
|
+
{ name: "export" },
|
|
197
|
+
);
|
|
160
198
|
```
|
|
161
199
|
|
|
162
200
|
## Client-Side Type Safety
|
|
@@ -272,18 +310,29 @@ A self-contained module with RSC pages + JSON APIs, mountable via `include()`:
|
|
|
272
310
|
import { urls, RouterError } from "@rangojs/router";
|
|
273
311
|
|
|
274
312
|
export const blogApiPatterns = urls(({ path }) => [
|
|
275
|
-
path.json(
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
path.json(
|
|
285
|
-
|
|
286
|
-
|
|
313
|
+
path.json(
|
|
314
|
+
"/stats",
|
|
315
|
+
(ctx) => ({
|
|
316
|
+
views: 1200,
|
|
317
|
+
visitors: 450,
|
|
318
|
+
}),
|
|
319
|
+
{ name: "stats" },
|
|
320
|
+
),
|
|
321
|
+
|
|
322
|
+
path.json(
|
|
323
|
+
"/:slug/likes",
|
|
324
|
+
(ctx) => ({
|
|
325
|
+
slug: ctx.params.slug,
|
|
326
|
+
count: 42,
|
|
327
|
+
}),
|
|
328
|
+
{ name: "likes" },
|
|
329
|
+
),
|
|
330
|
+
|
|
331
|
+
path.json(
|
|
332
|
+
"/:slug/comments",
|
|
333
|
+
(ctx) => [{ id: "c1", body: "Great post", author: "alice" }],
|
|
334
|
+
{ name: "comments" },
|
|
335
|
+
),
|
|
287
336
|
]);
|
|
288
337
|
|
|
289
338
|
// blog/urls.tsx
|
|
@@ -333,13 +382,17 @@ Response route handlers inside a mounted module can reference local names:
|
|
|
333
382
|
|
|
334
383
|
```typescript
|
|
335
384
|
// Inside blogApiPatterns handler
|
|
336
|
-
path(
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
385
|
+
path(
|
|
386
|
+
"/:slug/likes",
|
|
387
|
+
(ctx) => {
|
|
388
|
+
// ctx.reverse resolves names relative to the mount point
|
|
389
|
+
const commentsUrl = ctx.reverse("comments", { slug: ctx.params.slug });
|
|
390
|
+
// -> "/blog/api/my-post/comments"
|
|
391
|
+
|
|
392
|
+
return { slug: ctx.params.slug, count: 42, commentsUrl };
|
|
393
|
+
},
|
|
394
|
+
{ name: "likes" },
|
|
395
|
+
);
|
|
343
396
|
```
|
|
344
397
|
|
|
345
398
|
## Content Negotiation
|
package/skills/route/SKILL.md
CHANGED
|
@@ -74,19 +74,19 @@ path("/product/:slug", async (ctx) => {
|
|
|
74
74
|
|
|
75
75
|
```typescript
|
|
76
76
|
path("/product/:slug", ProductPage, {
|
|
77
|
-
name: "product",
|
|
78
|
-
})
|
|
77
|
+
name: "product", // Route name for href() and navigation
|
|
78
|
+
});
|
|
79
79
|
```
|
|
80
80
|
|
|
81
81
|
### Typed Search Params
|
|
82
82
|
|
|
83
|
-
Add a `search` schema to get typed `ctx.
|
|
83
|
+
Add a `search` schema to get typed `ctx.search`:
|
|
84
84
|
|
|
85
85
|
```typescript
|
|
86
86
|
path("/search", SearchPage, {
|
|
87
87
|
name: "search",
|
|
88
88
|
search: { q: "string", page: "number?", sort: "string?" },
|
|
89
|
-
})
|
|
89
|
+
});
|
|
90
90
|
```
|
|
91
91
|
|
|
92
92
|
Use `Handler<"name">` for typed search params (resolves from the generated route map automatically):
|
|
@@ -95,23 +95,24 @@ Use `Handler<"name">` for typed search params (resolves from the generated route
|
|
|
95
95
|
import type { Handler } from "@rangojs/router";
|
|
96
96
|
|
|
97
97
|
export const SearchPage: Handler<"search"> = (ctx) => {
|
|
98
|
-
// ctx.
|
|
99
|
-
const { q, page, sort } = ctx.
|
|
98
|
+
// ctx.search is typed: { q: string; page?: number; sort?: string }
|
|
99
|
+
const { q, page, sort } = ctx.search;
|
|
100
|
+
// ctx.searchParams is always URLSearchParams
|
|
100
101
|
return <SearchResults q={q} page={page} sort={sort} />;
|
|
101
102
|
};
|
|
102
103
|
```
|
|
103
104
|
|
|
104
105
|
Supported types: `"string"`, `"number"`, `"boolean"`, with `?` suffix for optional.
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
Missing params are `undefined` regardless of required/optional. The required/optional
|
|
107
|
+
distinction is a consumer-facing contract (for `href()` and `reverse()` autocomplete).
|
|
107
108
|
|
|
108
109
|
Use `RouteSearchParams<"name">` and `RouteParams<"name">` to extract types for props:
|
|
109
110
|
|
|
110
111
|
```typescript
|
|
111
112
|
import type { RouteSearchParams, RouteParams } from "@rangojs/router";
|
|
112
113
|
|
|
113
|
-
type SP = RouteSearchParams<"search">;
|
|
114
|
-
type P = RouteParams<"blogPost">;
|
|
114
|
+
type SP = RouteSearchParams<"search">; // { q: string; page?: number; sort?: string }
|
|
115
|
+
type P = RouteParams<"blogPost">; // { slug: string }
|
|
115
116
|
```
|
|
116
117
|
|
|
117
118
|
## Route Children
|
|
@@ -126,19 +127,193 @@ path("/product/:slug", ProductPage, { name: "product" }, () => [
|
|
|
126
127
|
])
|
|
127
128
|
```
|
|
128
129
|
|
|
130
|
+
## Handler Data Ownership
|
|
131
|
+
|
|
132
|
+
When a route has children (orphan layouts, parallels), the handler executes
|
|
133
|
+
first. Use `ctx.set(key, value)` to share data with children, who read it
|
|
134
|
+
via `ctx.get(key)`. Caching wraps all segments together, so either all run
|
|
135
|
+
or none do.
|
|
136
|
+
|
|
137
|
+
### Typed context variables with createVar
|
|
138
|
+
|
|
139
|
+
Use `createVar<T>()` to create a typed token for `ctx.set()`/`ctx.get()`.
|
|
140
|
+
The token is imported by both the handler (producer) and layout (consumer),
|
|
141
|
+
making the data contract explicit and compile-time verified:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import { createVar } from "@rangojs/router";
|
|
145
|
+
import { Outlet, ParallelOutlet } from "@rangojs/router/client";
|
|
146
|
+
|
|
147
|
+
// Typed token -- shared between handler and layout
|
|
148
|
+
interface DashboardData {
|
|
149
|
+
title: string;
|
|
150
|
+
stats: { views: number };
|
|
151
|
+
}
|
|
152
|
+
const Dashboard = createVar<DashboardData>();
|
|
153
|
+
|
|
154
|
+
path("/dashboard/:id", async (ctx) => {
|
|
155
|
+
const data = await fetchDashboard(ctx.params.id);
|
|
156
|
+
ctx.set(Dashboard, data); // type-checked
|
|
157
|
+
return <DashboardPage data={data} />;
|
|
158
|
+
}, { name: "dashboard" }, () => [
|
|
159
|
+
layout((ctx) => {
|
|
160
|
+
const data = ctx.get(Dashboard); // typed as DashboardData | undefined
|
|
161
|
+
return (
|
|
162
|
+
<div>
|
|
163
|
+
<h1>{data?.title}</h1>
|
|
164
|
+
<Outlet />
|
|
165
|
+
<ParallelOutlet name="@sidebar" />
|
|
166
|
+
</div>
|
|
167
|
+
);
|
|
168
|
+
}),
|
|
169
|
+
parallel({
|
|
170
|
+
"@sidebar": (ctx) => {
|
|
171
|
+
const data = ctx.get(Dashboard);
|
|
172
|
+
return <Sidebar stats={data?.stats} />;
|
|
173
|
+
},
|
|
174
|
+
}),
|
|
175
|
+
])
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
String keys still work (`ctx.set("key", value)` / `ctx.get("key")`), but
|
|
179
|
+
`createVar<T>()` is preferred for type safety.
|
|
180
|
+
|
|
181
|
+
Only route handlers and middleware can call `ctx.set()`. Layouts, parallels,
|
|
182
|
+
and intercepts can only read via `ctx.get()`.
|
|
183
|
+
|
|
184
|
+
### Revalidation Contracts for Handler Data
|
|
185
|
+
|
|
186
|
+
Handler-first guarantees apply within a single full render pass. For partial
|
|
187
|
+
action revalidation, define named revalidation contracts and reuse them on both
|
|
188
|
+
the producer route and the consumer child segments.
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
// revalidation-contracts.ts
|
|
192
|
+
export const revalidateCheckoutData = ({ actionId }) =>
|
|
193
|
+
actionId?.includes("src/actions/checkout.ts#") ?? false;
|
|
194
|
+
|
|
195
|
+
path("/checkout", CheckoutPage, { name: "checkout" }, () => [
|
|
196
|
+
revalidate(revalidateCheckoutData), // producer (route handler) reruns
|
|
197
|
+
layout(CheckoutLayout, () => [
|
|
198
|
+
revalidate(revalidateCheckoutData), // consumer reruns
|
|
199
|
+
parallel({ "@summary": CheckoutSummary }, () => [
|
|
200
|
+
revalidate(revalidateCheckoutData),
|
|
201
|
+
]),
|
|
202
|
+
]),
|
|
203
|
+
]);
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
If children depend on multiple upstream domains, compose multiple contracts on
|
|
207
|
+
the same segment (`revalidateAuthData`, `revalidateCheckoutData`, and so on).
|
|
208
|
+
|
|
209
|
+
For cleaner route trees, expose contract helpers and spread them:
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
import { revalidate } from "@rangojs/router";
|
|
213
|
+
|
|
214
|
+
export const revalidateCheckout = () => [revalidate(revalidateCheckoutData)];
|
|
215
|
+
|
|
216
|
+
path("/checkout", CheckoutPage, { name: "checkout" }, () => [
|
|
217
|
+
revalidateCheckout(),
|
|
218
|
+
layout(CheckoutLayout, () => [revalidateCheckout()]),
|
|
219
|
+
]);
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
For scope/revalidation guarantees and non-guarantees, see:
|
|
223
|
+
[docs/execution-model.md](../../docs/internal/execution-model.md)
|
|
224
|
+
|
|
225
|
+
## Redirects
|
|
226
|
+
|
|
227
|
+
### Basic redirect
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
import { redirect } from "@rangojs/router";
|
|
231
|
+
|
|
232
|
+
path("/old-page", () => redirect("/new-page"), { name: "oldPage" });
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Redirect with custom status
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
path("/moved", () => redirect("/new-location", 301), { name: "moved" });
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Redirect with location state
|
|
242
|
+
|
|
243
|
+
Carry typed state through redirects (e.g. flash messages):
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
import { redirect, createLocationState } from "@rangojs/router";
|
|
247
|
+
|
|
248
|
+
export const FlashMessage = createLocationState<{ text: string }>({
|
|
249
|
+
flash: true,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
path(
|
|
253
|
+
"/save",
|
|
254
|
+
(ctx) => {
|
|
255
|
+
// ... save logic
|
|
256
|
+
return redirect("/dashboard", {
|
|
257
|
+
state: [FlashMessage({ text: "Item saved!" })],
|
|
258
|
+
});
|
|
259
|
+
},
|
|
260
|
+
{ name: "save" },
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
// With custom status + state
|
|
264
|
+
path(
|
|
265
|
+
"/action",
|
|
266
|
+
(ctx) => {
|
|
267
|
+
return redirect("/target", {
|
|
268
|
+
status: 303,
|
|
269
|
+
state: [FlashMessage({ text: "Action complete" })],
|
|
270
|
+
});
|
|
271
|
+
},
|
|
272
|
+
{ name: "action" },
|
|
273
|
+
);
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Read the state on the target page with `useLocationState(FlashMessage)`. The
|
|
277
|
+
`{ flash: true }` option makes it auto-clear. Without `{ flash: true }`,
|
|
278
|
+
state persists on back/forward. See `/hooks` for details.
|
|
279
|
+
|
|
280
|
+
### ctx.setLocationState()
|
|
281
|
+
|
|
282
|
+
Attach location state to any server response (not just redirects):
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
path("/dashboard", (ctx) => {
|
|
286
|
+
ctx.setLocationState(ServerInfo({ data: "welcome" }));
|
|
287
|
+
return <Dashboard />;
|
|
288
|
+
}, { name: "dashboard" })
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
State flows to the browser via the RSC payload and is merged into
|
|
292
|
+
`history.pushState()`. Only works for SPA (partial) navigations.
|
|
293
|
+
|
|
129
294
|
## Handler Context
|
|
130
295
|
|
|
131
296
|
Every handler receives a context object:
|
|
132
297
|
|
|
133
298
|
```typescript
|
|
134
299
|
interface HandlerContext<TParams = {}, TEnv = DefaultEnv, TSearch = {}> {
|
|
135
|
-
params: TParams;
|
|
136
|
-
request: Request;
|
|
137
|
-
searchParams: URLSearchParams
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
300
|
+
params: TParams; // URL parameters
|
|
301
|
+
request: Request; // Original request
|
|
302
|
+
searchParams: URLSearchParams; // Query params (always URLSearchParams)
|
|
303
|
+
search: {} | ResolveSearchSchema<TSearch>; // Typed search params (from search schema)
|
|
304
|
+
url: URL; // Parsed URL
|
|
305
|
+
env: TEnv; // Environment (bindings + variables)
|
|
306
|
+
set(key: string, value: any): void; // Set context variable (untyped string key)
|
|
307
|
+
set<T>(contextVar: ContextVar<T>, value: T): void; // Set typed context variable
|
|
308
|
+
get(key: string): any; // Read context variable (untyped string key)
|
|
309
|
+
get<T>(contextVar: ContextVar<T>): T | undefined; // Read typed context variable
|
|
310
|
+
use<T>(handle: Handle<T>): T; // Access handles
|
|
311
|
+
reverse(
|
|
312
|
+
name: string,
|
|
313
|
+
params?: Record<string, string>,
|
|
314
|
+
search?: Record<string, unknown>,
|
|
315
|
+
): string; // URL generation
|
|
316
|
+
setLocationState(entries: LocationStateEntry[]): void; // Attach state to response
|
|
142
317
|
}
|
|
143
318
|
```
|
|
144
319
|
|
|
@@ -152,8 +327,8 @@ path("/product/:slug", (ctx) => {
|
|
|
152
327
|
// Access query params (untyped - use search schema for typed access)
|
|
153
328
|
const tab = ctx.searchParams.get("tab");
|
|
154
329
|
|
|
155
|
-
// Access
|
|
156
|
-
const db = ctx.env.
|
|
330
|
+
// Access platform bindings
|
|
331
|
+
const db = ctx.env.DB;
|
|
157
332
|
|
|
158
333
|
// Access handles
|
|
159
334
|
const breadcrumbs = ctx.use(Breadcrumbs);
|
|
@@ -180,8 +355,7 @@ urls(({ path, layout }) => [
|
|
|
180
355
|
## Complete Example
|
|
181
356
|
|
|
182
357
|
```typescript
|
|
183
|
-
import { urls } from "@rangojs/router";
|
|
184
|
-
import { Breadcrumbs } from "./handles/breadcrumbs";
|
|
358
|
+
import { urls, Breadcrumbs } from "@rangojs/router";
|
|
185
359
|
|
|
186
360
|
export const urlpatterns = urls(({ path, layout, loader, loading }) => [
|
|
187
361
|
// Simple route
|