@rangojs/router 0.0.0-experimental.14 → 0.0.0-experimental.140

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 (460) hide show
  1. package/AGENTS.md +17 -0
  2. package/README.md +432 -7
  3. package/dist/bin/rango.js +2073 -213
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +7258 -2714
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +140 -67
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +329 -0
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +487 -0
  12. package/skills/caching/SKILL.md +357 -25
  13. package/skills/comparison/SKILL.md +50 -0
  14. package/skills/comparison/agents/openai.yaml +4 -0
  15. package/skills/comparison/references/framework-comparison.md +837 -0
  16. package/skills/composability/SKILL.md +246 -0
  17. package/skills/css/SKILL.md +76 -0
  18. package/skills/debug-manifest/SKILL.md +16 -10
  19. package/skills/document-cache/SKILL.md +87 -62
  20. package/skills/fonts/SKILL.md +6 -4
  21. package/skills/handler-use/SKILL.md +364 -0
  22. package/skills/hooks/SKILL.md +557 -79
  23. package/skills/host-router/SKILL.md +320 -0
  24. package/skills/i18n/SKILL.md +276 -0
  25. package/skills/intercept/SKILL.md +207 -15
  26. package/skills/layout/SKILL.md +146 -6
  27. package/skills/links/SKILL.md +304 -25
  28. package/skills/loader/SKILL.md +616 -54
  29. package/skills/middleware/SKILL.md +217 -37
  30. package/skills/migrate-nextjs/SKILL.md +611 -0
  31. package/skills/migrate-react-router/SKILL.md +927 -0
  32. package/skills/mime-routes/SKILL.md +42 -11
  33. package/skills/observability/SKILL.md +194 -0
  34. package/skills/parallel/SKILL.md +284 -3
  35. package/skills/ppr/SKILL.md +426 -0
  36. package/skills/prerender/SKILL.md +437 -52
  37. package/skills/rango/SKILL.md +369 -22
  38. package/skills/react-compiler/SKILL.md +168 -0
  39. package/skills/response-routes/SKILL.md +263 -121
  40. package/skills/route/SKILL.md +350 -21
  41. package/skills/router-setup/SKILL.md +246 -33
  42. package/skills/scripts/SKILL.md +179 -0
  43. package/skills/server-actions/SKILL.md +775 -0
  44. package/skills/shell-manifest/SKILL.md +185 -0
  45. package/skills/streams-and-websockets/SKILL.md +283 -0
  46. package/skills/tailwind/SKILL.md +27 -3
  47. package/skills/testing/SKILL.md +126 -222
  48. package/skills/testing/bindings.md +103 -0
  49. package/skills/testing/cache-prerender.md +127 -0
  50. package/skills/testing/client-components.md +124 -0
  51. package/skills/testing/e2e-parity.md +125 -0
  52. package/skills/testing/flight.md +91 -0
  53. package/skills/testing/handles.md +131 -0
  54. package/skills/testing/loader.md +128 -0
  55. package/skills/testing/middleware.md +99 -0
  56. package/skills/testing/render-handler.md +122 -0
  57. package/skills/testing/response-routes.md +95 -0
  58. package/skills/testing/reverse-and-types.md +85 -0
  59. package/skills/testing/server-actions.md +107 -0
  60. package/skills/testing/server-tree.md +128 -0
  61. package/skills/testing/setup.md +123 -0
  62. package/skills/theme/SKILL.md +9 -8
  63. package/skills/typesafety/SKILL.md +532 -103
  64. package/skills/use-cache/SKILL.md +367 -0
  65. package/skills/vercel/SKILL.md +128 -0
  66. package/skills/view-transitions/SKILL.md +337 -0
  67. package/src/__augment-tests__/augment.ts +81 -0
  68. package/src/__augment-tests__/augmented.check.ts +116 -0
  69. package/src/__internal.ts +77 -44
  70. package/src/bin/rango.ts +312 -15
  71. package/src/browser/action-coordinator.ts +114 -0
  72. package/src/browser/action-fence.ts +47 -0
  73. package/src/browser/app-shell.ts +39 -0
  74. package/src/browser/app-version.ts +14 -0
  75. package/src/browser/connection-warmup.ts +134 -0
  76. package/src/browser/cookie-name.ts +140 -0
  77. package/src/browser/event-controller.ts +293 -202
  78. package/src/browser/history-state.ts +101 -0
  79. package/src/browser/index.ts +3 -3
  80. package/src/browser/intercept-utils.ts +52 -0
  81. package/src/browser/invalidate-client-cache.ts +52 -0
  82. package/src/browser/link-interceptor.ts +24 -4
  83. package/src/browser/logging.ts +11 -0
  84. package/src/browser/merge-segment-loaders.ts +20 -12
  85. package/src/browser/navigation-bridge.ts +385 -576
  86. package/src/browser/navigation-client.ts +245 -75
  87. package/src/browser/navigation-store-handle.ts +38 -0
  88. package/src/browser/navigation-store.ts +184 -118
  89. package/src/browser/navigation-transaction.ts +247 -0
  90. package/src/browser/network-error-handler.ts +88 -0
  91. package/src/browser/partial-update.ts +412 -364
  92. package/src/browser/prefetch/cache.ts +359 -0
  93. package/src/browser/prefetch/fetch.ts +452 -0
  94. package/src/browser/prefetch/observer.ts +65 -0
  95. package/src/browser/prefetch/policy.ts +48 -0
  96. package/src/browser/prefetch/queue.ts +209 -0
  97. package/src/browser/prefetch/resource-ready.ts +77 -0
  98. package/src/browser/rango-state.ts +194 -0
  99. package/src/browser/react/Link.tsx +275 -68
  100. package/src/browser/react/NavigationProvider.tsx +265 -109
  101. package/src/browser/react/ScrollRestoration.tsx +10 -6
  102. package/src/browser/react/context.ts +11 -0
  103. package/src/browser/react/filter-segment-order.ts +70 -0
  104. package/src/browser/react/index.ts +0 -48
  105. package/src/browser/react/location-state-shared.ts +272 -60
  106. package/src/browser/react/location-state.ts +90 -20
  107. package/src/browser/react/mount-context.ts +6 -1
  108. package/src/browser/react/nonce-context.ts +23 -0
  109. package/src/browser/react/shallow-equal.ts +27 -0
  110. package/src/browser/react/use-action.ts +35 -66
  111. package/src/browser/react/use-handle.ts +39 -126
  112. package/src/browser/react/use-href.tsx +8 -1
  113. package/src/browser/react/use-link-status.ts +39 -13
  114. package/src/browser/react/use-navigation.ts +53 -69
  115. package/src/browser/react/use-params.ts +75 -0
  116. package/src/browser/react/use-pathname.ts +47 -0
  117. package/src/browser/react/use-reverse.ts +106 -0
  118. package/src/browser/react/use-router.ts +98 -0
  119. package/src/browser/react/use-search-params.ts +51 -0
  120. package/src/browser/react/use-segments.ts +72 -99
  121. package/src/browser/response-adapter.ts +164 -0
  122. package/src/browser/rsc-router.tsx +300 -72
  123. package/src/browser/scroll-restoration.ts +138 -50
  124. package/src/browser/segment-reconciler.ts +243 -0
  125. package/src/browser/segment-structure-assert.ts +17 -1
  126. package/src/browser/server-action-bridge.ts +668 -613
  127. package/src/browser/types.ts +223 -51
  128. package/src/browser/validate-redirect-origin.ts +56 -0
  129. package/src/build/collect-fallback-refs.ts +107 -0
  130. package/src/build/generate-manifest.ts +252 -161
  131. package/src/build/generate-route-types.ts +41 -1038
  132. package/src/build/index.ts +12 -7
  133. package/src/build/prefix-tree-utils.ts +123 -0
  134. package/src/build/route-trie.ts +225 -42
  135. package/src/build/route-types/ast-helpers.ts +25 -0
  136. package/src/build/route-types/ast-route-extraction.ts +105 -0
  137. package/src/build/route-types/codegen.ts +113 -0
  138. package/src/build/route-types/include-resolution.ts +812 -0
  139. package/src/build/route-types/param-extraction.ts +51 -0
  140. package/src/build/route-types/per-module-writer.ts +144 -0
  141. package/src/build/route-types/router-processing.ts +695 -0
  142. package/src/build/route-types/scan-filter.ts +85 -0
  143. package/src/build/route-types/source-scan.ts +216 -0
  144. package/src/build/runtime-discovery.ts +223 -0
  145. package/src/cache/background-task.ts +34 -0
  146. package/src/cache/cache-error.ts +104 -0
  147. package/src/cache/cache-key-utils.ts +60 -0
  148. package/src/cache/cache-policy.ts +199 -0
  149. package/src/cache/cache-runtime.ts +525 -0
  150. package/src/cache/cache-scope.ts +298 -332
  151. package/src/cache/cache-tag.ts +103 -0
  152. package/src/cache/cf/cf-base64.ts +33 -0
  153. package/src/cache/cf/cf-cache-constants.ts +127 -0
  154. package/src/cache/cf/cf-cache-store.ts +2500 -158
  155. package/src/cache/cf/cf-cache-types.ts +349 -0
  156. package/src/cache/cf/cf-kv-utils.ts +46 -0
  157. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  158. package/src/cache/cf/index.ts +17 -17
  159. package/src/cache/document-cache.ts +199 -92
  160. package/src/cache/handle-capture.ts +81 -0
  161. package/src/cache/handle-snapshot.ts +111 -0
  162. package/src/cache/index.ts +29 -35
  163. package/src/cache/memory-segment-store.ts +363 -30
  164. package/src/cache/profile-registry.ts +88 -0
  165. package/src/cache/read-through-swr.ts +178 -0
  166. package/src/cache/segment-codec.ts +248 -0
  167. package/src/cache/shell-cache.ts +386 -0
  168. package/src/cache/tag-invalidation.ts +230 -0
  169. package/src/cache/taint.ts +153 -0
  170. package/src/cache/types.ts +156 -211
  171. package/src/cache/vercel/index.ts +11 -0
  172. package/src/cache/vercel/vercel-cache-store.ts +1102 -0
  173. package/src/client.rsc.tsx +43 -21
  174. package/src/client.tsx +131 -347
  175. package/src/cloudflare/index.ts +11 -0
  176. package/src/cloudflare/tracing.ts +109 -0
  177. package/src/component-utils.ts +23 -4
  178. package/src/components/DefaultDocument.tsx +13 -3
  179. package/src/context-var.ts +168 -0
  180. package/src/debug.ts +19 -9
  181. package/src/decode-loader-results.ts +52 -0
  182. package/src/defer.ts +185 -0
  183. package/src/deps/ssr.ts +0 -1
  184. package/src/encode-kv.ts +49 -0
  185. package/src/errors.ts +106 -10
  186. package/src/escape-script.ts +52 -0
  187. package/src/handle.ts +110 -35
  188. package/src/handles/MetaTags.tsx +83 -59
  189. package/src/handles/Scripts.tsx +183 -0
  190. package/src/handles/breadcrumbs.ts +93 -0
  191. package/src/handles/deferred-resolution.ts +127 -0
  192. package/src/handles/is-thenable.ts +18 -0
  193. package/src/handles/meta.ts +44 -53
  194. package/src/handles/script.ts +244 -0
  195. package/src/host/cookie-handler.ts +20 -65
  196. package/src/host/errors.ts +21 -30
  197. package/src/host/index.ts +13 -9
  198. package/src/host/pattern-matcher.ts +50 -79
  199. package/src/host/router.ts +151 -121
  200. package/src/host/testing.ts +45 -32
  201. package/src/host/types.ts +52 -11
  202. package/src/host/utils.ts +2 -2
  203. package/src/href-client.ts +192 -57
  204. package/src/index.rsc.ts +177 -35
  205. package/src/index.ts +255 -71
  206. package/src/internal-debug.ts +9 -2
  207. package/src/loader-store.ts +500 -0
  208. package/src/loader.rsc.ts +31 -99
  209. package/src/loader.ts +30 -12
  210. package/src/missing-id-error.ts +68 -0
  211. package/src/outlet-context.ts +1 -1
  212. package/src/outlet-provider.tsx +41 -0
  213. package/src/prerender/param-hash.ts +16 -14
  214. package/src/prerender/store.ts +121 -21
  215. package/src/prerender.ts +460 -26
  216. package/src/redirect-origin.ts +100 -0
  217. package/src/regex-escape.ts +8 -0
  218. package/src/render-error-thrower.tsx +20 -0
  219. package/src/response-utils.ts +62 -0
  220. package/src/reverse.ts +198 -128
  221. package/src/root-error-boundary.tsx +42 -48
  222. package/src/route-content-wrapper.tsx +22 -77
  223. package/src/route-definition/dsl-helpers.ts +1116 -0
  224. package/src/route-definition/helper-factories.ts +88 -0
  225. package/src/route-definition/helpers-types.ts +505 -0
  226. package/src/route-definition/index.ts +54 -0
  227. package/src/route-definition/redirect.ts +134 -0
  228. package/src/route-definition/resolve-handler-use.ts +160 -0
  229. package/src/route-definition/use-item-types.ts +29 -0
  230. package/src/route-definition.ts +1 -1481
  231. package/src/route-map-builder.ts +82 -144
  232. package/src/route-name.ts +53 -0
  233. package/src/route-types.ts +71 -45
  234. package/src/router/basename.ts +14 -0
  235. package/src/router/content-negotiation.ts +263 -0
  236. package/src/router/debug-manifest.ts +72 -0
  237. package/src/router/error-handling.ts +54 -27
  238. package/src/router/find-match.ts +245 -0
  239. package/src/router/handler-context.ts +377 -125
  240. package/src/router/instrument.ts +350 -0
  241. package/src/router/intercept-resolution.ts +59 -28
  242. package/src/router/lazy-includes.ts +254 -0
  243. package/src/router/loader-resolution.ts +421 -157
  244. package/src/router/logging.ts +106 -6
  245. package/src/router/manifest.ts +131 -57
  246. package/src/router/match-api.ts +167 -246
  247. package/src/router/match-context.ts +4 -24
  248. package/src/router/match-handlers.ts +440 -0
  249. package/src/router/match-middleware/background-revalidation.ts +117 -93
  250. package/src/router/match-middleware/cache-lookup.ts +297 -150
  251. package/src/router/match-middleware/cache-store.ts +123 -51
  252. package/src/router/match-middleware/intercept-resolution.ts +44 -43
  253. package/src/router/match-middleware/segment-resolution.ts +64 -22
  254. package/src/router/match-pipelines.ts +11 -87
  255. package/src/router/match-result.ts +121 -50
  256. package/src/router/metrics.ts +219 -28
  257. package/src/router/middleware-types.ts +93 -0
  258. package/src/router/middleware.ts +505 -441
  259. package/src/router/navigation-snapshot.ts +133 -0
  260. package/src/router/params-util.ts +23 -0
  261. package/src/router/parse-pattern.ts +115 -0
  262. package/src/router/pattern-matching.ts +311 -142
  263. package/src/router/prefetch-cache-ttl.ts +51 -0
  264. package/src/router/prefetch-limits.ts +37 -0
  265. package/src/router/prerender-match.ts +547 -0
  266. package/src/router/preview-match.ts +102 -0
  267. package/src/router/request-classification.ts +278 -0
  268. package/src/router/revalidation.ts +203 -62
  269. package/src/router/route-snapshot.ts +246 -0
  270. package/src/router/router-context.ts +45 -48
  271. package/src/router/router-interfaces.ts +554 -0
  272. package/src/router/router-options.ts +779 -0
  273. package/src/router/router-registry.ts +21 -0
  274. package/src/router/segment-resolution/fresh.ts +772 -0
  275. package/src/router/segment-resolution/helpers.ts +348 -0
  276. package/src/router/segment-resolution/loader-cache.ts +250 -0
  277. package/src/router/segment-resolution/loader-mask.ts +44 -0
  278. package/src/router/segment-resolution/revalidation.ts +1331 -0
  279. package/src/router/segment-resolution/static-store.ts +81 -0
  280. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  281. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  282. package/src/router/segment-resolution.ts +25 -1354
  283. package/src/router/segment-wrappers.ts +292 -0
  284. package/src/router/state-cookie-name.ts +33 -0
  285. package/src/router/substitute-pattern-params.ts +75 -0
  286. package/src/router/telemetry-otel.ts +261 -0
  287. package/src/router/telemetry.ts +377 -0
  288. package/src/router/timeout.ts +128 -0
  289. package/src/router/tracing.ts +206 -0
  290. package/src/router/trie-matching.ts +240 -61
  291. package/src/router/types.ts +23 -70
  292. package/src/router/url-params.ts +57 -0
  293. package/src/router.ts +781 -2378
  294. package/src/rsc/full-payload.ts +70 -0
  295. package/src/rsc/handler-context.ts +46 -0
  296. package/src/rsc/handler.ts +905 -1142
  297. package/src/rsc/helpers.ts +275 -19
  298. package/src/rsc/index.ts +2 -25
  299. package/src/rsc/json-route-result.ts +38 -0
  300. package/src/rsc/loader-fetch.ts +305 -0
  301. package/src/rsc/manifest-init.ts +77 -0
  302. package/src/rsc/nonce.ts +14 -0
  303. package/src/rsc/origin-guard.ts +155 -0
  304. package/src/rsc/progressive-enhancement.ts +502 -0
  305. package/src/rsc/redirect-guard.ts +99 -0
  306. package/src/rsc/response-cache-serve.ts +238 -0
  307. package/src/rsc/response-error.ts +104 -0
  308. package/src/rsc/response-route-handler.ts +257 -0
  309. package/src/rsc/rsc-rendering.ts +337 -0
  310. package/src/rsc/runtime-warnings.ts +55 -0
  311. package/src/rsc/server-action.ts +522 -0
  312. package/src/rsc/shell-capture.ts +439 -0
  313. package/src/rsc/ssr-setup.ts +144 -0
  314. package/src/rsc/transition-gate.ts +89 -0
  315. package/src/rsc/types.ts +95 -12
  316. package/src/runtime-env.ts +18 -0
  317. package/src/search-params.ts +99 -82
  318. package/src/segment-content-promise.ts +67 -0
  319. package/src/segment-loader-promise.ts +149 -0
  320. package/src/segment-system.tsx +349 -134
  321. package/src/serialize.ts +243 -0
  322. package/src/server/context.ts +452 -85
  323. package/src/server/cookie-parse.ts +32 -0
  324. package/src/server/cookie-store.ts +310 -0
  325. package/src/server/fetchable-loader-store.ts +11 -6
  326. package/src/server/handle-store.ts +123 -42
  327. package/src/server/live.ts +130 -0
  328. package/src/server/loader-registry.ts +51 -100
  329. package/src/server/request-context.ts +842 -157
  330. package/src/server.ts +15 -8
  331. package/src/ssr/index.tsx +412 -136
  332. package/src/ssr/ssr-root.tsx +228 -0
  333. package/src/static-handler.ts +45 -18
  334. package/src/testing/cache-status.ts +162 -0
  335. package/src/testing/collect-handle.ts +46 -0
  336. package/src/testing/dispatch.ts +701 -0
  337. package/src/testing/dom.entry.ts +22 -0
  338. package/src/testing/e2e/fixture.ts +188 -0
  339. package/src/testing/e2e/index.ts +128 -0
  340. package/src/testing/e2e/matchers.ts +35 -0
  341. package/src/testing/e2e/page-helpers.ts +272 -0
  342. package/src/testing/e2e/parity.ts +387 -0
  343. package/src/testing/e2e/server.ts +195 -0
  344. package/src/testing/flight-matchers.ts +97 -0
  345. package/src/testing/flight-normalize.ts +11 -0
  346. package/src/testing/flight-runtime.d.ts +57 -0
  347. package/src/testing/flight-tree.ts +682 -0
  348. package/src/testing/flight.entry.ts +52 -0
  349. package/src/testing/flight.ts +257 -0
  350. package/src/testing/generated-routes.ts +199 -0
  351. package/src/testing/index.ts +105 -0
  352. package/src/testing/internal/context.ts +371 -0
  353. package/src/testing/internal/flight-client-globals.ts +30 -0
  354. package/src/testing/internal/seed-vars.ts +54 -0
  355. package/src/testing/render-handler.ts +357 -0
  356. package/src/testing/render-route.tsx +584 -0
  357. package/src/testing/run-loader.ts +385 -0
  358. package/src/testing/run-middleware.ts +205 -0
  359. package/src/testing/run-transition-when.ts +164 -0
  360. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  361. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  362. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  363. package/src/testing/vitest-stubs/version.ts +5 -0
  364. package/src/testing/vitest.ts +305 -0
  365. package/src/theme/ThemeProvider.tsx +40 -72
  366. package/src/theme/ThemeScript.tsx +12 -14
  367. package/src/theme/constants.ts +57 -15
  368. package/src/theme/index.ts +3 -20
  369. package/src/theme/theme-context.ts +5 -35
  370. package/src/theme/theme-script.ts +43 -39
  371. package/src/theme/use-theme.ts +0 -3
  372. package/src/types/boundaries.ts +123 -0
  373. package/src/types/cache-types.ts +207 -0
  374. package/src/types/error-types.ts +132 -0
  375. package/src/types/global-namespace.ts +113 -0
  376. package/src/types/handler-context.ts +839 -0
  377. package/src/types/index.ts +81 -0
  378. package/src/types/loader-types.ts +212 -0
  379. package/src/types/request-scope.ts +112 -0
  380. package/src/types/route-config.ts +138 -0
  381. package/src/types/route-entry.ts +114 -0
  382. package/src/types/segments.ts +271 -0
  383. package/src/types.ts +1 -1795
  384. package/src/urls/include-helper.ts +162 -0
  385. package/src/urls/include-provider.ts +71 -0
  386. package/src/urls/index.ts +43 -0
  387. package/src/urls/path-helper-types.ts +413 -0
  388. package/src/urls/path-helper.ts +275 -0
  389. package/src/urls/pattern-types.ts +124 -0
  390. package/src/urls/response-types.ts +109 -0
  391. package/src/urls/type-extraction.ts +316 -0
  392. package/src/urls/urls-function.ts +80 -0
  393. package/src/urls.ts +1 -1341
  394. package/src/use-loader.tsx +406 -141
  395. package/src/vercel/index.ts +11 -0
  396. package/src/vercel/tracing.ts +88 -0
  397. package/src/vite/debug.ts +185 -0
  398. package/src/vite/discovery/bundle-postprocess.ts +182 -0
  399. package/src/vite/discovery/discover-routers.ts +389 -0
  400. package/src/vite/discovery/discovery-errors.ts +255 -0
  401. package/src/vite/discovery/gate-state.ts +171 -0
  402. package/src/vite/discovery/prerender-collection.ts +467 -0
  403. package/src/vite/discovery/route-types-writer.ts +214 -0
  404. package/src/vite/discovery/self-gen-tracking.ts +73 -0
  405. package/src/vite/discovery/state.ts +161 -0
  406. package/src/vite/discovery/virtual-module-codegen.ts +183 -0
  407. package/src/vite/index.ts +23 -2255
  408. package/src/vite/inject-client-debug.ts +36 -0
  409. package/src/vite/plugin-types.ts +303 -0
  410. package/src/vite/plugins/cjs-to-esm.ts +90 -0
  411. package/src/vite/plugins/client-ref-dedup.ts +120 -0
  412. package/src/vite/plugins/client-ref-hashing.ts +118 -0
  413. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  414. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  415. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  416. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +88 -110
  417. package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +89 -79
  418. package/src/vite/plugins/expose-ids/export-analysis.ts +363 -0
  419. package/src/vite/plugins/expose-ids/handler-transform.ts +130 -0
  420. package/src/vite/plugins/expose-ids/loader-transform.ts +64 -0
  421. package/src/vite/plugins/expose-ids/router-transform.ts +199 -0
  422. package/src/vite/plugins/expose-ids/types.ts +45 -0
  423. package/src/vite/plugins/expose-internal-ids.ts +805 -0
  424. package/src/vite/plugins/performance-tracks.ts +89 -0
  425. package/src/vite/plugins/refresh-cmd.ts +127 -0
  426. package/src/vite/plugins/use-cache-transform.ts +313 -0
  427. package/src/vite/plugins/vercel-output.ts +384 -0
  428. package/src/vite/plugins/version-injector.ts +94 -0
  429. package/src/vite/plugins/version-plugin.ts +263 -0
  430. package/src/vite/plugins/virtual-entries.ts +234 -0
  431. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  432. package/src/vite/rango.ts +560 -0
  433. package/src/vite/router-discovery.ts +1638 -0
  434. package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +200 -37
  435. package/src/vite/utils/banner.ts +36 -0
  436. package/src/vite/utils/bundle-analysis.ts +132 -0
  437. package/src/vite/utils/client-chunks.ts +184 -0
  438. package/src/vite/utils/directive-prologue.ts +40 -0
  439. package/src/vite/utils/forward-user-plugins.ts +171 -0
  440. package/src/vite/utils/manifest-utils.ts +15 -0
  441. package/src/vite/utils/package-resolution.ts +89 -0
  442. package/src/vite/utils/prerender-utils.ts +249 -0
  443. package/src/vite/utils/shared-utils.ts +269 -0
  444. package/CLAUDE.md +0 -43
  445. package/dist/vite/index.named-routes.gen.ts +0 -103
  446. package/src/browser/lru-cache.ts +0 -69
  447. package/src/browser/react/use-client-cache.ts +0 -56
  448. package/src/browser/request-controller.ts +0 -164
  449. package/src/browser/shallow.ts +0 -35
  450. package/src/cache/memory-store.ts +0 -253
  451. package/src/handles/index.ts +0 -6
  452. package/src/href-context.ts +0 -33
  453. package/src/network-error-thrower.tsx +0 -21
  454. package/src/router.gen.ts +0 -6
  455. package/src/static-handler.gen.ts +0 -5
  456. package/src/urls.gen.ts +0 -8
  457. package/src/vite/expose-internal-ids.ts +0 -1167
  458. package/src/vite/package-resolution.ts +0 -125
  459. package/src/vite/virtual-entries.ts +0 -114
  460. /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,109 @@ 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">` (type annotation), `Prerender<"name">(...)` (function
36
+ call with type arg for param inference), server `ctx.reverse()`, and
37
+ named-route param/search inference.
38
+ - `typeof router.routeMap` — the real merged route map from your router
39
+ instance, including response-route metadata such as `{ path, response }`.
40
+ - `RegisteredRoutes` — manual global hook for exposing `typeof router.routeMap`
41
+ to global utilities that need the exact router-builder map, especially
42
+ `Rango.PathResponse`.
43
+
44
+ ### Generated Route Type Surfaces
45
+
46
+ There are three distinct typing surfaces. They are **not** interchangeable —
47
+ pick the one that matches what you need to type:
48
+
49
+ | Surface | Source | Scope | Gives | Does not give |
50
+ | ------------------- | ---------------------------------------- | ------ | ---------------------------------------- | ------------------------------------------------------------------------------------------------ |
51
+ | `GeneratedRouteMap` | `router.named-routes.gen.ts` (auto) | global | route names, path params, search schemas | response/MIME payloads |
52
+ | `routes` | per-module `*.gen.ts` (`rango generate`) | local | local names, params, search | the global app map |
53
+ | `RegisteredRoutes` | manual `extends typeof router.routeMap` | global | paths, params, **response payloads** | the `Handler`/`Prerender` default (those read `GeneratedRouteMap` to avoid a `router.tsx` cycle) |
54
+
55
+ Key consequence: `href()` and the ambient `Rango.Path` type are typed from
56
+ whichever map is present — they prefer `RegisteredRoutes` when you wire it, otherwise fall back to
57
+ the auto-generated `GeneratedRouteMap`, so **`rango generate` alone gives you
58
+ path-checked `href()`** with no manual augmentation. Response and MIME payload
59
+ inference is the exception: it comes only from `typeof router.routeMap` (via
60
+ `RegisteredRoutes`), because `GeneratedRouteMap` carries paths + search but no
61
+ payloads — so `Rango.PathResponse` resolves to `never` until you wire
62
+ `RegisteredRoutes`.
63
+
64
+ Recommended setup:
65
+
66
+ ```typescript
67
+ // router.tsx
68
+ import { createRouter } from "@rangojs/router";
69
+ import { urlpatterns } from "./urls";
70
+ import type { AppBindings, AppVars } from "./env";
71
+
72
+ export const router = createRouter<AppBindings>({}).routes(urlpatterns);
73
+
74
+ declare global {
75
+ namespace Rango {
76
+ interface Env extends AppBindings {}
77
+ interface Vars extends AppVars {}
78
+ interface RegisteredRoutes extends typeof router.routeMap {}
79
+ }
80
+ }
81
+ ```
82
+
83
+ ### Single-App Setup Checklist
84
+
85
+ For one app, keep the ambient types, generated named-routes file, and router
86
+ instance in the same TypeScript program:
87
+
88
+ ```jsonc
89
+ // tsconfig.json
90
+ {
91
+ "compilerOptions": {
92
+ "strict": true,
93
+ "moduleResolution": "bundler",
94
+ "jsx": "react-jsx",
95
+ "noEmit": true,
96
+ },
97
+ "include": ["src"],
98
+ "files": ["src/router.tsx"],
99
+ }
100
+ ```
101
+
102
+ Then generate the route types from the router file:
103
+
104
+ ```bash
105
+ npx rango generate src/router.tsx
106
+ ```
107
+
108
+ This creates `src/router.named-routes.gen.ts`, which augments
109
+ `Rango.GeneratedRouteMap`. Keep that generated file committed with the router
110
+ source. The `files` entry keeps `router.tsx` in the program even when nothing
111
+ imports it directly, so `Rango.Env`, `Rango.Vars`, and optional
112
+ `Rango.RegisteredRoutes` augmentation are visible to handlers, loaders, actions,
113
+ and client helpers.
114
+
115
+ ### Named Routes, `$$routeNames`, And `router.routeMap`
116
+
117
+ There are two runtime/type surfaces with similar names:
118
+
119
+ - `router.named-routes.gen.ts` exports `NamedRoutes` and augments
120
+ `Rango.GeneratedRouteMap`. The Vite plugin imports that file internally and
121
+ injects it as `$$routeNames` so `router.reverse` has the static route-name map.
122
+ App code should not pass or import `$$routeNames` directly.
123
+ - `router.routeMap` is the public router instance property for type extraction.
124
+ Use `typeof router.routeMap` when augmenting `Rango.RegisteredRoutes` for
125
+ global response payload helpers such as `Rango.PathResponse`.
126
+
127
+ Do not document or use a public `router.routeNames` API unless one is
128
+ intentionally added. Today, the public extraction surface is `router.routeMap`;
129
+ the generated file and `$$routeNames` are build machinery.
130
+
29
131
  ## Route Definition with Type-Safe Names
30
132
 
31
133
  ```typescript
@@ -45,34 +147,34 @@ export const urlpatterns = urls(({ path, layout }) => [
45
147
 
46
148
  ## Type-Safe href()
47
149
 
48
- ### Server: ctx.reverse with global route names
150
+ ### Server: ctx.reverse with route names
151
+
152
+ In route handlers, `ctx.reverse()` uses two namespaces:
49
153
 
50
- In route handlers, use `ctx.reverse()` with the global dotted route names
51
- from `router.named-routes.gen.ts`:
154
+ - **`.name`** — local route, resolved within the current `include()` scope
155
+ - **`name`** — global route, from the named-routes definition
52
156
 
53
157
  ```typescript
54
158
  import type { Handler } from "@rangojs/router";
55
159
 
56
160
  export const ProductHandler: Handler<"shop.product"> = (ctx) => {
57
- ctx.reverse("shop.cart"); // Global name
58
- ctx.reverse("shop.product", { slug: "widget" }); // With params
59
- ctx.reverse("blog.post", { postId: "1" }); // Cross-module
60
-
61
- return <ProductPage slug={ctx.params.slug} />;
161
+ ctx.reverse(".cart"); // Local: /shop/cart
162
+ ctx.reverse(".product", { slug: "widget" }); // Local: /shop/product/widget
163
+ ctx.reverse("blog.post", { slug: "1" }); // Global: /blog/1
62
164
  };
63
165
  ```
64
166
 
65
- For opt-in per-module isolation (after running `npx rango generate urls/shop.tsx`),
66
- use `scopedReverse()` with a local route map:
167
+ For type-safe local names, generate a route types file with `npx rango generate urls/shop.tsx`
168
+ and pass it as the second generic to `Handler` or `Prerender`:
67
169
 
68
170
  ```typescript
69
- import { scopedReverse } from "@rangojs/router";
171
+ import type { Handler } from "@rangojs/router";
70
172
  import type { routes } from "./shop.gen.js";
71
173
 
72
- export const ProductHandler: Handler<"product", routes> = (ctx) => {
73
- const reverse = scopedReverse<routes>(ctx.reverse);
74
- reverse("cart"); // Local name
75
- reverse("product", { slug: "widget" }); // Local with params
174
+ export const ProductHandler: Handler<"shop.product", routes> = (ctx) => {
175
+ ctx.reverse(".cart"); // Type-safe local name
176
+ ctx.reverse(".product", { slug: "widget" }); // Type-safe local with params
177
+ ctx.reverse("blog.post", { slug: "hi" }); // Type-safe global name
76
178
  };
77
179
  ```
78
180
 
@@ -95,6 +197,107 @@ function ShopNav() {
95
197
  }
96
198
  ```
97
199
 
200
+ `href()` and the `Rango.Path` type read from `RegisteredRoutes` when you augment
201
+ it, otherwise from the auto-generated `GeneratedRouteMap` — so `rango generate`
202
+ alone type-checks `href()` paths with no manual augmentation. The augmentation
203
+ below is only needed for **`Rango.PathResponse`** (response-payload inference), which
204
+ `GeneratedRouteMap` cannot provide:
205
+
206
+ ```typescript
207
+ declare global {
208
+ namespace Rango {
209
+ interface RegisteredRoutes extends typeof router.routeMap {}
210
+ }
211
+ }
212
+ ```
213
+
214
+ For wrapper helpers, type the path parameter as `Rango.Path`. It is ambient (no
215
+ import) and shares `href()`'s compile-time path checking, so a wrapper stays in
216
+ sync with your routes automatically:
217
+
218
+ ```typescript
219
+ import { href } from "@rangojs/router/client";
220
+
221
+ export const appHref = (path: Rango.Path): string => href(path);
222
+ ```
223
+
224
+ For response-route payloads, `Rango.PathResponse<T>` is the ambient lookup. It
225
+ accepts a route _pattern_ **or** a concrete path, so it also serves as the return
226
+ type of a typed `fetch` wrapper. It only resolves once `RegisteredRoutes` carries
227
+ response metadata:
228
+
229
+ ```typescript
230
+ import { href } from "@rangojs/router/client";
231
+
232
+ type Product = Rango.PathResponse<"/api/products/:id">; // by pattern
233
+ type Same = Rango.PathResponse<"/api/products/42">; // by concrete path
234
+
235
+ // Response inferred from the concrete path passed in:
236
+ async function get<T extends Rango.Path>(
237
+ path: T,
238
+ ): Promise<Rango.PathResponse<T>> {
239
+ return fetch(href(path)).then((r) => r.json());
240
+ }
241
+ const product = await get("/api/products/42"); // Product (bare value)
242
+ ```
243
+
244
+ Pattern keys (`/:id`) match exactly; a concrete path under a _nested_ dynamic
245
+ route can match several patterns and union their responses.
246
+
247
+ `Rango.PathResponse` describes the JSON **wire** shape, not the handler's raw
248
+ return. A `path.json()` handler returning `{ createdAt: Date }` resolves here to
249
+ `{ createdAt: string }` (bare value), matching what `r.json()` yields. This
250
+ is applied via the ambient `Rango.JsonSerialize<T>` transform (`Date -> string`,
251
+ honors `toJSON()`, drops functions/`undefined`, `bigint -> never`). A separate
252
+ `Rango.FlightSerialize<T>` models the higher-fidelity RSC Flight boundary
253
+ (loaders / RSC props, where `Date` is preserved) — do **not** use it for
254
+ `path.json()`.
255
+
256
+ ### Overriding serialization globally
257
+
258
+ For your own types, the zero-config way to control the JSON wire shape is a
259
+ `toJSON()` method — `Rango.JsonSerialize` honors it, and it matches the runtime
260
+ exactly (`JSON.stringify` calls `toJSON()`):
261
+
262
+ ```typescript
263
+ class Money {
264
+ constructor(private cents: number) {}
265
+ toJSON(): number {
266
+ return this.cents;
267
+ }
268
+ }
269
+ // Rango.JsonSerialize<Money> is number; Rango.PathResponse reflects it.
270
+ ```
271
+
272
+ To override a transform for types you **don't** own (or for the Flight boundary,
273
+ which has no `toJSON()`), augment its override slot. Because `Rango.JsonSerialize`
274
+ / `Rango.FlightSerialize` are type _aliases_ (TS can't merge those), you provide a
275
+ single member that is your **complete** transform, delegating to the built-in for
276
+ the cases you don't change:
277
+
278
+ ```typescript
279
+ declare global {
280
+ namespace Rango {
281
+ interface JsonSerializeOverride<T> {
282
+ app: T extends Decimal ? string : Rango.JsonSerializeBuiltin<T>;
283
+ }
284
+ interface FlightSerializeOverride<T> {
285
+ app: T extends Money ? number : Rango.FlightSerializeBuiltin<T>;
286
+ }
287
+ }
288
+ }
289
+ // Rango.JsonSerialize<Decimal> -> string; Rango.FlightSerialize<Money> -> number;
290
+ // everything else stays on the built-in, recursively (nested fields too).
291
+ ```
292
+
293
+ Rules: provide **exactly one** member (the slot is read as
294
+ `Override<T>[keyof Override<T>]`, so multiple members union and conflict).
295
+ Overrides win over `toJSON()` and apply at every nesting level. Caveat for JSON:
296
+ the `path.json()` runtime is plain `JSON.stringify`, which only honors `toJSON()`,
297
+ so a `JsonSerializeOverride` that disagrees with what the runtime emits will lie —
298
+ prefer `toJSON()` for your own types and use the slot only for types you can't
299
+ modify.
300
+
98
301
  See `/links` for full URL generation guide.
99
302
 
100
303
  ## Environment Type Setup
@@ -103,50 +306,57 @@ Define your app's environment for type-safe bindings and variables:
103
306
 
104
307
  ```typescript
105
308
  // env.ts
106
- import type { RouterEnv } from "@rangojs/router";
107
309
 
108
- // Cloudflare bindings
109
- interface AppBindings {
310
+ // Cloudflare bindings — passed as TEnv to createRouter<TEnv>()
311
+ export interface AppBindings {
110
312
  DB: D1Database;
111
313
  KV: KVNamespace;
112
314
  CACHE: KVNamespace;
113
315
  AI: Ai;
114
316
  }
115
317
 
116
- // Variables set by middleware
117
- interface AppVariables {
318
+ // Variables set by middleware — declared via global namespace augmentation
319
+ export interface AppVariables {
118
320
  user?: { id: string; email: string; role: string };
119
321
  requestId?: string;
120
322
  permissions?: string[];
121
323
  }
122
-
123
- // Combined environment type
124
- export type AppEnv = RouterEnv<AppBindings, AppVariables>;
125
324
  ```
126
325
 
127
326
  ### Using Environment Types
128
327
 
129
328
  ```typescript
130
329
  // router.tsx
131
- import type { AppEnv } from "./env";
330
+ import type { AppBindings, AppVariables } from "./env";
132
331
 
133
- const router = createRouter<AppEnv>({
332
+ const router = createRouter<AppBindings>({
134
333
  document: Document,
135
- urls: urlpatterns,
136
- });
334
+ }).routes(urlpatterns);
137
335
 
138
- // middleware - typed ctx.env.Variables
139
- import { createMiddleware } from "@rangojs/router";
336
+ // Register bindings and variables globally for implicit typing
337
+ declare global {
338
+ namespace Rango {
339
+ interface Env extends AppBindings {}
340
+ interface Vars extends AppVariables {}
341
+ }
342
+ }
140
343
 
141
- export const authMiddleware = createMiddleware(async (ctx, next) => {
142
- ctx.env.Variables.user = { id: "123", email: "user@example.com", role: "admin" };
344
+ // middleware - typed via ctx.set / ctx.get
345
+ import type { Middleware } from "@rangojs/router";
346
+
347
+ export const authMiddleware: Middleware = async (ctx, next) => {
348
+ ctx.set("user", {
349
+ id: "123",
350
+ email: "user@example.com",
351
+ role: "admin",
352
+ });
143
353
  await next();
144
- });
354
+ };
145
355
 
146
356
  // loaders - typed context
147
- export const UserLoader = createLoader("user", async (ctx) => {
148
- const db = ctx.env.Bindings.DB; // D1Database
149
- const userId = ctx.env.Variables.user?.id;
357
+ export const UserLoader = createLoader(async (ctx) => {
358
+ const db = ctx.env.DB; // D1Database (plain bindings)
359
+ const userId = ctx.get("user")?.id; // from Rango.Vars
150
360
  return db.prepare("SELECT * FROM users WHERE id = ?").bind(userId).first();
151
361
  });
152
362
  ```
@@ -158,8 +368,9 @@ Register environment types globally for implicit typing:
158
368
  ```typescript
159
369
  // router.tsx
160
370
  declare global {
161
- namespace RSCRouter {
162
- interface Env extends AppEnv {}
371
+ namespace Rango {
372
+ interface Env extends AppBindings {}
373
+ interface Vars extends AppVariables {}
163
374
  }
164
375
  }
165
376
  ```
@@ -168,10 +379,10 @@ Now handlers have typed context without explicit imports:
168
379
 
169
380
  ```typescript
170
381
  // In loaders
171
- export const DashboardLoader = createLoader("dashboard", async (ctx) => {
172
- // ctx.env.Variables.user is typed from global Env
173
- // ctx.params is typed from route pattern
174
- const user = ctx.env.Variables.user;
382
+ export const DashboardLoader = createLoader(async (ctx) => {
383
+ // ctx.env.DB is typed from global Rango.Env
384
+ // ctx.get("user") is typed from global Rango.Vars
385
+ const user = ctx.get("user");
175
386
  return { user };
176
387
  });
177
388
  ```
@@ -185,7 +396,7 @@ Add a `search` schema to `path()` options for type-safe query parameters:
185
396
  path("/search", SearchPage, {
186
397
  name: "search",
187
398
  search: { q: "string", page: "number?", sort: "string?" },
188
- })
399
+ });
189
400
  ```
190
401
 
191
402
  ### Handler with typed search params
@@ -198,8 +409,8 @@ global `GeneratedRouteMap` (the gen file). No explicit route map import needed:
198
409
  import type { Handler } from "@rangojs/router";
199
410
 
200
411
  export const SearchPage: Handler<"search"> = (ctx) => {
201
- // ctx.searchParams is typed: { q: string; page?: number; sort?: string }
202
- const { q, page, sort } = ctx.searchParams;
412
+ // ctx.search is typed: { q: string; page?: number; sort?: string }
413
+ const { q, page, sort } = ctx.search;
203
414
  return <SearchResults q={q} page={page} sort={sort} />;
204
415
  };
205
416
  ```
@@ -208,15 +419,21 @@ This avoids circular references because `Handler` defaults to `GeneratedRouteMap
208
419
  (from `router.named-routes.gen.ts`) instead of `RegisteredRoutes` (which depends on `router.tsx`).
209
420
 
210
421
  You can also pass an explicit route map for per-module isolation (opt-in,
211
- after running `npx rango generate`):
422
+ after running `npx rango generate`). With a local map, the route name is
423
+ **dot-prefixed** so params and search resolve from `routes`, not the global map:
212
424
 
213
425
  ```typescript
214
426
  import type { Handler } from "@rangojs/router";
215
427
  import type { routes } from "./urls.gen.js";
216
428
 
217
- export const SearchPage: Handler<"search", routes> = (ctx) => { ... };
429
+ export const SearchPage: Handler<".search", routes> = (ctx) => { ... };
218
430
  ```
219
431
 
432
+ Note the difference: `Handler<"search">` (no dot) resolves against the global
433
+ `GeneratedRouteMap`; `Handler<".search", routes>` resolves against the local
434
+ `routes` map. Mixing them — `Handler<"search", routes>` — silently ignores
435
+ `routes` for param/search inference and only uses it for local `ctx.reverse(".x")`.
436
+
220
437
  Supported types: `"string"`, `"number"`, `"boolean"`, with `?` suffix for optional.
221
438
  Values are automatically coerced from query string (e.g., `"2"` becomes `2` for numbers).
222
439
  Routes without a `search` schema keep the standard `URLSearchParams` behavior.
@@ -230,12 +447,18 @@ import type { RouteSearchParams, RouteParams } from "@rangojs/router";
230
447
 
231
448
  // RouteSearchParams<"name"> resolves the search schema to a typed object
232
449
  type SP = RouteSearchParams<"search">;
233
- // { q: string; page?: number; sort?: string }
450
+ // { q: string | undefined; page?: number; sort?: string }
234
451
 
235
452
  // RouteParams<"name"> resolves URL params from the route pattern
236
453
  type P = RouteParams<"blogPost">;
237
454
  // { slug: string }
238
455
 
456
+ // Optional URL params (`:slug?`) resolve to `string | undefined`
457
+ // because absent segments are omitted from `ctx.params` at runtime.
458
+ type C = RouteParams<"checkout">;
459
+ // { step?: string }
460
+ // → ctx.params.step is `string | undefined`; use `?? "default"` to coalesce.
461
+
239
462
  // Use in component props
240
463
  interface SearchResultsProps {
241
464
  params: RouteSearchParams<"search">;
@@ -260,18 +483,34 @@ use `{ path, search }` objects:
260
483
  ```typescript
261
484
  // router.named-routes.gen.ts (auto-generated)
262
485
  export const NamedRoutes = {
263
- "search.index": { path: "/search", search: { q: "string", page: "number?", sort: "string?" } },
264
- "home.index": "/", // No search schema -> plain string
486
+ "search.index": {
487
+ path: "/search",
488
+ search: { q: "string", page: "number?", sort: "string?" },
489
+ },
490
+ "home.index": "/", // No search schema -> plain string
265
491
  } as const;
266
492
  ```
267
493
 
494
+ You never open a `.gen.ts` by hand. Treat the generated types as call-site
495
+ honesty checks, not modules to read:
496
+
497
+ - **Do not import `router.named-routes.gen.ts` directly**, and don't reach for
498
+ `Rango.GeneratedRouteMap`. It is the whole-app manifest, auto-wired
499
+ globally — `Handler<"name">` and `ctx.reverse("name")` already see it.
500
+ - **Per-module `*.gen.ts` imports are fine** — they are the opt-in local-route
501
+ pattern for `useReverse(routes)` and explicit local handler typing
502
+ (`Handler<".name", routes>`). See `/links`.
503
+
504
+ If a type error points at a generated map instead of your call site, that's a
505
+ smell — fix the call site (or regenerate), never edit the generated file.
506
+
268
507
  ## Loader Type Safety
269
508
 
270
509
  Loaders have typed return values:
271
510
 
272
511
  ```typescript
273
512
  // loaders/product.ts
274
- export const ProductLoader = createLoader("product", async (ctx) => {
513
+ export const ProductLoader = createLoader(async (ctx) => {
275
514
  return {
276
515
  id: ctx.params.slug,
277
516
  name: "Widget",
@@ -280,7 +519,7 @@ export const ProductLoader = createLoader("product", async (ctx) => {
280
519
  });
281
520
 
282
521
  // In server component - type is inferred
283
- import { useLoader } from "@rangojs/router";
522
+ import { useLoader } from "@rangojs/router/client";
284
523
 
285
524
  async function ProductPage() {
286
525
  const product = await useLoader(ProductLoader);
@@ -290,40 +529,110 @@ async function ProductPage() {
290
529
 
291
530
  // In client component - same type
292
531
  "use client";
293
- import { useLoaderData } from "@rangojs/router/client";
532
+ import { useLoader } from "@rangojs/router/client";
294
533
 
295
534
  function ProductPrice() {
296
- const { product } = useLoaderData(ProductLoader);
297
- // product: { id: string; name: string; price: number }
535
+ const { data } = useLoader(ProductLoader);
536
+ // data: { id: string; name: string; price: number }
537
+ const product = data;
298
538
  return <span>${product.price}</span>;
299
539
  }
300
540
  ```
301
541
 
302
- ## Handle Type Safety
542
+ ## Typed Context Variables
303
543
 
304
- Handles have typed data:
544
+ `createVar<T>()` creates a typed token for `ctx.set()`/`ctx.get()`, making
545
+ handler-to-layout data contracts explicit and compile-time verified:
305
546
 
306
547
  ```typescript
307
- // handles/breadcrumbs.ts
308
- import { createHandle } from "@rangojs/router";
548
+ import { createVar } from "@rangojs/router";
309
549
 
310
- // All export patterns work: export const, const + export { X }, export { X as Y }
311
- export const Breadcrumbs = createHandle<{ label: string; href: string }>();
550
+ // Define a typed token (shared between producer and consumer)
551
+ interface PaginationData {
552
+ current: number;
553
+ total: number;
554
+ perPage: number;
555
+ }
556
+ export const Pagination = createVar<PaginationData>();
312
557
 
313
- // In route definition - use handle() DSL
314
- import { urls } from "@rangojs/router";
558
+ // Non-cacheable var — reading inside cache() or "use cache" throws at runtime
559
+ const Session = createVar<SessionData>({ cache: false });
560
+ ```
315
561
 
316
- export const urlpatterns = urls(({ path, handle }) => [
317
- path("/shop/product/:slug", ProductPage, { name: "product" }, () => [
318
- handle(Breadcrumbs, { label: "Products", href: "/shop/products" }),
319
- ]),
320
- ]);
562
+ `createVar` accepts an optional options object. The `cache` option (default
563
+ `true`) controls whether the var's values can be read inside cache scopes.
564
+ Write-level escalation is also supported: `ctx.set(Var, value, { cache: false })`
565
+ marks a specific write as non-cacheable even if the var itself is cacheable.
566
+ "Least cacheable wins" — if either says `cache: false`, the value throws on
567
+ read inside `cache()` or `"use cache"`.
568
+
569
+ ### Producer (handler or middleware)
321
570
 
322
- // In client - typed array
571
+ ```typescript
572
+ import { Pagination } from "../vars/pagination.js";
573
+
574
+ const ArticleList: Handler<"articles.list"> = async (ctx) => {
575
+ ctx.set(Pagination, { // type-checked
576
+ current: 1,
577
+ total: 10,
578
+ perPage: 5,
579
+ });
580
+ return <Articles />;
581
+ };
582
+ ```
583
+
584
+ ### Consumer (layout, parallel, or any context with get)
585
+
586
+ ```typescript
587
+ import { Pagination } from "../vars/pagination.js";
588
+
589
+ export function PaginationLayout(ctx: any) {
590
+ const pagination = ctx.get(Pagination); // typed as PaginationData | undefined
591
+ if (!pagination) return <Outlet />;
592
+ return <nav>Page {pagination.current} of {pagination.total}</nav>;
593
+ }
594
+ ```
595
+
596
+ ### Why not just use Rango.Vars?
597
+
598
+ `Rango.Vars` (via global namespace augmentation) provides app-global typing for
599
+ `ctx.get("key")` / `ctx.set("key", value)`. It works for middleware state
600
+ shared app-wide. `createVar<T>()` is for route-local or feature-scoped
601
+ context -- the producer and consumer import the same token, creating a
602
+ scoped contract without polluting global types.
603
+
604
+ Both approaches coexist: `ctx.get("user")` (global via Vars) and
605
+ `ctx.get(Pagination)` (scoped via createVar) work side by side.
606
+
607
+ ## Handle Type Safety
608
+
609
+ Handles have typed data:
610
+
611
+ ```typescript
612
+ // Built-in Breadcrumbs handle — import from "@rangojs/router"
613
+ import { Breadcrumbs } from "@rangojs/router";
614
+ // Type: Handle<BreadcrumbItem, BreadcrumbItem[]>
615
+ // BreadcrumbItem: { label: string; href: string; content?: ReactNode | Promise<ReactNode> }
616
+
617
+ // In route handler — push is fully typed
618
+ path("/shop/product/:slug", (ctx) => {
619
+ const breadcrumb = ctx.use(Breadcrumbs);
620
+ breadcrumb({ label: "Products", href: "/shop/products" });
621
+ return <ProductPage />;
622
+ }, { name: "product" });
623
+
624
+ // In client — typed array
625
+ import { useHandle, Breadcrumbs } from "@rangojs/router/client";
323
626
  function BreadcrumbNav() {
324
627
  const crumbs = useHandle(Breadcrumbs);
325
- // crumbs: Array<{ label: string; href: string }>
628
+ // crumbs: BreadcrumbItem[]
326
629
  }
630
+
631
+ // Custom handles also work the same way
632
+ import { createHandle } from "@rangojs/router";
633
+ export const PageTitle = createHandle<string, string>(
634
+ (segments) => segments.flat().at(-1) ?? "Default Title"
635
+ );
327
636
  ```
328
637
 
329
638
  ## Ref Prop Type Safety (Loaders & Handles)
@@ -337,24 +646,24 @@ export const ProductLoader = createLoader(async (ctx) => {
337
646
  return { product: await fetchProduct(ctx.params.slug) };
338
647
  });
339
648
 
340
- // handles.ts
341
- export const Breadcrumbs = createHandle<{ label: string; href: string }>();
649
+ // Built-in Breadcrumbs — or any custom handle created with createHandle()
650
+ ```
342
651
 
652
+ ```tsx
343
653
  // Client component — typeof infers all generics
344
654
  "use client";
345
- import { useLoader, useHandle } from "@rangojs/router/client";
655
+ import { useLoader, useHandle, type Breadcrumbs } from "@rangojs/router/client";
346
656
  import type { ProductLoader } from "../loaders";
347
- import type { Breadcrumbs } from "../handles";
348
657
 
349
658
  function MyComponent({
350
659
  loader,
351
660
  handle,
352
661
  }: {
353
- loader: typeof ProductLoader; // LoaderDefinition<{ product: Product }>
354
- handle: typeof Breadcrumbs; // Handle<{ label: string; href: string }>
662
+ loader: typeof ProductLoader; // LoaderDefinition<{ product: Product }>
663
+ handle: typeof Breadcrumbs; // Handle<{ label: string; href: string }>
355
664
  }) {
356
- const { data } = useLoader(loader); // data is typed
357
- const crumbs = useHandle(handle); // crumbs is typed array
665
+ const { data } = useLoader(loader); // data is typed
666
+ const crumbs = useHandle(handle); // crumbs is typed array
358
667
  // ...
359
668
  }
360
669
  ```
@@ -363,6 +672,42 @@ RSC Flight serialization calls `toJSON()` on both loaders and handles,
363
672
  sending only `{ __brand, $$id }` to the client. The hooks recover the
364
673
  full functionality from module-level registries.
365
674
 
675
+ ## Stable identity: `path#export`
676
+
677
+ Loaders, handles, cached functions (`functionId`), and server actions
678
+ (`actionId`) all share one identity scheme: `{modulePath}#{exportName}`,
679
+ injected at build by the `exposeInternalIds` and `exposeActionId` Vite plugins.
680
+ This is also the identity React server actions carry across the Flight boundary,
681
+ which is why a `revalidate()` predicate sees an action as a `path#export` string:
682
+
683
+ ```typescript
684
+ revalidate(
685
+ ({ actionId }) => actionId === "src/actions/cart.ts#addToCart" || undefined,
686
+ );
687
+ ```
688
+
689
+ `actionId` is the only stable reference React exposes across the Flight boundary,
690
+ so it stays as the floor and escape hatch. The hand-written-string surface
691
+ (`actionId?.includes("cart.ts#")`) is brittle: a renamed action or moved file
692
+ silently stops matching with no compile error. Prefer **`ctx.isAction()`** in a
693
+ revalidate predicate — it resolves the action's id from an imported reference, so
694
+ a rename is a type error in one place instead of silent drift:
695
+
696
+ ```ts
697
+ import { addToCart, removeFromCart } from "./actions/cart";
698
+ import * as CartActions from "./actions/cart";
699
+
700
+ revalidate((ctx) => ctx.isAction(addToCart) || undefined); // one action
701
+ revalidate((ctx) => ctx.isAction(addToCart, removeFromCart) || undefined); // several
702
+ revalidate((ctx) => ctx.isAction(CartActions) || undefined); // any action in the module
703
+ ```
704
+
705
+ `ctx.isAction()` (only available on the revalidate predicate's context) returns a
706
+ raw boolean — combine with `|| undefined` for the "revalidate on match, else
707
+ defer" intent. It resolves the reference the same way the router derives
708
+ `actionId` (`$id` in production, `$$id` in dev), so matching
709
+ works in both modes. `actionId` stays available for advanced cases.
710
+
366
711
  ## Location State Type Safety
367
712
 
368
713
  ```typescript
@@ -398,9 +743,37 @@ function ProductHeader() {
398
743
 
399
744
  ## Multi-Project tsconfig Setup
400
745
 
401
- For monorepos or multi-app setups, use a shared base tsconfig. Each app only needs
402
- to extend the base and add its `router.tsx` to `files` so TypeScript picks up the
403
- global type declarations (like `RSCRouter.Env`).
746
+ For monorepos or multi-app setups, each app should have its own TypeScript
747
+ program. Do not typecheck two Rango apps with different `Rango.Env`,
748
+ `Rango.Vars`, or `Rango.RegisteredRoutes` declarations in one tsconfig, because
749
+ ambient global interfaces merge across the whole program.
750
+
751
+ ### Multiple routers in one program
752
+
753
+ `Rango.GeneratedRouteMap` is a **single global interface**. Each router's
754
+ generated `router.named-routes.gen.ts` augments it, so two routers in the **same
755
+ TS program** that define overlapping route names (e.g. both have a `home`) make
756
+ the augmentations collide:
757
+
758
+ ```text
759
+ Interface 'GeneratedRouteMap' cannot simultaneously extend ...
760
+ Named property 'home' ... are not identical.
761
+ ```
762
+
763
+ This is the multi-router / host-router case. Resolve it by:
764
+
765
+ - **Separate TS programs** — give each router its own tsconfig (as below) so only
766
+ one generated map is in scope per program. Recommended.
767
+ - **Unique route-name prefixes** — name routes per router (`appA.home`,
768
+ `appB.home`) so the merged global map has no duplicate keys.
769
+
770
+ A single global generated map is a single-router convenience; global named-route
771
+ typing across multiple routers in one program is not supported today (it would
772
+ need per-router scoping in the generated map).
773
+
774
+ Use a shared base tsconfig for common compiler options, then make every app
775
+ tsconfig include its own source tree, its own `router.tsx`, and the generated
776
+ `router.named-routes.gen.ts` that lives beside that router.
404
777
 
405
778
  ```jsonc
406
779
  // tsconfig.base.json (root)
@@ -416,8 +789,8 @@ global type declarations (like `RSCRouter.Env`).
416
789
  "skipLibCheck": true,
417
790
  "isolatedModules": true,
418
791
  "esModuleInterop": true,
419
- "resolveJsonModule": true
420
- }
792
+ "resolveJsonModule": true,
793
+ },
421
794
  }
422
795
  ```
423
796
 
@@ -426,7 +799,7 @@ global type declarations (like `RSCRouter.Env`).
426
799
  {
427
800
  "extends": "../../tsconfig.base.json",
428
801
  "include": ["src"],
429
- "files": ["src/router.tsx"]
802
+ "files": ["src/router.tsx"],
430
803
  }
431
804
  ```
432
805
 
@@ -435,19 +808,66 @@ global type declarations (like `RSCRouter.Env`).
435
808
  {
436
809
  "extends": "../../tsconfig.base.json",
437
810
  "include": ["src"],
438
- "files": ["src/router.tsx"]
811
+ "files": ["src/router.tsx"],
439
812
  }
440
813
  ```
441
814
 
442
- The `files` array ensures `router.tsx` (which contains `declare global { namespace RSCRouter { ... } }`)
443
- is always included in the compilation even if nothing directly imports it. Each app gets its own
444
- typed environment without interfering with other apps.
815
+ Run generation per app:
816
+
817
+ ```bash
818
+ npx rango generate apps/shop/src/router.tsx
819
+ npx rango generate apps/blog/src/router.tsx
820
+ ```
821
+
822
+ If an app has multiple tsconfigs (`tsconfig.app.json`, `tsconfig.test.json`,
823
+ `tsconfig.worker.json`), every tsconfig that typechecks Rango handlers,
824
+ components, loaders, actions, or client navigation must see the same app-local
825
+ type surfaces:
826
+
827
+ ```jsonc
828
+ // apps/shop/tsconfig.test.json
829
+ {
830
+ "extends": "./tsconfig.json",
831
+ "include": ["src", "tests"],
832
+ "files": ["src/router.tsx"],
833
+ }
834
+ ```
835
+
836
+ The `files` array ensures `router.tsx` is always included even if nothing
837
+ directly imports it. The generated `router.named-routes.gen.ts` is normally
838
+ covered by `include: ["src"]`; if a tsconfig uses a narrow `include`, add the
839
+ generated file explicitly. Each app gets its own typed environment and named
840
+ route map without interfering with other apps.
841
+
842
+ For response and MIME payload lookup in each app, augment `RegisteredRoutes`
843
+ inside that app's router file:
844
+
845
+ ```typescript
846
+ // apps/shop/src/router.tsx
847
+ export const router = createRouter<ShopEnv>({ document: Document }).routes(
848
+ urlpatterns,
849
+ );
850
+
851
+ declare global {
852
+ namespace Rango {
853
+ interface Env extends ShopEnv {}
854
+ interface RegisteredRoutes extends typeof router.routeMap {}
855
+ }
856
+ }
857
+ ```
445
858
 
446
859
  ## Complete Type-Safe Setup
447
860
 
448
861
  ```typescript
449
862
  // 1. env.ts - Environment types
450
- export type AppEnv = RouterEnv<AppBindings, AppVariables>;
863
+ export interface AppBindings {
864
+ DB: D1Database;
865
+ KV: KVNamespace;
866
+ }
867
+
868
+ export interface AppVariables {
869
+ user?: { id: string; email: string; role: string };
870
+ }
451
871
 
452
872
  // 2. urls.tsx - Route definitions with names
453
873
  import { urls } from "@rangojs/router";
@@ -463,34 +883,43 @@ export const urlpatterns = urls(({ path, layout, loader }) => [
463
883
  ]),
464
884
  ]);
465
885
 
466
- // 3. router.tsx - Registration
467
- const router = createRouter<AppEnv>({
886
+ // 3. router.tsx - Create router and export reverse
887
+ const router = createRouter<AppBindings>({
468
888
  document: Document,
469
- urls: urlpatterns,
470
- });
889
+ }).routes(urlpatterns);
471
890
 
891
+ // Register bindings and variables globally for implicit typing
472
892
  declare global {
473
- namespace RSCRouter {
474
- interface Env extends AppEnv {}
893
+ namespace Rango {
894
+ interface Env extends AppBindings {}
895
+ interface Vars extends AppVariables {}
475
896
  }
476
897
  }
477
898
 
899
+ export const reverse = router.reverse;
478
900
  export default router;
479
901
 
480
- // 4. loaders/*.ts - Type-safe loaders
481
- export const ProductLoader = createLoader("product", async (ctx) => {
902
+ // 4. Run `npx rango generate src/router.tsx` to generate
903
+ // router.named-routes.gen.ts (auto-registers GeneratedRouteMap globally).
904
+ // No manual RegisteredRoutes declaration is needed for named-route handlers,
905
+ // ctx.reverse, prerender, href(), or Rango.Path. Add `RegisteredRoutes
906
+ // extends typeof router.routeMap` when global response payload helpers such
907
+ // as Rango.PathResponse need the richer router.routeMap metadata.
908
+
909
+ // 5. loaders/*.ts - Type-safe loaders
910
+ export const ProductLoader = createLoader(async (ctx) => {
482
911
  // ctx.params: { slug: string }
483
- // ctx.env.Variables.user: User | undefined
484
- // ctx.env.Bindings.DB: D1Database
912
+ // ctx.get("user"): User | undefined (from Rango.Vars)
913
+ // ctx.env.DB: D1Database (plain bindings from Rango.Env)
485
914
  return { product: await fetchProduct(ctx.params.slug) };
486
915
  });
487
916
 
488
- // 5. Server: ctx.reverse for named routes
917
+ // 6. Server: ctx.reverse for named routes
489
918
  path("/product/:slug", (ctx) => {
490
919
  return <Link to={ctx.reverse("shop")}>Back to Shop</Link>;
491
920
  }, { name: "product" })
492
921
 
493
- // 6. Client: useHref for mounted paths, href for absolute
922
+ // 7. Client: useHref for mounted paths, href for absolute
494
923
  "use client";
495
924
  import { useHref, href, Link } from "@rangojs/router/client";
496
925
  <Link to={href("/shop/product/widget")}>Widget</Link>