@rangojs/router 0.0.0-experimental.135c6902 → 0.0.0-experimental.136

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 (404) hide show
  1. package/AGENTS.md +8 -0
  2. package/README.md +245 -49
  3. package/dist/bin/rango.js +440 -133
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3386 -1111
  6. package/dist/vite/index.js.bak +5448 -0
  7. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  8. package/package.json +78 -25
  9. package/skills/api-client/SKILL.md +211 -0
  10. package/skills/breadcrumbs/SKILL.md +64 -2
  11. package/skills/bundle-analysis/SKILL.md +159 -0
  12. package/skills/cache-guide/SKILL.md +247 -23
  13. package/skills/caching/SKILL.md +281 -11
  14. package/skills/composability/SKILL.md +27 -3
  15. package/skills/css/SKILL.md +76 -0
  16. package/skills/debug-manifest/SKILL.md +4 -2
  17. package/skills/document-cache/SKILL.md +78 -55
  18. package/skills/handler-use/SKILL.md +364 -0
  19. package/skills/hooks/SKILL.md +250 -30
  20. package/skills/host-router/SKILL.md +83 -23
  21. package/skills/i18n/SKILL.md +276 -0
  22. package/skills/intercept/SKILL.md +87 -18
  23. package/skills/layout/SKILL.md +35 -9
  24. package/skills/links/SKILL.md +249 -17
  25. package/skills/loader/SKILL.md +289 -53
  26. package/skills/middleware/SKILL.md +52 -13
  27. package/skills/migrate-nextjs/SKILL.md +584 -0
  28. package/skills/migrate-react-router/SKILL.md +914 -0
  29. package/skills/mime-routes/SKILL.md +28 -1
  30. package/skills/observability/SKILL.md +172 -0
  31. package/skills/parallel/SKILL.md +144 -7
  32. package/skills/prerender/SKILL.md +172 -125
  33. package/skills/rango/SKILL.md +251 -22
  34. package/skills/react-compiler/SKILL.md +168 -0
  35. package/skills/response-routes/SKILL.md +123 -48
  36. package/skills/route/SKILL.md +101 -5
  37. package/skills/router-setup/SKILL.md +117 -10
  38. package/skills/scripts/SKILL.md +179 -0
  39. package/skills/server-actions/SKILL.md +775 -0
  40. package/skills/streams-and-websockets/SKILL.md +283 -0
  41. package/skills/tailwind/SKILL.md +27 -3
  42. package/skills/testing/SKILL.md +130 -0
  43. package/skills/testing/bindings.md +103 -0
  44. package/skills/testing/cache-prerender.md +127 -0
  45. package/skills/testing/client-components.md +124 -0
  46. package/skills/testing/e2e-parity.md +125 -0
  47. package/skills/testing/flight.md +91 -0
  48. package/skills/testing/handles.md +129 -0
  49. package/skills/testing/loader.md +128 -0
  50. package/skills/testing/middleware.md +99 -0
  51. package/skills/testing/render-handler.md +122 -0
  52. package/skills/testing/response-routes.md +95 -0
  53. package/skills/testing/reverse-and-types.md +84 -0
  54. package/skills/testing/server-actions.md +107 -0
  55. package/skills/testing/server-tree.md +128 -0
  56. package/skills/testing/setup.md +123 -0
  57. package/skills/typesafety/SKILL.md +332 -29
  58. package/skills/use-cache/SKILL.md +57 -14
  59. package/skills/view-transitions/SKILL.md +337 -0
  60. package/src/__augment-tests__/augment.ts +81 -0
  61. package/src/__augment-tests__/augmented.check.ts +116 -0
  62. package/src/__internal.ts +1 -66
  63. package/src/browser/action-coordinator.ts +53 -36
  64. package/src/browser/action-fence.ts +47 -0
  65. package/src/browser/app-shell.ts +39 -0
  66. package/src/browser/app-version.ts +14 -0
  67. package/src/browser/connection-warmup.ts +134 -0
  68. package/src/browser/cookie-name.ts +140 -0
  69. package/src/browser/event-controller.ts +192 -150
  70. package/src/browser/history-state.ts +21 -0
  71. package/src/browser/index.ts +3 -3
  72. package/src/browser/invalidate-client-cache.ts +52 -0
  73. package/src/browser/navigation-bridge.ts +131 -30
  74. package/src/browser/navigation-client.ts +199 -86
  75. package/src/browser/navigation-store-handle.ts +38 -0
  76. package/src/browser/navigation-store.ts +157 -74
  77. package/src/browser/navigation-transaction.ts +9 -59
  78. package/src/browser/network-error-handler.ts +34 -7
  79. package/src/browser/partial-update.ts +174 -109
  80. package/src/browser/prefetch/cache.ts +222 -69
  81. package/src/browser/prefetch/fetch.ts +347 -39
  82. package/src/browser/prefetch/queue.ts +113 -32
  83. package/src/browser/prefetch/resource-ready.ts +77 -0
  84. package/src/browser/rango-state.ts +158 -76
  85. package/src/browser/react/Link.tsx +102 -15
  86. package/src/browser/react/NavigationProvider.tsx +300 -122
  87. package/src/browser/react/ScrollRestoration.tsx +10 -6
  88. package/src/browser/react/context.ts +7 -2
  89. package/src/browser/react/deferred-handle-resolution.ts +75 -0
  90. package/src/browser/react/filter-segment-order.ts +66 -7
  91. package/src/browser/react/index.ts +0 -48
  92. package/src/browser/react/location-state-shared.ts +178 -8
  93. package/src/browser/react/location-state.ts +39 -14
  94. package/src/browser/react/use-action.ts +6 -15
  95. package/src/browser/react/use-handle.ts +23 -69
  96. package/src/browser/react/use-href.tsx +8 -1
  97. package/src/browser/react/use-link-status.ts +33 -8
  98. package/src/browser/react/use-navigation.ts +32 -7
  99. package/src/browser/react/use-params.ts +20 -10
  100. package/src/browser/react/use-reverse.ts +106 -0
  101. package/src/browser/react/use-router.ts +46 -11
  102. package/src/browser/react/use-search-params.ts +0 -5
  103. package/src/browser/react/use-segments.ts +11 -21
  104. package/src/browser/response-adapter.ts +99 -8
  105. package/src/browser/rsc-router.tsx +125 -28
  106. package/src/browser/scroll-restoration.ts +37 -22
  107. package/src/browser/segment-reconciler.ts +36 -14
  108. package/src/browser/segment-structure-assert.ts +2 -2
  109. package/src/browser/server-action-bridge.ts +222 -61
  110. package/src/browser/types.ts +115 -12
  111. package/src/browser/validate-redirect-origin.ts +43 -16
  112. package/src/build/collect-fallback-refs.ts +107 -0
  113. package/src/build/generate-manifest.ts +65 -40
  114. package/src/build/generate-route-types.ts +5 -1
  115. package/src/build/index.ts +8 -2
  116. package/src/build/prefix-tree-utils.ts +123 -0
  117. package/src/build/route-trie.ts +165 -36
  118. package/src/build/route-types/ast-route-extraction.ts +15 -8
  119. package/src/build/route-types/codegen.ts +16 -5
  120. package/src/build/route-types/include-resolution.ts +125 -24
  121. package/src/build/route-types/param-extraction.ts +6 -3
  122. package/src/build/route-types/per-module-writer.ts +22 -6
  123. package/src/build/route-types/router-processing.ts +260 -94
  124. package/src/build/route-types/scan-filter.ts +9 -2
  125. package/src/build/route-types/source-scan.ts +216 -0
  126. package/src/build/runtime-discovery.ts +9 -20
  127. package/src/cache/cache-error.ts +104 -0
  128. package/src/cache/cache-key-utils.ts +29 -13
  129. package/src/cache/cache-policy.ts +108 -34
  130. package/src/cache/cache-runtime.ts +239 -52
  131. package/src/cache/cache-scope.ts +234 -87
  132. package/src/cache/cache-tag.ts +103 -0
  133. package/src/cache/cf/cf-base64.ts +33 -0
  134. package/src/cache/cf/cf-cache-constants.ts +127 -0
  135. package/src/cache/cf/cf-cache-store.ts +1989 -378
  136. package/src/cache/cf/cf-cache-types.ts +349 -0
  137. package/src/cache/cf/cf-kv-utils.ts +46 -0
  138. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  139. package/src/cache/cf/index.ts +6 -16
  140. package/src/cache/document-cache.ts +89 -21
  141. package/src/cache/handle-snapshot.ts +70 -0
  142. package/src/cache/index.ts +10 -20
  143. package/src/cache/memory-segment-store.ts +136 -37
  144. package/src/cache/profile-registry.ts +46 -31
  145. package/src/cache/read-through-swr.ts +56 -12
  146. package/src/cache/segment-codec.ts +9 -17
  147. package/src/cache/tag-invalidation.ts +230 -0
  148. package/src/cache/taint.ts +55 -0
  149. package/src/cache/types.ts +37 -100
  150. package/src/client.rsc.tsx +44 -21
  151. package/src/client.tsx +119 -290
  152. package/src/cloudflare/index.ts +11 -0
  153. package/src/cloudflare/tracing.ts +109 -0
  154. package/src/component-utils.ts +19 -0
  155. package/src/components/DefaultDocument.tsx +8 -2
  156. package/src/context-var.ts +84 -2
  157. package/src/decode-loader-results.ts +52 -0
  158. package/src/defer.ts +196 -0
  159. package/src/deps/ssr.ts +0 -1
  160. package/src/encode-kv.ts +49 -0
  161. package/src/errors.ts +30 -4
  162. package/src/escape-script.ts +52 -0
  163. package/src/handle.ts +70 -22
  164. package/src/handles/MetaTags.tsx +62 -19
  165. package/src/handles/Scripts.tsx +183 -0
  166. package/src/handles/breadcrumbs.ts +37 -8
  167. package/src/handles/is-thenable.ts +19 -0
  168. package/src/handles/meta.ts +51 -40
  169. package/src/handles/script.ts +244 -0
  170. package/src/host/cookie-handler.ts +9 -60
  171. package/src/host/errors.ts +0 -24
  172. package/src/host/index.ts +8 -2
  173. package/src/host/pattern-matcher.ts +23 -52
  174. package/src/host/router.ts +107 -99
  175. package/src/host/testing.ts +40 -27
  176. package/src/host/types.ts +37 -4
  177. package/src/host/utils.ts +1 -1
  178. package/src/href-client.ts +137 -22
  179. package/src/index.rsc.ts +99 -13
  180. package/src/index.ts +139 -19
  181. package/src/internal-debug.ts +11 -10
  182. package/src/loader-store.ts +500 -0
  183. package/src/loader.rsc.ts +20 -13
  184. package/src/loader.ts +12 -11
  185. package/src/missing-id-error.ts +68 -0
  186. package/src/outlet-context.ts +1 -1
  187. package/src/outlet-provider.tsx +1 -5
  188. package/src/prerender/param-hash.ts +16 -16
  189. package/src/prerender/store.ts +37 -41
  190. package/src/prerender.ts +198 -82
  191. package/src/redirect-origin.ts +100 -0
  192. package/src/regex-escape.ts +8 -0
  193. package/src/render-error-thrower.tsx +20 -0
  194. package/src/response-utils.ts +62 -0
  195. package/src/reverse.ts +65 -15
  196. package/src/root-error-boundary.tsx +1 -19
  197. package/src/route-content-wrapper.tsx +19 -77
  198. package/src/route-definition/dsl-helpers.ts +461 -304
  199. package/src/route-definition/helper-factories.ts +28 -140
  200. package/src/route-definition/helpers-types.ts +149 -74
  201. package/src/route-definition/index.ts +4 -2
  202. package/src/route-definition/redirect.ts +51 -10
  203. package/src/route-definition/resolve-handler-use.ts +160 -0
  204. package/src/route-definition/use-item-types.ts +29 -0
  205. package/src/route-map-builder.ts +0 -16
  206. package/src/route-types.ts +37 -46
  207. package/src/router/basename.ts +14 -0
  208. package/src/router/content-negotiation.ts +164 -17
  209. package/src/router/error-handling.ts +45 -18
  210. package/src/router/find-match.ts +44 -23
  211. package/src/router/handler-context.ts +83 -39
  212. package/src/router/instrument.ts +350 -0
  213. package/src/router/intercept-resolution.ts +48 -24
  214. package/src/router/lazy-includes.ts +15 -52
  215. package/src/router/loader-resolution.ts +274 -56
  216. package/src/router/logging.ts +0 -6
  217. package/src/router/manifest.ts +40 -42
  218. package/src/router/match-api.ts +124 -204
  219. package/src/router/match-context.ts +0 -22
  220. package/src/router/match-handlers.ts +58 -58
  221. package/src/router/match-middleware/background-revalidation.ts +54 -25
  222. package/src/router/match-middleware/cache-lookup.ts +205 -271
  223. package/src/router/match-middleware/cache-store.ts +81 -50
  224. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  225. package/src/router/match-middleware/segment-resolution.ts +45 -14
  226. package/src/router/match-pipelines.ts +1 -42
  227. package/src/router/match-result.ts +93 -39
  228. package/src/router/metrics.ts +5 -34
  229. package/src/router/middleware-types.ts +13 -142
  230. package/src/router/middleware.ts +268 -171
  231. package/src/router/navigation-snapshot.ts +131 -0
  232. package/src/router/params-util.ts +23 -0
  233. package/src/router/pattern-matching.ts +132 -90
  234. package/src/router/prefetch-cache-ttl.ts +51 -0
  235. package/src/router/prefetch-limits.ts +37 -0
  236. package/src/router/prerender-match.ts +195 -56
  237. package/src/router/preview-match.ts +32 -102
  238. package/src/router/request-classification.ts +276 -0
  239. package/src/router/revalidation.ts +123 -73
  240. package/src/router/route-snapshot.ts +244 -0
  241. package/src/router/router-context.ts +3 -27
  242. package/src/router/router-interfaces.ts +129 -35
  243. package/src/router/router-options.ts +202 -15
  244. package/src/router/router-registry.ts +2 -5
  245. package/src/router/segment-resolution/fresh.ts +197 -92
  246. package/src/router/segment-resolution/helpers.ts +115 -30
  247. package/src/router/segment-resolution/loader-cache.ts +76 -39
  248. package/src/router/segment-resolution/revalidation.ts +392 -338
  249. package/src/router/segment-resolution/static-store.ts +19 -5
  250. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  251. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  252. package/src/router/segment-resolution.ts +5 -1
  253. package/src/router/segment-wrappers.ts +6 -5
  254. package/src/router/state-cookie-name.ts +33 -0
  255. package/src/router/substitute-pattern-params.ts +56 -0
  256. package/src/router/telemetry-otel.ts +161 -199
  257. package/src/router/telemetry.ts +96 -19
  258. package/src/router/timeout.ts +0 -20
  259. package/src/router/tracing.ts +206 -0
  260. package/src/router/trie-matching.ts +163 -59
  261. package/src/router/types.ts +10 -63
  262. package/src/router/url-params.ts +44 -0
  263. package/src/router.ts +174 -54
  264. package/src/rsc/handler-context.ts +3 -2
  265. package/src/rsc/handler.ts +660 -518
  266. package/src/rsc/helpers.ts +168 -46
  267. package/src/rsc/index.ts +2 -5
  268. package/src/rsc/json-route-result.ts +38 -0
  269. package/src/rsc/loader-fetch.ts +127 -31
  270. package/src/rsc/manifest-init.ts +33 -42
  271. package/src/rsc/origin-guard.ts +39 -25
  272. package/src/rsc/progressive-enhancement.ts +133 -13
  273. package/src/rsc/redirect-guard.ts +99 -0
  274. package/src/rsc/response-cache-serve.ts +238 -0
  275. package/src/rsc/response-error.ts +79 -12
  276. package/src/rsc/response-route-handler.ts +99 -189
  277. package/src/rsc/rsc-rendering.ts +115 -73
  278. package/src/rsc/runtime-warnings.ts +23 -10
  279. package/src/rsc/server-action.ts +287 -113
  280. package/src/rsc/ssr-setup.ts +18 -2
  281. package/src/rsc/transition-gate.ts +89 -0
  282. package/src/rsc/types.ts +36 -6
  283. package/src/runtime-env.ts +18 -0
  284. package/src/search-params.ts +35 -30
  285. package/src/segment-content-promise.ts +67 -0
  286. package/src/segment-loader-promise.ts +149 -0
  287. package/src/segment-system.tsx +269 -202
  288. package/src/serialize.ts +243 -0
  289. package/src/server/context.ts +235 -51
  290. package/src/server/cookie-parse.ts +32 -0
  291. package/src/server/cookie-store.ts +80 -5
  292. package/src/server/handle-store.ts +40 -38
  293. package/src/server/loader-registry.ts +38 -46
  294. package/src/server/request-context.ts +442 -173
  295. package/src/ssr/index.tsx +24 -16
  296. package/src/static-handler.ts +27 -18
  297. package/src/testing/cache-status.ts +162 -0
  298. package/src/testing/collect-handle.ts +40 -0
  299. package/src/testing/dispatch.ts +701 -0
  300. package/src/testing/dom.entry.ts +22 -0
  301. package/src/testing/e2e/fixture.ts +188 -0
  302. package/src/testing/e2e/index.ts +128 -0
  303. package/src/testing/e2e/matchers.ts +35 -0
  304. package/src/testing/e2e/page-helpers.ts +272 -0
  305. package/src/testing/e2e/parity.ts +387 -0
  306. package/src/testing/e2e/server.ts +195 -0
  307. package/src/testing/flight-matchers.ts +97 -0
  308. package/src/testing/flight-normalize.ts +11 -0
  309. package/src/testing/flight-runtime.d.ts +57 -0
  310. package/src/testing/flight-tree.ts +682 -0
  311. package/src/testing/flight.entry.ts +52 -0
  312. package/src/testing/flight.ts +257 -0
  313. package/src/testing/generated-routes.ts +183 -0
  314. package/src/testing/index.ts +105 -0
  315. package/src/testing/internal/context.ts +371 -0
  316. package/src/testing/internal/flight-client-globals.ts +30 -0
  317. package/src/testing/internal/seed-vars.ts +54 -0
  318. package/src/testing/render-handler.ts +357 -0
  319. package/src/testing/render-route.tsx +581 -0
  320. package/src/testing/run-loader.ts +385 -0
  321. package/src/testing/run-middleware.ts +205 -0
  322. package/src/testing/run-transition-when.ts +164 -0
  323. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  324. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  325. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  326. package/src/testing/vitest-stubs/version.ts +5 -0
  327. package/src/testing/vitest.ts +305 -0
  328. package/src/theme/ThemeProvider.tsx +20 -58
  329. package/src/theme/ThemeScript.tsx +7 -9
  330. package/src/theme/constants.ts +52 -13
  331. package/src/theme/index.ts +0 -7
  332. package/src/theme/theme-context.ts +1 -5
  333. package/src/theme/theme-script.ts +22 -21
  334. package/src/theme/use-theme.ts +0 -3
  335. package/src/types/boundaries.ts +0 -35
  336. package/src/types/cache-types.ts +17 -8
  337. package/src/types/error-types.ts +30 -90
  338. package/src/types/global-namespace.ts +54 -41
  339. package/src/types/handler-context.ts +234 -82
  340. package/src/types/index.ts +3 -10
  341. package/src/types/loader-types.ts +44 -15
  342. package/src/types/request-scope.ts +112 -0
  343. package/src/types/route-config.ts +6 -50
  344. package/src/types/route-entry.ts +12 -7
  345. package/src/types/segments.ts +136 -15
  346. package/src/urls/include-helper.ts +33 -70
  347. package/src/urls/index.ts +1 -11
  348. package/src/urls/path-helper-types.ts +68 -18
  349. package/src/urls/path-helper.ts +57 -111
  350. package/src/urls/pattern-types.ts +48 -19
  351. package/src/urls/response-types.ts +25 -22
  352. package/src/urls/type-extraction.ts +58 -139
  353. package/src/urls/urls-function.ts +1 -19
  354. package/src/use-loader.tsx +346 -89
  355. package/src/vite/debug.ts +185 -0
  356. package/src/vite/discovery/bundle-postprocess.ts +36 -38
  357. package/src/vite/discovery/discover-routers.ts +130 -85
  358. package/src/vite/discovery/discovery-errors.ts +194 -0
  359. package/src/vite/discovery/gate-state.ts +171 -0
  360. package/src/vite/discovery/prerender-collection.ts +214 -132
  361. package/src/vite/discovery/route-types-writer.ts +40 -84
  362. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  363. package/src/vite/discovery/state.ts +57 -4
  364. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  365. package/src/vite/index.ts +6 -0
  366. package/src/vite/inject-client-debug.ts +36 -0
  367. package/src/vite/plugin-types.ts +178 -5
  368. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  369. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  370. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  371. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  372. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  373. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  374. package/src/vite/plugins/expose-action-id.ts +48 -95
  375. package/src/vite/plugins/expose-id-utils.ts +96 -51
  376. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  377. package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
  378. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  379. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  380. package/src/vite/plugins/expose-internal-ids.ts +553 -317
  381. package/src/vite/plugins/performance-tracks.ts +89 -0
  382. package/src/vite/plugins/refresh-cmd.ts +89 -27
  383. package/src/vite/plugins/use-cache-transform.ts +73 -83
  384. package/src/vite/plugins/version-injector.ts +40 -29
  385. package/src/vite/plugins/version-plugin.ts +37 -40
  386. package/src/vite/plugins/virtual-entries.ts +39 -25
  387. package/src/vite/rango.ts +119 -111
  388. package/src/vite/router-discovery.ts +941 -142
  389. package/src/vite/utils/ast-handler-extract.ts +26 -35
  390. package/src/vite/utils/banner.ts +1 -1
  391. package/src/vite/utils/bundle-analysis.ts +10 -15
  392. package/src/vite/utils/client-chunks.ts +184 -0
  393. package/src/vite/utils/directive-prologue.ts +40 -0
  394. package/src/vite/utils/forward-user-plugins.ts +171 -0
  395. package/src/vite/utils/manifest-utils.ts +4 -59
  396. package/src/vite/utils/package-resolution.ts +20 -52
  397. package/src/vite/utils/prerender-utils.ts +81 -34
  398. package/src/vite/utils/shared-utils.ts +92 -42
  399. package/src/browser/action-response-classifier.ts +0 -99
  400. package/src/browser/react/use-client-cache.ts +0 -58
  401. package/src/browser/shallow.ts +0 -40
  402. package/src/handles/index.ts +0 -7
  403. package/src/network-error-thrower.tsx +0 -23
  404. package/src/router/middleware-cookies.ts +0 -55
@@ -8,19 +8,25 @@ import type {
8
8
  import { NetworkError, ServerRedirect, isNetworkError } from "../errors.js";
9
9
  import {
10
10
  browserDebugLog,
11
+ debugLog,
11
12
  isBrowserDebugEnabled,
12
13
  startBrowserTransaction,
13
14
  } from "./logging.js";
14
15
  import { getRangoState } from "./rango-state.js";
16
+ import { isActionFenceActive } from "./action-fence.js";
15
17
  import {
16
18
  extractRscHeaderUrl,
17
19
  emptyResponse,
20
+ handleReloadHeader,
18
21
  teeWithCompletion,
22
+ isForeignRouterId,
19
23
  } from "./response-adapter.js";
20
24
  import {
21
25
  buildPrefetchKey,
26
+ buildSourceKey,
22
27
  consumeInflightPrefetch,
23
28
  consumePrefetch,
29
+ type DecodedPrefetch,
24
30
  } from "./prefetch/cache.js";
25
31
 
26
32
  /**
@@ -30,8 +36,10 @@ import {
30
36
  * deserializing the response using the RSC runtime.
31
37
  *
32
38
  * Checks the in-memory prefetch cache before making a network request.
33
- * The cache key is source-dependent (includes the previous URL) so
34
- * prefetch responses match the exact diff the server would produce.
39
+ * Tries the source-scoped key first (populated when the server tagged
40
+ * the response as source-sensitive via `X-RSC-Prefetch-Scope: source`)
41
+ * and falls back to the Rango-state-keyed wildcard slot used for the
42
+ * common source-agnostic case.
35
43
  *
36
44
  * @param deps - RSC browser dependencies (createFromFetch)
37
45
  * @returns NavigationClient instance
@@ -61,6 +69,7 @@ export function createNavigationClient(
61
69
  staleRevalidation,
62
70
  interceptSourceUrl,
63
71
  version,
72
+ routerId,
64
73
  hmr,
65
74
  } = options;
66
75
 
@@ -88,25 +97,116 @@ export function createNavigationClient(
88
97
  if (version) {
89
98
  fetchUrl.searchParams.set("_rsc_v", version);
90
99
  }
100
+ if (routerId) {
101
+ fetchUrl.searchParams.set("_rsc_rid", routerId);
102
+ }
91
103
 
92
- // Check completed in-memory prefetch cache before making a network request.
93
- // The cache key includes the source URL (previousUrl) because the
94
- // server's diff response depends on the source page context.
104
+ // Check completed in-memory prefetch cache before making a network
105
+ // request. Try the source-scoped key first (populated when the server
106
+ // tagged the prefetch response as source-sensitive, e.g. intercepts,
107
+ // or when a Link opted in with `prefetchKey=":source"`), then fall
108
+ // back to the wildcard slot shared across source pages.
109
+ // Both keys embed the Rango state, so state rotation (deploy or
110
+ // server-action invalidation) auto-invalidates both scopes.
95
111
  // Skip cache for stale revalidation (needs fresh data), HMR (needs
96
112
  // fresh modules), and intercept contexts (source-dependent responses).
97
- //
98
- const canUsePrefetch = !staleRevalidation && !hmr && !interceptSourceUrl;
99
- const cacheKey = buildPrefetchKey(previousUrl, fetchUrl);
100
- const cachedResponse = canUsePrefetch ? consumePrefetch(cacheKey) : null;
101
- const inflightResponsePromise = canUsePrefetch
102
- ? consumeInflightPrefetch(cacheKey)
103
- : null;
113
+ // Suspend prefetch consumption while an action is in flight: a queued
114
+ // prefetch holds pre-mutation data and must not be served until the
115
+ // action's response decides whether anything changed.
116
+ const canUsePrefetch =
117
+ !staleRevalidation &&
118
+ !hmr &&
119
+ !interceptSourceUrl &&
120
+ !isActionFenceActive();
121
+ const rangoState = getRangoState();
122
+ const wildcardKey = buildPrefetchKey(rangoState, fetchUrl);
123
+ const cacheKey = buildSourceKey(rangoState, previousUrl, fetchUrl);
124
+
125
+ let cachedEntry: DecodedPrefetch | null = null;
126
+ let hitKey: string | null = null;
127
+ if (canUsePrefetch) {
128
+ cachedEntry = consumePrefetch(cacheKey);
129
+ if (cachedEntry) {
130
+ hitKey = cacheKey;
131
+ } else {
132
+ cachedEntry = consumePrefetch(wildcardKey);
133
+ if (cachedEntry) hitKey = wildcardKey;
134
+ }
135
+ }
136
+
137
+ let inflightEntryPromise: Promise<DecodedPrefetch | null> | null = null;
138
+ if (canUsePrefetch && !cachedEntry) {
139
+ inflightEntryPromise = consumeInflightPrefetch(cacheKey);
140
+ if (inflightEntryPromise) {
141
+ hitKey = cacheKey;
142
+ } else {
143
+ inflightEntryPromise = consumeInflightPrefetch(wildcardKey);
144
+ if (inflightEntryPromise) hitKey = wildcardKey;
145
+ }
146
+ }
104
147
  // Track when the stream completes
105
148
  let resolveStreamComplete: () => void;
106
149
  const streamComplete = new Promise<void>((resolve) => {
107
150
  resolveStreamComplete = resolve;
108
151
  });
109
152
 
153
+ /**
154
+ * Validate RSC control headers on any response (fresh, cached, or
155
+ * in-flight). Handles version-mismatch reloads and server redirects.
156
+ * Returns the response unchanged when no control header is present.
157
+ */
158
+ const validateRscHeaders = (
159
+ response: Response,
160
+ source: string,
161
+ ): Response | Promise<Response> => {
162
+ // Version mismatch — server wants a full page reload
163
+ const reloadResult = handleReloadHeader(response, {
164
+ onBlocked: resolveStreamComplete,
165
+ onReload: (url) => {
166
+ if (tx) {
167
+ browserDebugLog(tx, `version mismatch, reloading (${source})`, {
168
+ reloadUrl: url,
169
+ });
170
+ }
171
+ },
172
+ });
173
+ if (reloadResult) return reloadResult;
174
+
175
+ // Server-side redirect without state: the server returned 204 with
176
+ // X-RSC-Redirect instead of a 3xx (which fetch would auto-follow
177
+ // to a URL rendering full HTML). Throw ServerRedirect so the
178
+ // navigation bridge catches it and re-navigates with _skipCache.
179
+ const redirect = extractRscHeaderUrl(response, "X-RSC-Redirect");
180
+ if (redirect === "blocked") {
181
+ resolveStreamComplete();
182
+ return emptyResponse();
183
+ }
184
+ if (redirect) {
185
+ if (tx) {
186
+ browserDebugLog(tx, `server redirect (${source})`, {
187
+ redirectUrl: redirect.url,
188
+ });
189
+ }
190
+ resolveStreamComplete();
191
+ throw new ServerRedirect(redirect.url, undefined);
192
+ }
193
+
194
+ // Integrity check (pre-decode): refuse a foreign app's content response
195
+ // before createFromFetch imports its chunks. Ordered AFTER the reload
196
+ // and redirect handlers — control responses are never stamped with
197
+ // X-RSC-Router-Id, so they are steered first and never reach here.
198
+ if (isForeignRouterId(response, routerId)) {
199
+ if (tx) {
200
+ browserDebugLog(tx, `router id mismatch, reloading (${source})`);
201
+ }
202
+ resolveStreamComplete();
203
+ window.location.href = targetUrl;
204
+ return new Promise<Response>(() => {});
205
+ }
206
+
207
+ return response;
208
+ };
209
+
110
210
  /** Start a fresh navigation fetch (no cache / inflight hit). */
111
211
  const doFreshFetch = (): Promise<Response> => {
112
212
  if (tx) {
@@ -116,6 +216,11 @@ export function createNavigationClient(
116
216
  }
117
217
 
118
218
  return fetch(fetchUrl, {
219
+ // During an action's flight the state is not rotated, so the old
220
+ // X-Rango-State still matches the Vary-keyed HTTP-cache entry; bypass
221
+ // it so a genuine mid-action navigation fetches fresh instead of being
222
+ // served the stale prefetched bytes.
223
+ ...(isActionFenceActive() && { cache: "no-store" as RequestCache }),
119
224
  headers: {
120
225
  "X-RSC-Router-Client-Path": previousUrl,
121
226
  "X-Rango-State": getRangoState(),
@@ -127,43 +232,11 @@ export function createNavigationClient(
127
232
  },
128
233
  signal,
129
234
  }).then((response) => {
130
- // Check for version mismatch - server wants us to reload
131
- const reload = extractRscHeaderUrl(response, "X-RSC-Reload");
132
- if (reload === "blocked") {
133
- resolveStreamComplete();
134
- return emptyResponse();
135
- }
136
- if (reload) {
137
- if (tx) {
138
- browserDebugLog(tx, "version mismatch, reloading", {
139
- reloadUrl: reload.url,
140
- });
141
- }
142
- window.location.href = reload.url;
143
- return new Promise<Response>(() => {});
144
- }
145
-
146
- // Server-side redirect without state: the server returned 204 with
147
- // X-RSC-Redirect instead of a 3xx (which fetch would auto-follow
148
- // to a URL rendering full HTML). Throw ServerRedirect so the
149
- // navigation bridge catches it and re-navigates with _skipCache.
150
- const redirect = extractRscHeaderUrl(response, "X-RSC-Redirect");
151
- if (redirect === "blocked") {
152
- resolveStreamComplete();
153
- return emptyResponse();
154
- }
155
- if (redirect) {
156
- if (tx) {
157
- browserDebugLog(tx, "server redirect", {
158
- redirectUrl: redirect.url,
159
- });
160
- }
161
- resolveStreamComplete();
162
- throw new ServerRedirect(redirect.url, undefined);
163
- }
235
+ const validated = validateRscHeaders(response, "fetch");
236
+ if (validated instanceof Promise) return validated;
164
237
 
165
238
  return teeWithCompletion(
166
- response,
239
+ validated,
167
240
  () => {
168
241
  if (tx) browserDebugLog(tx, "stream complete");
169
242
  resolveStreamComplete();
@@ -173,54 +246,90 @@ export function createNavigationClient(
173
246
  });
174
247
  };
175
248
 
176
- let responsePromise: Promise<Response>;
249
+ // A warm prefetch hit returns its eagerly-decoded payload directly: the
250
+ // route's chunks were imported during the prefetch, so this click runs
251
+ // no decode and no network. Only the fresh path runs createFromFetch and
252
+ // resolves the local streamComplete (via doFreshFetch's teeWithCompletion
253
+ // and the control-header short-circuits in validateRscHeaders).
254
+ const freshResult = (): {
255
+ payload: Promise<RscPayload>;
256
+ streamComplete: Promise<void>;
257
+ } => ({
258
+ payload: deps.createFromFetch<RscPayload>(doFreshFetch()),
259
+ streamComplete,
260
+ });
261
+
262
+ let payloadPromise: Promise<RscPayload>;
263
+ let streamCompletePromise: Promise<void>;
264
+ // True only for a prefetch-cache hit whose stream had already fully drained
265
+ // (complete === true). A still-streaming hit and the fresh path stay false,
266
+ // so only a fully-warmed prefetch commits in a transition (no fallback flash).
267
+ let fullyPrefetched = false;
177
268
 
178
- if (cachedResponse) {
269
+ if (cachedEntry) {
179
270
  if (tx) {
180
- browserDebugLog(tx, "prefetch cache hit", { key: cacheKey });
271
+ browserDebugLog(tx, "prefetch cache hit (warm)", {
272
+ key: hitKey,
273
+ wildcard: hitKey === wildcardKey,
274
+ });
181
275
  }
182
- // Cached response body is already fully buffered (arrayBuffer),
183
- // so stream completion is immediate.
184
- responsePromise = Promise.resolve(cachedResponse).then((response) => {
185
- return teeWithCompletion(
186
- response,
187
- () => {
188
- if (tx) browserDebugLog(tx, "stream complete (from cache)");
189
- resolveStreamComplete();
190
- },
191
- signal,
192
- );
193
- });
194
- } else if (inflightResponsePromise) {
276
+ payloadPromise = cachedEntry.payload;
277
+ streamCompletePromise = cachedEntry.streamComplete;
278
+ // Only a hit whose stream already fully drained is "fully prefetched";
279
+ // a still-streaming hit must keep streaming its fallbacks like a cold load.
280
+ fullyPrefetched = cachedEntry.complete;
281
+ } else if (inflightEntryPromise) {
195
282
  if (tx) {
196
- browserDebugLog(tx, "reusing inflight prefetch", { key: cacheKey });
283
+ browserDebugLog(tx, "reusing inflight prefetch", {
284
+ key: hitKey,
285
+ wildcard: hitKey === wildcardKey,
286
+ });
197
287
  }
198
- responsePromise = inflightResponsePromise.then(async (response) => {
199
- if (!response) {
200
- if (tx) {
201
- browserDebugLog(tx, "inflight prefetch unavailable, refetching");
202
- }
203
- return doFreshFetch();
288
+ const adoptedViaWildcard = hitKey === wildcardKey;
289
+ const entry = await inflightEntryPromise;
290
+ if (!entry) {
291
+ if (tx) {
292
+ browserDebugLog(tx, "inflight prefetch unavailable, refetching");
204
293
  }
205
-
206
- return teeWithCompletion(
207
- response,
208
- () => {
209
- if (tx) {
210
- browserDebugLog(tx, "stream complete (from inflight prefetch)");
211
- }
212
- resolveStreamComplete();
213
- },
214
- signal,
215
- );
216
- });
294
+ ({ payload: payloadPromise, streamComplete: streamCompletePromise } =
295
+ freshResult());
296
+ } else if (adoptedViaWildcard && entry.scope === "source") {
297
+ // A wildcard-adopted inflight that turned out source-scoped was
298
+ // built for a different source page. Discard and refetch.
299
+ if (tx) {
300
+ browserDebugLog(
301
+ tx,
302
+ "wildcard inflight turned out source-scoped, refetching",
303
+ );
304
+ }
305
+ ({ payload: payloadPromise, streamComplete: streamCompletePromise } =
306
+ freshResult());
307
+ } else {
308
+ payloadPromise = entry.payload;
309
+ streamCompletePromise = entry.streamComplete;
310
+ // Adopted inflight is normally still streaming (false), but read the
311
+ // flag in case it completed between publish and adoption.
312
+ fullyPrefetched = entry.complete;
313
+ }
217
314
  } else {
218
- responsePromise = doFreshFetch();
315
+ ({ payload: payloadPromise, streamComplete: streamCompletePromise } =
316
+ freshResult());
219
317
  }
220
318
 
221
319
  try {
222
- // Deserialize RSC payload
223
- const payload = await deps.createFromFetch<RscPayload>(responsePromise);
320
+ // [VT-DIAG] Gated behind INTERNAL_RANGO_DEBUG. Times how long the RSC
321
+ // payload ROOT takes to resolve: ~full-stream duration means the root
322
+ // model is not flushed early (server/runtime buffering, e.g. wrangler
323
+ // dev gzip); fast means the block, if any, is downstream in render.
324
+ const vtDebugStart = isBrowserDebugEnabled() ? performance.now() : 0;
325
+ const payload = await payloadPromise;
326
+ if (isBrowserDebugEnabled()) {
327
+ debugLog("[VT-DIAG] payloadResolved", {
328
+ ms: Math.round(performance.now() - vtDebugStart),
329
+ isPartial: payload.metadata?.isPartial,
330
+ });
331
+ }
332
+
224
333
  if (tx) {
225
334
  browserDebugLog(tx, "response received", {
226
335
  isPartial: payload.metadata?.isPartial,
@@ -228,7 +337,11 @@ export function createNavigationClient(
228
337
  diffCount: payload.metadata?.diff?.length ?? 0,
229
338
  });
230
339
  }
231
- return { payload, streamComplete };
340
+ return {
341
+ payload,
342
+ streamComplete: streamCompletePromise,
343
+ fullyPrefetched,
344
+ };
232
345
  } catch (error) {
233
346
  // Convert network-level errors to NetworkError for proper handling
234
347
  if (isNetworkError(error)) {
@@ -0,0 +1,38 @@
1
+ /**
2
+ * A module-level handle to the active navigation store.
3
+ *
4
+ * The boot path (`rsc-router.tsx`) calls `createNavigationStore()` directly;
5
+ * there is no global store singleton. This handle is the live reference for
6
+ * code that needs the store but does not
7
+ * receive it by argument: the jar-divergence observer (below) and the client
8
+ * seat of `invalidateClientCache()` (added later).
9
+ *
10
+ * Dependency-light on purpose: it imports only `setRangoStateObserver` and the
11
+ * store type, so pulling it into the default root entry does not drag the
12
+ * navigation store into bundles that previously lacked it.
13
+ */
14
+
15
+ import { setRangoStateObserver } from "./rango-state.js";
16
+ import type { NavigationStore } from "./types.js";
17
+
18
+ let registeredStore: NavigationStore | null = null;
19
+
20
+ /**
21
+ * Register the active navigation store at boot, and wire the jar-divergence
22
+ * observer: when a per-request cookie read detects an EXTERNAL rotation (a
23
+ * sibling tab, a server `Set-Cookie`, or a cookie clear), mark this tab's
24
+ * history cache stale. The history cache is not state-keyed, so the value
25
+ * rotation alone does not reach it. No broadcast, no prefetch clear, no
26
+ * re-rotation — the value already changed externally.
27
+ */
28
+ export function registerNavigationStore(store: NavigationStore): void {
29
+ registeredStore = store;
30
+ setRangoStateObserver(() => {
31
+ registeredStore?.markHistoryCacheStale();
32
+ });
33
+ }
34
+
35
+ /** The active navigation store, or null before boot has registered it. */
36
+ export function getRegisteredStore(): NavigationStore | null {
37
+ return registeredStore;
38
+ }