@rangojs/router 0.0.0-experimental.3 → 0.0.0-experimental.31

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.
Files changed (300) hide show
  1. package/AGENTS.md +5 -0
  2. package/README.md +883 -4
  3. package/dist/bin/rango.js +1601 -0
  4. package/dist/vite/index.js +4655 -747
  5. package/package.json +78 -50
  6. package/skills/breadcrumbs/SKILL.md +206 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +54 -25
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +12 -8
  11. package/skills/document-cache/SKILL.md +23 -21
  12. package/skills/fonts/SKILL.md +167 -0
  13. package/skills/hooks/SKILL.md +389 -64
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +133 -10
  16. package/skills/layout/SKILL.md +102 -5
  17. package/skills/links/SKILL.md +239 -0
  18. package/skills/loader/SKILL.md +366 -29
  19. package/skills/middleware/SKILL.md +173 -36
  20. package/skills/mime-routes/SKILL.md +128 -0
  21. package/skills/parallel/SKILL.md +80 -3
  22. package/skills/prerender/SKILL.md +643 -0
  23. package/skills/rango/SKILL.md +86 -16
  24. package/skills/response-routes/SKILL.md +411 -0
  25. package/skills/route/SKILL.md +227 -15
  26. package/skills/router-setup/SKILL.md +225 -32
  27. package/skills/tailwind/SKILL.md +129 -0
  28. package/skills/theme/SKILL.md +12 -11
  29. package/skills/typesafety/SKILL.md +415 -87
  30. package/skills/use-cache/SKILL.md +324 -0
  31. package/src/__internal.ts +10 -4
  32. package/src/bin/rango.ts +321 -0
  33. package/src/browser/action-coordinator.ts +97 -0
  34. package/src/browser/action-response-classifier.ts +99 -0
  35. package/src/browser/event-controller.ts +87 -64
  36. package/src/browser/history-state.ts +80 -0
  37. package/src/browser/intercept-utils.ts +52 -0
  38. package/src/browser/link-interceptor.ts +20 -4
  39. package/src/browser/logging.ts +55 -0
  40. package/src/browser/merge-segment-loaders.ts +20 -12
  41. package/src/browser/navigation-bridge.ts +201 -553
  42. package/src/browser/navigation-client.ts +124 -71
  43. package/src/browser/navigation-store.ts +33 -50
  44. package/src/browser/navigation-transaction.ts +295 -0
  45. package/src/browser/network-error-handler.ts +61 -0
  46. package/src/browser/partial-update.ts +267 -317
  47. package/src/browser/prefetch/cache.ts +146 -0
  48. package/src/browser/prefetch/fetch.ts +135 -0
  49. package/src/browser/prefetch/observer.ts +65 -0
  50. package/src/browser/prefetch/policy.ts +42 -0
  51. package/src/browser/prefetch/queue.ts +88 -0
  52. package/src/browser/rango-state.ts +112 -0
  53. package/src/browser/react/Link.tsx +173 -73
  54. package/src/browser/react/NavigationProvider.tsx +138 -27
  55. package/src/browser/react/context.ts +6 -0
  56. package/src/browser/react/filter-segment-order.ts +11 -0
  57. package/src/browser/react/index.ts +12 -12
  58. package/src/browser/react/location-state-shared.ts +95 -53
  59. package/src/browser/react/location-state.ts +60 -15
  60. package/src/browser/react/mount-context.ts +37 -0
  61. package/src/browser/react/nonce-context.ts +23 -0
  62. package/src/browser/react/shallow-equal.ts +27 -0
  63. package/src/browser/react/use-action.ts +29 -51
  64. package/src/browser/react/use-client-cache.ts +5 -3
  65. package/src/browser/react/use-handle.ts +49 -65
  66. package/src/browser/react/use-href.tsx +20 -188
  67. package/src/browser/react/use-link-status.ts +6 -5
  68. package/src/browser/react/use-mount.ts +31 -0
  69. package/src/browser/react/use-navigation.ts +27 -78
  70. package/src/browser/react/use-params.ts +65 -0
  71. package/src/browser/react/use-pathname.ts +47 -0
  72. package/src/browser/react/use-router.ts +63 -0
  73. package/src/browser/react/use-search-params.ts +56 -0
  74. package/src/browser/react/use-segments.ts +80 -97
  75. package/src/browser/response-adapter.ts +73 -0
  76. package/src/browser/rsc-router.tsx +111 -26
  77. package/src/browser/scroll-restoration.ts +92 -16
  78. package/src/browser/segment-reconciler.ts +216 -0
  79. package/src/browser/segment-structure-assert.ts +83 -0
  80. package/src/browser/server-action-bridge.ts +504 -584
  81. package/src/browser/shallow.ts +6 -1
  82. package/src/browser/types.ts +92 -57
  83. package/src/browser/validate-redirect-origin.ts +29 -0
  84. package/src/build/generate-manifest.ts +438 -0
  85. package/src/build/generate-route-types.ts +36 -0
  86. package/src/build/index.ts +35 -0
  87. package/src/build/route-trie.ts +265 -0
  88. package/src/build/route-types/ast-helpers.ts +25 -0
  89. package/src/build/route-types/ast-route-extraction.ts +98 -0
  90. package/src/build/route-types/codegen.ts +102 -0
  91. package/src/build/route-types/include-resolution.ts +411 -0
  92. package/src/build/route-types/param-extraction.ts +48 -0
  93. package/src/build/route-types/per-module-writer.ts +128 -0
  94. package/src/build/route-types/router-processing.ts +469 -0
  95. package/src/build/route-types/scan-filter.ts +78 -0
  96. package/src/build/runtime-discovery.ts +231 -0
  97. package/src/cache/background-task.ts +34 -0
  98. package/src/cache/cache-key-utils.ts +44 -0
  99. package/src/cache/cache-policy.ts +125 -0
  100. package/src/cache/cache-runtime.ts +338 -0
  101. package/src/cache/cache-scope.ts +120 -303
  102. package/src/cache/cf/cf-cache-store.ts +119 -7
  103. package/src/cache/cf/index.ts +8 -2
  104. package/src/cache/document-cache.ts +101 -72
  105. package/src/cache/handle-capture.ts +81 -0
  106. package/src/cache/handle-snapshot.ts +41 -0
  107. package/src/cache/index.ts +0 -15
  108. package/src/cache/memory-segment-store.ts +191 -13
  109. package/src/cache/profile-registry.ts +73 -0
  110. package/src/cache/read-through-swr.ts +134 -0
  111. package/src/cache/segment-codec.ts +256 -0
  112. package/src/cache/taint.ts +98 -0
  113. package/src/cache/types.ts +72 -122
  114. package/src/client.rsc.tsx +12 -15
  115. package/src/client.tsx +115 -135
  116. package/src/component-utils.ts +4 -4
  117. package/src/components/DefaultDocument.tsx +5 -1
  118. package/src/context-var.ts +86 -0
  119. package/src/debug.ts +17 -7
  120. package/src/errors.ts +108 -2
  121. package/src/handle.ts +34 -19
  122. package/src/handles/MetaTags.tsx +73 -20
  123. package/src/handles/breadcrumbs.ts +66 -0
  124. package/src/handles/index.ts +1 -0
  125. package/src/handles/meta.ts +30 -13
  126. package/src/host/cookie-handler.ts +165 -0
  127. package/src/host/errors.ts +97 -0
  128. package/src/host/index.ts +53 -0
  129. package/src/host/pattern-matcher.ts +214 -0
  130. package/src/host/router.ts +352 -0
  131. package/src/host/testing.ts +79 -0
  132. package/src/host/types.ts +146 -0
  133. package/src/host/utils.ts +25 -0
  134. package/src/href-client.ts +135 -49
  135. package/src/index.rsc.ts +183 -17
  136. package/src/index.ts +241 -24
  137. package/src/internal-debug.ts +11 -0
  138. package/src/loader.rsc.ts +27 -142
  139. package/src/loader.ts +27 -10
  140. package/src/network-error-thrower.tsx +3 -1
  141. package/src/outlet-provider.tsx +45 -0
  142. package/src/prerender/param-hash.ts +37 -0
  143. package/src/prerender/store.ts +185 -0
  144. package/src/prerender.ts +463 -0
  145. package/src/reverse.ts +330 -0
  146. package/src/root-error-boundary.tsx +41 -29
  147. package/src/route-content-wrapper.tsx +9 -11
  148. package/src/route-definition/dsl-helpers.ts +934 -0
  149. package/src/route-definition/helper-factories.ts +200 -0
  150. package/src/route-definition/helpers-types.ts +430 -0
  151. package/src/route-definition/index.ts +52 -0
  152. package/src/route-definition/redirect.ts +93 -0
  153. package/src/route-definition.ts +1 -1388
  154. package/src/route-map-builder.ts +241 -112
  155. package/src/route-name.ts +53 -0
  156. package/src/route-types.ts +70 -9
  157. package/src/router/content-negotiation.ts +116 -0
  158. package/src/router/debug-manifest.ts +72 -0
  159. package/src/router/error-handling.ts +9 -9
  160. package/src/router/find-match.ts +158 -0
  161. package/src/router/handler-context.ts +371 -81
  162. package/src/router/intercept-resolution.ts +395 -0
  163. package/src/router/lazy-includes.ts +234 -0
  164. package/src/router/loader-resolution.ts +215 -122
  165. package/src/router/logging.ts +248 -0
  166. package/src/router/manifest.ts +155 -32
  167. package/src/router/match-api.ts +620 -0
  168. package/src/router/match-context.ts +5 -3
  169. package/src/router/match-handlers.ts +440 -0
  170. package/src/router/match-middleware/background-revalidation.ts +80 -93
  171. package/src/router/match-middleware/cache-lookup.ts +382 -9
  172. package/src/router/match-middleware/cache-store.ts +51 -22
  173. package/src/router/match-middleware/intercept-resolution.ts +55 -17
  174. package/src/router/match-middleware/segment-resolution.ts +24 -6
  175. package/src/router/match-pipelines.ts +10 -45
  176. package/src/router/match-result.ts +34 -29
  177. package/src/router/metrics.ts +235 -15
  178. package/src/router/middleware-cookies.ts +55 -0
  179. package/src/router/middleware-types.ts +222 -0
  180. package/src/router/middleware.ts +324 -367
  181. package/src/router/pattern-matching.ts +321 -30
  182. package/src/router/prerender-match.ts +400 -0
  183. package/src/router/preview-match.ts +170 -0
  184. package/src/router/revalidation.ts +137 -38
  185. package/src/router/router-context.ts +36 -21
  186. package/src/router/router-interfaces.ts +452 -0
  187. package/src/router/router-options.ts +592 -0
  188. package/src/router/router-registry.ts +24 -0
  189. package/src/router/segment-resolution/fresh.ts +570 -0
  190. package/src/router/segment-resolution/helpers.ts +263 -0
  191. package/src/router/segment-resolution/loader-cache.ts +198 -0
  192. package/src/router/segment-resolution/revalidation.ts +1241 -0
  193. package/src/router/segment-resolution/static-store.ts +67 -0
  194. package/src/router/segment-resolution.ts +21 -0
  195. package/src/router/segment-wrappers.ts +289 -0
  196. package/src/router/telemetry-otel.ts +299 -0
  197. package/src/router/telemetry.ts +300 -0
  198. package/src/router/timeout.ts +148 -0
  199. package/src/router/trie-matching.ts +239 -0
  200. package/src/router/types.ts +77 -3
  201. package/src/router.ts +688 -3656
  202. package/src/rsc/handler-context.ts +45 -0
  203. package/src/rsc/handler.ts +786 -760
  204. package/src/rsc/helpers.ts +140 -6
  205. package/src/rsc/index.ts +5 -25
  206. package/src/rsc/loader-fetch.ts +209 -0
  207. package/src/rsc/manifest-init.ts +86 -0
  208. package/src/rsc/nonce.ts +14 -0
  209. package/src/rsc/origin-guard.ts +141 -0
  210. package/src/rsc/progressive-enhancement.ts +379 -0
  211. package/src/rsc/response-error.ts +37 -0
  212. package/src/rsc/response-route-handler.ts +347 -0
  213. package/src/rsc/rsc-rendering.ts +235 -0
  214. package/src/rsc/runtime-warnings.ts +42 -0
  215. package/src/rsc/server-action.ts +348 -0
  216. package/src/rsc/ssr-setup.ts +128 -0
  217. package/src/rsc/types.ts +40 -14
  218. package/src/search-params.ts +230 -0
  219. package/src/segment-system.tsx +57 -61
  220. package/src/server/context.ts +202 -51
  221. package/src/server/cookie-store.ts +190 -0
  222. package/src/server/fetchable-loader-store.ts +37 -0
  223. package/src/server/handle-store.ts +94 -15
  224. package/src/server/loader-registry.ts +15 -56
  225. package/src/server/request-context.ts +422 -70
  226. package/src/server.ts +36 -120
  227. package/src/ssr/index.tsx +157 -26
  228. package/src/static-handler.ts +114 -0
  229. package/src/theme/ThemeProvider.tsx +21 -15
  230. package/src/theme/ThemeScript.tsx +5 -5
  231. package/src/theme/constants.ts +5 -2
  232. package/src/theme/index.ts +4 -14
  233. package/src/theme/theme-context.ts +4 -30
  234. package/src/theme/theme-script.ts +21 -18
  235. package/src/types/boundaries.ts +158 -0
  236. package/src/types/cache-types.ts +198 -0
  237. package/src/types/error-types.ts +192 -0
  238. package/src/types/global-namespace.ts +100 -0
  239. package/src/types/handler-context.ts +687 -0
  240. package/src/types/index.ts +88 -0
  241. package/src/types/loader-types.ts +183 -0
  242. package/src/types/route-config.ts +170 -0
  243. package/src/types/route-entry.ts +102 -0
  244. package/src/types/segments.ts +148 -0
  245. package/src/types.ts +1 -1577
  246. package/src/urls/include-helper.ts +197 -0
  247. package/src/urls/index.ts +53 -0
  248. package/src/urls/path-helper-types.ts +339 -0
  249. package/src/urls/path-helper.ts +329 -0
  250. package/src/urls/pattern-types.ts +95 -0
  251. package/src/urls/response-types.ts +106 -0
  252. package/src/urls/type-extraction.ts +372 -0
  253. package/src/urls/urls-function.ts +98 -0
  254. package/src/urls.ts +1 -726
  255. package/src/use-loader.tsx +85 -77
  256. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  257. package/src/vite/discovery/discover-routers.ts +344 -0
  258. package/src/vite/discovery/prerender-collection.ts +385 -0
  259. package/src/vite/discovery/route-types-writer.ts +258 -0
  260. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  261. package/src/vite/discovery/state.ts +110 -0
  262. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  263. package/src/vite/index.ts +11 -782
  264. package/src/vite/plugin-types.ts +131 -0
  265. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  266. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  267. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  268. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -51
  269. package/src/vite/plugins/expose-id-utils.ts +287 -0
  270. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  271. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  272. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  273. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  274. package/src/vite/plugins/expose-ids/types.ts +45 -0
  275. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  276. package/src/vite/plugins/refresh-cmd.ts +65 -0
  277. package/src/vite/plugins/use-cache-transform.ts +323 -0
  278. package/src/vite/plugins/version-injector.ts +83 -0
  279. package/src/vite/plugins/version-plugin.ts +254 -0
  280. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +29 -15
  281. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  282. package/src/vite/rango.ts +510 -0
  283. package/src/vite/router-discovery.ts +785 -0
  284. package/src/vite/utils/ast-handler-extract.ts +517 -0
  285. package/src/vite/utils/banner.ts +36 -0
  286. package/src/vite/utils/bundle-analysis.ts +137 -0
  287. package/src/vite/utils/manifest-utils.ts +70 -0
  288. package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
  289. package/src/vite/utils/prerender-utils.ts +189 -0
  290. package/src/vite/utils/shared-utils.ts +169 -0
  291. package/CLAUDE.md +0 -3
  292. package/src/browser/lru-cache.ts +0 -69
  293. package/src/browser/request-controller.ts +0 -164
  294. package/src/cache/memory-store.ts +0 -253
  295. package/src/href-context.ts +0 -33
  296. package/src/href.ts +0 -255
  297. package/src/vite/expose-handle-id.ts +0 -209
  298. package/src/vite/expose-loader-id.ts +0 -357
  299. package/src/vite/expose-location-state-id.ts +0 -177
  300. /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
- ## Route Type Registration
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 { createRSCRouter } from "@rangojs/router/server";
15
+ import { createRouter } from "@rangojs/router";
18
16
  import { urlpatterns } from "./urls";
19
17
 
20
- const router = createRSCRouter<AppEnv>({
18
+ const router = createRouter<AppBindings>({
21
19
  document: Document,
22
- urls: urlpatterns,
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
- // Extract route types for href()
26
- export const href = router.href;
42
+ Recommended setup:
27
43
 
28
- // Register globally via module augmentation
29
- type AppRoutes = typeof router.routeMap;
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 RegisteredRoutes extends AppRoutes {}
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/server";
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
- After registration, `href()` has full autocomplete:
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 { href } from "./router";
88
+ import type { Handler } from "@rangojs/router";
63
89
 
64
- // Autocomplete shows all registered route names
65
- href("home"); // "/"
66
- href("products"); // "/products"
67
- href("product", { slug: "widget" }); // "/product/widget"
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
- // TypeScript errors for:
70
- href("invalid"); // Error: not a valid route name
71
- href("product"); // Error: missing required param 'slug'
72
- href("product", { wrong: "param" }); // Error: 'wrong' not in params
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 { AppEnv } from "./env";
170
+ import type { AppBindings, AppVariables } from "./env";
107
171
 
108
- const router = createRSCRouter<AppEnv>({
172
+ const router = createRouter<AppBindings>({
109
173
  document: Document,
110
- urls: urlpatterns,
111
- });
174
+ }).routes(urlpatterns);
112
175
 
113
- // middleware - typed ctx.env.Variables
114
- import { createMiddleware } from "@rangojs/router/server";
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
- export const authMiddleware = createMiddleware(async (ctx, next) => {
117
- ctx.env.Variables.user = { id: "123", email: "user@example.com", role: "admin" };
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("user", async (ctx) => {
123
- const db = ctx.env.Bindings.DB; // D1Database
124
- const userId = ctx.env.Variables.user?.id;
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 Type Registration
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 RegisteredRoutes extends AppRoutes {}
138
- interface Env extends AppEnv {}
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("dashboard", async (ctx) => {
148
- // ctx.env.Variables.user is typed from global Env
149
- // ctx.params is typed from route pattern
150
- const user = ctx.env.Variables.user;
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("product", async (ctx) => {
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/server";
337
+ import { useLoader } from "@rangojs/router/client";
171
338
 
172
339
  async function ProductPage() {
173
340
  const product = await useLoader(ProductLoader);
@@ -177,47 +344,144 @@ async function ProductPage() {
177
344
 
178
345
  // In client component - same type
179
346
  "use client";
180
- import { useLoaderData } from "@rangojs/router/client";
347
+ import { useLoader } from "@rangojs/router/client";
181
348
 
182
349
  function ProductPrice() {
183
- const { product } = useLoaderData(ProductLoader);
184
- // product: { id: string; name: string; price: number }
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:
192
415
 
193
416
  ```typescript
194
- // handles/breadcrumbs.ts
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
195
437
  import { createHandle } from "@rangojs/router";
438
+ export const PageTitle = createHandle<string, string>(
439
+ (segments) => segments.flat().at(-1) ?? "Default Title"
440
+ );
441
+ ```
196
442
 
197
- export const Breadcrumbs = createHandle<{ label: string; href: string }>();
443
+ ## Ref Prop Type Safety (Loaders & Handles)
198
444
 
199
- // In route definition - use handle() DSL
200
- import { urls } from "@rangojs/router/server";
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:
201
447
 
202
- export const urlpatterns = urls(({ path, handle }) => [
203
- path("/shop/product/:slug", ProductPage, { name: "product" }, () => [
204
- handle(Breadcrumbs, { label: "Products", href: "/shop/products" }),
205
- ]),
206
- ]);
448
+ ```typescript
449
+ // loaders.ts
450
+ export const ProductLoader = createLoader(async (ctx) => {
451
+ return { product: await fetchProduct(ctx.params.slug) };
452
+ });
207
453
 
208
- // In client - typed array
209
- function BreadcrumbNav() {
210
- const crumbs = useHandle(Breadcrumbs);
211
- // crumbs: Array<{ label: string; href: string }>
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
+ // ...
212
471
  }
213
472
  ```
214
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
+
215
478
  ## Location State Type Safety
216
479
 
217
480
  ```typescript
218
481
  // location-states.ts
219
482
  import { createLocationState } from "@rangojs/router";
220
483
 
484
+ // All export patterns work: export const, const + export { X }, export { X as Y }
221
485
  export const ProductPreview = createLocationState<{
222
486
  name: string;
223
487
  price: number;
@@ -244,14 +508,69 @@ function ProductHeader() {
244
508
  }
245
509
  ```
246
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
+
247
559
  ## Complete Type-Safe Setup
248
560
 
249
561
  ```typescript
250
562
  // 1. env.ts - Environment types
251
- export type AppEnv = RouterEnv<AppBindings, AppVariables>;
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
+ }
252
571
 
253
572
  // 2. urls.tsx - Route definitions with names
254
- import { urls } from "@rangojs/router/server";
573
+ import { urls } from "@rangojs/router";
255
574
 
256
575
  export const urlpatterns = urls(({ path, layout, loader }) => [
257
576
  path("/", HomePage, { name: "home" }),
@@ -264,32 +583,41 @@ export const urlpatterns = urls(({ path, layout, loader }) => [
264
583
  ]),
265
584
  ]);
266
585
 
267
- // 3. router.tsx - Registration
268
- const router = createRSCRouter<AppEnv>({
586
+ // 3. router.tsx - Create router and export reverse
587
+ const router = createRouter<AppBindings>({
269
588
  document: Document,
270
- urls: urlpatterns,
271
- });
272
-
273
- type AppRoutes = typeof router.routeMap;
589
+ }).routes(urlpatterns);
274
590
 
591
+ // Register bindings and variables globally for implicit typing
275
592
  declare global {
276
593
  namespace RSCRouter {
277
- interface RegisteredRoutes extends AppRoutes {}
278
- interface Env extends AppEnv {}
594
+ interface Env extends AppBindings {}
595
+ interface Vars extends AppVariables {}
279
596
  }
280
597
  }
281
598
 
599
+ export const reverse = router.reverse;
282
600
  export default router;
283
- export const href = router.href;
284
601
 
285
- // 4. loaders/*.ts - Type-safe loaders
286
- export const ProductLoader = createLoader("product", async (ctx) => {
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) => {
287
608
  // ctx.params: { slug: string }
288
- // ctx.env.Variables.user: User | undefined
289
- // ctx.env.Bindings.DB: D1Database
609
+ // ctx.get("user"): User | undefined (from RSCRouter.Vars)
610
+ // ctx.env.DB: D1Database (plain bindings from RSCRouter.Env)
290
611
  return { product: await fetchProduct(ctx.params.slug) };
291
612
  });
292
613
 
293
- // 5. components/*.tsx - Type-safe client code
294
- <Link to={href("product", { slug: "widget" })}>Widget</Link>
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>
295
623
  ```