@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.
Files changed (297) 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/cache-guide/SKILL.md +262 -0
  7. package/skills/caching/SKILL.md +54 -25
  8. package/skills/composability/SKILL.md +172 -0
  9. package/skills/debug-manifest/SKILL.md +12 -8
  10. package/skills/document-cache/SKILL.md +23 -21
  11. package/skills/fonts/SKILL.md +167 -0
  12. package/skills/hooks/SKILL.md +390 -63
  13. package/skills/host-router/SKILL.md +218 -0
  14. package/skills/intercept/SKILL.md +133 -10
  15. package/skills/layout/SKILL.md +102 -5
  16. package/skills/links/SKILL.md +239 -0
  17. package/skills/loader/SKILL.md +366 -29
  18. package/skills/middleware/SKILL.md +173 -36
  19. package/skills/mime-routes/SKILL.md +128 -0
  20. package/skills/parallel/SKILL.md +80 -3
  21. package/skills/prerender/SKILL.md +643 -0
  22. package/skills/rango/SKILL.md +86 -16
  23. package/skills/response-routes/SKILL.md +411 -0
  24. package/skills/route/SKILL.md +227 -14
  25. package/skills/router-setup/SKILL.md +225 -32
  26. package/skills/tailwind/SKILL.md +129 -0
  27. package/skills/theme/SKILL.md +12 -11
  28. package/skills/typesafety/SKILL.md +401 -75
  29. package/skills/use-cache/SKILL.md +324 -0
  30. package/src/__internal.ts +10 -4
  31. package/src/bin/rango.ts +321 -0
  32. package/src/browser/action-coordinator.ts +97 -0
  33. package/src/browser/action-response-classifier.ts +99 -0
  34. package/src/browser/event-controller.ts +87 -64
  35. package/src/browser/history-state.ts +80 -0
  36. package/src/browser/intercept-utils.ts +52 -0
  37. package/src/browser/link-interceptor.ts +20 -4
  38. package/src/browser/logging.ts +55 -0
  39. package/src/browser/merge-segment-loaders.ts +20 -12
  40. package/src/browser/navigation-bridge.ts +201 -553
  41. package/src/browser/navigation-client.ts +124 -71
  42. package/src/browser/navigation-store.ts +33 -50
  43. package/src/browser/navigation-transaction.ts +295 -0
  44. package/src/browser/network-error-handler.ts +61 -0
  45. package/src/browser/partial-update.ts +267 -317
  46. package/src/browser/prefetch/cache.ts +146 -0
  47. package/src/browser/prefetch/fetch.ts +135 -0
  48. package/src/browser/prefetch/observer.ts +65 -0
  49. package/src/browser/prefetch/policy.ts +42 -0
  50. package/src/browser/prefetch/queue.ts +88 -0
  51. package/src/browser/rango-state.ts +112 -0
  52. package/src/browser/react/Link.tsx +173 -73
  53. package/src/browser/react/NavigationProvider.tsx +138 -27
  54. package/src/browser/react/context.ts +6 -0
  55. package/src/browser/react/filter-segment-order.ts +11 -0
  56. package/src/browser/react/index.ts +12 -12
  57. package/src/browser/react/location-state-shared.ts +95 -53
  58. package/src/browser/react/location-state.ts +60 -15
  59. package/src/browser/react/mount-context.ts +37 -0
  60. package/src/browser/react/nonce-context.ts +23 -0
  61. package/src/browser/react/shallow-equal.ts +27 -0
  62. package/src/browser/react/use-action.ts +29 -51
  63. package/src/browser/react/use-client-cache.ts +5 -3
  64. package/src/browser/react/use-handle.ts +49 -65
  65. package/src/browser/react/use-href.tsx +20 -188
  66. package/src/browser/react/use-link-status.ts +6 -5
  67. package/src/browser/react/use-mount.ts +31 -0
  68. package/src/browser/react/use-navigation.ts +27 -78
  69. package/src/browser/react/use-params.ts +65 -0
  70. package/src/browser/react/use-pathname.ts +47 -0
  71. package/src/browser/react/use-router.ts +63 -0
  72. package/src/browser/react/use-search-params.ts +56 -0
  73. package/src/browser/react/use-segments.ts +80 -97
  74. package/src/browser/response-adapter.ts +73 -0
  75. package/src/browser/rsc-router.tsx +111 -26
  76. package/src/browser/scroll-restoration.ts +92 -16
  77. package/src/browser/segment-reconciler.ts +216 -0
  78. package/src/browser/segment-structure-assert.ts +83 -0
  79. package/src/browser/server-action-bridge.ts +504 -584
  80. package/src/browser/shallow.ts +6 -1
  81. package/src/browser/types.ts +92 -57
  82. package/src/browser/validate-redirect-origin.ts +29 -0
  83. package/src/build/generate-manifest.ts +438 -0
  84. package/src/build/generate-route-types.ts +36 -0
  85. package/src/build/index.ts +35 -0
  86. package/src/build/route-trie.ts +265 -0
  87. package/src/build/route-types/ast-helpers.ts +25 -0
  88. package/src/build/route-types/ast-route-extraction.ts +98 -0
  89. package/src/build/route-types/codegen.ts +102 -0
  90. package/src/build/route-types/include-resolution.ts +411 -0
  91. package/src/build/route-types/param-extraction.ts +48 -0
  92. package/src/build/route-types/per-module-writer.ts +128 -0
  93. package/src/build/route-types/router-processing.ts +469 -0
  94. package/src/build/route-types/scan-filter.ts +78 -0
  95. package/src/build/runtime-discovery.ts +231 -0
  96. package/src/cache/background-task.ts +34 -0
  97. package/src/cache/cache-key-utils.ts +44 -0
  98. package/src/cache/cache-policy.ts +125 -0
  99. package/src/cache/cache-runtime.ts +338 -0
  100. package/src/cache/cache-scope.ts +120 -303
  101. package/src/cache/cf/cf-cache-store.ts +119 -7
  102. package/src/cache/cf/index.ts +8 -2
  103. package/src/cache/document-cache.ts +101 -72
  104. package/src/cache/handle-capture.ts +81 -0
  105. package/src/cache/handle-snapshot.ts +41 -0
  106. package/src/cache/index.ts +0 -15
  107. package/src/cache/memory-segment-store.ts +191 -13
  108. package/src/cache/profile-registry.ts +73 -0
  109. package/src/cache/read-through-swr.ts +134 -0
  110. package/src/cache/segment-codec.ts +256 -0
  111. package/src/cache/taint.ts +98 -0
  112. package/src/cache/types.ts +72 -122
  113. package/src/client.rsc.tsx +10 -15
  114. package/src/client.tsx +114 -135
  115. package/src/component-utils.ts +4 -4
  116. package/src/components/DefaultDocument.tsx +5 -1
  117. package/src/context-var.ts +86 -0
  118. package/src/debug.ts +17 -7
  119. package/src/errors.ts +108 -2
  120. package/src/handle.ts +34 -19
  121. package/src/handles/MetaTags.tsx +73 -20
  122. package/src/handles/meta.ts +30 -13
  123. package/src/host/cookie-handler.ts +165 -0
  124. package/src/host/errors.ts +97 -0
  125. package/src/host/index.ts +53 -0
  126. package/src/host/pattern-matcher.ts +214 -0
  127. package/src/host/router.ts +352 -0
  128. package/src/host/testing.ts +79 -0
  129. package/src/host/types.ts +146 -0
  130. package/src/host/utils.ts +25 -0
  131. package/src/href-client.ts +135 -49
  132. package/src/index.rsc.ts +182 -17
  133. package/src/index.ts +238 -24
  134. package/src/internal-debug.ts +11 -0
  135. package/src/loader.rsc.ts +27 -142
  136. package/src/loader.ts +27 -10
  137. package/src/network-error-thrower.tsx +3 -1
  138. package/src/outlet-provider.tsx +45 -0
  139. package/src/prerender/param-hash.ts +37 -0
  140. package/src/prerender/store.ts +185 -0
  141. package/src/prerender.ts +463 -0
  142. package/src/reverse.ts +330 -0
  143. package/src/root-error-boundary.tsx +41 -29
  144. package/src/route-content-wrapper.tsx +9 -11
  145. package/src/route-definition/dsl-helpers.ts +934 -0
  146. package/src/route-definition/helper-factories.ts +200 -0
  147. package/src/route-definition/helpers-types.ts +430 -0
  148. package/src/route-definition/index.ts +52 -0
  149. package/src/route-definition/redirect.ts +93 -0
  150. package/src/route-definition.ts +1 -1388
  151. package/src/route-map-builder.ts +241 -112
  152. package/src/route-name.ts +53 -0
  153. package/src/route-types.ts +70 -9
  154. package/src/router/content-negotiation.ts +116 -0
  155. package/src/router/debug-manifest.ts +72 -0
  156. package/src/router/error-handling.ts +9 -9
  157. package/src/router/find-match.ts +158 -0
  158. package/src/router/handler-context.ts +371 -81
  159. package/src/router/intercept-resolution.ts +395 -0
  160. package/src/router/lazy-includes.ts +234 -0
  161. package/src/router/loader-resolution.ts +215 -122
  162. package/src/router/logging.ts +248 -0
  163. package/src/router/manifest.ts +155 -32
  164. package/src/router/match-api.ts +620 -0
  165. package/src/router/match-context.ts +5 -3
  166. package/src/router/match-handlers.ts +440 -0
  167. package/src/router/match-middleware/background-revalidation.ts +80 -93
  168. package/src/router/match-middleware/cache-lookup.ts +382 -9
  169. package/src/router/match-middleware/cache-store.ts +51 -22
  170. package/src/router/match-middleware/intercept-resolution.ts +55 -17
  171. package/src/router/match-middleware/segment-resolution.ts +24 -6
  172. package/src/router/match-pipelines.ts +10 -45
  173. package/src/router/match-result.ts +34 -29
  174. package/src/router/metrics.ts +235 -15
  175. package/src/router/middleware-cookies.ts +55 -0
  176. package/src/router/middleware-types.ts +222 -0
  177. package/src/router/middleware.ts +324 -367
  178. package/src/router/pattern-matching.ts +321 -30
  179. package/src/router/prerender-match.ts +400 -0
  180. package/src/router/preview-match.ts +170 -0
  181. package/src/router/revalidation.ts +137 -38
  182. package/src/router/router-context.ts +36 -21
  183. package/src/router/router-interfaces.ts +452 -0
  184. package/src/router/router-options.ts +592 -0
  185. package/src/router/router-registry.ts +24 -0
  186. package/src/router/segment-resolution/fresh.ts +570 -0
  187. package/src/router/segment-resolution/helpers.ts +263 -0
  188. package/src/router/segment-resolution/loader-cache.ts +198 -0
  189. package/src/router/segment-resolution/revalidation.ts +1241 -0
  190. package/src/router/segment-resolution/static-store.ts +67 -0
  191. package/src/router/segment-resolution.ts +21 -0
  192. package/src/router/segment-wrappers.ts +289 -0
  193. package/src/router/telemetry-otel.ts +299 -0
  194. package/src/router/telemetry.ts +300 -0
  195. package/src/router/timeout.ts +148 -0
  196. package/src/router/trie-matching.ts +239 -0
  197. package/src/router/types.ts +77 -3
  198. package/src/router.ts +688 -3656
  199. package/src/rsc/handler-context.ts +45 -0
  200. package/src/rsc/handler.ts +786 -760
  201. package/src/rsc/helpers.ts +140 -6
  202. package/src/rsc/index.ts +5 -25
  203. package/src/rsc/loader-fetch.ts +209 -0
  204. package/src/rsc/manifest-init.ts +86 -0
  205. package/src/rsc/nonce.ts +14 -0
  206. package/src/rsc/origin-guard.ts +141 -0
  207. package/src/rsc/progressive-enhancement.ts +379 -0
  208. package/src/rsc/response-error.ts +37 -0
  209. package/src/rsc/response-route-handler.ts +347 -0
  210. package/src/rsc/rsc-rendering.ts +235 -0
  211. package/src/rsc/runtime-warnings.ts +42 -0
  212. package/src/rsc/server-action.ts +348 -0
  213. package/src/rsc/ssr-setup.ts +128 -0
  214. package/src/rsc/types.ts +40 -14
  215. package/src/search-params.ts +230 -0
  216. package/src/segment-system.tsx +57 -61
  217. package/src/server/context.ts +202 -51
  218. package/src/server/cookie-store.ts +190 -0
  219. package/src/server/fetchable-loader-store.ts +37 -0
  220. package/src/server/handle-store.ts +94 -15
  221. package/src/server/loader-registry.ts +15 -56
  222. package/src/server/request-context.ts +422 -70
  223. package/src/server.ts +36 -120
  224. package/src/ssr/index.tsx +157 -26
  225. package/src/static-handler.ts +114 -0
  226. package/src/theme/ThemeProvider.tsx +21 -15
  227. package/src/theme/ThemeScript.tsx +5 -5
  228. package/src/theme/constants.ts +5 -2
  229. package/src/theme/index.ts +4 -14
  230. package/src/theme/theme-context.ts +4 -30
  231. package/src/theme/theme-script.ts +21 -18
  232. package/src/types/boundaries.ts +158 -0
  233. package/src/types/cache-types.ts +198 -0
  234. package/src/types/error-types.ts +192 -0
  235. package/src/types/global-namespace.ts +100 -0
  236. package/src/types/handler-context.ts +687 -0
  237. package/src/types/index.ts +88 -0
  238. package/src/types/loader-types.ts +183 -0
  239. package/src/types/route-config.ts +170 -0
  240. package/src/types/route-entry.ts +102 -0
  241. package/src/types/segments.ts +148 -0
  242. package/src/types.ts +1 -1577
  243. package/src/urls/include-helper.ts +197 -0
  244. package/src/urls/index.ts +53 -0
  245. package/src/urls/path-helper-types.ts +339 -0
  246. package/src/urls/path-helper.ts +329 -0
  247. package/src/urls/pattern-types.ts +95 -0
  248. package/src/urls/response-types.ts +106 -0
  249. package/src/urls/type-extraction.ts +372 -0
  250. package/src/urls/urls-function.ts +98 -0
  251. package/src/urls.ts +1 -726
  252. package/src/use-loader.tsx +85 -77
  253. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  254. package/src/vite/discovery/discover-routers.ts +344 -0
  255. package/src/vite/discovery/prerender-collection.ts +385 -0
  256. package/src/vite/discovery/route-types-writer.ts +258 -0
  257. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  258. package/src/vite/discovery/state.ts +110 -0
  259. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  260. package/src/vite/index.ts +11 -782
  261. package/src/vite/plugin-types.ts +131 -0
  262. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  263. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  264. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  265. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -51
  266. package/src/vite/plugins/expose-id-utils.ts +287 -0
  267. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  268. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  269. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  270. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  271. package/src/vite/plugins/expose-ids/types.ts +45 -0
  272. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  273. package/src/vite/plugins/refresh-cmd.ts +65 -0
  274. package/src/vite/plugins/use-cache-transform.ts +323 -0
  275. package/src/vite/plugins/version-injector.ts +83 -0
  276. package/src/vite/plugins/version-plugin.ts +254 -0
  277. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +29 -15
  278. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  279. package/src/vite/rango.ts +510 -0
  280. package/src/vite/router-discovery.ts +785 -0
  281. package/src/vite/utils/ast-handler-extract.ts +517 -0
  282. package/src/vite/utils/banner.ts +36 -0
  283. package/src/vite/utils/bundle-analysis.ts +137 -0
  284. package/src/vite/utils/manifest-utils.ts +70 -0
  285. package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
  286. package/src/vite/utils/prerender-utils.ts +189 -0
  287. package/src/vite/utils/shared-utils.ts +169 -0
  288. package/CLAUDE.md +0 -3
  289. package/src/browser/lru-cache.ts +0 -69
  290. package/src/browser/request-controller.ts +0 -164
  291. package/src/cache/memory-store.ts +0 -253
  292. package/src/href-context.ts +0 -33
  293. package/src/href.ts +0 -255
  294. package/src/vite/expose-handle-id.ts +0 -209
  295. package/src/vite/expose-loader-id.ts +0 -357
  296. package/src/vite/expose-location-state-id.ts +0 -177
  297. /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,15 +344,71 @@ 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:
@@ -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/server";
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 type AppEnv = RouterEnv<AppBindings, AppVariables>;
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/server";
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 - Registration
268
- const router = createRSCRouter<AppEnv>({
584
+ // 3. router.tsx - Create router and export reverse
585
+ const router = createRouter<AppBindings>({
269
586
  document: Document,
270
- urls: urlpatterns,
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 RegisteredRoutes extends AppRoutes {}
278
- interface Env extends AppEnv {}
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. loaders/*.ts - Type-safe loaders
286
- export const ProductLoader = createLoader("product", async (ctx) => {
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.env.Variables.user: User | undefined
289
- // ctx.env.Bindings.DB: D1Database
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
- // 5. components/*.tsx - Type-safe client code
294
- <Link to={href("product", { slug: "widget" })}>Widget</Link>
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
  ```