@rangojs/router 0.0.0-experimental.0f44aca1
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 +5 -0
- package/README.md +899 -0
- package/dist/bin/rango.js +1601 -0
- package/dist/vite/index.js +5214 -0
- package/package.json +176 -0
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +220 -0
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +112 -0
- package/skills/document-cache/SKILL.md +182 -0
- package/skills/fonts/SKILL.md +167 -0
- package/skills/hooks/SKILL.md +704 -0
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +313 -0
- package/skills/layout/SKILL.md +310 -0
- package/skills/links/SKILL.md +239 -0
- package/skills/loader/SKILL.md +596 -0
- package/skills/middleware/SKILL.md +339 -0
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +305 -0
- package/skills/prerender/SKILL.md +643 -0
- package/skills/rango/SKILL.md +118 -0
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +385 -0
- package/skills/router-setup/SKILL.md +439 -0
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +79 -0
- package/skills/typesafety/SKILL.md +623 -0
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +273 -0
- package/src/bin/rango.ts +321 -0
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/event-controller.ts +899 -0
- package/src/browser/history-state.ts +80 -0
- package/src/browser/index.ts +18 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +141 -0
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +134 -0
- package/src/browser/navigation-bridge.ts +645 -0
- package/src/browser/navigation-client.ts +215 -0
- package/src/browser/navigation-store.ts +806 -0
- package/src/browser/navigation-transaction.ts +295 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +550 -0
- package/src/browser/prefetch/cache.ts +146 -0
- package/src/browser/prefetch/fetch.ts +135 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +42 -0
- package/src/browser/prefetch/queue.ts +88 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +360 -0
- package/src/browser/react/NavigationProvider.tsx +386 -0
- package/src/browser/react/ScrollRestoration.tsx +94 -0
- package/src/browser/react/context.ts +59 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +52 -0
- package/src/browser/react/location-state-shared.ts +162 -0
- package/src/browser/react/location-state.ts +107 -0
- package/src/browser/react/mount-context.ts +37 -0
- 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 +218 -0
- package/src/browser/react/use-client-cache.ts +58 -0
- package/src/browser/react/use-handle.ts +162 -0
- package/src/browser/react/use-href.tsx +40 -0
- package/src/browser/react/use-link-status.ts +135 -0
- package/src/browser/react/use-mount.ts +31 -0
- package/src/browser/react/use-navigation.ts +99 -0
- 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 +171 -0
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +431 -0
- package/src/browser/scroll-restoration.ts +400 -0
- package/src/browser/segment-reconciler.ts +216 -0
- package/src/browser/segment-structure-assert.ts +83 -0
- package/src/browser/server-action-bridge.ts +667 -0
- package/src/browser/shallow.ts +40 -0
- package/src/browser/types.ts +538 -0
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +438 -0
- package/src/build/generate-route-types.ts +36 -0
- package/src/build/index.ts +35 -0
- package/src/build/route-trie.ts +265 -0
- 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 +469 -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 +338 -0
- package/src/cache/cache-scope.ts +382 -0
- package/src/cache/cf/cf-cache-store.ts +540 -0
- package/src/cache/cf/index.ts +25 -0
- package/src/cache/document-cache.ts +369 -0
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +43 -0
- package/src/cache/memory-segment-store.ts +328 -0
- 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 +342 -0
- package/src/client.rsc.tsx +85 -0
- package/src/client.tsx +601 -0
- package/src/component-utils.ts +76 -0
- package/src/components/DefaultDocument.tsx +27 -0
- package/src/context-var.ts +86 -0
- package/src/debug.ts +243 -0
- package/src/default-error-boundary.tsx +88 -0
- package/src/deps/browser.ts +8 -0
- package/src/deps/html-stream-client.ts +2 -0
- package/src/deps/html-stream-server.ts +2 -0
- package/src/deps/rsc.ts +10 -0
- package/src/deps/ssr.ts +2 -0
- package/src/errors.ts +365 -0
- package/src/handle.ts +135 -0
- package/src/handles/MetaTags.tsx +246 -0
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +7 -0
- package/src/handles/meta.ts +264 -0
- package/src/host/cookie-handler.ts +165 -0
- package/src/host/errors.ts +97 -0
- package/src/host/index.ts +53 -0
- package/src/host/pattern-matcher.ts +214 -0
- package/src/host/router.ts +352 -0
- package/src/host/testing.ts +79 -0
- package/src/host/types.ts +146 -0
- package/src/host/utils.ts +25 -0
- package/src/href-client.ts +222 -0
- package/src/index.rsc.ts +233 -0
- package/src/index.ts +277 -0
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +89 -0
- package/src/loader.ts +64 -0
- package/src/network-error-thrower.tsx +23 -0
- package/src/outlet-context.ts +15 -0
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +37 -0
- package/src/prerender/store.ts +185 -0
- package/src/prerender.ts +463 -0
- package/src/reverse.ts +330 -0
- package/src/root-error-boundary.tsx +289 -0
- package/src/route-content-wrapper.tsx +196 -0
- package/src/route-definition/dsl-helpers.ts +934 -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 -0
- package/src/route-map-builder.ts +275 -0
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +259 -0
- package/src/router/content-negotiation.ts +116 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +287 -0
- package/src/router/find-match.ts +158 -0
- package/src/router/handler-context.ts +451 -0
- package/src/router/intercept-resolution.ts +395 -0
- package/src/router/lazy-includes.ts +234 -0
- package/src/router/loader-resolution.ts +420 -0
- package/src/router/logging.ts +248 -0
- package/src/router/manifest.ts +267 -0
- package/src/router/match-api.ts +620 -0
- package/src/router/match-context.ts +266 -0
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +223 -0
- package/src/router/match-middleware/cache-lookup.ts +634 -0
- package/src/router/match-middleware/cache-store.ts +295 -0
- package/src/router/match-middleware/index.ts +81 -0
- package/src/router/match-middleware/intercept-resolution.ts +306 -0
- package/src/router/match-middleware/segment-resolution.ts +192 -0
- package/src/router/match-pipelines.ts +179 -0
- package/src/router/match-result.ts +219 -0
- package/src/router/metrics.ts +282 -0
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +222 -0
- package/src/router/middleware.ts +748 -0
- package/src/router/pattern-matching.ts +563 -0
- package/src/router/prerender-match.ts +402 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +289 -0
- package/src/router/router-context.ts +316 -0
- 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 +570 -0
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +198 -0
- package/src/router/segment-resolution/revalidation.ts +1239 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -0
- package/src/router/segment-wrappers.ts +289 -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 +239 -0
- package/src/router/types.ts +170 -0
- package/src/router.ts +1002 -0
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +1089 -0
- package/src/rsc/helpers.ts +198 -0
- package/src/rsc/index.ts +36 -0
- package/src/rsc/loader-fetch.ts +209 -0
- package/src/rsc/manifest-init.ts +86 -0
- package/src/rsc/nonce.ts +32 -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 +235 -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 +263 -0
- package/src/search-params.ts +230 -0
- package/src/segment-system.tsx +454 -0
- package/src/server/context.ts +591 -0
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +308 -0
- package/src/server/loader-registry.ts +133 -0
- package/src/server/request-context.ts +914 -0
- package/src/server/root-layout.tsx +10 -0
- package/src/server/tsconfig.json +14 -0
- package/src/server.ts +51 -0
- package/src/ssr/index.tsx +365 -0
- package/src/static-handler.ts +114 -0
- package/src/theme/ThemeProvider.tsx +297 -0
- package/src/theme/ThemeScript.tsx +61 -0
- package/src/theme/constants.ts +62 -0
- package/src/theme/index.ts +48 -0
- package/src/theme/theme-context.ts +44 -0
- package/src/theme/theme-script.ts +155 -0
- package/src/theme/types.ts +182 -0
- package/src/theme/use-theme.ts +44 -0
- 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 +687 -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 +102 -0
- package/src/types/segments.ts +148 -0
- package/src/types.ts +1 -0
- 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 -0
- package/src/use-loader.tsx +354 -0
- 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 +110 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +16 -0
- package/src/vite/plugin-types.ts +131 -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/plugins/expose-action-id.ts +365 -0
- package/src/vite/plugins/expose-id-utils.ts +287 -0
- 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 +254 -0
- package/src/vite/plugins/version.d.ts +12 -0
- package/src/vite/plugins/virtual-entries.ts +123 -0
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +510 -0
- package/src/vite/router-discovery.ts +785 -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 +121 -0
- package/src/vite/utils/prerender-utils.ts +189 -0
- package/src/vite/utils/shared-utils.ts +169 -0
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: typesafety
|
|
3
|
+
description: Set up type-safe routes, params, and environment types in @rangojs/router
|
|
4
|
+
argument-hint: [setup]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Type Safety Setup
|
|
8
|
+
|
|
9
|
+
@rangojs/router provides end-to-end type safety for routes, parameters, and environment.
|
|
10
|
+
|
|
11
|
+
## Router Setup
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
// router.tsx
|
|
15
|
+
import { createRouter } from "@rangojs/router";
|
|
16
|
+
import { urlpatterns } from "./urls";
|
|
17
|
+
|
|
18
|
+
const router = createRouter<AppBindings>({
|
|
19
|
+
document: Document,
|
|
20
|
+
}).routes(urlpatterns);
|
|
21
|
+
|
|
22
|
+
// Server-side named-route reverse (type-safe via routeMap)
|
|
23
|
+
export const reverse = router.reverse;
|
|
24
|
+
|
|
25
|
+
export default router;
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Which global type should I use?
|
|
29
|
+
|
|
30
|
+
Use the generated route map by default. Manual `RegisteredRoutes` augmentation
|
|
31
|
+
is only needed when you want the richer `typeof router.routeMap` shape
|
|
32
|
+
available globally.
|
|
33
|
+
|
|
34
|
+
- `GeneratedRouteMap` — auto-registered by `router.named-routes.gen.ts`
|
|
35
|
+
Use for `Handler<"name">`, `Prerender<"name">`, server `ctx.reverse()`,
|
|
36
|
+
and named-route param/search inference.
|
|
37
|
+
- `typeof router.routeMap` — the real merged route map from your router
|
|
38
|
+
instance, including response-route metadata such as `{ path, response }`.
|
|
39
|
+
- `RegisteredRoutes` — manual global hook for exposing `typeof router.routeMap`
|
|
40
|
+
to utilities like `href()`, `ValidPaths`, and `PathResponse`.
|
|
41
|
+
|
|
42
|
+
Recommended setup:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// router.tsx
|
|
46
|
+
import { createRouter } from "@rangojs/router";
|
|
47
|
+
import { urlpatterns } from "./urls";
|
|
48
|
+
import type { AppBindings, AppVars } from "./env";
|
|
49
|
+
|
|
50
|
+
export const router = createRouter<AppBindings>({}).routes(urlpatterns);
|
|
51
|
+
|
|
52
|
+
declare global {
|
|
53
|
+
namespace RSCRouter {
|
|
54
|
+
interface Env extends AppBindings {}
|
|
55
|
+
interface Vars extends AppVars {}
|
|
56
|
+
interface RegisteredRoutes extends typeof router.routeMap {}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Route Definition with Type-Safe Names
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
// urls.tsx
|
|
65
|
+
import { urls } from "@rangojs/router";
|
|
66
|
+
|
|
67
|
+
export const urlpatterns = urls(({ path, layout }) => [
|
|
68
|
+
path("/", HomePage, { name: "home" }),
|
|
69
|
+
path("/products", ProductsPage, { name: "products" }),
|
|
70
|
+
path("/product/:slug", ProductPage, { name: "product" }),
|
|
71
|
+
path("/cart", CartPage, { name: "cart" }),
|
|
72
|
+
path("/checkout/:step?", CheckoutPage, { name: "checkout" }),
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
// Route names are inferred from the { name } option
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Type-Safe href()
|
|
79
|
+
|
|
80
|
+
### Server: ctx.reverse with route names
|
|
81
|
+
|
|
82
|
+
In route handlers, `ctx.reverse()` uses two namespaces:
|
|
83
|
+
|
|
84
|
+
- **`.name`** — local route, resolved within the current `include()` scope
|
|
85
|
+
- **`name`** — global route, from the named-routes definition
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import type { Handler } from "@rangojs/router";
|
|
89
|
+
|
|
90
|
+
export const ProductHandler: Handler<"shop.product"> = (ctx) => {
|
|
91
|
+
ctx.reverse(".cart"); // Local: /shop/cart
|
|
92
|
+
ctx.reverse(".product", { slug: "widget" }); // Local: /shop/product/widget
|
|
93
|
+
ctx.reverse("blog.post", { slug: "1" }); // Global: /blog/1
|
|
94
|
+
};
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
For type-safe local names, generate a route types file with `npx rango generate urls/shop.tsx`
|
|
98
|
+
and pass it as the second generic to `Handler` or `Prerender`:
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import type { Handler } from "@rangojs/router";
|
|
102
|
+
import type { routes } from "./shop.gen.js";
|
|
103
|
+
|
|
104
|
+
export const ProductHandler: Handler<"shop.product", routes> = (ctx) => {
|
|
105
|
+
ctx.reverse(".cart"); // Type-safe local name
|
|
106
|
+
ctx.reverse(".product", { slug: "widget" }); // Type-safe local with params
|
|
107
|
+
ctx.reverse("blog.post", { slug: "hi" }); // Type-safe global name
|
|
108
|
+
};
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Client: href + useHref
|
|
112
|
+
|
|
113
|
+
On the client, `href()` validates paths against registered route patterns at compile time:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
"use client";
|
|
117
|
+
import { href, useHref, Link } from "@rangojs/router/client";
|
|
118
|
+
|
|
119
|
+
// href() validates absolute paths via PatternToPath types
|
|
120
|
+
href("/about"); // Valid path
|
|
121
|
+
href("/blog/hello"); // Matches /blog/:slug
|
|
122
|
+
|
|
123
|
+
// useHref() auto-prefixes with include() mount
|
|
124
|
+
function ShopNav() {
|
|
125
|
+
const href = useHref();
|
|
126
|
+
return <Link to={href("/cart")}>Cart</Link>; // "/shop/cart"
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`href()` and path-based response utilities read from `RegisteredRoutes`, so if
|
|
131
|
+
you want them typed globally you should augment:
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
declare global {
|
|
135
|
+
namespace RSCRouter {
|
|
136
|
+
interface RegisteredRoutes extends typeof router.routeMap {}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
See `/links` for full URL generation guide.
|
|
142
|
+
|
|
143
|
+
## Environment Type Setup
|
|
144
|
+
|
|
145
|
+
Define your app's environment for type-safe bindings and variables:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
// env.ts
|
|
149
|
+
|
|
150
|
+
// Cloudflare bindings — passed as TEnv to createRouter<TEnv>()
|
|
151
|
+
export interface AppBindings {
|
|
152
|
+
DB: D1Database;
|
|
153
|
+
KV: KVNamespace;
|
|
154
|
+
CACHE: KVNamespace;
|
|
155
|
+
AI: Ai;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Variables set by middleware — declared via module augmentation
|
|
159
|
+
export interface AppVariables {
|
|
160
|
+
user?: { id: string; email: string; role: string };
|
|
161
|
+
requestId?: string;
|
|
162
|
+
permissions?: string[];
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Using Environment Types
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
// router.tsx
|
|
170
|
+
import type { AppBindings, AppVariables } from "./env";
|
|
171
|
+
|
|
172
|
+
const router = createRouter<AppBindings>({
|
|
173
|
+
document: Document,
|
|
174
|
+
}).routes(urlpatterns);
|
|
175
|
+
|
|
176
|
+
// Register bindings and variables globally for implicit typing
|
|
177
|
+
declare global {
|
|
178
|
+
namespace RSCRouter {
|
|
179
|
+
interface Env extends AppBindings {}
|
|
180
|
+
interface Vars extends AppVariables {}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// middleware - typed via ctx.set / ctx.get
|
|
185
|
+
import type { Middleware } from "@rangojs/router";
|
|
186
|
+
|
|
187
|
+
export const authMiddleware: Middleware = async (ctx, next) => {
|
|
188
|
+
ctx.set("user", {
|
|
189
|
+
id: "123",
|
|
190
|
+
email: "user@example.com",
|
|
191
|
+
role: "admin",
|
|
192
|
+
});
|
|
193
|
+
await next();
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// loaders - typed context
|
|
197
|
+
export const UserLoader = createLoader(async (ctx) => {
|
|
198
|
+
const db = ctx.env.DB; // D1Database (plain bindings)
|
|
199
|
+
const userId = ctx.get("user")?.id; // from RSCRouter.Vars
|
|
200
|
+
return db.prepare("SELECT * FROM users WHERE id = ?").bind(userId).first();
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Global Environment Registration
|
|
205
|
+
|
|
206
|
+
Register environment types globally for implicit typing:
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
// router.tsx
|
|
210
|
+
declare global {
|
|
211
|
+
namespace RSCRouter {
|
|
212
|
+
interface Env extends AppBindings {}
|
|
213
|
+
interface Vars extends AppVariables {}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Now handlers have typed context without explicit imports:
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
// In loaders
|
|
222
|
+
export const DashboardLoader = createLoader(async (ctx) => {
|
|
223
|
+
// ctx.env.DB is typed from global RSCRouter.Env
|
|
224
|
+
// ctx.get("user") is typed from global RSCRouter.Vars
|
|
225
|
+
const user = ctx.get("user");
|
|
226
|
+
return { user };
|
|
227
|
+
});
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Typed Search Params
|
|
231
|
+
|
|
232
|
+
Add a `search` schema to `path()` options for type-safe query parameters:
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
// Route definition with search schema
|
|
236
|
+
path("/search", SearchPage, {
|
|
237
|
+
name: "search",
|
|
238
|
+
search: { q: "string", page: "number?", sort: "string?" },
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Handler with typed search params
|
|
243
|
+
|
|
244
|
+
`Handler<"name">` automatically resolves route params and search params from the
|
|
245
|
+
global `GeneratedRouteMap` (the gen file). No explicit route map import needed:
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
// pages/search.tsx
|
|
249
|
+
import type { Handler } from "@rangojs/router";
|
|
250
|
+
|
|
251
|
+
export const SearchPage: Handler<"search"> = (ctx) => {
|
|
252
|
+
// ctx.search is typed: { q: string; page?: number; sort?: string }
|
|
253
|
+
const { q, page, sort } = ctx.search;
|
|
254
|
+
return <SearchResults q={q} page={page} sort={sort} />;
|
|
255
|
+
};
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
This avoids circular references because `Handler` defaults to `GeneratedRouteMap`
|
|
259
|
+
(from `router.named-routes.gen.ts`) instead of `RegisteredRoutes` (which depends on `router.tsx`).
|
|
260
|
+
|
|
261
|
+
You can also pass an explicit route map for per-module isolation (opt-in,
|
|
262
|
+
after running `npx rango generate`):
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
import type { Handler } from "@rangojs/router";
|
|
266
|
+
import type { routes } from "./urls.gen.js";
|
|
267
|
+
|
|
268
|
+
export const SearchPage: Handler<"search", routes> = (ctx) => { ... };
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Supported types: `"string"`, `"number"`, `"boolean"`, with `?` suffix for optional.
|
|
272
|
+
Values are automatically coerced from query string (e.g., `"2"` becomes `2` for numbers).
|
|
273
|
+
Routes without a `search` schema keep the standard `URLSearchParams` behavior.
|
|
274
|
+
|
|
275
|
+
### RouteSearchParams and RouteParams utility types
|
|
276
|
+
|
|
277
|
+
Extract typed params by route name for use in component props, return types, or anywhere:
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
import type { RouteSearchParams, RouteParams } from "@rangojs/router";
|
|
281
|
+
|
|
282
|
+
// RouteSearchParams<"name"> resolves the search schema to a typed object
|
|
283
|
+
type SP = RouteSearchParams<"search">;
|
|
284
|
+
// { q: string | undefined; page?: number; sort?: string }
|
|
285
|
+
|
|
286
|
+
// RouteParams<"name"> resolves URL params from the route pattern
|
|
287
|
+
type P = RouteParams<"blogPost">;
|
|
288
|
+
// { slug: string }
|
|
289
|
+
|
|
290
|
+
// Use in component props
|
|
291
|
+
interface SearchResultsProps {
|
|
292
|
+
params: RouteSearchParams<"search">;
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Both default to the global route map (`RegisteredRoutes` or `GeneratedRouteMap`).
|
|
297
|
+
Pass an explicit route map as the second type argument when needed:
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
import type { routes } from "./urls.gen.js";
|
|
301
|
+
|
|
302
|
+
type SP = RouteSearchParams<"search", routes>;
|
|
303
|
+
type P = RouteParams<"blogPost", routes>;
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Generated route types
|
|
307
|
+
|
|
308
|
+
In the generated `router.named-routes.gen.ts`, routes with search schemas
|
|
309
|
+
use `{ path, search }` objects:
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
// router.named-routes.gen.ts (auto-generated)
|
|
313
|
+
export const NamedRoutes = {
|
|
314
|
+
"search.index": {
|
|
315
|
+
path: "/search",
|
|
316
|
+
search: { q: "string", page: "number?", sort: "string?" },
|
|
317
|
+
},
|
|
318
|
+
"home.index": "/", // No search schema -> plain string
|
|
319
|
+
} as const;
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Loader Type Safety
|
|
323
|
+
|
|
324
|
+
Loaders have typed return values:
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
// loaders/product.ts
|
|
328
|
+
export const ProductLoader = createLoader(async (ctx) => {
|
|
329
|
+
return {
|
|
330
|
+
id: ctx.params.slug,
|
|
331
|
+
name: "Widget",
|
|
332
|
+
price: 99,
|
|
333
|
+
};
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// In server component - type is inferred
|
|
337
|
+
import { useLoader } from "@rangojs/router/client";
|
|
338
|
+
|
|
339
|
+
async function ProductPage() {
|
|
340
|
+
const product = await useLoader(ProductLoader);
|
|
341
|
+
// product: { id: string; name: string; price: number }
|
|
342
|
+
return <h1>{product.name}</h1>;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// In client component - same type
|
|
346
|
+
"use client";
|
|
347
|
+
import { useLoader } from "@rangojs/router/client";
|
|
348
|
+
|
|
349
|
+
function ProductPrice() {
|
|
350
|
+
const { data } = useLoader(ProductLoader);
|
|
351
|
+
// data: { id: string; name: string; price: number }
|
|
352
|
+
const product = data;
|
|
353
|
+
return <span>${product.price}</span>;
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
## Typed Context Variables
|
|
358
|
+
|
|
359
|
+
`createVar<T>()` creates a typed token for `ctx.set()`/`ctx.get()`, making
|
|
360
|
+
handler-to-layout data contracts explicit and compile-time verified:
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
import { createVar } from "@rangojs/router";
|
|
364
|
+
|
|
365
|
+
// Define a typed token (shared between producer and consumer)
|
|
366
|
+
interface PaginationData {
|
|
367
|
+
current: number;
|
|
368
|
+
total: number;
|
|
369
|
+
perPage: number;
|
|
370
|
+
}
|
|
371
|
+
export const Pagination = createVar<PaginationData>();
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Producer (handler or middleware)
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
import { Pagination } from "../vars/pagination.js";
|
|
378
|
+
|
|
379
|
+
const ArticleList: Handler<"articles.list"> = async (ctx) => {
|
|
380
|
+
ctx.set(Pagination, { // type-checked
|
|
381
|
+
current: 1,
|
|
382
|
+
total: 10,
|
|
383
|
+
perPage: 5,
|
|
384
|
+
});
|
|
385
|
+
return <Articles />;
|
|
386
|
+
};
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Consumer (layout, parallel, or any context with get)
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
import { Pagination } from "../vars/pagination.js";
|
|
393
|
+
|
|
394
|
+
export function PaginationLayout(ctx: any) {
|
|
395
|
+
const pagination = ctx.get(Pagination); // typed as PaginationData | undefined
|
|
396
|
+
if (!pagination) return <Outlet />;
|
|
397
|
+
return <nav>Page {pagination.current} of {pagination.total}</nav>;
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### Why not just use RSCRouter.Vars?
|
|
402
|
+
|
|
403
|
+
`RSCRouter.Vars` (via module augmentation) provides app-global typing for
|
|
404
|
+
`ctx.get("key")` / `ctx.set("key", value)`. It works for middleware state
|
|
405
|
+
shared app-wide. `createVar<T>()` is for route-local or feature-scoped
|
|
406
|
+
context -- the producer and consumer import the same token, creating a
|
|
407
|
+
scoped contract without polluting global types.
|
|
408
|
+
|
|
409
|
+
Both approaches coexist: `ctx.get("user")` (global via Vars) and
|
|
410
|
+
`ctx.get(Pagination)` (scoped via createVar) work side by side.
|
|
411
|
+
|
|
412
|
+
## Handle Type Safety
|
|
413
|
+
|
|
414
|
+
Handles have typed data:
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
// Built-in Breadcrumbs handle — import from "@rangojs/router"
|
|
418
|
+
import { Breadcrumbs } from "@rangojs/router";
|
|
419
|
+
// Type: Handle<BreadcrumbItem, BreadcrumbItem[]>
|
|
420
|
+
// BreadcrumbItem: { label: string; href: string; content?: ReactNode | Promise<ReactNode> }
|
|
421
|
+
|
|
422
|
+
// In route handler — push is fully typed
|
|
423
|
+
path("/shop/product/:slug", (ctx) => {
|
|
424
|
+
const breadcrumb = ctx.use(Breadcrumbs);
|
|
425
|
+
breadcrumb({ label: "Products", href: "/shop/products" });
|
|
426
|
+
return <ProductPage />;
|
|
427
|
+
}, { name: "product" });
|
|
428
|
+
|
|
429
|
+
// In client — typed array
|
|
430
|
+
import { useHandle, Breadcrumbs } from "@rangojs/router/client";
|
|
431
|
+
function BreadcrumbNav() {
|
|
432
|
+
const crumbs = useHandle(Breadcrumbs);
|
|
433
|
+
// crumbs: BreadcrumbItem[]
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Custom handles also work the same way
|
|
437
|
+
import { createHandle } from "@rangojs/router";
|
|
438
|
+
export const PageTitle = createHandle<string, string>(
|
|
439
|
+
(segments) => segments.flat().at(-1) ?? "Default Title"
|
|
440
|
+
);
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
## Ref Prop Type Safety (Loaders & Handles)
|
|
444
|
+
|
|
445
|
+
Loaders and handles can be passed as props from server to client components.
|
|
446
|
+
Use `typeof` to get the full typed definition without manually specifying generics:
|
|
447
|
+
|
|
448
|
+
```typescript
|
|
449
|
+
// loaders.ts
|
|
450
|
+
export const ProductLoader = createLoader(async (ctx) => {
|
|
451
|
+
return { product: await fetchProduct(ctx.params.slug) };
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
// Built-in Breadcrumbs — or any custom handle created with createHandle()
|
|
455
|
+
|
|
456
|
+
// Client component — typeof infers all generics
|
|
457
|
+
("use client");
|
|
458
|
+
import { useLoader, useHandle, type Breadcrumbs } from "@rangojs/router/client";
|
|
459
|
+
import type { ProductLoader } from "../loaders";
|
|
460
|
+
|
|
461
|
+
function MyComponent({
|
|
462
|
+
loader,
|
|
463
|
+
handle,
|
|
464
|
+
}: {
|
|
465
|
+
loader: typeof ProductLoader; // LoaderDefinition<{ product: Product }>
|
|
466
|
+
handle: typeof Breadcrumbs; // Handle<{ label: string; href: string }>
|
|
467
|
+
}) {
|
|
468
|
+
const { data } = useLoader(loader); // data is typed
|
|
469
|
+
const crumbs = useHandle(handle); // crumbs is typed array
|
|
470
|
+
// ...
|
|
471
|
+
}
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
RSC Flight serialization calls `toJSON()` on both loaders and handles,
|
|
475
|
+
sending only `{ __brand, $$id }` to the client. The hooks recover the
|
|
476
|
+
full functionality from module-level registries.
|
|
477
|
+
|
|
478
|
+
## Location State Type Safety
|
|
479
|
+
|
|
480
|
+
```typescript
|
|
481
|
+
// location-states.ts
|
|
482
|
+
import { createLocationState } from "@rangojs/router";
|
|
483
|
+
|
|
484
|
+
// All export patterns work: export const, const + export { X }, export { X as Y }
|
|
485
|
+
export const ProductPreview = createLocationState<{
|
|
486
|
+
name: string;
|
|
487
|
+
price: number;
|
|
488
|
+
image: string;
|
|
489
|
+
}>();
|
|
490
|
+
|
|
491
|
+
// Passing state through Link
|
|
492
|
+
<Link
|
|
493
|
+
to={href("product", { slug: "widget" })}
|
|
494
|
+
state={[ProductPreview({ name: "Widget", price: 99, image: "/img.jpg" })]}
|
|
495
|
+
>
|
|
496
|
+
View Product
|
|
497
|
+
</Link>
|
|
498
|
+
|
|
499
|
+
// Reading state in component
|
|
500
|
+
function ProductHeader() {
|
|
501
|
+
const preview = useLocationState(ProductPreview);
|
|
502
|
+
// preview: { name: string; price: number; image: string } | undefined
|
|
503
|
+
|
|
504
|
+
if (preview) {
|
|
505
|
+
return <h1>{preview.name} - ${preview.price}</h1>;
|
|
506
|
+
}
|
|
507
|
+
return <h1>Loading...</h1>;
|
|
508
|
+
}
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
## Multi-Project tsconfig Setup
|
|
512
|
+
|
|
513
|
+
For monorepos or multi-app setups, use a shared base tsconfig. Each app only needs
|
|
514
|
+
to extend the base and add its `router.tsx` to `files` so TypeScript picks up the
|
|
515
|
+
global type declarations (like `RSCRouter.Env`).
|
|
516
|
+
|
|
517
|
+
```jsonc
|
|
518
|
+
// tsconfig.base.json (root)
|
|
519
|
+
{
|
|
520
|
+
"compilerOptions": {
|
|
521
|
+
"target": "ES2022",
|
|
522
|
+
"module": "ESNext",
|
|
523
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
524
|
+
"jsx": "react-jsx",
|
|
525
|
+
"moduleResolution": "bundler",
|
|
526
|
+
"strict": true,
|
|
527
|
+
"noEmit": true,
|
|
528
|
+
"skipLibCheck": true,
|
|
529
|
+
"isolatedModules": true,
|
|
530
|
+
"esModuleInterop": true,
|
|
531
|
+
"resolveJsonModule": true,
|
|
532
|
+
},
|
|
533
|
+
}
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
```jsonc
|
|
537
|
+
// apps/shop/tsconfig.json
|
|
538
|
+
{
|
|
539
|
+
"extends": "../../tsconfig.base.json",
|
|
540
|
+
"include": ["src"],
|
|
541
|
+
"files": ["src/router.tsx"],
|
|
542
|
+
}
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
```jsonc
|
|
546
|
+
// apps/blog/tsconfig.json
|
|
547
|
+
{
|
|
548
|
+
"extends": "../../tsconfig.base.json",
|
|
549
|
+
"include": ["src"],
|
|
550
|
+
"files": ["src/router.tsx"],
|
|
551
|
+
}
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
The `files` array ensures `router.tsx` (which contains `declare global { namespace RSCRouter { interface Env; interface Vars } }`)
|
|
555
|
+
is always included in the compilation even if nothing directly imports it. Route types come from the
|
|
556
|
+
auto-generated `*.named-routes.gen.ts` file (via `rango generate`), not from manual declaration.
|
|
557
|
+
Each app gets its own typed environment without interfering with other apps.
|
|
558
|
+
|
|
559
|
+
## Complete Type-Safe Setup
|
|
560
|
+
|
|
561
|
+
```typescript
|
|
562
|
+
// 1. env.ts - Environment types
|
|
563
|
+
export interface AppBindings {
|
|
564
|
+
DB: D1Database;
|
|
565
|
+
KV: KVNamespace;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
export interface AppVariables {
|
|
569
|
+
user?: { id: string; email: string; role: string };
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// 2. urls.tsx - Route definitions with names
|
|
573
|
+
import { urls } from "@rangojs/router";
|
|
574
|
+
|
|
575
|
+
export const urlpatterns = urls(({ path, layout, loader }) => [
|
|
576
|
+
path("/", HomePage, { name: "home" }),
|
|
577
|
+
|
|
578
|
+
layout(<ShopLayout />, () => [
|
|
579
|
+
path("/shop", ShopIndex, { name: "shop" }),
|
|
580
|
+
path("/shop/product/:slug", ProductPage, { name: "product" }, () => [
|
|
581
|
+
loader(ProductLoader),
|
|
582
|
+
]),
|
|
583
|
+
]),
|
|
584
|
+
]);
|
|
585
|
+
|
|
586
|
+
// 3. router.tsx - Create router and export reverse
|
|
587
|
+
const router = createRouter<AppBindings>({
|
|
588
|
+
document: Document,
|
|
589
|
+
}).routes(urlpatterns);
|
|
590
|
+
|
|
591
|
+
// Register bindings and variables globally for implicit typing
|
|
592
|
+
declare global {
|
|
593
|
+
namespace RSCRouter {
|
|
594
|
+
interface Env extends AppBindings {}
|
|
595
|
+
interface Vars extends AppVariables {}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
export const reverse = router.reverse;
|
|
600
|
+
export default router;
|
|
601
|
+
|
|
602
|
+
// 4. Run `npx rango generate src/router.tsx` to generate
|
|
603
|
+
// router.named-routes.gen.ts (auto-registers GeneratedRouteMap globally).
|
|
604
|
+
// No manual RegisteredRoutes declaration needed.
|
|
605
|
+
|
|
606
|
+
// 5. loaders/*.ts - Type-safe loaders
|
|
607
|
+
export const ProductLoader = createLoader(async (ctx) => {
|
|
608
|
+
// ctx.params: { slug: string }
|
|
609
|
+
// ctx.get("user"): User | undefined (from RSCRouter.Vars)
|
|
610
|
+
// ctx.env.DB: D1Database (plain bindings from RSCRouter.Env)
|
|
611
|
+
return { product: await fetchProduct(ctx.params.slug) };
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
// 6. Server: ctx.reverse for named routes
|
|
615
|
+
path("/product/:slug", (ctx) => {
|
|
616
|
+
return <Link to={ctx.reverse("shop")}>Back to Shop</Link>;
|
|
617
|
+
}, { name: "product" })
|
|
618
|
+
|
|
619
|
+
// 7. Client: useHref for mounted paths, href for absolute
|
|
620
|
+
"use client";
|
|
621
|
+
import { useHref, href, Link } from "@rangojs/router/client";
|
|
622
|
+
<Link to={href("/shop/product/widget")}>Widget</Link>
|
|
623
|
+
```
|