@rangojs/router 0.0.0-experimental.10 → 0.0.0-experimental.100

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 (329) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +1037 -4
  3. package/dist/bin/rango.js +1619 -157
  4. package/dist/vite/index.js +5762 -2301
  5. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  6. package/package.json +71 -63
  7. package/skills/breadcrumbs/SKILL.md +252 -0
  8. package/skills/cache-guide/SKILL.md +294 -0
  9. package/skills/caching/SKILL.md +93 -23
  10. package/skills/composability/SKILL.md +172 -0
  11. package/skills/debug-manifest/SKILL.md +12 -8
  12. package/skills/document-cache/SKILL.md +18 -16
  13. package/skills/fonts/SKILL.md +6 -4
  14. package/skills/handler-use/SKILL.md +364 -0
  15. package/skills/hooks/SKILL.md +367 -71
  16. package/skills/host-router/SKILL.md +218 -0
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +176 -8
  19. package/skills/layout/SKILL.md +124 -3
  20. package/skills/links/SKILL.md +304 -25
  21. package/skills/loader/SKILL.md +474 -47
  22. package/skills/middleware/SKILL.md +207 -37
  23. package/skills/migrate-nextjs/SKILL.md +562 -0
  24. package/skills/migrate-react-router/SKILL.md +769 -0
  25. package/skills/mime-routes/SKILL.md +15 -11
  26. package/skills/parallel/SKILL.md +272 -1
  27. package/skills/prerender/SKILL.md +467 -65
  28. package/skills/rango/SKILL.md +89 -21
  29. package/skills/response-routes/SKILL.md +152 -91
  30. package/skills/route/SKILL.md +305 -14
  31. package/skills/router-setup/SKILL.md +210 -32
  32. package/skills/server-actions/SKILL.md +739 -0
  33. package/skills/streams-and-websockets/SKILL.md +283 -0
  34. package/skills/theme/SKILL.md +9 -8
  35. package/skills/typesafety/SKILL.md +333 -86
  36. package/skills/use-cache/SKILL.md +324 -0
  37. package/skills/view-transitions/SKILL.md +212 -0
  38. package/src/__internal.ts +102 -4
  39. package/src/bin/rango.ts +312 -15
  40. package/src/browser/action-coordinator.ts +97 -0
  41. package/src/browser/action-response-classifier.ts +99 -0
  42. package/src/browser/app-shell.ts +52 -0
  43. package/src/browser/app-version.ts +14 -0
  44. package/src/browser/event-controller.ts +136 -68
  45. package/src/browser/history-state.ts +80 -0
  46. package/src/browser/intercept-utils.ts +52 -0
  47. package/src/browser/link-interceptor.ts +24 -4
  48. package/src/browser/logging.ts +55 -0
  49. package/src/browser/merge-segment-loaders.ts +20 -12
  50. package/src/browser/navigation-bridge.ts +374 -561
  51. package/src/browser/navigation-client.ts +228 -70
  52. package/src/browser/navigation-store.ts +97 -55
  53. package/src/browser/navigation-transaction.ts +297 -0
  54. package/src/browser/network-error-handler.ts +61 -0
  55. package/src/browser/partial-update.ts +376 -315
  56. package/src/browser/prefetch/cache.ts +314 -0
  57. package/src/browser/prefetch/fetch.ts +282 -0
  58. package/src/browser/prefetch/observer.ts +65 -0
  59. package/src/browser/prefetch/policy.ts +48 -0
  60. package/src/browser/prefetch/queue.ts +191 -0
  61. package/src/browser/prefetch/resource-ready.ts +77 -0
  62. package/src/browser/rango-state.ts +152 -0
  63. package/src/browser/react/Link.tsx +255 -71
  64. package/src/browser/react/NavigationProvider.tsx +152 -24
  65. package/src/browser/react/context.ts +11 -0
  66. package/src/browser/react/filter-segment-order.ts +55 -0
  67. package/src/browser/react/index.ts +15 -12
  68. package/src/browser/react/location-state-shared.ts +95 -53
  69. package/src/browser/react/location-state.ts +60 -15
  70. package/src/browser/react/mount-context.ts +6 -1
  71. package/src/browser/react/nonce-context.ts +23 -0
  72. package/src/browser/react/shallow-equal.ts +27 -0
  73. package/src/browser/react/use-action.ts +29 -51
  74. package/src/browser/react/use-client-cache.ts +5 -3
  75. package/src/browser/react/use-handle.ts +30 -120
  76. package/src/browser/react/use-link-status.ts +6 -5
  77. package/src/browser/react/use-navigation.ts +44 -65
  78. package/src/browser/react/use-params.ts +78 -0
  79. package/src/browser/react/use-pathname.ts +47 -0
  80. package/src/browser/react/use-reverse.ts +99 -0
  81. package/src/browser/react/use-router.ts +83 -0
  82. package/src/browser/react/use-search-params.ts +56 -0
  83. package/src/browser/react/use-segments.ts +85 -99
  84. package/src/browser/response-adapter.ts +73 -0
  85. package/src/browser/rsc-router.tsx +246 -64
  86. package/src/browser/scroll-restoration.ts +127 -52
  87. package/src/browser/segment-reconciler.ts +243 -0
  88. package/src/browser/segment-structure-assert.ts +16 -0
  89. package/src/browser/server-action-bridge.ts +510 -603
  90. package/src/browser/shallow.ts +6 -1
  91. package/src/browser/types.ts +158 -48
  92. package/src/browser/validate-redirect-origin.ts +29 -0
  93. package/src/build/generate-manifest.ts +84 -23
  94. package/src/build/generate-route-types.ts +39 -828
  95. package/src/build/index.ts +4 -5
  96. package/src/build/route-trie.ts +85 -32
  97. package/src/build/route-types/ast-helpers.ts +25 -0
  98. package/src/build/route-types/ast-route-extraction.ts +98 -0
  99. package/src/build/route-types/codegen.ts +102 -0
  100. package/src/build/route-types/include-resolution.ts +418 -0
  101. package/src/build/route-types/param-extraction.ts +48 -0
  102. package/src/build/route-types/per-module-writer.ts +128 -0
  103. package/src/build/route-types/router-processing.ts +618 -0
  104. package/src/build/route-types/scan-filter.ts +85 -0
  105. package/src/build/runtime-discovery.ts +231 -0
  106. package/src/cache/background-task.ts +34 -0
  107. package/src/cache/cache-key-utils.ts +44 -0
  108. package/src/cache/cache-policy.ts +125 -0
  109. package/src/cache/cache-runtime.ts +342 -0
  110. package/src/cache/cache-scope.ts +167 -307
  111. package/src/cache/cf/cf-cache-store.ts +573 -21
  112. package/src/cache/cf/index.ts +13 -3
  113. package/src/cache/document-cache.ts +116 -77
  114. package/src/cache/handle-capture.ts +81 -0
  115. package/src/cache/handle-snapshot.ts +41 -0
  116. package/src/cache/index.ts +1 -15
  117. package/src/cache/memory-segment-store.ts +191 -13
  118. package/src/cache/profile-registry.ts +73 -0
  119. package/src/cache/read-through-swr.ts +134 -0
  120. package/src/cache/segment-codec.ts +256 -0
  121. package/src/cache/taint.ts +153 -0
  122. package/src/cache/types.ts +72 -122
  123. package/src/client.rsc.tsx +6 -1
  124. package/src/client.tsx +118 -302
  125. package/src/component-utils.ts +4 -4
  126. package/src/components/DefaultDocument.tsx +5 -1
  127. package/src/context-var.ts +156 -0
  128. package/src/debug.ts +19 -9
  129. package/src/errors.ts +77 -7
  130. package/src/handle.ts +55 -10
  131. package/src/handles/MetaTags.tsx +73 -20
  132. package/src/handles/breadcrumbs.ts +66 -0
  133. package/src/handles/index.ts +1 -0
  134. package/src/handles/meta.ts +30 -13
  135. package/src/host/cookie-handler.ts +21 -15
  136. package/src/host/errors.ts +8 -8
  137. package/src/host/index.ts +4 -7
  138. package/src/host/pattern-matcher.ts +27 -27
  139. package/src/host/router.ts +61 -39
  140. package/src/host/testing.ts +8 -8
  141. package/src/host/types.ts +15 -7
  142. package/src/host/utils.ts +1 -1
  143. package/src/href-client.ts +65 -45
  144. package/src/index.rsc.ts +138 -21
  145. package/src/index.ts +206 -51
  146. package/src/internal-debug.ts +11 -0
  147. package/src/loader.rsc.ts +25 -143
  148. package/src/loader.ts +27 -10
  149. package/src/network-error-thrower.tsx +3 -1
  150. package/src/outlet-context.ts +1 -1
  151. package/src/outlet-provider.tsx +45 -0
  152. package/src/prerender/param-hash.ts +4 -2
  153. package/src/prerender/store.ts +159 -13
  154. package/src/prerender.ts +397 -29
  155. package/src/response-utils.ts +28 -0
  156. package/src/reverse.ts +231 -121
  157. package/src/root-error-boundary.tsx +41 -29
  158. package/src/route-content-wrapper.tsx +7 -4
  159. package/src/route-definition/dsl-helpers.ts +1134 -0
  160. package/src/route-definition/helper-factories.ts +200 -0
  161. package/src/route-definition/helpers-types.ts +483 -0
  162. package/src/route-definition/index.ts +55 -0
  163. package/src/route-definition/redirect.ts +101 -0
  164. package/src/route-definition/resolve-handler-use.ts +155 -0
  165. package/src/route-definition.ts +1 -1431
  166. package/src/route-map-builder.ts +162 -123
  167. package/src/route-name.ts +53 -0
  168. package/src/route-types.ts +66 -9
  169. package/src/router/content-negotiation.ts +215 -0
  170. package/src/router/debug-manifest.ts +72 -0
  171. package/src/router/error-handling.ts +9 -9
  172. package/src/router/find-match.ts +160 -0
  173. package/src/router/handler-context.ts +418 -86
  174. package/src/router/intercept-resolution.ts +35 -20
  175. package/src/router/lazy-includes.ts +237 -0
  176. package/src/router/loader-resolution.ts +359 -128
  177. package/src/router/logging.ts +251 -0
  178. package/src/router/manifest.ts +98 -32
  179. package/src/router/match-api.ts +196 -261
  180. package/src/router/match-context.ts +4 -2
  181. package/src/router/match-handlers.ts +441 -0
  182. package/src/router/match-middleware/background-revalidation.ts +108 -93
  183. package/src/router/match-middleware/cache-lookup.ts +415 -86
  184. package/src/router/match-middleware/cache-store.ts +91 -29
  185. package/src/router/match-middleware/intercept-resolution.ts +48 -21
  186. package/src/router/match-middleware/segment-resolution.ts +73 -9
  187. package/src/router/match-pipelines.ts +10 -45
  188. package/src/router/match-result.ts +154 -35
  189. package/src/router/metrics.ts +240 -15
  190. package/src/router/middleware-cookies.ts +55 -0
  191. package/src/router/middleware-types.ts +209 -0
  192. package/src/router/middleware.ts +373 -371
  193. package/src/router/navigation-snapshot.ts +182 -0
  194. package/src/router/pattern-matching.ts +292 -52
  195. package/src/router/prerender-match.ts +502 -0
  196. package/src/router/preview-match.ts +98 -0
  197. package/src/router/request-classification.ts +310 -0
  198. package/src/router/revalidation.ts +152 -39
  199. package/src/router/route-snapshot.ts +245 -0
  200. package/src/router/router-context.ts +41 -21
  201. package/src/router/router-interfaces.ts +484 -0
  202. package/src/router/router-options.ts +618 -0
  203. package/src/router/router-registry.ts +24 -0
  204. package/src/router/segment-resolution/fresh.ts +756 -0
  205. package/src/router/segment-resolution/helpers.ts +268 -0
  206. package/src/router/segment-resolution/loader-cache.ts +199 -0
  207. package/src/router/segment-resolution/revalidation.ts +1407 -0
  208. package/src/router/segment-resolution/static-store.ts +67 -0
  209. package/src/router/segment-resolution.ts +21 -1315
  210. package/src/router/segment-wrappers.ts +291 -0
  211. package/src/router/substitute-pattern-params.ts +56 -0
  212. package/src/router/telemetry-otel.ts +299 -0
  213. package/src/router/telemetry.ts +300 -0
  214. package/src/router/timeout.ts +148 -0
  215. package/src/router/trie-matching.ts +111 -39
  216. package/src/router/types.ts +17 -9
  217. package/src/router/url-params.ts +49 -0
  218. package/src/router.ts +642 -2011
  219. package/src/rsc/handler-context.ts +45 -0
  220. package/src/rsc/handler.ts +864 -1114
  221. package/src/rsc/helpers.ts +181 -19
  222. package/src/rsc/index.ts +0 -20
  223. package/src/rsc/loader-fetch.ts +229 -0
  224. package/src/rsc/manifest-init.ts +90 -0
  225. package/src/rsc/nonce.ts +14 -0
  226. package/src/rsc/origin-guard.ts +141 -0
  227. package/src/rsc/progressive-enhancement.ts +395 -0
  228. package/src/rsc/response-error.ts +37 -0
  229. package/src/rsc/response-route-handler.ts +360 -0
  230. package/src/rsc/rsc-rendering.ts +256 -0
  231. package/src/rsc/runtime-warnings.ts +42 -0
  232. package/src/rsc/server-action.ts +360 -0
  233. package/src/rsc/ssr-setup.ts +128 -0
  234. package/src/rsc/types.ts +52 -11
  235. package/src/search-params.ts +230 -0
  236. package/src/segment-content-promise.ts +67 -0
  237. package/src/segment-loader-promise.ts +122 -0
  238. package/src/segment-system.tsx +187 -38
  239. package/src/server/context.ts +333 -59
  240. package/src/server/cookie-store.ts +190 -0
  241. package/src/server/fetchable-loader-store.ts +37 -0
  242. package/src/server/handle-store.ts +113 -15
  243. package/src/server/loader-registry.ts +24 -64
  244. package/src/server/request-context.ts +603 -109
  245. package/src/server.ts +35 -155
  246. package/src/ssr/index.tsx +107 -30
  247. package/src/static-handler.ts +126 -0
  248. package/src/theme/ThemeProvider.tsx +21 -15
  249. package/src/theme/ThemeScript.tsx +5 -5
  250. package/src/theme/constants.ts +5 -2
  251. package/src/theme/index.ts +4 -14
  252. package/src/theme/theme-context.ts +4 -30
  253. package/src/theme/theme-script.ts +21 -18
  254. package/src/types/boundaries.ts +158 -0
  255. package/src/types/cache-types.ts +198 -0
  256. package/src/types/error-types.ts +192 -0
  257. package/src/types/global-namespace.ts +100 -0
  258. package/src/types/handler-context.ts +764 -0
  259. package/src/types/index.ts +88 -0
  260. package/src/types/loader-types.ts +209 -0
  261. package/src/types/request-scope.ts +126 -0
  262. package/src/types/route-config.ts +170 -0
  263. package/src/types/route-entry.ts +120 -0
  264. package/src/types/segments.ts +167 -0
  265. package/src/types.ts +1 -1757
  266. package/src/urls/include-helper.ts +207 -0
  267. package/src/urls/index.ts +53 -0
  268. package/src/urls/path-helper-types.ts +372 -0
  269. package/src/urls/path-helper.ts +364 -0
  270. package/src/urls/pattern-types.ts +107 -0
  271. package/src/urls/response-types.ts +108 -0
  272. package/src/urls/type-extraction.ts +372 -0
  273. package/src/urls/urls-function.ts +98 -0
  274. package/src/urls.ts +1 -1282
  275. package/src/use-loader.tsx +161 -81
  276. package/src/vite/debug.ts +184 -0
  277. package/src/vite/discovery/bundle-postprocess.ts +181 -0
  278. package/src/vite/discovery/discover-routers.ts +376 -0
  279. package/src/vite/discovery/gate-state.ts +171 -0
  280. package/src/vite/discovery/prerender-collection.ts +486 -0
  281. package/src/vite/discovery/route-types-writer.ts +258 -0
  282. package/src/vite/discovery/self-gen-tracking.ts +73 -0
  283. package/src/vite/discovery/state.ts +117 -0
  284. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  285. package/src/vite/index.ts +15 -2063
  286. package/src/vite/plugin-types.ts +103 -0
  287. package/src/vite/plugins/cjs-to-esm.ts +98 -0
  288. package/src/vite/plugins/client-ref-dedup.ts +131 -0
  289. package/src/vite/plugins/client-ref-hashing.ts +117 -0
  290. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  291. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  292. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  293. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +107 -64
  294. package/src/vite/plugins/expose-id-utils.ts +299 -0
  295. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  296. package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
  297. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  298. package/src/vite/plugins/expose-ids/router-transform.ts +127 -0
  299. package/src/vite/plugins/expose-ids/types.ts +45 -0
  300. package/src/vite/plugins/expose-internal-ids.ts +816 -0
  301. package/src/vite/plugins/performance-tracks.ts +96 -0
  302. package/src/vite/plugins/refresh-cmd.ts +127 -0
  303. package/src/vite/plugins/use-cache-transform.ts +336 -0
  304. package/src/vite/plugins/version-injector.ts +109 -0
  305. package/src/vite/plugins/version-plugin.ts +266 -0
  306. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  307. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  308. package/src/vite/rango.ts +497 -0
  309. package/src/vite/router-discovery.ts +1423 -0
  310. package/src/vite/utils/ast-handler-extract.ts +517 -0
  311. package/src/vite/utils/banner.ts +36 -0
  312. package/src/vite/utils/bundle-analysis.ts +137 -0
  313. package/src/vite/utils/manifest-utils.ts +70 -0
  314. package/src/vite/utils/package-resolution.ts +161 -0
  315. package/src/vite/utils/prerender-utils.ts +222 -0
  316. package/src/vite/utils/shared-utils.ts +170 -0
  317. package/CLAUDE.md +0 -43
  318. package/src/browser/lru-cache.ts +0 -69
  319. package/src/browser/request-controller.ts +0 -164
  320. package/src/cache/memory-store.ts +0 -253
  321. package/src/href-context.ts +0 -33
  322. package/src/router.gen.ts +0 -6
  323. package/src/urls.gen.ts +0 -8
  324. package/src/vite/expose-handle-id.ts +0 -209
  325. package/src/vite/expose-loader-id.ts +0 -426
  326. package/src/vite/expose-location-state-id.ts +0 -177
  327. package/src/vite/expose-prerender-handler-id.ts +0 -429
  328. package/src/vite/package-resolution.ts +0 -125
  329. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -15,10 +15,9 @@ argument-hint: [setup]
15
15
  import { createRouter } from "@rangojs/router";
16
16
  import { urlpatterns } from "./urls";
17
17
 
18
- const router = createRouter<AppEnv>({
18
+ const router = createRouter<AppBindings>({
19
19
  document: Document,
20
- urls: urlpatterns,
21
- });
20
+ }).routes(urlpatterns);
22
21
 
23
22
  // Server-side named-route reverse (type-safe via routeMap)
24
23
  export const reverse = router.reverse;
@@ -26,6 +25,39 @@ export const reverse = router.reverse;
26
25
  export default router;
27
26
  ```
28
27
 
28
+ ### Which global type should I use?
29
+
30
+ Use the generated route map by default. Manual `RegisteredRoutes` augmentation
31
+ is only needed when you want the richer `typeof router.routeMap` shape
32
+ available globally.
33
+
34
+ - `GeneratedRouteMap` — auto-registered by `router.named-routes.gen.ts`
35
+ Use for `Handler<"name">`, `Prerender<"name">`, server `ctx.reverse()`,
36
+ and named-route param/search inference.
37
+ - `typeof router.routeMap` — the real merged route map from your router
38
+ instance, including response-route metadata such as `{ path, response }`.
39
+ - `RegisteredRoutes` — manual global hook for exposing `typeof router.routeMap`
40
+ to utilities like `href()`, `ValidPaths`, and `PathResponse`.
41
+
42
+ Recommended setup:
43
+
44
+ ```typescript
45
+ // router.tsx
46
+ import { createRouter } from "@rangojs/router";
47
+ import { urlpatterns } from "./urls";
48
+ import type { AppBindings, AppVars } from "./env";
49
+
50
+ export const router = createRouter<AppBindings>({}).routes(urlpatterns);
51
+
52
+ declare global {
53
+ namespace RSCRouter {
54
+ interface Env extends AppBindings {}
55
+ interface Vars extends AppVars {}
56
+ interface RegisteredRoutes extends typeof router.routeMap {}
57
+ }
58
+ }
59
+ ```
60
+
29
61
  ## Route Definition with Type-Safe Names
30
62
 
31
63
  ```typescript
@@ -45,22 +77,35 @@ export const urlpatterns = urls(({ path, layout }) => [
45
77
 
46
78
  ## Type-Safe href()
47
79
 
48
- ### Server: ctx.reverse + scopedReverse
80
+ ### Server: ctx.reverse with route names
49
81
 
50
- In route handlers, use `scopedReverse()` for local route name autocomplete:
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
51
86
 
52
87
  ```typescript
53
- import { scopedReverse } from "@rangojs/router";
88
+ import type { Handler } from "@rangojs/router";
54
89
 
55
- path("/product/:slug", (ctx) => {
56
- const reverse = scopedReverse<typeof shopPatterns>(ctx.reverse);
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
+ ```
57
96
 
58
- reverse("cart"); // Type-safe local name
59
- reverse("product", { slug: "widget" }); // Type-safe with params
60
- reverse("blog.post"); // Absolute names always allowed
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`:
61
99
 
62
- return <ProductPage slug={ctx.params.slug} />;
63
- }, { name: "product" })
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
+ };
64
109
  ```
65
110
 
66
111
  ### Client: href + useHref
@@ -82,6 +127,17 @@ function ShopNav() {
82
127
  }
83
128
  ```
84
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
+
85
141
  See `/links` for full URL generation guide.
86
142
 
87
143
  ## Environment Type Setup
@@ -90,50 +146,57 @@ Define your app's environment for type-safe bindings and variables:
90
146
 
91
147
  ```typescript
92
148
  // env.ts
93
- import type { RouterEnv } from "@rangojs/router";
94
149
 
95
- // Cloudflare bindings
96
- interface AppBindings {
150
+ // Cloudflare bindings — passed as TEnv to createRouter<TEnv>()
151
+ export interface AppBindings {
97
152
  DB: D1Database;
98
153
  KV: KVNamespace;
99
154
  CACHE: KVNamespace;
100
155
  AI: Ai;
101
156
  }
102
157
 
103
- // Variables set by middleware
104
- interface AppVariables {
158
+ // Variables set by middleware — declared via module augmentation
159
+ export interface AppVariables {
105
160
  user?: { id: string; email: string; role: string };
106
161
  requestId?: string;
107
162
  permissions?: string[];
108
163
  }
109
-
110
- // Combined environment type
111
- export type AppEnv = RouterEnv<AppBindings, AppVariables>;
112
164
  ```
113
165
 
114
166
  ### Using Environment Types
115
167
 
116
168
  ```typescript
117
169
  // router.tsx
118
- import type { AppEnv } from "./env";
170
+ import type { AppBindings, AppVariables } from "./env";
119
171
 
120
- const router = createRouter<AppEnv>({
172
+ const router = createRouter<AppBindings>({
121
173
  document: Document,
122
- urls: urlpatterns,
123
- });
174
+ }).routes(urlpatterns);
175
+
176
+ // Register bindings and variables globally for implicit typing
177
+ declare global {
178
+ namespace RSCRouter {
179
+ interface Env extends AppBindings {}
180
+ interface Vars extends AppVariables {}
181
+ }
182
+ }
124
183
 
125
- // middleware - typed ctx.env.Variables
126
- import { createMiddleware } from "@rangojs/router";
184
+ // middleware - typed via ctx.set / ctx.get
185
+ import type { Middleware } from "@rangojs/router";
127
186
 
128
- export const authMiddleware = createMiddleware(async (ctx, next) => {
129
- ctx.env.Variables.user = { id: "123", email: "user@example.com", role: "admin" };
187
+ export const authMiddleware: Middleware = async (ctx, next) => {
188
+ ctx.set("user", {
189
+ id: "123",
190
+ email: "user@example.com",
191
+ role: "admin",
192
+ });
130
193
  await next();
131
- });
194
+ };
132
195
 
133
196
  // loaders - typed context
134
- export const UserLoader = createLoader("user", async (ctx) => {
135
- const db = ctx.env.Bindings.DB; // D1Database
136
- 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
137
200
  return db.prepare("SELECT * FROM users WHERE id = ?").bind(userId).first();
138
201
  });
139
202
  ```
@@ -146,7 +209,8 @@ Register environment types globally for implicit typing:
146
209
  // router.tsx
147
210
  declare global {
148
211
  namespace RSCRouter {
149
- interface Env extends AppEnv {}
212
+ interface Env extends AppBindings {}
213
+ interface Vars extends AppVariables {}
150
214
  }
151
215
  }
152
216
  ```
@@ -155,21 +219,119 @@ Now handlers have typed context without explicit imports:
155
219
 
156
220
  ```typescript
157
221
  // In loaders
158
- export const DashboardLoader = createLoader("dashboard", async (ctx) => {
159
- // ctx.env.Variables.user is typed from global Env
160
- // ctx.params is typed from route pattern
161
- 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");
162
226
  return { user };
163
227
  });
164
228
  ```
165
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
+ // Optional URL params (`:slug?`) resolve to `string | undefined`
291
+ // because absent segments are omitted from `ctx.params` at runtime.
292
+ type C = RouteParams<"checkout">;
293
+ // { step?: string }
294
+ // → ctx.params.step is `string | undefined`; use `?? "default"` to coalesce.
295
+
296
+ // Use in component props
297
+ interface SearchResultsProps {
298
+ params: RouteSearchParams<"search">;
299
+ }
300
+ ```
301
+
302
+ Both default to the global route map (`RegisteredRoutes` or `GeneratedRouteMap`).
303
+ Pass an explicit route map as the second type argument when needed:
304
+
305
+ ```typescript
306
+ import type { routes } from "./urls.gen.js";
307
+
308
+ type SP = RouteSearchParams<"search", routes>;
309
+ type P = RouteParams<"blogPost", routes>;
310
+ ```
311
+
312
+ ### Generated route types
313
+
314
+ In the generated `router.named-routes.gen.ts`, routes with search schemas
315
+ use `{ path, search }` objects:
316
+
317
+ ```typescript
318
+ // router.named-routes.gen.ts (auto-generated)
319
+ export const NamedRoutes = {
320
+ "search.index": {
321
+ path: "/search",
322
+ search: { q: "string", page: "number?", sort: "string?" },
323
+ },
324
+ "home.index": "/", // No search schema -> plain string
325
+ } as const;
326
+ ```
327
+
166
328
  ## Loader Type Safety
167
329
 
168
330
  Loaders have typed return values:
169
331
 
170
332
  ```typescript
171
333
  // loaders/product.ts
172
- export const ProductLoader = createLoader("product", async (ctx) => {
334
+ export const ProductLoader = createLoader(async (ctx) => {
173
335
  return {
174
336
  id: ctx.params.slug,
175
337
  name: "Widget",
@@ -178,7 +340,7 @@ export const ProductLoader = createLoader("product", async (ctx) => {
178
340
  });
179
341
 
180
342
  // In server component - type is inferred
181
- import { useLoader } from "@rangojs/router";
343
+ import { useLoader } from "@rangojs/router/client";
182
344
 
183
345
  async function ProductPage() {
184
346
  const product = await useLoader(ProductLoader);
@@ -188,39 +350,110 @@ async function ProductPage() {
188
350
 
189
351
  // In client component - same type
190
352
  "use client";
191
- import { useLoaderData } from "@rangojs/router/client";
353
+ import { useLoader } from "@rangojs/router/client";
192
354
 
193
355
  function ProductPrice() {
194
- const { product } = useLoaderData(ProductLoader);
195
- // product: { id: string; name: string; price: number }
356
+ const { data } = useLoader(ProductLoader);
357
+ // data: { id: string; name: string; price: number }
358
+ const product = data;
196
359
  return <span>${product.price}</span>;
197
360
  }
198
361
  ```
199
362
 
200
- ## Handle Type Safety
363
+ ## Typed Context Variables
201
364
 
202
- Handles have typed data:
365
+ `createVar<T>()` creates a typed token for `ctx.set()`/`ctx.get()`, making
366
+ handler-to-layout data contracts explicit and compile-time verified:
203
367
 
204
368
  ```typescript
205
- // handles/breadcrumbs.ts
206
- import { createHandle } from "@rangojs/router";
369
+ import { createVar } from "@rangojs/router";
207
370
 
208
- export const Breadcrumbs = createHandle<{ label: string; href: string }>();
371
+ // Define a typed token (shared between producer and consumer)
372
+ interface PaginationData {
373
+ current: number;
374
+ total: number;
375
+ perPage: number;
376
+ }
377
+ export const Pagination = createVar<PaginationData>();
209
378
 
210
- // In route definition - use handle() DSL
211
- import { urls } from "@rangojs/router";
379
+ // Non-cacheable var reading inside cache() or "use cache" throws at runtime
380
+ const Session = createVar<SessionData>({ cache: false });
381
+ ```
212
382
 
213
- export const urlpatterns = urls(({ path, handle }) => [
214
- path("/shop/product/:slug", ProductPage, { name: "product" }, () => [
215
- handle(Breadcrumbs, { label: "Products", href: "/shop/products" }),
216
- ]),
217
- ]);
383
+ `createVar` accepts an optional options object. The `cache` option (default
384
+ `true`) controls whether the var's values can be read inside cache scopes.
385
+ Write-level escalation is also supported: `ctx.set(Var, value, { cache: false })`
386
+ marks a specific write as non-cacheable even if the var itself is cacheable.
387
+ "Least cacheable wins" — if either says `cache: false`, the value throws on
388
+ read inside `cache()` or `"use cache"`.
218
389
 
219
- // In client - typed array
390
+ ### Producer (handler or middleware)
391
+
392
+ ```typescript
393
+ import { Pagination } from "../vars/pagination.js";
394
+
395
+ const ArticleList: Handler<"articles.list"> = async (ctx) => {
396
+ ctx.set(Pagination, { // type-checked
397
+ current: 1,
398
+ total: 10,
399
+ perPage: 5,
400
+ });
401
+ return <Articles />;
402
+ };
403
+ ```
404
+
405
+ ### Consumer (layout, parallel, or any context with get)
406
+
407
+ ```typescript
408
+ import { Pagination } from "../vars/pagination.js";
409
+
410
+ export function PaginationLayout(ctx: any) {
411
+ const pagination = ctx.get(Pagination); // typed as PaginationData | undefined
412
+ if (!pagination) return <Outlet />;
413
+ return <nav>Page {pagination.current} of {pagination.total}</nav>;
414
+ }
415
+ ```
416
+
417
+ ### Why not just use RSCRouter.Vars?
418
+
419
+ `RSCRouter.Vars` (via module augmentation) provides app-global typing for
420
+ `ctx.get("key")` / `ctx.set("key", value)`. It works for middleware state
421
+ shared app-wide. `createVar<T>()` is for route-local or feature-scoped
422
+ context -- the producer and consumer import the same token, creating a
423
+ scoped contract without polluting global types.
424
+
425
+ Both approaches coexist: `ctx.get("user")` (global via Vars) and
426
+ `ctx.get(Pagination)` (scoped via createVar) work side by side.
427
+
428
+ ## Handle Type Safety
429
+
430
+ Handles have typed data:
431
+
432
+ ```typescript
433
+ // Built-in Breadcrumbs handle — import from "@rangojs/router"
434
+ import { Breadcrumbs } from "@rangojs/router";
435
+ // Type: Handle<BreadcrumbItem, BreadcrumbItem[]>
436
+ // BreadcrumbItem: { label: string; href: string; content?: ReactNode | Promise<ReactNode> }
437
+
438
+ // In route handler — push is fully typed
439
+ path("/shop/product/:slug", (ctx) => {
440
+ const breadcrumb = ctx.use(Breadcrumbs);
441
+ breadcrumb({ label: "Products", href: "/shop/products" });
442
+ return <ProductPage />;
443
+ }, { name: "product" });
444
+
445
+ // In client — typed array
446
+ import { useHandle, Breadcrumbs } from "@rangojs/router/client";
220
447
  function BreadcrumbNav() {
221
448
  const crumbs = useHandle(Breadcrumbs);
222
- // crumbs: Array<{ label: string; href: string }>
449
+ // crumbs: BreadcrumbItem[]
223
450
  }
451
+
452
+ // Custom handles also work the same way
453
+ import { createHandle } from "@rangojs/router";
454
+ export const PageTitle = createHandle<string, string>(
455
+ (segments) => segments.flat().at(-1) ?? "Default Title"
456
+ );
224
457
  ```
225
458
 
226
459
  ## Ref Prop Type Safety (Loaders & Handles)
@@ -234,24 +467,24 @@ export const ProductLoader = createLoader(async (ctx) => {
234
467
  return { product: await fetchProduct(ctx.params.slug) };
235
468
  });
236
469
 
237
- // handles.ts
238
- export const Breadcrumbs = createHandle<{ label: string; href: string }>();
470
+ // Built-in Breadcrumbs — or any custom handle created with createHandle()
471
+ ```
239
472
 
473
+ ```tsx
240
474
  // Client component — typeof infers all generics
241
475
  "use client";
242
- import { useLoader, useHandle } from "@rangojs/router/client";
476
+ import { useLoader, useHandle, type Breadcrumbs } from "@rangojs/router/client";
243
477
  import type { ProductLoader } from "../loaders";
244
- import type { Breadcrumbs } from "../handles";
245
478
 
246
479
  function MyComponent({
247
480
  loader,
248
481
  handle,
249
482
  }: {
250
- loader: typeof ProductLoader; // LoaderDefinition<{ product: Product }>
251
- handle: typeof Breadcrumbs; // Handle<{ label: string; href: string }>
483
+ loader: typeof ProductLoader; // LoaderDefinition<{ product: Product }>
484
+ handle: typeof Breadcrumbs; // Handle<{ label: string; href: string }>
252
485
  }) {
253
- const { data } = useLoader(loader); // data is typed
254
- const crumbs = useHandle(handle); // crumbs is typed array
486
+ const { data } = useLoader(loader); // data is typed
487
+ const crumbs = useHandle(handle); // crumbs is typed array
255
488
  // ...
256
489
  }
257
490
  ```
@@ -266,6 +499,7 @@ full functionality from module-level registries.
266
499
  // location-states.ts
267
500
  import { createLocationState } from "@rangojs/router";
268
501
 
502
+ // All export patterns work: export const, const + export { X }, export { X as Y }
269
503
  export const ProductPreview = createLocationState<{
270
504
  name: string;
271
505
  price: number;
@@ -312,8 +546,8 @@ global type declarations (like `RSCRouter.Env`).
312
546
  "skipLibCheck": true,
313
547
  "isolatedModules": true,
314
548
  "esModuleInterop": true,
315
- "resolveJsonModule": true
316
- }
549
+ "resolveJsonModule": true,
550
+ },
317
551
  }
318
552
  ```
319
553
 
@@ -322,7 +556,7 @@ global type declarations (like `RSCRouter.Env`).
322
556
  {
323
557
  "extends": "../../tsconfig.base.json",
324
558
  "include": ["src"],
325
- "files": ["src/router.tsx"]
559
+ "files": ["src/router.tsx"],
326
560
  }
327
561
  ```
328
562
 
@@ -331,19 +565,27 @@ global type declarations (like `RSCRouter.Env`).
331
565
  {
332
566
  "extends": "../../tsconfig.base.json",
333
567
  "include": ["src"],
334
- "files": ["src/router.tsx"]
568
+ "files": ["src/router.tsx"],
335
569
  }
336
570
  ```
337
571
 
338
- The `files` array ensures `router.tsx` (which contains `declare global { namespace RSCRouter { ... } }`)
339
- is always included in the compilation even if nothing directly imports it. Each app gets its own
340
- typed environment without interfering with other apps.
572
+ The `files` array ensures `router.tsx` (which contains `declare global { namespace RSCRouter { interface Env; interface Vars } }`)
573
+ is always included in the compilation even if nothing directly imports it. Route types come from the
574
+ auto-generated `*.named-routes.gen.ts` file (via `rango generate`), not from manual declaration.
575
+ Each app gets its own typed environment without interfering with other apps.
341
576
 
342
577
  ## Complete Type-Safe Setup
343
578
 
344
579
  ```typescript
345
580
  // 1. env.ts - Environment types
346
- export type AppEnv = RouterEnv<AppBindings, AppVariables>;
581
+ export interface AppBindings {
582
+ DB: D1Database;
583
+ KV: KVNamespace;
584
+ }
585
+
586
+ export interface AppVariables {
587
+ user?: { id: string; email: string; role: string };
588
+ }
347
589
 
348
590
  // 2. urls.tsx - Route definitions with names
349
591
  import { urls } from "@rangojs/router";
@@ -359,35 +601,40 @@ export const urlpatterns = urls(({ path, layout, loader }) => [
359
601
  ]),
360
602
  ]);
361
603
 
362
- // 3. router.tsx - Registration
363
- const router = createRouter<AppEnv>({
604
+ // 3. router.tsx - Create router and export reverse
605
+ const router = createRouter<AppBindings>({
364
606
  document: Document,
365
- urls: urlpatterns,
366
- });
607
+ }).routes(urlpatterns);
367
608
 
609
+ // Register bindings and variables globally for implicit typing
368
610
  declare global {
369
611
  namespace RSCRouter {
370
- interface Env extends AppEnv {}
612
+ interface Env extends AppBindings {}
613
+ interface Vars extends AppVariables {}
371
614
  }
372
615
  }
373
616
 
617
+ export const reverse = router.reverse;
374
618
  export default router;
375
619
 
376
- // 4. loaders/*.ts - Type-safe loaders
377
- export const ProductLoader = createLoader("product", async (ctx) => {
620
+ // 4. Run `npx rango generate src/router.tsx` to generate
621
+ // router.named-routes.gen.ts (auto-registers GeneratedRouteMap globally).
622
+ // No manual RegisteredRoutes declaration needed.
623
+
624
+ // 5. loaders/*.ts - Type-safe loaders
625
+ export const ProductLoader = createLoader(async (ctx) => {
378
626
  // ctx.params: { slug: string }
379
- // ctx.env.Variables.user: User | undefined
380
- // ctx.env.Bindings.DB: D1Database
627
+ // ctx.get("user"): User | undefined (from RSCRouter.Vars)
628
+ // ctx.env.DB: D1Database (plain bindings from RSCRouter.Env)
381
629
  return { product: await fetchProduct(ctx.params.slug) };
382
630
  });
383
631
 
384
- // 5. Server: ctx.reverse for named routes
632
+ // 6. Server: ctx.reverse for named routes
385
633
  path("/product/:slug", (ctx) => {
386
- const reverse = scopedReverse<typeof urlpatterns>(ctx.reverse);
387
- return <Link to={reverse("shop")}>Back to Shop</Link>;
634
+ return <Link to={ctx.reverse("shop")}>Back to Shop</Link>;
388
635
  }, { name: "product" })
389
636
 
390
- // 6. Client: useHref for mounted paths, href for absolute
637
+ // 7. Client: useHref for mounted paths, href for absolute
391
638
  "use client";
392
639
  import { useHref, href, Link } from "@rangojs/router/client";
393
640
  <Link to={href("/shop/product/widget")}>Widget</Link>