@rangojs/router 0.0.0-experimental.e9c0b2f2 → 0.0.0-experimental.ea9f40f2
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 +6 -10
- package/README.md +289 -938
- package/dist/bin/rango.js +271 -46
- package/dist/vite/index.js +673 -193
- package/package.json +10 -8
- package/skills/api-client/SKILL.md +1 -1
- package/skills/breadcrumbs/SKILL.md +31 -14
- package/skills/cache-guide/SKILL.md +5 -2
- package/skills/caching/SKILL.md +59 -4
- package/skills/catalog.json +271 -0
- package/skills/comparison/SKILL.md +50 -0
- package/skills/comparison/agents/openai.yaml +4 -0
- package/skills/comparison/references/framework-comparison.md +837 -0
- package/skills/composability/SKILL.md +83 -2
- package/skills/debug-manifest/SKILL.md +1 -1
- package/skills/defer-hydration/SKILL.md +235 -0
- package/skills/document-cache/SKILL.md +9 -1
- package/skills/fonts/SKILL.md +1 -1
- package/skills/handler-use/SKILL.md +8 -8
- package/skills/hooks/SKILL.md +54 -892
- package/skills/hooks/data.md +273 -0
- package/skills/hooks/handle-and-actions.md +103 -0
- package/skills/hooks/navigation.md +110 -0
- package/skills/hooks/outlets.md +41 -0
- package/skills/hooks/state.md +228 -0
- package/skills/hooks/urls.md +135 -0
- package/skills/host-router/SKILL.md +4 -4
- package/skills/i18n/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +46 -14
- package/skills/layout/SKILL.md +27 -10
- package/skills/links/SKILL.md +1 -1
- package/skills/loader/SKILL.md +23 -1
- package/skills/middleware/SKILL.md +7 -3
- package/skills/migrate-nextjs/SKILL.md +167 -6
- package/skills/migrate-react-router/SKILL.md +59 -677
- package/skills/migrate-react-router/cloudflare-workers.md +129 -0
- package/skills/migrate-react-router/component-migration.md +196 -0
- package/skills/migrate-react-router/data-and-actions.md +225 -0
- package/skills/migrate-react-router/route-mapping.md +271 -0
- package/skills/mime-routes/SKILL.md +1 -1
- package/skills/observability/SKILL.md +9 -1
- package/skills/parallel/SKILL.md +23 -4
- package/skills/ppr/SKILL.md +622 -0
- package/skills/prerender/SKILL.md +28 -18
- package/skills/rango/SKILL.md +84 -25
- package/skills/response-routes/SKILL.md +15 -1
- package/skills/route/SKILL.md +71 -4
- package/skills/router-setup/SKILL.md +14 -3
- package/skills/scripts/SKILL.md +1 -1
- package/skills/server-actions/SKILL.md +3 -2
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +1 -1
- package/skills/tailwind/SKILL.md +1 -1
- package/skills/testing/SKILL.md +2 -1
- package/skills/testing/handles.md +4 -2
- package/skills/testing/render-handler.md +15 -14
- package/skills/testing/reverse-and-types.md +8 -7
- package/skills/theme/SKILL.md +1 -1
- package/skills/typesafety/SKILL.md +45 -919
- package/skills/typesafety/env-and-bindings.md +254 -0
- package/skills/typesafety/generated-files-and-cli.md +335 -0
- package/skills/typesafety/params-and-search.md +153 -0
- package/skills/typesafety/route-types.md +209 -0
- package/skills/use-cache/SKILL.md +30 -3
- package/skills/vercel/SKILL.md +1 -1
- package/skills/view-transitions/SKILL.md +44 -1
- package/src/browser/event-controller.ts +62 -10
- package/src/browser/logging.ts +28 -0
- package/src/browser/merge-segment-loaders.ts +6 -4
- package/src/browser/navigation-bridge.ts +65 -16
- package/src/browser/navigation-client.ts +32 -2
- package/src/browser/navigation-store.ts +128 -14
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +76 -17
- package/src/browser/prefetch/cache.ts +51 -11
- package/src/browser/prefetch/fetch.ts +59 -21
- package/src/browser/prefetch/queue.ts +19 -4
- package/src/browser/react/Link.tsx +13 -3
- package/src/browser/react/NavigationProvider.tsx +108 -4
- package/src/browser/response-adapter.ts +38 -9
- package/src/browser/rsc-router.tsx +54 -4
- package/src/browser/scroll-restoration.ts +7 -5
- package/src/browser/segment-reconciler.ts +31 -21
- package/src/browser/server-action-bridge.ts +22 -10
- package/src/browser/types.ts +54 -1
- package/src/build/generate-manifest.ts +155 -131
- package/src/build/index.ts +3 -1
- package/src/build/route-trie.ts +35 -7
- package/src/build/route-types/include-resolution.ts +347 -47
- package/src/build/runtime-discovery.ts +4 -1
- package/src/cache/cache-key-utils.ts +29 -0
- package/src/cache/cache-runtime.ts +262 -71
- package/src/cache/cache-scope.ts +2 -17
- package/src/cache/cache-tag.ts +60 -14
- package/src/cache/cf/cf-cache-store.ts +243 -20
- package/src/cache/document-cache.ts +54 -21
- package/src/cache/index.ts +1 -0
- package/src/cache/memory-segment-store.ts +110 -3
- package/src/cache/profile-registry.ts +15 -0
- package/src/cache/read-through-swr.ts +15 -1
- package/src/cache/segment-codec.ts +4 -4
- package/src/cache/shell-snapshot.ts +417 -0
- package/src/cache/types.ts +158 -0
- package/src/cache/vercel/vercel-cache-store.ts +401 -124
- package/src/client.rsc.tsx +0 -3
- package/src/client.tsx +0 -3
- package/src/cloudflare/tracing.ts +7 -8
- package/src/defer.ts +11 -22
- package/src/handle.ts +37 -15
- package/src/handles/MetaTags.tsx +16 -82
- package/src/handles/breadcrumbs.ts +12 -14
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +7 -8
- package/src/handles/meta.ts +7 -44
- package/src/host/errors.ts +15 -0
- package/src/host/index.ts +1 -0
- package/src/index.rsc.ts +8 -2
- package/src/index.ts +19 -13
- package/src/internal-debug.ts +11 -8
- package/src/prerender.ts +17 -4
- package/src/redirect-origin.ts +14 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/route-content-wrapper.tsx +12 -5
- package/src/route-definition/dsl-helpers.ts +21 -32
- package/src/route-definition/helper-factories.ts +0 -2
- package/src/route-definition/helpers-types.ts +43 -43
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/resolve-handler-use.ts +0 -1
- package/src/route-definition/use-item-types.ts +3 -6
- package/src/route-map-builder.ts +41 -4
- package/src/route-types.ts +0 -5
- package/src/router/find-match.ts +86 -8
- package/src/router/instrument.ts +9 -4
- package/src/router/lazy-includes.ts +72 -12
- package/src/router/loader-resolution.ts +14 -2
- package/src/router/manifest.ts +56 -11
- package/src/router/match-api.ts +76 -32
- package/src/router/match-handlers.ts +181 -135
- package/src/router/match-middleware/background-revalidation.ts +40 -23
- package/src/router/match-middleware/cache-store.ts +39 -24
- package/src/router/match-result.ts +35 -15
- package/src/router/middleware.ts +64 -38
- package/src/router/navigation-snapshot.ts +7 -5
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +53 -64
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +11 -5
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +23 -8
- package/src/router/route-snapshot.ts +14 -2
- package/src/router/router-context.ts +3 -1
- package/src/router/router-interfaces.ts +32 -1
- package/src/router/router-options.ts +30 -0
- package/src/router/segment-resolution/fresh.ts +39 -3
- package/src/router/segment-resolution/loader-cache.ts +93 -2
- package/src/router/segment-resolution/loader-mask.ts +60 -0
- package/src/router/segment-resolution/loader-snapshot.ts +259 -0
- package/src/router/segment-resolution/mask-nested.ts +83 -0
- package/src/router/segment-resolution/revalidation.ts +3 -0
- package/src/router/segment-resolution/view-transition-default.ts +35 -15
- package/src/router/substitute-pattern-params.ts +54 -35
- package/src/router/telemetry-otel.ts +6 -8
- package/src/router/telemetry.ts +9 -1
- package/src/router/tracing.ts +14 -5
- package/src/router/trie-matching.ts +19 -11
- package/src/router/url-params.ts +13 -0
- package/src/router.ts +47 -16
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler.ts +60 -33
- package/src/rsc/manifest-init.ts +1 -1
- package/src/rsc/nonce.ts +10 -1
- package/src/rsc/progressive-enhancement.ts +61 -4
- package/src/rsc/redirect-guard.ts +2 -1
- package/src/rsc/rsc-rendering.ts +429 -37
- package/src/rsc/server-action.ts +25 -2
- package/src/rsc/shell-capture.ts +1190 -0
- package/src/rsc/shell-serve.ts +181 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +30 -0
- package/src/segment-loader-promise.ts +18 -0
- package/src/segment-system.tsx +149 -14
- package/src/server/context.ts +67 -9
- package/src/server/cookie-store.ts +73 -1
- package/src/server/loader-registry.ts +13 -1
- package/src/server/request-context.ts +169 -10
- package/src/ssr/index.tsx +462 -178
- package/src/ssr/inject-rsc-eager.ts +167 -0
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/testing/collect-handle.ts +14 -8
- package/src/testing/dispatch.ts +152 -40
- package/src/testing/generated-routes.ts +27 -11
- package/src/testing/index.ts +6 -0
- package/src/testing/render-handler.ts +14 -0
- package/src/testing/render-route.tsx +13 -10
- package/src/testing/run-transition-when.ts +164 -0
- package/src/theme/ThemeProvider.tsx +36 -26
- package/src/types/handler-context.ts +1 -1
- package/src/types/index.ts +2 -0
- package/src/types/route-config.ts +19 -7
- package/src/types/segments.ts +100 -0
- package/src/urls/include-helper.ts +10 -8
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +1 -0
- package/src/urls/path-helper-types.ts +44 -12
- package/src/urls/path-helper.ts +5 -0
- package/src/urls/pattern-types.ts +36 -0
- package/src/urls/type-extraction.ts +43 -18
- package/src/urls/urls-function.ts +0 -1
- package/src/vercel/tracing.ts +7 -7
- package/src/vite/discovery/dev-prerender-cache.ts +117 -0
- package/src/vite/discovery/discover-routers.ts +1 -1
- package/src/vite/discovery/discovery-errors.ts +61 -0
- package/src/vite/index.ts +7 -0
- package/src/vite/inject-client-debug.ts +88 -0
- package/src/vite/plugins/vercel-output.ts +114 -25
- package/src/vite/plugins/version-injector.ts +22 -7
- package/src/vite/plugins/virtual-entries.ts +80 -22
- package/src/vite/rango.ts +29 -19
- package/src/vite/router-discovery.ts +171 -43
- package/src/vite/utils/prerender-utils.ts +17 -4
- package/src/vite/utils/shared-utils.ts +47 -0
- package/src/network-error-thrower.tsx +0 -18
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# Environment, Context, and State Types
|
|
2
|
+
|
|
3
|
+
## Environment Type Setup
|
|
4
|
+
|
|
5
|
+
Define your app's environment for type-safe bindings and variables:
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
// env.ts
|
|
9
|
+
|
|
10
|
+
// Cloudflare bindings — passed as TEnv to createRouter<TEnv>()
|
|
11
|
+
export interface AppBindings {
|
|
12
|
+
DB: D1Database;
|
|
13
|
+
KV: KVNamespace;
|
|
14
|
+
CACHE: KVNamespace;
|
|
15
|
+
AI: Ai;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Variables set by middleware — declared via global namespace augmentation
|
|
19
|
+
export interface AppVariables {
|
|
20
|
+
user?: { id: string; email: string; role: string };
|
|
21
|
+
requestId?: string;
|
|
22
|
+
permissions?: string[];
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Using Environment Types
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
// router.tsx
|
|
30
|
+
import type { AppBindings, AppVariables } from "./env";
|
|
31
|
+
|
|
32
|
+
const router = createRouter<AppBindings>({
|
|
33
|
+
document: Document,
|
|
34
|
+
}).routes(urlpatterns);
|
|
35
|
+
|
|
36
|
+
// Register bindings and variables globally for implicit typing
|
|
37
|
+
declare global {
|
|
38
|
+
namespace Rango {
|
|
39
|
+
interface Env extends AppBindings {}
|
|
40
|
+
interface Vars extends AppVariables {}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// middleware - typed via ctx.set / ctx.get
|
|
45
|
+
import type { Middleware } from "@rangojs/router";
|
|
46
|
+
|
|
47
|
+
export const authMiddleware: Middleware = async (ctx, next) => {
|
|
48
|
+
ctx.set("user", {
|
|
49
|
+
id: "123",
|
|
50
|
+
email: "user@example.com",
|
|
51
|
+
role: "admin",
|
|
52
|
+
});
|
|
53
|
+
await next();
|
|
54
|
+
};
|
|
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
|
+
```
|
|
63
|
+
|
|
64
|
+
## Global Environment Registration
|
|
65
|
+
|
|
66
|
+
Register environment types globally for implicit typing:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// router.tsx
|
|
70
|
+
declare global {
|
|
71
|
+
namespace Rango {
|
|
72
|
+
interface Env extends AppBindings {}
|
|
73
|
+
interface Vars extends AppVariables {}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Now handlers have typed context without explicit imports:
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
// In loaders
|
|
82
|
+
export const DashboardLoader = createLoader(async (ctx) => {
|
|
83
|
+
// ctx.env.DB is typed from global Rango.Env
|
|
84
|
+
// ctx.get("user") is typed from global Rango.Vars
|
|
85
|
+
const user = ctx.get("user");
|
|
86
|
+
return { user };
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Typed Context Variables
|
|
91
|
+
|
|
92
|
+
`createVar<T>()` creates a typed token for `ctx.set()`/`ctx.get()`, making
|
|
93
|
+
handler-to-layout data contracts explicit and compile-time verified:
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { createVar } from "@rangojs/router";
|
|
97
|
+
|
|
98
|
+
// Define a typed token (shared between producer and consumer)
|
|
99
|
+
interface PaginationData {
|
|
100
|
+
current: number;
|
|
101
|
+
total: number;
|
|
102
|
+
perPage: number;
|
|
103
|
+
}
|
|
104
|
+
export const Pagination = createVar<PaginationData>();
|
|
105
|
+
|
|
106
|
+
// Non-cacheable var — reading inside cache() or "use cache" throws at runtime
|
|
107
|
+
const Session = createVar<SessionData>({ cache: false });
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`createVar` accepts an optional options object. The `cache` option (default
|
|
111
|
+
`true`) controls whether the var's values can be read inside cache scopes.
|
|
112
|
+
Write-level escalation is also supported: `ctx.set(Var, value, { cache: false })`
|
|
113
|
+
marks a specific write as non-cacheable even if the var itself is cacheable.
|
|
114
|
+
"Least cacheable wins" — if either says `cache: false`, the value throws on
|
|
115
|
+
read inside `cache()` or `"use cache"`.
|
|
116
|
+
|
|
117
|
+
### Producer (handler or middleware)
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
import { Pagination } from "../vars/pagination.js";
|
|
121
|
+
|
|
122
|
+
const ArticleList: Handler<"articles.list"> = async (ctx) => {
|
|
123
|
+
ctx.set(Pagination, { // type-checked
|
|
124
|
+
current: 1,
|
|
125
|
+
total: 10,
|
|
126
|
+
perPage: 5,
|
|
127
|
+
});
|
|
128
|
+
return <Articles />;
|
|
129
|
+
};
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Consumer (layout, parallel, or any context with get)
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import { Pagination } from "../vars/pagination.js";
|
|
136
|
+
|
|
137
|
+
export function PaginationLayout(ctx: any) {
|
|
138
|
+
const pagination = ctx.get(Pagination); // typed as PaginationData | undefined
|
|
139
|
+
if (!pagination) return <Outlet />;
|
|
140
|
+
return <nav>Page {pagination.current} of {pagination.total}</nav>;
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Why not just use Rango.Vars?
|
|
145
|
+
|
|
146
|
+
`Rango.Vars` (via global namespace augmentation) provides app-global typing for
|
|
147
|
+
`ctx.get("key")` / `ctx.set("key", value)`. It works for middleware state
|
|
148
|
+
shared app-wide. `createVar<T>()` is for route-local or feature-scoped
|
|
149
|
+
context -- the producer and consumer import the same token, creating a
|
|
150
|
+
scoped contract without polluting global types.
|
|
151
|
+
|
|
152
|
+
Both approaches coexist: `ctx.get("user")` (global via Vars) and
|
|
153
|
+
`ctx.get(Pagination)` (scoped via createVar) work side by side.
|
|
154
|
+
|
|
155
|
+
## Handle Type Safety
|
|
156
|
+
|
|
157
|
+
Handles have typed data:
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
// Built-in Breadcrumbs handle — import from "@rangojs/router"
|
|
161
|
+
import { Breadcrumbs } from "@rangojs/router";
|
|
162
|
+
// Type: Handle<BreadcrumbItem, BreadcrumbItem[]>
|
|
163
|
+
// BreadcrumbItem: { label: string; href: string; content?: ReactNode | Promise<ReactNode> }
|
|
164
|
+
|
|
165
|
+
// In route handler — push is fully typed
|
|
166
|
+
path("/shop/product/:slug", (ctx) => {
|
|
167
|
+
const breadcrumb = ctx.use(Breadcrumbs);
|
|
168
|
+
breadcrumb({ label: "Products", href: "/shop/products" });
|
|
169
|
+
return <ProductPage />;
|
|
170
|
+
}, { name: "product" });
|
|
171
|
+
|
|
172
|
+
// In client — typed array
|
|
173
|
+
import { useHandle, Breadcrumbs } from "@rangojs/router/client";
|
|
174
|
+
function BreadcrumbNav() {
|
|
175
|
+
const crumbs = useHandle(Breadcrumbs);
|
|
176
|
+
// crumbs: BreadcrumbItem[]
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Custom handles also work the same way
|
|
180
|
+
import { createHandle } from "@rangojs/router";
|
|
181
|
+
export const PageTitle = createHandle<string, string>(
|
|
182
|
+
(segments) => segments.flat().at(-1) ?? "Default Title"
|
|
183
|
+
);
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Ref Prop Type Safety (Loaders & Handles)
|
|
187
|
+
|
|
188
|
+
Loaders and handles can be passed as props from server to client components.
|
|
189
|
+
Use `typeof` to get the full typed definition without manually specifying generics:
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
// loaders.ts
|
|
193
|
+
export const ProductLoader = createLoader(async (ctx) => {
|
|
194
|
+
return { product: await fetchProduct(ctx.params.slug) };
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Built-in Breadcrumbs — or any custom handle created with createHandle()
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
```tsx
|
|
201
|
+
// Client component — typeof infers all generics
|
|
202
|
+
"use client";
|
|
203
|
+
import { useLoader, useHandle, type Breadcrumbs } from "@rangojs/router/client";
|
|
204
|
+
import type { ProductLoader } from "../loaders";
|
|
205
|
+
|
|
206
|
+
function MyComponent({
|
|
207
|
+
loader,
|
|
208
|
+
handle,
|
|
209
|
+
}: {
|
|
210
|
+
loader: typeof ProductLoader; // LoaderDefinition<{ product: Product }>
|
|
211
|
+
handle: typeof Breadcrumbs; // Handle<{ label: string; href: string }>
|
|
212
|
+
}) {
|
|
213
|
+
const { data } = useLoader(loader); // data is typed
|
|
214
|
+
const crumbs = useHandle(handle); // crumbs is typed array
|
|
215
|
+
// ...
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
RSC Flight serialization calls `toJSON()` on both loaders and handles,
|
|
220
|
+
sending only `{ __brand, $$id }` to the client. The hooks recover the
|
|
221
|
+
full functionality from module-level registries.
|
|
222
|
+
|
|
223
|
+
## Location State Type Safety
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
// location-states.ts
|
|
227
|
+
import { createLocationState } from "@rangojs/router";
|
|
228
|
+
|
|
229
|
+
// All export patterns work: export const, const + export { X }, export { X as Y }
|
|
230
|
+
export const ProductPreview = createLocationState<{
|
|
231
|
+
name: string;
|
|
232
|
+
price: number;
|
|
233
|
+
image: string;
|
|
234
|
+
}>();
|
|
235
|
+
|
|
236
|
+
// Passing state through Link
|
|
237
|
+
<Link
|
|
238
|
+
to={href("product", { slug: "widget" })}
|
|
239
|
+
state={[ProductPreview({ name: "Widget", price: 99, image: "/img.jpg" })]}
|
|
240
|
+
>
|
|
241
|
+
View Product
|
|
242
|
+
</Link>
|
|
243
|
+
|
|
244
|
+
// Reading state in component
|
|
245
|
+
function ProductHeader() {
|
|
246
|
+
const preview = useLocationState(ProductPreview);
|
|
247
|
+
// preview: { name: string; price: number; image: string } | undefined
|
|
248
|
+
|
|
249
|
+
if (preview) {
|
|
250
|
+
return <h1>{preview.name} - ${preview.price}</h1>;
|
|
251
|
+
}
|
|
252
|
+
return <h1>Loading...</h1>;
|
|
253
|
+
}
|
|
254
|
+
```
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
# Generated Files and CLI Setup
|
|
2
|
+
|
|
3
|
+
## Router Setup
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
// router.tsx
|
|
7
|
+
import { createRouter } from "@rangojs/router";
|
|
8
|
+
import { urlpatterns } from "./urls";
|
|
9
|
+
|
|
10
|
+
const router = createRouter<AppBindings>({
|
|
11
|
+
document: Document,
|
|
12
|
+
}).routes(urlpatterns);
|
|
13
|
+
|
|
14
|
+
// Server-side named-route reverse (type-safe via routeMap)
|
|
15
|
+
export const reverse = router.reverse;
|
|
16
|
+
|
|
17
|
+
export default router;
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Which global type should I use?
|
|
21
|
+
|
|
22
|
+
Use the generated route map by default. Manual `RegisteredRoutes` augmentation
|
|
23
|
+
is only needed when you want the richer `typeof router.routeMap` shape
|
|
24
|
+
available globally.
|
|
25
|
+
|
|
26
|
+
- `GeneratedRouteMap` — auto-registered by `router.named-routes.gen.ts`
|
|
27
|
+
Use for `Handler<"name">` (type annotation), `Prerender<"name">(...)` (function
|
|
28
|
+
call with type arg for param inference), server `ctx.reverse()`, and
|
|
29
|
+
named-route param/search inference.
|
|
30
|
+
- `typeof router.routeMap` — the real merged route map from your router
|
|
31
|
+
instance, including response-route metadata such as `{ path, response }`.
|
|
32
|
+
- `RegisteredRoutes` — manual global hook for exposing `typeof router.routeMap`
|
|
33
|
+
to global utilities that need the exact router-builder map, especially
|
|
34
|
+
`Rango.PathResponse`.
|
|
35
|
+
|
|
36
|
+
### Generated Route Type Surfaces
|
|
37
|
+
|
|
38
|
+
There are three distinct typing surfaces. They are **not** interchangeable —
|
|
39
|
+
pick the one that matches what you need to type:
|
|
40
|
+
|
|
41
|
+
| Surface | Source | Scope | Gives | Does not give |
|
|
42
|
+
| ------------------- | ---------------------------------------- | ------ | ---------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
43
|
+
| `GeneratedRouteMap` | `router.named-routes.gen.ts` (auto) | global | route names, path params, search schemas | response/MIME payloads |
|
|
44
|
+
| `routes` | per-module `*.gen.ts` (`rango generate`) | local | local names, params, search | the global app map |
|
|
45
|
+
| `RegisteredRoutes` | manual `extends typeof router.routeMap` | global | paths, params, **response payloads** | the `Handler`/`Prerender` default (those read `GeneratedRouteMap` to avoid a `router.tsx` cycle) |
|
|
46
|
+
|
|
47
|
+
Key consequence: `href()` and the ambient `Rango.Path` type are typed from
|
|
48
|
+
whichever map is present — they prefer `RegisteredRoutes` when you wire it, otherwise fall back to
|
|
49
|
+
the auto-generated `GeneratedRouteMap`, so **`rango generate` alone gives you
|
|
50
|
+
path-checked `href()`** with no manual augmentation. Response and MIME payload
|
|
51
|
+
inference is the exception: it comes only from `typeof router.routeMap` (via
|
|
52
|
+
`RegisteredRoutes`), because `GeneratedRouteMap` carries paths + search but no
|
|
53
|
+
payloads — so `Rango.PathResponse` resolves to `never` until you wire
|
|
54
|
+
`RegisteredRoutes`.
|
|
55
|
+
|
|
56
|
+
Recommended setup:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
// router.tsx
|
|
60
|
+
import { createRouter } from "@rangojs/router";
|
|
61
|
+
import { urlpatterns } from "./urls";
|
|
62
|
+
import type { AppBindings, AppVars } from "./env";
|
|
63
|
+
|
|
64
|
+
export const router = createRouter<AppBindings>({}).routes(urlpatterns);
|
|
65
|
+
|
|
66
|
+
declare global {
|
|
67
|
+
namespace Rango {
|
|
68
|
+
interface Env extends AppBindings {}
|
|
69
|
+
interface Vars extends AppVars {}
|
|
70
|
+
interface RegisteredRoutes extends typeof router.routeMap {}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Single-App Setup Checklist
|
|
76
|
+
|
|
77
|
+
For one app, keep the ambient types, generated named-routes file, and router
|
|
78
|
+
instance in the same TypeScript program:
|
|
79
|
+
|
|
80
|
+
```jsonc
|
|
81
|
+
// tsconfig.json
|
|
82
|
+
{
|
|
83
|
+
"compilerOptions": {
|
|
84
|
+
"strict": true,
|
|
85
|
+
"moduleResolution": "bundler",
|
|
86
|
+
"jsx": "react-jsx",
|
|
87
|
+
"noEmit": true,
|
|
88
|
+
},
|
|
89
|
+
"include": ["src"],
|
|
90
|
+
"files": ["src/router.tsx"],
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Then generate the route types from the router file:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npx rango generate src/router.tsx
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
This creates `src/router.named-routes.gen.ts`, which augments
|
|
101
|
+
`Rango.GeneratedRouteMap`. Keep that generated file committed with the router
|
|
102
|
+
source. The `files` entry keeps `router.tsx` in the program even when nothing
|
|
103
|
+
imports it directly, so `Rango.Env`, `Rango.Vars`, and optional
|
|
104
|
+
`Rango.RegisteredRoutes` augmentation are visible to handlers, loaders, actions,
|
|
105
|
+
and client helpers.
|
|
106
|
+
|
|
107
|
+
### Named Routes, `$$routeNames`, And `router.routeMap`
|
|
108
|
+
|
|
109
|
+
There are two runtime/type surfaces with similar names:
|
|
110
|
+
|
|
111
|
+
- `router.named-routes.gen.ts` exports `NamedRoutes` and augments
|
|
112
|
+
`Rango.GeneratedRouteMap`. The Vite plugin imports that file internally and
|
|
113
|
+
injects it as `$$routeNames` so `router.reverse` has the static route-name map.
|
|
114
|
+
App code should not pass or import `$$routeNames` directly.
|
|
115
|
+
- `router.routeMap` is the public router instance property for type extraction.
|
|
116
|
+
Use `typeof router.routeMap` when augmenting `Rango.RegisteredRoutes` for
|
|
117
|
+
global response payload helpers such as `Rango.PathResponse`.
|
|
118
|
+
|
|
119
|
+
Do not document or use a public `router.routeNames` API unless one is
|
|
120
|
+
intentionally added. Today, the public extraction surface is `router.routeMap`;
|
|
121
|
+
the generated file and `$$routeNames` are build machinery.
|
|
122
|
+
|
|
123
|
+
### Typecheck cost when composing many include modules
|
|
124
|
+
|
|
125
|
+
`urls()` infers a route registry from everything in its array — including the
|
|
126
|
+
module types behind every `include()` thunk, recursively. In an app composing
|
|
127
|
+
MANY include modules (dozens of groups, or factory-produced groups), that
|
|
128
|
+
inference chain can explode: measured on a 26k-route app with 50 nested
|
|
129
|
+
include modules, root inference hit 4.05M type instantiations / 20 s check
|
|
130
|
+
time; the same app checks at ~140k / 3.6 s after widening.
|
|
131
|
+
|
|
132
|
+
The fix is to annotate the intermediate modules' exports with the wide
|
|
133
|
+
`UrlPatterns` type, which stops per-route literal types from propagating
|
|
134
|
+
upward:
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
import { urls, type UrlPatterns } from "@rangojs/router";
|
|
138
|
+
|
|
139
|
+
export const shopPatterns: UrlPatterns<any> = urls(({ path }) => [
|
|
140
|
+
// ...hundreds of routes
|
|
141
|
+
]);
|
|
142
|
+
export default shopPatterns;
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Nothing is lost: named-route typing (`Handler<"name">`, `ctx.reverse`,
|
|
146
|
+
`href`) comes from the generated `router.named-routes.gen.ts`, not from the
|
|
147
|
+
inferred `urls()` type. Keep full inference on modules whose
|
|
148
|
+
`Rango.PathResponse` payloads you assert (e.g. `path.json` response routes);
|
|
149
|
+
widen the big mechanical groups. Use `UrlPatterns<any>` (not
|
|
150
|
+
`UrlPatterns<unknown>` — `unknown` env breaks handler assignability).
|
|
151
|
+
Diagnose with `tsc --extendedDiagnostics` and watch the Instantiations count.
|
|
152
|
+
|
|
153
|
+
## Multi-Project tsconfig Setup
|
|
154
|
+
|
|
155
|
+
For monorepos or multi-app setups, each app should have its own TypeScript
|
|
156
|
+
program. Do not typecheck two Rango apps with different `Rango.Env`,
|
|
157
|
+
`Rango.Vars`, or `Rango.RegisteredRoutes` declarations in one tsconfig, because
|
|
158
|
+
ambient global interfaces merge across the whole program.
|
|
159
|
+
|
|
160
|
+
### Multiple routers in one program
|
|
161
|
+
|
|
162
|
+
`Rango.GeneratedRouteMap` is a **single global interface**. Each router's
|
|
163
|
+
generated `router.named-routes.gen.ts` augments it, so two routers in the **same
|
|
164
|
+
TS program** that define overlapping route names (e.g. both have a `home`) make
|
|
165
|
+
the augmentations collide:
|
|
166
|
+
|
|
167
|
+
```text
|
|
168
|
+
Interface 'GeneratedRouteMap' cannot simultaneously extend ...
|
|
169
|
+
Named property 'home' ... are not identical.
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
This is the multi-router / host-router case. Resolve it by:
|
|
173
|
+
|
|
174
|
+
- **Separate TS programs** — give each router its own tsconfig (as below) so only
|
|
175
|
+
one generated map is in scope per program. Recommended.
|
|
176
|
+
- **Unique route-name prefixes** — name routes per router (`appA.home`,
|
|
177
|
+
`appB.home`) so the merged global map has no duplicate keys.
|
|
178
|
+
|
|
179
|
+
A single global generated map is a single-router convenience; global named-route
|
|
180
|
+
typing across multiple routers in one program is not supported today (it would
|
|
181
|
+
need per-router scoping in the generated map).
|
|
182
|
+
|
|
183
|
+
Use a shared base tsconfig for common compiler options, then make every app
|
|
184
|
+
tsconfig include its own source tree, its own `router.tsx`, and the generated
|
|
185
|
+
`router.named-routes.gen.ts` that lives beside that router.
|
|
186
|
+
|
|
187
|
+
```jsonc
|
|
188
|
+
// tsconfig.base.json (root)
|
|
189
|
+
{
|
|
190
|
+
"compilerOptions": {
|
|
191
|
+
"target": "ES2022",
|
|
192
|
+
"module": "ESNext",
|
|
193
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
194
|
+
"jsx": "react-jsx",
|
|
195
|
+
"moduleResolution": "bundler",
|
|
196
|
+
"strict": true,
|
|
197
|
+
"noEmit": true,
|
|
198
|
+
"skipLibCheck": true,
|
|
199
|
+
"isolatedModules": true,
|
|
200
|
+
"esModuleInterop": true,
|
|
201
|
+
"resolveJsonModule": true,
|
|
202
|
+
},
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
```jsonc
|
|
207
|
+
// apps/shop/tsconfig.json
|
|
208
|
+
{
|
|
209
|
+
"extends": "../../tsconfig.base.json",
|
|
210
|
+
"include": ["src"],
|
|
211
|
+
"files": ["src/router.tsx"],
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
```jsonc
|
|
216
|
+
// apps/blog/tsconfig.json
|
|
217
|
+
{
|
|
218
|
+
"extends": "../../tsconfig.base.json",
|
|
219
|
+
"include": ["src"],
|
|
220
|
+
"files": ["src/router.tsx"],
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Run generation per app:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
npx rango generate apps/shop/src/router.tsx
|
|
228
|
+
npx rango generate apps/blog/src/router.tsx
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
If an app has multiple tsconfigs (`tsconfig.app.json`, `tsconfig.test.json`,
|
|
232
|
+
`tsconfig.worker.json`), every tsconfig that typechecks Rango handlers,
|
|
233
|
+
components, loaders, actions, or client navigation must see the same app-local
|
|
234
|
+
type surfaces:
|
|
235
|
+
|
|
236
|
+
```jsonc
|
|
237
|
+
// apps/shop/tsconfig.test.json
|
|
238
|
+
{
|
|
239
|
+
"extends": "./tsconfig.json",
|
|
240
|
+
"include": ["src", "tests"],
|
|
241
|
+
"files": ["src/router.tsx"],
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
The `files` array ensures `router.tsx` is always included even if nothing
|
|
246
|
+
directly imports it. The generated `router.named-routes.gen.ts` is normally
|
|
247
|
+
covered by `include: ["src"]`; if a tsconfig uses a narrow `include`, add the
|
|
248
|
+
generated file explicitly. Each app gets its own typed environment and named
|
|
249
|
+
route map without interfering with other apps.
|
|
250
|
+
|
|
251
|
+
For response and MIME payload lookup in each app, augment `RegisteredRoutes`
|
|
252
|
+
inside that app's router file:
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
// apps/shop/src/router.tsx
|
|
256
|
+
export const router = createRouter<ShopEnv>({ document: Document }).routes(
|
|
257
|
+
urlpatterns,
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
declare global {
|
|
261
|
+
namespace Rango {
|
|
262
|
+
interface Env extends ShopEnv {}
|
|
263
|
+
interface RegisteredRoutes extends typeof router.routeMap {}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Complete Type-Safe Setup
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
// 1. env.ts - Environment types
|
|
272
|
+
export interface AppBindings {
|
|
273
|
+
DB: D1Database;
|
|
274
|
+
KV: KVNamespace;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export interface AppVariables {
|
|
278
|
+
user?: { id: string; email: string; role: string };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// 2. urls.tsx - Route definitions with names
|
|
282
|
+
import { urls } from "@rangojs/router";
|
|
283
|
+
|
|
284
|
+
export const urlpatterns = urls(({ path, layout, loader }) => [
|
|
285
|
+
path("/", HomePage, { name: "home" }),
|
|
286
|
+
|
|
287
|
+
layout(<ShopLayout />, () => [
|
|
288
|
+
path("/shop", ShopIndex, { name: "shop" }),
|
|
289
|
+
path("/shop/product/:slug", ProductPage, { name: "product" }, () => [
|
|
290
|
+
loader(ProductLoader),
|
|
291
|
+
]),
|
|
292
|
+
]),
|
|
293
|
+
]);
|
|
294
|
+
|
|
295
|
+
// 3. router.tsx - Create router and export reverse
|
|
296
|
+
const router = createRouter<AppBindings>({
|
|
297
|
+
document: Document,
|
|
298
|
+
}).routes(urlpatterns);
|
|
299
|
+
|
|
300
|
+
// Register bindings and variables globally for implicit typing
|
|
301
|
+
declare global {
|
|
302
|
+
namespace Rango {
|
|
303
|
+
interface Env extends AppBindings {}
|
|
304
|
+
interface Vars extends AppVariables {}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export const reverse = router.reverse;
|
|
309
|
+
export default router;
|
|
310
|
+
|
|
311
|
+
// 4. Run `npx rango generate src/router.tsx` to generate
|
|
312
|
+
// router.named-routes.gen.ts (auto-registers GeneratedRouteMap globally).
|
|
313
|
+
// No manual RegisteredRoutes declaration is needed for named-route handlers,
|
|
314
|
+
// ctx.reverse, prerender, href(), or Rango.Path. Add `RegisteredRoutes
|
|
315
|
+
// extends typeof router.routeMap` when global response payload helpers such
|
|
316
|
+
// as Rango.PathResponse need the richer router.routeMap metadata.
|
|
317
|
+
|
|
318
|
+
// 5. loaders/*.ts - Type-safe loaders
|
|
319
|
+
export const ProductLoader = createLoader(async (ctx) => {
|
|
320
|
+
// ctx.params: { slug: string }
|
|
321
|
+
// ctx.get("user"): User | undefined (from Rango.Vars)
|
|
322
|
+
// ctx.env.DB: D1Database (plain bindings from Rango.Env)
|
|
323
|
+
return { product: await fetchProduct(ctx.params.slug) };
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// 6. Server: ctx.reverse for named routes
|
|
327
|
+
path("/product/:slug", (ctx) => {
|
|
328
|
+
return <Link to={ctx.reverse("shop")}>Back to Shop</Link>;
|
|
329
|
+
}, { name: "product" })
|
|
330
|
+
|
|
331
|
+
// 7. Client: useHref for mounted paths, href for absolute
|
|
332
|
+
"use client";
|
|
333
|
+
import { useHref, href, Link } from "@rangojs/router/client";
|
|
334
|
+
<Link to={href("/shop/product/widget")}>Widget</Link>
|
|
335
|
+
```
|