@rangojs/router 0.0.0-experimental.10 → 0.0.0-experimental.100
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 +1037 -4
- package/dist/bin/rango.js +1619 -157
- package/dist/vite/index.js +5762 -2301
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +71 -63
- package/skills/breadcrumbs/SKILL.md +252 -0
- package/skills/cache-guide/SKILL.md +294 -0
- package/skills/caching/SKILL.md +93 -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/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +367 -71
- package/skills/host-router/SKILL.md +218 -0
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +176 -8
- package/skills/layout/SKILL.md +124 -3
- package/skills/links/SKILL.md +304 -25
- package/skills/loader/SKILL.md +474 -47
- package/skills/middleware/SKILL.md +207 -37
- package/skills/migrate-nextjs/SKILL.md +562 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +15 -11
- package/skills/parallel/SKILL.md +272 -1
- package/skills/prerender/SKILL.md +467 -65
- package/skills/rango/SKILL.md +89 -21
- package/skills/response-routes/SKILL.md +152 -91
- package/skills/route/SKILL.md +305 -14
- package/skills/router-setup/SKILL.md +210 -32
- package/skills/server-actions/SKILL.md +739 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +333 -86
- package/skills/use-cache/SKILL.md +324 -0
- package/skills/view-transitions/SKILL.md +212 -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/app-shell.ts +52 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +136 -68
- 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 +55 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +374 -561
- package/src/browser/navigation-client.ts +228 -70
- package/src/browser/navigation-store.ts +97 -55
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +376 -315
- package/src/browser/prefetch/cache.ts +314 -0
- package/src/browser/prefetch/fetch.ts +282 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +191 -0
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +152 -0
- package/src/browser/react/Link.tsx +255 -71
- package/src/browser/react/NavigationProvider.tsx +152 -24
- package/src/browser/react/context.ts +11 -0
- package/src/browser/react/filter-segment-order.ts +55 -0
- package/src/browser/react/index.ts +15 -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 +30 -120
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-navigation.ts +44 -65
- package/src/browser/react/use-params.ts +78 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-reverse.ts +99 -0
- package/src/browser/react/use-router.ts +83 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +85 -99
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +246 -64
- package/src/browser/scroll-restoration.ts +127 -52
- package/src/browser/segment-reconciler.ts +243 -0
- package/src/browser/segment-structure-assert.ts +16 -0
- package/src/browser/server-action-bridge.ts +510 -603
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +158 -48
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +84 -23
- package/src/build/generate-route-types.ts +39 -828
- package/src/build/index.ts +4 -5
- package/src/build/route-trie.ts +85 -32
- 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 +418 -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 +618 -0
- package/src/build/route-types/scan-filter.ts +85 -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 +167 -307
- package/src/cache/cf/cf-cache-store.ts +573 -21
- 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 +153 -0
- package/src/cache/types.ts +72 -122
- package/src/client.rsc.tsx +6 -1
- package/src/client.tsx +118 -302
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +156 -0
- package/src/debug.ts +19 -9
- package/src/errors.ts +77 -7
- package/src/handle.ts +55 -10
- 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 +138 -21
- package/src/index.ts +206 -51
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +25 -143
- package/src/loader.ts +27 -10
- package/src/network-error-thrower.tsx +3 -1
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +4 -2
- package/src/prerender/store.ts +159 -13
- package/src/prerender.ts +397 -29
- package/src/response-utils.ts +28 -0
- package/src/reverse.ts +231 -121
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- package/src/route-definition/dsl-helpers.ts +1134 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +483 -0
- package/src/route-definition/index.ts +55 -0
- package/src/route-definition/redirect.ts +101 -0
- package/src/route-definition/resolve-handler-use.ts +155 -0
- package/src/route-definition.ts +1 -1431
- package/src/route-map-builder.ts +162 -123
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +66 -9
- package/src/router/content-negotiation.ts +215 -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 +418 -86
- package/src/router/intercept-resolution.ts +35 -20
- package/src/router/lazy-includes.ts +237 -0
- package/src/router/loader-resolution.ts +359 -128
- package/src/router/logging.ts +251 -0
- package/src/router/manifest.ts +98 -32
- package/src/router/match-api.ts +196 -261
- package/src/router/match-context.ts +4 -2
- package/src/router/match-handlers.ts +441 -0
- package/src/router/match-middleware/background-revalidation.ts +108 -93
- package/src/router/match-middleware/cache-lookup.ts +415 -86
- package/src/router/match-middleware/cache-store.ts +91 -29
- package/src/router/match-middleware/intercept-resolution.ts +48 -21
- package/src/router/match-middleware/segment-resolution.ts +73 -9
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +154 -35
- package/src/router/metrics.ts +240 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +209 -0
- package/src/router/middleware.ts +373 -371
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +292 -52
- package/src/router/prerender-match.ts +502 -0
- package/src/router/preview-match.ts +98 -0
- package/src/router/request-classification.ts +310 -0
- package/src/router/revalidation.ts +152 -39
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +41 -21
- package/src/router/router-interfaces.ts +484 -0
- package/src/router/router-options.ts +618 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +756 -0
- package/src/router/segment-resolution/helpers.ts +268 -0
- package/src/router/segment-resolution/loader-cache.ts +199 -0
- package/src/router/segment-resolution/revalidation.ts +1407 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -1315
- package/src/router/segment-wrappers.ts +291 -0
- package/src/router/substitute-pattern-params.ts +56 -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 +111 -39
- package/src/router/types.ts +17 -9
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +642 -2011
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +864 -1114
- package/src/rsc/helpers.ts +181 -19
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +229 -0
- package/src/rsc/manifest-init.ts +90 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +395 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +360 -0
- package/src/rsc/rsc-rendering.ts +256 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +360 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +52 -11
- package/src/search-params.ts +230 -0
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +187 -38
- package/src/server/context.ts +333 -59
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +113 -15
- package/src/server/loader-registry.ts +24 -64
- package/src/server/request-context.ts +603 -109
- package/src/server.ts +35 -155
- package/src/ssr/index.tsx +107 -30
- package/src/static-handler.ts +126 -0
- 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 +764 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +209 -0
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +120 -0
- package/src/types/segments.ts +167 -0
- package/src/types.ts +1 -1757
- package/src/urls/include-helper.ts +207 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +372 -0
- package/src/urls/path-helper.ts +364 -0
- package/src/urls/pattern-types.ts +107 -0
- package/src/urls/response-types.ts +108 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -1282
- package/src/use-loader.tsx +161 -81
- package/src/vite/debug.ts +184 -0
- package/src/vite/discovery/bundle-postprocess.ts +181 -0
- package/src/vite/discovery/discover-routers.ts +376 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +486 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +73 -0
- package/src/vite/discovery/state.ts +117 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +15 -2063
- package/src/vite/plugin-types.ts +103 -0
- package/src/vite/plugins/cjs-to-esm.ts +98 -0
- package/src/vite/plugins/client-ref-dedup.ts +131 -0
- package/src/vite/plugins/client-ref-hashing.ts +117 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
- package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +107 -64
- package/src/vite/plugins/expose-id-utils.ts +299 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +127 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +816 -0
- package/src/vite/plugins/performance-tracks.ts +96 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/use-cache-transform.ts +336 -0
- package/src/vite/plugins/version-injector.ts +109 -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 +497 -0
- package/src/vite/router-discovery.ts +1423 -0
- package/src/vite/utils/ast-handler-extract.ts +517 -0
- 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/utils/package-resolution.ts +161 -0
- package/src/vite/utils/prerender-utils.ts +222 -0
- package/src/vite/utils/shared-utils.ts +170 -0
- package/CLAUDE.md +0 -43
- 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/urls.gen.ts +0 -8
- package/src/vite/expose-handle-id.ts +0 -209
- package/src/vite/expose-loader-id.ts +0 -426
- package/src/vite/expose-location-state-id.ts +0 -177
- package/src/vite/expose-prerender-handler-id.ts +0 -429
- package/src/vite/package-resolution.ts +0 -125
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
package/skills/route/SKILL.md
CHANGED
|
@@ -33,6 +33,26 @@ urls(({ path }) => [
|
|
|
33
33
|
]);
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
### Optional URL params at runtime
|
|
37
|
+
|
|
38
|
+
Absent optional params are **omitted from `ctx.params`** — `ctx.params.<name>`
|
|
39
|
+
reads as `undefined`, matching the `RouteParams<"name">` type
|
|
40
|
+
(`{ query?: string }`). Use `??` to default and `=== undefined` to check
|
|
41
|
+
absence:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
path("/search/:query?", (ctx) => {
|
|
45
|
+
const query = ctx.params.query ?? ""; // works — undefined coalesces
|
|
46
|
+
if (ctx.params.query === undefined) return <EmptySearch />;
|
|
47
|
+
return <Results query={ctx.params.query} />;
|
|
48
|
+
}, { name: "search" });
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
For the common pattern of an optional locale prefix
|
|
52
|
+
(`include("/:locale?", routes)`) and the wider react-intl integration —
|
|
53
|
+
locale detection, fallback chains, URL generation with absent locale —
|
|
54
|
+
see `/i18n`.
|
|
55
|
+
|
|
36
56
|
## Route Handler Patterns
|
|
37
57
|
|
|
38
58
|
### Component Function
|
|
@@ -74,8 +94,45 @@ path("/product/:slug", async (ctx) => {
|
|
|
74
94
|
|
|
75
95
|
```typescript
|
|
76
96
|
path("/product/:slug", ProductPage, {
|
|
77
|
-
name: "product",
|
|
78
|
-
})
|
|
97
|
+
name: "product", // Route name for href() and navigation
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Typed Search Params
|
|
102
|
+
|
|
103
|
+
Add a `search` schema to get typed `ctx.search`:
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
path("/search", SearchPage, {
|
|
107
|
+
name: "search",
|
|
108
|
+
search: { q: "string", page: "number?", sort: "string?" },
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Use `Handler<"name">` for typed search params (resolves from the generated route map automatically):
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import type { Handler } from "@rangojs/router";
|
|
116
|
+
|
|
117
|
+
export const SearchPage: Handler<"search"> = (ctx) => {
|
|
118
|
+
// ctx.search is typed: { q: string; page?: number; sort?: string }
|
|
119
|
+
const { q, page, sort } = ctx.search;
|
|
120
|
+
// ctx.searchParams is always URLSearchParams
|
|
121
|
+
return <SearchResults q={q} page={page} sort={sort} />;
|
|
122
|
+
};
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Supported types: `"string"`, `"number"`, `"boolean"`, with `?` suffix for optional.
|
|
126
|
+
Missing params are `undefined` regardless of required/optional. The required/optional
|
|
127
|
+
distinction is a consumer-facing contract (for `href()` and `reverse()` autocomplete).
|
|
128
|
+
|
|
129
|
+
Use `RouteSearchParams<"name">` and `RouteParams<"name">` to extract types for props:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import type { RouteSearchParams, RouteParams } from "@rangojs/router";
|
|
133
|
+
|
|
134
|
+
type SP = RouteSearchParams<"search">; // { q: string; page?: number; sort?: string }
|
|
135
|
+
type P = RouteParams<"blogPost">; // { slug: string }
|
|
79
136
|
```
|
|
80
137
|
|
|
81
138
|
## Route Children
|
|
@@ -90,17 +147,224 @@ path("/product/:slug", ProductPage, { name: "product" }, () => [
|
|
|
90
147
|
])
|
|
91
148
|
```
|
|
92
149
|
|
|
150
|
+
## Handler Data Ownership
|
|
151
|
+
|
|
152
|
+
When a route has children (orphan layouts, parallels), the handler executes
|
|
153
|
+
first. Use `ctx.set(key, value)` to share data with children, who read it
|
|
154
|
+
via `ctx.get(key)`. Caching wraps all segments together, so either all run
|
|
155
|
+
or none do.
|
|
156
|
+
|
|
157
|
+
### Typed context variables with createVar
|
|
158
|
+
|
|
159
|
+
Use `createVar<T>()` to create a typed token for `ctx.set()`/`ctx.get()`.
|
|
160
|
+
The token is imported by both the handler (producer) and layout (consumer),
|
|
161
|
+
making the data contract explicit and compile-time verified:
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
import { createVar } from "@rangojs/router";
|
|
165
|
+
import { Outlet, ParallelOutlet } from "@rangojs/router/client";
|
|
166
|
+
|
|
167
|
+
// Typed token -- shared between handler and layout
|
|
168
|
+
interface DashboardData {
|
|
169
|
+
title: string;
|
|
170
|
+
stats: { views: number };
|
|
171
|
+
}
|
|
172
|
+
const Dashboard = createVar<DashboardData>();
|
|
173
|
+
|
|
174
|
+
path("/dashboard/:id", async (ctx) => {
|
|
175
|
+
const data = await fetchDashboard(ctx.params.id);
|
|
176
|
+
ctx.set(Dashboard, data); // type-checked
|
|
177
|
+
return <DashboardPage data={data} />;
|
|
178
|
+
}, { name: "dashboard" }, () => [
|
|
179
|
+
layout((ctx) => {
|
|
180
|
+
const data = ctx.get(Dashboard); // typed as DashboardData | undefined
|
|
181
|
+
return (
|
|
182
|
+
<div>
|
|
183
|
+
<h1>{data?.title}</h1>
|
|
184
|
+
<Outlet />
|
|
185
|
+
<ParallelOutlet name="@sidebar" />
|
|
186
|
+
</div>
|
|
187
|
+
);
|
|
188
|
+
}),
|
|
189
|
+
parallel({
|
|
190
|
+
"@sidebar": (ctx) => {
|
|
191
|
+
const data = ctx.get(Dashboard);
|
|
192
|
+
return <Sidebar stats={data?.stats} />;
|
|
193
|
+
},
|
|
194
|
+
}),
|
|
195
|
+
])
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
String keys still work (`ctx.set("key", value)` / `ctx.get("key")`), but
|
|
199
|
+
`createVar<T>()` is preferred for type safety.
|
|
200
|
+
|
|
201
|
+
Only route handlers and middleware can call `ctx.set()`. Layouts, parallels,
|
|
202
|
+
and intercepts can only read via `ctx.get()`.
|
|
203
|
+
|
|
204
|
+
#### Non-cacheable context variables
|
|
205
|
+
|
|
206
|
+
Mark a var as non-cacheable when it holds inherently request-specific data
|
|
207
|
+
(sessions, auth tokens, per-request IDs). There are two ways:
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
// Var-level: every value written to this var is non-cacheable
|
|
211
|
+
const Session = createVar<SessionData>({ cache: false });
|
|
212
|
+
|
|
213
|
+
// Write-level: escalate a normally-cacheable var for this specific write
|
|
214
|
+
const Theme = createVar<string>();
|
|
215
|
+
ctx.set(Theme, userTheme, { cache: false });
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
"Least cacheable wins" — if either the var definition or the write site says
|
|
219
|
+
`cache: false`, the value is non-cacheable.
|
|
220
|
+
|
|
221
|
+
Reading a non-cacheable var inside `cache()` or `"use cache"` throws at
|
|
222
|
+
runtime. This prevents request-specific data from leaking into cached output:
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
// This throws — Session is non-cacheable
|
|
226
|
+
async function CachedWidget(ctx) {
|
|
227
|
+
"use cache";
|
|
228
|
+
const session = ctx.get(Session); // Error: non-cacheable var read inside cache scope
|
|
229
|
+
return <Widget />;
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Cacheable vars (the default) can be read freely inside cache scopes.
|
|
234
|
+
|
|
235
|
+
### Revalidation Contracts for Handler Data
|
|
236
|
+
|
|
237
|
+
Handler-first guarantees apply within a single full render pass. For partial
|
|
238
|
+
action revalidation, define named revalidation contracts and reuse them on both
|
|
239
|
+
the producer route and the consumer child segments.
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
// revalidation-contracts.ts
|
|
243
|
+
export const revalidateCheckoutData = ({ actionId }) =>
|
|
244
|
+
actionId?.includes("src/actions/checkout.ts#") ?? false;
|
|
245
|
+
|
|
246
|
+
path("/checkout", CheckoutPage, { name: "checkout" }, () => [
|
|
247
|
+
revalidate(revalidateCheckoutData), // producer (route handler) reruns
|
|
248
|
+
layout(CheckoutLayout, () => [
|
|
249
|
+
revalidate(revalidateCheckoutData), // consumer reruns
|
|
250
|
+
parallel({ "@summary": CheckoutSummary }, () => [
|
|
251
|
+
revalidate(revalidateCheckoutData),
|
|
252
|
+
]),
|
|
253
|
+
]),
|
|
254
|
+
]);
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
If children depend on multiple upstream domains, compose multiple contracts on
|
|
258
|
+
the same segment (`revalidateAuthData`, `revalidateCheckoutData`, and so on).
|
|
259
|
+
|
|
260
|
+
For cleaner route trees, expose contract helpers and spread them:
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
import { revalidate } from "@rangojs/router";
|
|
264
|
+
|
|
265
|
+
export const revalidateCheckout = () => [revalidate(revalidateCheckoutData)];
|
|
266
|
+
|
|
267
|
+
path("/checkout", CheckoutPage, { name: "checkout" }, () => [
|
|
268
|
+
revalidateCheckout(),
|
|
269
|
+
layout(CheckoutLayout, () => [revalidateCheckout()]),
|
|
270
|
+
]);
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
For scope/revalidation guarantees and non-guarantees, see:
|
|
274
|
+
[docs/execution-model.md](../../docs/internal/execution-model.md)
|
|
275
|
+
|
|
276
|
+
## Redirects
|
|
277
|
+
|
|
278
|
+
### Basic redirect
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
import { redirect } from "@rangojs/router";
|
|
282
|
+
|
|
283
|
+
path("/old-page", () => redirect("/new-page"), { name: "oldPage" });
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Redirect with custom status
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
path("/moved", () => redirect("/new-location", 301), { name: "moved" });
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Redirect with location state
|
|
293
|
+
|
|
294
|
+
Carry typed state through redirects (e.g. flash messages):
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
import { redirect, createLocationState } from "@rangojs/router";
|
|
298
|
+
|
|
299
|
+
export const FlashMessage = createLocationState<{ text: string }>({
|
|
300
|
+
flash: true,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
path(
|
|
304
|
+
"/save",
|
|
305
|
+
(ctx) => {
|
|
306
|
+
// ... save logic
|
|
307
|
+
return redirect("/dashboard", {
|
|
308
|
+
state: [FlashMessage({ text: "Item saved!" })],
|
|
309
|
+
});
|
|
310
|
+
},
|
|
311
|
+
{ name: "save" },
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
// With custom status + state
|
|
315
|
+
path(
|
|
316
|
+
"/action",
|
|
317
|
+
(ctx) => {
|
|
318
|
+
return redirect("/target", {
|
|
319
|
+
status: 303,
|
|
320
|
+
state: [FlashMessage({ text: "Action complete" })],
|
|
321
|
+
});
|
|
322
|
+
},
|
|
323
|
+
{ name: "action" },
|
|
324
|
+
);
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Read the state on the target page with `useLocationState(FlashMessage)`. The
|
|
328
|
+
`{ flash: true }` option makes it auto-clear. Without `{ flash: true }`,
|
|
329
|
+
state persists on back/forward. See `/hooks` for details.
|
|
330
|
+
|
|
331
|
+
### ctx.setLocationState()
|
|
332
|
+
|
|
333
|
+
Attach location state to any server response (not just redirects):
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
path("/dashboard", (ctx) => {
|
|
337
|
+
ctx.setLocationState(ServerInfo({ data: "welcome" }));
|
|
338
|
+
return <Dashboard />;
|
|
339
|
+
}, { name: "dashboard" })
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
State flows to the browser via the RSC payload and is merged into
|
|
343
|
+
`history.pushState()`. Only works for SPA (partial) navigations.
|
|
344
|
+
|
|
93
345
|
## Handler Context
|
|
94
346
|
|
|
95
347
|
Every handler receives a context object:
|
|
96
348
|
|
|
97
349
|
```typescript
|
|
98
|
-
interface HandlerContext<TParams =
|
|
99
|
-
params: TParams;
|
|
100
|
-
request: Request;
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
350
|
+
interface HandlerContext<TParams = {}, TEnv = DefaultEnv, TSearch = {}> {
|
|
351
|
+
params: TParams; // URL parameters
|
|
352
|
+
request: Request; // Original request
|
|
353
|
+
searchParams: URLSearchParams; // Query params (always URLSearchParams)
|
|
354
|
+
search: {} | ResolveSearchSchema<TSearch>; // Typed search params (from search schema)
|
|
355
|
+
url: URL; // Parsed URL
|
|
356
|
+
env: TEnv; // Environment (bindings + variables)
|
|
357
|
+
set(key: string, value: any): void; // Set context variable (untyped string key)
|
|
358
|
+
set<T>(contextVar: ContextVar<T>, value: T): void; // Set typed context variable
|
|
359
|
+
get(key: string): any; // Read context variable (untyped string key)
|
|
360
|
+
get<T>(contextVar: ContextVar<T>): T | undefined; // Read typed context variable
|
|
361
|
+
use<T>(handle: Handle<T>): T; // Access handles
|
|
362
|
+
reverse(
|
|
363
|
+
name: string,
|
|
364
|
+
params?: Record<string, string>,
|
|
365
|
+
search?: Record<string, unknown>,
|
|
366
|
+
): string; // URL generation
|
|
367
|
+
setLocationState(entries: LocationStateEntry[]): void; // Attach state to response
|
|
104
368
|
}
|
|
105
369
|
```
|
|
106
370
|
|
|
@@ -111,11 +375,11 @@ path("/product/:slug", (ctx) => {
|
|
|
111
375
|
// Access URL params
|
|
112
376
|
const { slug } = ctx.params;
|
|
113
377
|
|
|
114
|
-
// Access query params
|
|
115
|
-
const tab = ctx.
|
|
378
|
+
// Access query params (untyped - use search schema for typed access)
|
|
379
|
+
const tab = ctx.searchParams.get("tab");
|
|
116
380
|
|
|
117
|
-
// Access
|
|
118
|
-
const db = ctx.env.
|
|
381
|
+
// Access platform bindings
|
|
382
|
+
const db = ctx.env.DB;
|
|
119
383
|
|
|
120
384
|
// Access handles
|
|
121
385
|
const breadcrumbs = ctx.use(Breadcrumbs);
|
|
@@ -139,11 +403,38 @@ urls(({ path, layout }) => [
|
|
|
139
403
|
])
|
|
140
404
|
```
|
|
141
405
|
|
|
406
|
+
## View Transitions
|
|
407
|
+
|
|
408
|
+
A route can configure its own `transition()` — the wrap goes around the route's component itself (routes are leaves; they have no separate default outlet channel). If the route component renders a `<ParallelOutlet />` directly, that slot remains inside the route's VT subtree, so prefer mounting parallel slots in a layout when combining intercept modals with route-level transitions. See [skills/view-transitions](../view-transitions/SKILL.md) for examples and the wrap-location rules across layouts, routes, and slots.
|
|
409
|
+
|
|
410
|
+
## Handler-attached `.use`
|
|
411
|
+
|
|
412
|
+
Page handlers can carry their own loader, middleware, error boundaries, parallels, and other defaults via a `.use` callback — so the page is self-contained and reusable across mount sites without re-wiring the same items.
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
const ProductPage: Handler<"/product/:slug"> = async (ctx) => {
|
|
416
|
+
const product = await ctx.use(ProductLoader);
|
|
417
|
+
return <ProductView product={product} />;
|
|
418
|
+
};
|
|
419
|
+
ProductPage.use = () => [
|
|
420
|
+
loader(ProductLoader),
|
|
421
|
+
loading(<ProductSkeleton />),
|
|
422
|
+
middleware(async (ctx, next) => {
|
|
423
|
+
await next();
|
|
424
|
+
ctx.header("Cache-Control", "private, max-age=60");
|
|
425
|
+
}),
|
|
426
|
+
];
|
|
427
|
+
|
|
428
|
+
// Mount site has no per-page wiring — defaults travel with the handler.
|
|
429
|
+
path("/product/:slug", ProductPage, { name: "product" });
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Explicit `use()` at the mount site merges with `handler.use` (handler defaults first, explicit second). See [skills/handler-use](../handler-use/SKILL.md) for the merge order, allowed item types per mount site, and override semantics.
|
|
433
|
+
|
|
142
434
|
## Complete Example
|
|
143
435
|
|
|
144
436
|
```typescript
|
|
145
|
-
import { urls } from "@rangojs/router";
|
|
146
|
-
import { Breadcrumbs } from "./handles/breadcrumbs";
|
|
437
|
+
import { urls, Breadcrumbs } from "@rangojs/router";
|
|
147
438
|
|
|
148
439
|
export const urlpatterns = urls(({ path, layout, loader, loading }) => [
|
|
149
440
|
// Simple route
|