@rangojs/router 0.0.0-experimental.3 → 0.0.0-experimental.30
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 +883 -4
- package/dist/bin/rango.js +1601 -0
- package/dist/vite/index.js +4655 -747
- package/package.json +78 -50
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +54 -25
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +12 -8
- package/skills/document-cache/SKILL.md +23 -21
- package/skills/fonts/SKILL.md +167 -0
- package/skills/hooks/SKILL.md +390 -63
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +133 -10
- package/skills/layout/SKILL.md +102 -5
- package/skills/links/SKILL.md +239 -0
- package/skills/loader/SKILL.md +366 -29
- package/skills/middleware/SKILL.md +173 -36
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +80 -3
- package/skills/prerender/SKILL.md +643 -0
- package/skills/rango/SKILL.md +86 -16
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +227 -14
- package/skills/router-setup/SKILL.md +225 -32
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +12 -11
- package/skills/typesafety/SKILL.md +401 -75
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +10 -4
- 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 +87 -64
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +20 -4
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +201 -553
- package/src/browser/navigation-client.ts +124 -71
- package/src/browser/navigation-store.ts +33 -50
- package/src/browser/navigation-transaction.ts +295 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +267 -317
- 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 +173 -73
- package/src/browser/react/NavigationProvider.tsx +138 -27
- package/src/browser/react/context.ts +6 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +12 -12
- package/src/browser/react/location-state-shared.ts +95 -53
- package/src/browser/react/location-state.ts +60 -15
- package/src/browser/react/mount-context.ts +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 +29 -51
- package/src/browser/react/use-client-cache.ts +5 -3
- package/src/browser/react/use-handle.ts +49 -65
- package/src/browser/react/use-href.tsx +20 -188
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-mount.ts +31 -0
- package/src/browser/react/use-navigation.ts +27 -78
- package/src/browser/react/use-params.ts +65 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-router.ts +63 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +80 -97
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +111 -26
- package/src/browser/scroll-restoration.ts +92 -16
- package/src/browser/segment-reconciler.ts +216 -0
- package/src/browser/segment-structure-assert.ts +83 -0
- package/src/browser/server-action-bridge.ts +504 -584
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +92 -57
- 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 +120 -303
- package/src/cache/cf/cf-cache-store.ts +119 -7
- package/src/cache/cf/index.ts +8 -2
- package/src/cache/document-cache.ts +101 -72
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +0 -15
- package/src/cache/memory-segment-store.ts +191 -13
- package/src/cache/profile-registry.ts +73 -0
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +256 -0
- package/src/cache/taint.ts +98 -0
- package/src/cache/types.ts +72 -122
- package/src/client.rsc.tsx +10 -15
- package/src/client.tsx +114 -135
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +86 -0
- package/src/debug.ts +17 -7
- package/src/errors.ts +108 -2
- package/src/handle.ts +34 -19
- package/src/handles/MetaTags.tsx +73 -20
- package/src/handles/meta.ts +30 -13
- 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 +135 -49
- package/src/index.rsc.ts +182 -17
- package/src/index.ts +238 -24
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +27 -142
- package/src/loader.ts +27 -10
- package/src/network-error-thrower.tsx +3 -1
- 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 +41 -29
- package/src/route-content-wrapper.tsx +9 -11
- 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 -1388
- package/src/route-map-builder.ts +241 -112
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +70 -9
- package/src/router/content-negotiation.ts +116 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +9 -9
- package/src/router/find-match.ts +158 -0
- package/src/router/handler-context.ts +371 -81
- package/src/router/intercept-resolution.ts +395 -0
- package/src/router/lazy-includes.ts +234 -0
- package/src/router/loader-resolution.ts +215 -122
- package/src/router/logging.ts +248 -0
- package/src/router/manifest.ts +155 -32
- package/src/router/match-api.ts +620 -0
- package/src/router/match-context.ts +5 -3
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +80 -93
- package/src/router/match-middleware/cache-lookup.ts +382 -9
- package/src/router/match-middleware/cache-store.ts +51 -22
- package/src/router/match-middleware/intercept-resolution.ts +55 -17
- package/src/router/match-middleware/segment-resolution.ts +24 -6
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +34 -29
- package/src/router/metrics.ts +235 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +222 -0
- package/src/router/middleware.ts +324 -367
- package/src/router/pattern-matching.ts +321 -30
- package/src/router/prerender-match.ts +400 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +137 -38
- package/src/router/router-context.ts +36 -21
- package/src/router/router-interfaces.ts +452 -0
- package/src/router/router-options.ts +592 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +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 +1241 -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 +77 -3
- package/src/router.ts +688 -3656
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +786 -760
- package/src/rsc/helpers.ts +140 -6
- package/src/rsc/index.ts +5 -25
- package/src/rsc/loader-fetch.ts +209 -0
- package/src/rsc/manifest-init.ts +86 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +379 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +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 +40 -14
- package/src/search-params.ts +230 -0
- package/src/segment-system.tsx +57 -61
- package/src/server/context.ts +202 -51
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +94 -15
- package/src/server/loader-registry.ts +15 -56
- package/src/server/request-context.ts +422 -70
- package/src/server.ts +36 -120
- package/src/ssr/index.tsx +157 -26
- package/src/static-handler.ts +114 -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 +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 -1577
- 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 -726
- package/src/use-loader.tsx +85 -77
- package/src/vite/discovery/bundle-postprocess.ts +184 -0
- package/src/vite/discovery/discover-routers.ts +344 -0
- package/src/vite/discovery/prerender-collection.ts +385 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +47 -0
- package/src/vite/discovery/state.ts +110 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +11 -782
- 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/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -51
- 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/{virtual-entries.ts → plugins/virtual-entries.ts} +29 -15
- 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/{package-resolution.ts → utils/package-resolution.ts} +25 -29
- package/src/vite/utils/prerender-utils.ts +189 -0
- package/src/vite/utils/shared-utils.ts +169 -0
- package/CLAUDE.md +0 -3
- 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/href.ts +0 -255
- package/src/vite/expose-handle-id.ts +0 -209
- package/src/vite/expose-loader-id.ts +0 -357
- package/src/vite/expose-location-state-id.ts +0 -177
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -8,40 +8,61 @@ argument-hint: [setup]
|
|
|
8
8
|
|
|
9
9
|
@rangojs/router provides end-to-end type safety for routes, parameters, and environment.
|
|
10
10
|
|
|
11
|
-
##
|
|
12
|
-
|
|
13
|
-
Register route types globally for type-safe `href()` and params:
|
|
11
|
+
## Router Setup
|
|
14
12
|
|
|
15
13
|
```typescript
|
|
16
14
|
// router.tsx
|
|
17
|
-
import {
|
|
15
|
+
import { createRouter } from "@rangojs/router";
|
|
18
16
|
import { urlpatterns } from "./urls";
|
|
19
17
|
|
|
20
|
-
const router =
|
|
18
|
+
const router = createRouter<AppBindings>({
|
|
21
19
|
document: Document,
|
|
22
|
-
|
|
23
|
-
|
|
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`.
|
|
24
41
|
|
|
25
|
-
|
|
26
|
-
export const href = router.href;
|
|
42
|
+
Recommended setup:
|
|
27
43
|
|
|
28
|
-
|
|
29
|
-
|
|
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);
|
|
30
51
|
|
|
31
52
|
declare global {
|
|
32
53
|
namespace RSCRouter {
|
|
33
|
-
interface
|
|
54
|
+
interface Env extends AppBindings {}
|
|
55
|
+
interface Vars extends AppVars {}
|
|
56
|
+
interface RegisteredRoutes extends typeof router.routeMap {}
|
|
34
57
|
}
|
|
35
58
|
}
|
|
36
|
-
|
|
37
|
-
export default router;
|
|
38
59
|
```
|
|
39
60
|
|
|
40
61
|
## Route Definition with Type-Safe Names
|
|
41
62
|
|
|
42
63
|
```typescript
|
|
43
64
|
// urls.tsx
|
|
44
|
-
import { urls } from "@rangojs/router
|
|
65
|
+
import { urls } from "@rangojs/router";
|
|
45
66
|
|
|
46
67
|
export const urlpatterns = urls(({ path, layout }) => [
|
|
47
68
|
path("/", HomePage, { name: "home" }),
|
|
@@ -56,77 +77,131 @@ export const urlpatterns = urls(({ path, layout }) => [
|
|
|
56
77
|
|
|
57
78
|
## Type-Safe href()
|
|
58
79
|
|
|
59
|
-
|
|
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
|
|
60
86
|
|
|
61
87
|
```typescript
|
|
62
|
-
import {
|
|
88
|
+
import type { Handler } from "@rangojs/router";
|
|
63
89
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
+
```
|
|
68
96
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
+
};
|
|
73
109
|
```
|
|
74
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
|
+
|
|
75
143
|
## Environment Type Setup
|
|
76
144
|
|
|
77
145
|
Define your app's environment for type-safe bindings and variables:
|
|
78
146
|
|
|
79
147
|
```typescript
|
|
80
148
|
// env.ts
|
|
81
|
-
import type { RouterEnv } from "@rangojs/router/server";
|
|
82
149
|
|
|
83
|
-
// Cloudflare bindings
|
|
84
|
-
interface AppBindings {
|
|
150
|
+
// Cloudflare bindings — passed as TEnv to createRouter<TEnv>()
|
|
151
|
+
export interface AppBindings {
|
|
85
152
|
DB: D1Database;
|
|
86
153
|
KV: KVNamespace;
|
|
87
154
|
CACHE: KVNamespace;
|
|
88
155
|
AI: Ai;
|
|
89
156
|
}
|
|
90
157
|
|
|
91
|
-
// Variables set by middleware
|
|
92
|
-
interface AppVariables {
|
|
158
|
+
// Variables set by middleware — declared via module augmentation
|
|
159
|
+
export interface AppVariables {
|
|
93
160
|
user?: { id: string; email: string; role: string };
|
|
94
161
|
requestId?: string;
|
|
95
162
|
permissions?: string[];
|
|
96
163
|
}
|
|
97
|
-
|
|
98
|
-
// Combined environment type
|
|
99
|
-
export type AppEnv = RouterEnv<AppBindings, AppVariables>;
|
|
100
164
|
```
|
|
101
165
|
|
|
102
166
|
### Using Environment Types
|
|
103
167
|
|
|
104
168
|
```typescript
|
|
105
169
|
// router.tsx
|
|
106
|
-
import type {
|
|
170
|
+
import type { AppBindings, AppVariables } from "./env";
|
|
107
171
|
|
|
108
|
-
const router =
|
|
172
|
+
const router = createRouter<AppBindings>({
|
|
109
173
|
document: Document,
|
|
110
|
-
|
|
111
|
-
});
|
|
174
|
+
}).routes(urlpatterns);
|
|
112
175
|
|
|
113
|
-
//
|
|
114
|
-
|
|
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
|
+
}
|
|
115
183
|
|
|
116
|
-
|
|
117
|
-
|
|
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
|
+
});
|
|
118
193
|
await next();
|
|
119
|
-
}
|
|
194
|
+
};
|
|
120
195
|
|
|
121
196
|
// loaders - typed context
|
|
122
|
-
export const UserLoader = createLoader(
|
|
123
|
-
const db = ctx.env.
|
|
124
|
-
const userId = ctx.
|
|
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
|
|
125
200
|
return db.prepare("SELECT * FROM users WHERE id = ?").bind(userId).first();
|
|
126
201
|
});
|
|
127
202
|
```
|
|
128
203
|
|
|
129
|
-
## Global
|
|
204
|
+
## Global Environment Registration
|
|
130
205
|
|
|
131
206
|
Register environment types globally for implicit typing:
|
|
132
207
|
|
|
@@ -134,8 +209,8 @@ Register environment types globally for implicit typing:
|
|
|
134
209
|
// router.tsx
|
|
135
210
|
declare global {
|
|
136
211
|
namespace RSCRouter {
|
|
137
|
-
interface
|
|
138
|
-
interface
|
|
212
|
+
interface Env extends AppBindings {}
|
|
213
|
+
interface Vars extends AppVariables {}
|
|
139
214
|
}
|
|
140
215
|
}
|
|
141
216
|
```
|
|
@@ -144,21 +219,113 @@ Now handlers have typed context without explicit imports:
|
|
|
144
219
|
|
|
145
220
|
```typescript
|
|
146
221
|
// In loaders
|
|
147
|
-
export const DashboardLoader = createLoader(
|
|
148
|
-
// ctx.env.
|
|
149
|
-
// ctx.
|
|
150
|
-
const user = ctx.
|
|
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");
|
|
151
226
|
return { user };
|
|
152
227
|
});
|
|
153
228
|
```
|
|
154
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
|
+
|
|
155
322
|
## Loader Type Safety
|
|
156
323
|
|
|
157
324
|
Loaders have typed return values:
|
|
158
325
|
|
|
159
326
|
```typescript
|
|
160
327
|
// loaders/product.ts
|
|
161
|
-
export const ProductLoader = createLoader(
|
|
328
|
+
export const ProductLoader = createLoader(async (ctx) => {
|
|
162
329
|
return {
|
|
163
330
|
id: ctx.params.slug,
|
|
164
331
|
name: "Widget",
|
|
@@ -167,7 +334,7 @@ export const ProductLoader = createLoader("product", async (ctx) => {
|
|
|
167
334
|
});
|
|
168
335
|
|
|
169
336
|
// In server component - type is inferred
|
|
170
|
-
import { useLoader } from "@rangojs/router/
|
|
337
|
+
import { useLoader } from "@rangojs/router/client";
|
|
171
338
|
|
|
172
339
|
async function ProductPage() {
|
|
173
340
|
const product = await useLoader(ProductLoader);
|
|
@@ -177,15 +344,71 @@ async function ProductPage() {
|
|
|
177
344
|
|
|
178
345
|
// In client component - same type
|
|
179
346
|
"use client";
|
|
180
|
-
import {
|
|
347
|
+
import { useLoader } from "@rangojs/router/client";
|
|
181
348
|
|
|
182
349
|
function ProductPrice() {
|
|
183
|
-
const {
|
|
184
|
-
//
|
|
350
|
+
const { data } = useLoader(ProductLoader);
|
|
351
|
+
// data: { id: string; name: string; price: number }
|
|
352
|
+
const product = data;
|
|
185
353
|
return <span>${product.price}</span>;
|
|
186
354
|
}
|
|
187
355
|
```
|
|
188
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
|
+
|
|
189
412
|
## Handle Type Safety
|
|
190
413
|
|
|
191
414
|
Handles have typed data:
|
|
@@ -194,10 +417,11 @@ Handles have typed data:
|
|
|
194
417
|
// handles/breadcrumbs.ts
|
|
195
418
|
import { createHandle } from "@rangojs/router";
|
|
196
419
|
|
|
420
|
+
// All export patterns work: export const, const + export { X }, export { X as Y }
|
|
197
421
|
export const Breadcrumbs = createHandle<{ label: string; href: string }>();
|
|
198
422
|
|
|
199
423
|
// In route definition - use handle() DSL
|
|
200
|
-
import { urls } from "@rangojs/router
|
|
424
|
+
import { urls } from "@rangojs/router";
|
|
201
425
|
|
|
202
426
|
export const urlpatterns = urls(({ path, handle }) => [
|
|
203
427
|
path("/shop/product/:slug", ProductPage, { name: "product" }, () => [
|
|
@@ -212,12 +436,50 @@ function BreadcrumbNav() {
|
|
|
212
436
|
}
|
|
213
437
|
```
|
|
214
438
|
|
|
439
|
+
## Ref Prop Type Safety (Loaders & Handles)
|
|
440
|
+
|
|
441
|
+
Loaders and handles can be passed as props from server to client components.
|
|
442
|
+
Use `typeof` to get the full typed definition without manually specifying generics:
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
// loaders.ts
|
|
446
|
+
export const ProductLoader = createLoader(async (ctx) => {
|
|
447
|
+
return { product: await fetchProduct(ctx.params.slug) };
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
// handles.ts
|
|
451
|
+
export const Breadcrumbs = createHandle<{ label: string; href: string }>();
|
|
452
|
+
|
|
453
|
+
// Client component — typeof infers all generics
|
|
454
|
+
("use client");
|
|
455
|
+
import { useLoader, useHandle } from "@rangojs/router/client";
|
|
456
|
+
import type { ProductLoader } from "../loaders";
|
|
457
|
+
import type { Breadcrumbs } from "../handles";
|
|
458
|
+
|
|
459
|
+
function MyComponent({
|
|
460
|
+
loader,
|
|
461
|
+
handle,
|
|
462
|
+
}: {
|
|
463
|
+
loader: typeof ProductLoader; // LoaderDefinition<{ product: Product }>
|
|
464
|
+
handle: typeof Breadcrumbs; // Handle<{ label: string; href: string }>
|
|
465
|
+
}) {
|
|
466
|
+
const { data } = useLoader(loader); // data is typed
|
|
467
|
+
const crumbs = useHandle(handle); // crumbs is typed array
|
|
468
|
+
// ...
|
|
469
|
+
}
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
RSC Flight serialization calls `toJSON()` on both loaders and handles,
|
|
473
|
+
sending only `{ __brand, $$id }` to the client. The hooks recover the
|
|
474
|
+
full functionality from module-level registries.
|
|
475
|
+
|
|
215
476
|
## Location State Type Safety
|
|
216
477
|
|
|
217
478
|
```typescript
|
|
218
479
|
// location-states.ts
|
|
219
480
|
import { createLocationState } from "@rangojs/router";
|
|
220
481
|
|
|
482
|
+
// All export patterns work: export const, const + export { X }, export { X as Y }
|
|
221
483
|
export const ProductPreview = createLocationState<{
|
|
222
484
|
name: string;
|
|
223
485
|
price: number;
|
|
@@ -244,14 +506,69 @@ function ProductHeader() {
|
|
|
244
506
|
}
|
|
245
507
|
```
|
|
246
508
|
|
|
509
|
+
## Multi-Project tsconfig Setup
|
|
510
|
+
|
|
511
|
+
For monorepos or multi-app setups, use a shared base tsconfig. Each app only needs
|
|
512
|
+
to extend the base and add its `router.tsx` to `files` so TypeScript picks up the
|
|
513
|
+
global type declarations (like `RSCRouter.Env`).
|
|
514
|
+
|
|
515
|
+
```jsonc
|
|
516
|
+
// tsconfig.base.json (root)
|
|
517
|
+
{
|
|
518
|
+
"compilerOptions": {
|
|
519
|
+
"target": "ES2022",
|
|
520
|
+
"module": "ESNext",
|
|
521
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
522
|
+
"jsx": "react-jsx",
|
|
523
|
+
"moduleResolution": "bundler",
|
|
524
|
+
"strict": true,
|
|
525
|
+
"noEmit": true,
|
|
526
|
+
"skipLibCheck": true,
|
|
527
|
+
"isolatedModules": true,
|
|
528
|
+
"esModuleInterop": true,
|
|
529
|
+
"resolveJsonModule": true,
|
|
530
|
+
},
|
|
531
|
+
}
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
```jsonc
|
|
535
|
+
// apps/shop/tsconfig.json
|
|
536
|
+
{
|
|
537
|
+
"extends": "../../tsconfig.base.json",
|
|
538
|
+
"include": ["src"],
|
|
539
|
+
"files": ["src/router.tsx"],
|
|
540
|
+
}
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
```jsonc
|
|
544
|
+
// apps/blog/tsconfig.json
|
|
545
|
+
{
|
|
546
|
+
"extends": "../../tsconfig.base.json",
|
|
547
|
+
"include": ["src"],
|
|
548
|
+
"files": ["src/router.tsx"],
|
|
549
|
+
}
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
The `files` array ensures `router.tsx` (which contains `declare global { namespace RSCRouter { interface Env; interface Vars } }`)
|
|
553
|
+
is always included in the compilation even if nothing directly imports it. Route types come from the
|
|
554
|
+
auto-generated `*.named-routes.gen.ts` file (via `rango generate`), not from manual declaration.
|
|
555
|
+
Each app gets its own typed environment without interfering with other apps.
|
|
556
|
+
|
|
247
557
|
## Complete Type-Safe Setup
|
|
248
558
|
|
|
249
559
|
```typescript
|
|
250
560
|
// 1. env.ts - Environment types
|
|
251
|
-
export
|
|
561
|
+
export interface AppBindings {
|
|
562
|
+
DB: D1Database;
|
|
563
|
+
KV: KVNamespace;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
export interface AppVariables {
|
|
567
|
+
user?: { id: string; email: string; role: string };
|
|
568
|
+
}
|
|
252
569
|
|
|
253
570
|
// 2. urls.tsx - Route definitions with names
|
|
254
|
-
import { urls } from "@rangojs/router
|
|
571
|
+
import { urls } from "@rangojs/router";
|
|
255
572
|
|
|
256
573
|
export const urlpatterns = urls(({ path, layout, loader }) => [
|
|
257
574
|
path("/", HomePage, { name: "home" }),
|
|
@@ -264,32 +581,41 @@ export const urlpatterns = urls(({ path, layout, loader }) => [
|
|
|
264
581
|
]),
|
|
265
582
|
]);
|
|
266
583
|
|
|
267
|
-
// 3. router.tsx -
|
|
268
|
-
const router =
|
|
584
|
+
// 3. router.tsx - Create router and export reverse
|
|
585
|
+
const router = createRouter<AppBindings>({
|
|
269
586
|
document: Document,
|
|
270
|
-
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
type AppRoutes = typeof router.routeMap;
|
|
587
|
+
}).routes(urlpatterns);
|
|
274
588
|
|
|
589
|
+
// Register bindings and variables globally for implicit typing
|
|
275
590
|
declare global {
|
|
276
591
|
namespace RSCRouter {
|
|
277
|
-
interface
|
|
278
|
-
interface
|
|
592
|
+
interface Env extends AppBindings {}
|
|
593
|
+
interface Vars extends AppVariables {}
|
|
279
594
|
}
|
|
280
595
|
}
|
|
281
596
|
|
|
597
|
+
export const reverse = router.reverse;
|
|
282
598
|
export default router;
|
|
283
|
-
export const href = router.href;
|
|
284
599
|
|
|
285
|
-
// 4.
|
|
286
|
-
|
|
600
|
+
// 4. Run `npx rango generate src/router.tsx` to generate
|
|
601
|
+
// router.named-routes.gen.ts (auto-registers GeneratedRouteMap globally).
|
|
602
|
+
// No manual RegisteredRoutes declaration needed.
|
|
603
|
+
|
|
604
|
+
// 5. loaders/*.ts - Type-safe loaders
|
|
605
|
+
export const ProductLoader = createLoader(async (ctx) => {
|
|
287
606
|
// ctx.params: { slug: string }
|
|
288
|
-
// ctx.
|
|
289
|
-
// ctx.env.
|
|
607
|
+
// ctx.get("user"): User | undefined (from RSCRouter.Vars)
|
|
608
|
+
// ctx.env.DB: D1Database (plain bindings from RSCRouter.Env)
|
|
290
609
|
return { product: await fetchProduct(ctx.params.slug) };
|
|
291
610
|
});
|
|
292
611
|
|
|
293
|
-
//
|
|
294
|
-
|
|
612
|
+
// 6. Server: ctx.reverse for named routes
|
|
613
|
+
path("/product/:slug", (ctx) => {
|
|
614
|
+
return <Link to={ctx.reverse("shop")}>Back to Shop</Link>;
|
|
615
|
+
}, { name: "product" })
|
|
616
|
+
|
|
617
|
+
// 7. Client: useHref for mounted paths, href for absolute
|
|
618
|
+
"use client";
|
|
619
|
+
import { useHref, href, Link } from "@rangojs/router/client";
|
|
620
|
+
<Link to={href("/shop/product/widget")}>Widget</Link>
|
|
295
621
|
```
|