@rangojs/router 0.0.0-experimental.79 → 0.0.0-experimental.7c7e4327

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 (440) hide show
  1. package/AGENTS.md +8 -4
  2. package/README.md +301 -797
  3. package/dist/bin/rango.js +603 -145
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3750 -1160
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +96 -24
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +85 -6
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +228 -33
  12. package/skills/caching/SKILL.md +336 -19
  13. package/skills/catalog.json +271 -0
  14. package/skills/comparison/SKILL.md +50 -0
  15. package/skills/comparison/agents/openai.yaml +4 -0
  16. package/skills/comparison/references/framework-comparison.md +837 -0
  17. package/skills/composability/SKILL.md +110 -4
  18. package/skills/css/SKILL.md +76 -0
  19. package/skills/debug-manifest/SKILL.md +5 -3
  20. package/skills/defer-hydration/SKILL.md +235 -0
  21. package/skills/document-cache/SKILL.md +87 -56
  22. package/skills/fonts/SKILL.md +1 -1
  23. package/skills/handler-use/SKILL.md +12 -10
  24. package/skills/hooks/SKILL.md +73 -691
  25. package/skills/hooks/data.md +273 -0
  26. package/skills/hooks/handle-and-actions.md +103 -0
  27. package/skills/hooks/navigation.md +110 -0
  28. package/skills/hooks/outlets.md +41 -0
  29. package/skills/hooks/state.md +228 -0
  30. package/skills/hooks/urls.md +135 -0
  31. package/skills/host-router/SKILL.md +129 -27
  32. package/skills/i18n/SKILL.md +276 -0
  33. package/skills/intercept/SKILL.md +75 -19
  34. package/skills/layout/SKILL.md +40 -19
  35. package/skills/links/SKILL.md +247 -17
  36. package/skills/loader/SKILL.md +248 -10
  37. package/skills/middleware/SKILL.md +25 -13
  38. package/skills/migrate-nextjs/SKILL.md +205 -20
  39. package/skills/migrate-react-router/SKILL.md +59 -670
  40. package/skills/migrate-react-router/cloudflare-workers.md +129 -0
  41. package/skills/migrate-react-router/component-migration.md +196 -0
  42. package/skills/migrate-react-router/data-and-actions.md +225 -0
  43. package/skills/migrate-react-router/route-mapping.md +271 -0
  44. package/skills/mime-routes/SKILL.md +29 -2
  45. package/skills/observability/SKILL.md +202 -0
  46. package/skills/parallel/SKILL.md +40 -10
  47. package/skills/ppr/SKILL.md +616 -0
  48. package/skills/prerender/SKILL.md +72 -60
  49. package/skills/rango/SKILL.md +318 -26
  50. package/skills/react-compiler/SKILL.md +168 -0
  51. package/skills/response-routes/SKILL.md +138 -49
  52. package/skills/route/SKILL.md +117 -9
  53. package/skills/router-setup/SKILL.md +44 -9
  54. package/skills/scripts/SKILL.md +179 -0
  55. package/skills/server-actions/SKILL.md +776 -0
  56. package/skills/shell-manifest/SKILL.md +185 -0
  57. package/skills/streams-and-websockets/SKILL.md +283 -0
  58. package/skills/tailwind/SKILL.md +28 -4
  59. package/skills/testing/SKILL.md +130 -0
  60. package/skills/testing/bindings.md +103 -0
  61. package/skills/testing/cache-prerender.md +127 -0
  62. package/skills/testing/client-components.md +124 -0
  63. package/skills/testing/e2e-parity.md +125 -0
  64. package/skills/testing/flight.md +91 -0
  65. package/skills/testing/handles.md +131 -0
  66. package/skills/testing/loader.md +128 -0
  67. package/skills/testing/middleware.md +99 -0
  68. package/skills/testing/render-handler.md +122 -0
  69. package/skills/testing/response-routes.md +95 -0
  70. package/skills/testing/reverse-and-types.md +85 -0
  71. package/skills/testing/server-actions.md +107 -0
  72. package/skills/testing/server-tree.md +128 -0
  73. package/skills/testing/setup.md +123 -0
  74. package/skills/theme/SKILL.md +1 -1
  75. package/skills/typesafety/SKILL.md +45 -626
  76. package/skills/typesafety/env-and-bindings.md +254 -0
  77. package/skills/typesafety/generated-files-and-cli.md +335 -0
  78. package/skills/typesafety/params-and-search.md +153 -0
  79. package/skills/typesafety/route-types.md +209 -0
  80. package/skills/use-cache/SKILL.md +74 -15
  81. package/skills/vercel/SKILL.md +128 -0
  82. package/skills/view-transitions/SKILL.md +337 -0
  83. package/src/__augment-tests__/augment.ts +81 -0
  84. package/src/__augment-tests__/augmented.check.ts +116 -0
  85. package/src/__internal.ts +0 -65
  86. package/src/browser/action-coordinator.ts +53 -36
  87. package/src/browser/action-fence.ts +47 -0
  88. package/src/browser/app-shell.ts +39 -0
  89. package/src/browser/connection-warmup.ts +134 -0
  90. package/src/browser/cookie-name.ts +140 -0
  91. package/src/browser/event-controller.ts +252 -158
  92. package/src/browser/history-state.ts +21 -0
  93. package/src/browser/index.ts +3 -3
  94. package/src/browser/invalidate-client-cache.ts +52 -0
  95. package/src/browser/logging.ts +28 -0
  96. package/src/browser/merge-segment-loaders.ts +6 -4
  97. package/src/browser/navigation-bridge.ts +94 -25
  98. package/src/browser/navigation-client.ts +144 -79
  99. package/src/browser/navigation-store-handle.ts +38 -0
  100. package/src/browser/navigation-store.ts +161 -73
  101. package/src/browser/navigation-transaction.ts +9 -59
  102. package/src/browser/network-error-handler.ts +34 -7
  103. package/src/browser/partial-update.ts +183 -144
  104. package/src/browser/prefetch/cache.ts +242 -77
  105. package/src/browser/prefetch/fetch.ts +325 -69
  106. package/src/browser/prefetch/queue.ts +61 -12
  107. package/src/browser/rango-state.ts +158 -76
  108. package/src/browser/react/Link.tsx +58 -20
  109. package/src/browser/react/NavigationProvider.tsx +202 -120
  110. package/src/browser/react/ScrollRestoration.tsx +10 -6
  111. package/src/browser/react/filter-segment-order.ts +66 -7
  112. package/src/browser/react/index.ts +0 -48
  113. package/src/browser/react/location-state-shared.ts +178 -8
  114. package/src/browser/react/location-state.ts +39 -14
  115. package/src/browser/react/use-action.ts +6 -15
  116. package/src/browser/react/use-handle.ts +17 -14
  117. package/src/browser/react/use-href.tsx +8 -1
  118. package/src/browser/react/use-link-status.ts +33 -8
  119. package/src/browser/react/use-navigation.ts +32 -7
  120. package/src/browser/react/use-params.ts +20 -10
  121. package/src/browser/react/use-reverse.ts +106 -0
  122. package/src/browser/react/use-router.ts +25 -3
  123. package/src/browser/react/use-search-params.ts +0 -5
  124. package/src/browser/react/use-segments.ts +11 -21
  125. package/src/browser/response-adapter.ts +99 -8
  126. package/src/browser/rsc-router.tsx +145 -28
  127. package/src/browser/scroll-restoration.ts +37 -22
  128. package/src/browser/segment-reconciler.ts +31 -21
  129. package/src/browser/segment-structure-assert.ts +2 -2
  130. package/src/browser/server-action-bridge.ts +236 -65
  131. package/src/browser/types.ts +102 -9
  132. package/src/browser/validate-redirect-origin.ts +43 -16
  133. package/src/build/collect-fallback-refs.ts +107 -0
  134. package/src/build/generate-manifest.ts +203 -154
  135. package/src/build/generate-route-types.ts +3 -1
  136. package/src/build/index.ts +11 -3
  137. package/src/build/prefix-tree-utils.ts +123 -0
  138. package/src/build/route-trie.ts +152 -21
  139. package/src/build/route-types/ast-route-extraction.ts +15 -8
  140. package/src/build/route-types/codegen.ts +16 -5
  141. package/src/build/route-types/include-resolution.ts +456 -62
  142. package/src/build/route-types/param-extraction.ts +6 -3
  143. package/src/build/route-types/per-module-writer.ts +22 -6
  144. package/src/build/route-types/router-processing.ts +128 -51
  145. package/src/build/route-types/scan-filter.ts +1 -1
  146. package/src/build/route-types/source-scan.ts +216 -0
  147. package/src/build/runtime-discovery.ts +13 -21
  148. package/src/cache/cache-error.ts +104 -0
  149. package/src/cache/cache-key-utils.ts +58 -13
  150. package/src/cache/cache-policy.ts +108 -34
  151. package/src/cache/cache-runtime.ts +421 -58
  152. package/src/cache/cache-scope.ts +187 -96
  153. package/src/cache/cache-tag.ts +149 -0
  154. package/src/cache/cf/cf-base64.ts +33 -0
  155. package/src/cache/cf/cf-cache-constants.ts +127 -0
  156. package/src/cache/cf/cf-cache-store.ts +2202 -372
  157. package/src/cache/cf/cf-cache-types.ts +349 -0
  158. package/src/cache/cf/cf-kv-utils.ts +46 -0
  159. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  160. package/src/cache/cf/index.ts +6 -16
  161. package/src/cache/document-cache.ts +126 -41
  162. package/src/cache/handle-snapshot.ts +70 -0
  163. package/src/cache/index.ts +23 -20
  164. package/src/cache/memory-segment-store.ts +243 -37
  165. package/src/cache/profile-registry.ts +46 -31
  166. package/src/cache/read-through-swr.ts +56 -12
  167. package/src/cache/segment-codec.ts +13 -21
  168. package/src/cache/shell-snapshot.ts +417 -0
  169. package/src/cache/tag-invalidation.ts +230 -0
  170. package/src/cache/types.ts +180 -99
  171. package/src/cache/vercel/index.ts +11 -0
  172. package/src/cache/vercel/vercel-cache-store.ts +1127 -0
  173. package/src/client.rsc.tsx +41 -21
  174. package/src/client.tsx +33 -61
  175. package/src/cloudflare/index.ts +11 -0
  176. package/src/cloudflare/tracing.ts +108 -0
  177. package/src/component-utils.ts +19 -0
  178. package/src/components/DefaultDocument.tsx +8 -2
  179. package/src/context-var.ts +18 -6
  180. package/src/decode-loader-results.ts +52 -0
  181. package/src/defer.ts +185 -0
  182. package/src/deps/ssr.ts +0 -1
  183. package/src/encode-kv.ts +49 -0
  184. package/src/errors.ts +30 -4
  185. package/src/escape-script.ts +52 -0
  186. package/src/handle.ts +67 -37
  187. package/src/handles/MetaTags.tsx +24 -53
  188. package/src/handles/Scripts.tsx +183 -0
  189. package/src/handles/breadcrumbs.ts +35 -8
  190. package/src/handles/deferred-resolution.ts +127 -0
  191. package/src/handles/is-thenable.ts +18 -0
  192. package/src/handles/meta.ts +14 -40
  193. package/src/handles/script.ts +244 -0
  194. package/src/host/cookie-handler.ts +9 -60
  195. package/src/host/errors.ts +13 -22
  196. package/src/host/index.ts +9 -2
  197. package/src/host/pattern-matcher.ts +23 -52
  198. package/src/host/router.ts +107 -99
  199. package/src/host/testing.ts +40 -27
  200. package/src/host/types.ts +37 -4
  201. package/src/host/utils.ts +1 -1
  202. package/src/href-client.ts +137 -22
  203. package/src/index.rsc.ts +97 -12
  204. package/src/index.ts +98 -14
  205. package/src/internal-debug.ts +11 -10
  206. package/src/loader-store.ts +500 -0
  207. package/src/loader.rsc.ts +20 -13
  208. package/src/loader.ts +12 -11
  209. package/src/missing-id-error.ts +68 -0
  210. package/src/outlet-context.ts +1 -1
  211. package/src/outlet-provider.tsx +1 -5
  212. package/src/prerender/param-hash.ts +16 -16
  213. package/src/prerender/store.ts +32 -37
  214. package/src/prerender.ts +78 -10
  215. package/src/redirect-origin.ts +114 -0
  216. package/src/regex-escape.ts +8 -0
  217. package/src/render-error-thrower.tsx +20 -0
  218. package/src/response-utils.ts +62 -0
  219. package/src/reverse.ts +65 -39
  220. package/src/root-error-boundary.tsx +1 -19
  221. package/src/route-content-wrapper.tsx +19 -77
  222. package/src/route-definition/dsl-helpers.ts +304 -309
  223. package/src/route-definition/helper-factories.ts +28 -140
  224. package/src/route-definition/helpers-types.ts +87 -59
  225. package/src/route-definition/index.ts +1 -2
  226. package/src/route-definition/redirect.ts +44 -11
  227. package/src/route-definition/resolve-handler-use.ts +12 -1
  228. package/src/route-definition/use-item-types.ts +29 -0
  229. package/src/route-map-builder.ts +41 -20
  230. package/src/route-types.ts +19 -46
  231. package/src/router/basename.ts +14 -0
  232. package/src/router/content-negotiation.ts +73 -25
  233. package/src/router/error-handling.ts +45 -18
  234. package/src/router/find-match.ts +129 -30
  235. package/src/router/handler-context.ts +27 -42
  236. package/src/router/instrument.ts +355 -0
  237. package/src/router/intercept-resolution.ts +39 -20
  238. package/src/router/lazy-includes.ts +82 -59
  239. package/src/router/loader-resolution.ts +167 -72
  240. package/src/router/logging.ts +0 -6
  241. package/src/router/manifest.ts +74 -40
  242. package/src/router/match-api.ts +80 -55
  243. package/src/router/match-context.ts +0 -22
  244. package/src/router/match-handlers.ts +211 -165
  245. package/src/router/match-middleware/background-revalidation.ts +40 -24
  246. package/src/router/match-middleware/cache-lookup.ts +159 -285
  247. package/src/router/match-middleware/cache-store.ts +64 -52
  248. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  249. package/src/router/match-middleware/segment-resolution.ts +0 -22
  250. package/src/router/match-pipelines.ts +1 -42
  251. package/src/router/match-result.ts +69 -79
  252. package/src/router/metrics.ts +0 -34
  253. package/src/router/middleware-types.ts +7 -134
  254. package/src/router/middleware.ts +298 -172
  255. package/src/router/navigation-snapshot.ts +7 -56
  256. package/src/router/params-util.ts +23 -0
  257. package/src/router/parse-pattern.ts +115 -0
  258. package/src/router/pattern-matching.ts +181 -150
  259. package/src/router/prefetch-cache-ttl.ts +51 -0
  260. package/src/router/prefetch-limits.ts +37 -0
  261. package/src/router/prerender-match.ts +112 -67
  262. package/src/router/preview-match.ts +6 -2
  263. package/src/router/request-classification.ts +50 -69
  264. package/src/router/revalidation.ts +123 -73
  265. package/src/router/route-snapshot.ts +14 -3
  266. package/src/router/router-context.ts +6 -29
  267. package/src/router/router-interfaces.ts +115 -36
  268. package/src/router/router-options.ts +166 -5
  269. package/src/router/router-registry.ts +2 -5
  270. package/src/router/segment-resolution/fresh.ts +131 -86
  271. package/src/router/segment-resolution/helpers.ts +86 -6
  272. package/src/router/segment-resolution/loader-cache.ts +139 -39
  273. package/src/router/segment-resolution/loader-mask.ts +67 -0
  274. package/src/router/segment-resolution/loader-snapshot.ts +251 -0
  275. package/src/router/segment-resolution/revalidation.ts +272 -320
  276. package/src/router/segment-resolution/static-store.ts +19 -5
  277. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  278. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  279. package/src/router/segment-resolution.ts +5 -1
  280. package/src/router/segment-wrappers.ts +6 -5
  281. package/src/router/state-cookie-name.ts +33 -0
  282. package/src/router/substitute-pattern-params.ts +75 -0
  283. package/src/router/telemetry-otel.ts +160 -200
  284. package/src/router/telemetry.ts +105 -20
  285. package/src/router/timeout.ts +0 -20
  286. package/src/router/tracing.ts +215 -0
  287. package/src/router/trie-matching.ts +171 -59
  288. package/src/router/types.ts +9 -63
  289. package/src/router/url-params.ts +57 -0
  290. package/src/router.ts +157 -71
  291. package/src/rsc/full-payload.ts +70 -0
  292. package/src/rsc/handler-context.ts +3 -2
  293. package/src/rsc/handler.ts +291 -217
  294. package/src/rsc/helpers.ts +168 -46
  295. package/src/rsc/index.ts +2 -5
  296. package/src/rsc/json-route-result.ts +38 -0
  297. package/src/rsc/loader-fetch.ts +114 -38
  298. package/src/rsc/manifest-init.ts +29 -42
  299. package/src/rsc/nonce.ts +10 -1
  300. package/src/rsc/origin-guard.ts +39 -25
  301. package/src/rsc/progressive-enhancement.ts +124 -13
  302. package/src/rsc/redirect-guard.ts +100 -0
  303. package/src/rsc/response-cache-serve.ts +238 -0
  304. package/src/rsc/response-error.ts +79 -12
  305. package/src/rsc/response-route-handler.ts +99 -189
  306. package/src/rsc/rsc-rendering.ts +421 -76
  307. package/src/rsc/runtime-warnings.ts +23 -10
  308. package/src/rsc/server-action.ts +282 -116
  309. package/src/rsc/shell-capture.ts +1158 -0
  310. package/src/rsc/shell-serve.ts +150 -0
  311. package/src/rsc/ssr-setup.ts +16 -0
  312. package/src/rsc/transition-gate.ts +89 -0
  313. package/src/rsc/types.ts +53 -5
  314. package/src/runtime-env.ts +18 -0
  315. package/src/search-params.ts +35 -30
  316. package/src/segment-loader-promise.ts +49 -4
  317. package/src/segment-system.tsx +350 -149
  318. package/src/serialize.ts +243 -0
  319. package/src/server/context.ts +208 -51
  320. package/src/server/cookie-parse.ts +32 -0
  321. package/src/server/cookie-store.ts +152 -5
  322. package/src/server/handle-store.ts +21 -38
  323. package/src/server/loader-registry.ts +33 -42
  324. package/src/server/request-context.ts +395 -176
  325. package/src/ssr/index.tsx +458 -178
  326. package/src/ssr/ssr-root.tsx +228 -0
  327. package/src/static-handler.ts +10 -13
  328. package/src/testing/cache-status.ts +162 -0
  329. package/src/testing/collect-handle.ts +46 -0
  330. package/src/testing/dispatch.ts +813 -0
  331. package/src/testing/dom.entry.ts +22 -0
  332. package/src/testing/e2e/fixture.ts +188 -0
  333. package/src/testing/e2e/index.ts +128 -0
  334. package/src/testing/e2e/matchers.ts +35 -0
  335. package/src/testing/e2e/page-helpers.ts +272 -0
  336. package/src/testing/e2e/parity.ts +387 -0
  337. package/src/testing/e2e/server.ts +195 -0
  338. package/src/testing/flight-matchers.ts +97 -0
  339. package/src/testing/flight-normalize.ts +11 -0
  340. package/src/testing/flight-runtime.d.ts +57 -0
  341. package/src/testing/flight-tree.ts +682 -0
  342. package/src/testing/flight.entry.ts +52 -0
  343. package/src/testing/flight.ts +257 -0
  344. package/src/testing/generated-routes.ts +199 -0
  345. package/src/testing/index.ts +105 -0
  346. package/src/testing/internal/context.ts +371 -0
  347. package/src/testing/internal/flight-client-globals.ts +30 -0
  348. package/src/testing/internal/seed-vars.ts +54 -0
  349. package/src/testing/render-handler.ts +357 -0
  350. package/src/testing/render-route.tsx +584 -0
  351. package/src/testing/run-loader.ts +385 -0
  352. package/src/testing/run-middleware.ts +205 -0
  353. package/src/testing/run-transition-when.ts +164 -0
  354. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  355. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  356. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  357. package/src/testing/vitest-stubs/version.ts +5 -0
  358. package/src/testing/vitest.ts +305 -0
  359. package/src/theme/ThemeProvider.tsx +56 -84
  360. package/src/theme/ThemeScript.tsx +7 -9
  361. package/src/theme/constants.ts +52 -13
  362. package/src/theme/index.ts +0 -7
  363. package/src/theme/theme-context.ts +1 -5
  364. package/src/theme/theme-script.ts +22 -21
  365. package/src/theme/use-theme.ts +0 -3
  366. package/src/types/boundaries.ts +0 -35
  367. package/src/types/cache-types.ts +13 -4
  368. package/src/types/error-types.ts +30 -90
  369. package/src/types/global-namespace.ts +54 -41
  370. package/src/types/handler-context.ts +110 -62
  371. package/src/types/index.ts +3 -10
  372. package/src/types/loader-types.ts +11 -9
  373. package/src/types/request-scope.ts +112 -0
  374. package/src/types/route-config.ts +20 -52
  375. package/src/types/route-entry.ts +0 -6
  376. package/src/types/segments.ts +135 -14
  377. package/src/urls/include-helper.ts +19 -64
  378. package/src/urls/include-provider.ts +71 -0
  379. package/src/urls/index.ts +2 -11
  380. package/src/urls/path-helper-types.ts +63 -17
  381. package/src/urls/path-helper.ts +22 -106
  382. package/src/urls/pattern-types.ts +72 -19
  383. package/src/urls/response-types.ts +22 -29
  384. package/src/urls/type-extraction.ts +98 -154
  385. package/src/urls/urls-function.ts +1 -19
  386. package/src/use-loader.tsx +292 -107
  387. package/src/vercel/index.ts +11 -0
  388. package/src/vercel/tracing.ts +88 -0
  389. package/src/vite/debug.ts +185 -0
  390. package/src/vite/discovery/bundle-postprocess.ts +8 -7
  391. package/src/vite/discovery/dev-prerender-cache.ts +117 -0
  392. package/src/vite/discovery/discover-routers.ts +127 -86
  393. package/src/vite/discovery/discovery-errors.ts +255 -0
  394. package/src/vite/discovery/gate-state.ts +171 -0
  395. package/src/vite/discovery/prerender-collection.ts +96 -68
  396. package/src/vite/discovery/route-types-writer.ts +40 -84
  397. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  398. package/src/vite/discovery/state.ts +45 -1
  399. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  400. package/src/vite/index.ts +4 -0
  401. package/src/vite/inject-client-debug.ts +88 -0
  402. package/src/vite/plugin-types.ts +210 -10
  403. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  404. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  405. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  406. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  407. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  408. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  409. package/src/vite/plugins/expose-action-id.ts +48 -95
  410. package/src/vite/plugins/expose-id-utils.ts +88 -55
  411. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  412. package/src/vite/plugins/expose-ids/handler-transform.ts +11 -90
  413. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  414. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  415. package/src/vite/plugins/expose-internal-ids.ts +505 -486
  416. package/src/vite/plugins/performance-tracks.ts +26 -25
  417. package/src/vite/plugins/refresh-cmd.ts +1 -1
  418. package/src/vite/plugins/use-cache-transform.ts +73 -83
  419. package/src/vite/plugins/vercel-output.ts +384 -0
  420. package/src/vite/plugins/version-injector.ts +40 -29
  421. package/src/vite/plugins/version-plugin.ts +37 -40
  422. package/src/vite/plugins/virtual-entries.ts +138 -27
  423. package/src/vite/rango.ts +236 -138
  424. package/src/vite/router-discovery.ts +927 -136
  425. package/src/vite/utils/ast-handler-extract.ts +26 -35
  426. package/src/vite/utils/banner.ts +1 -1
  427. package/src/vite/utils/bundle-analysis.ts +10 -15
  428. package/src/vite/utils/client-chunks.ts +184 -0
  429. package/src/vite/utils/directive-prologue.ts +40 -0
  430. package/src/vite/utils/forward-user-plugins.ts +171 -0
  431. package/src/vite/utils/manifest-utils.ts +4 -59
  432. package/src/vite/utils/package-resolution.ts +20 -52
  433. package/src/vite/utils/prerender-utils.ts +71 -43
  434. package/src/vite/utils/shared-utils.ts +142 -43
  435. package/src/browser/action-response-classifier.ts +0 -99
  436. package/src/browser/react/use-client-cache.ts +0 -58
  437. package/src/browser/shallow.ts +0 -40
  438. package/src/handles/index.ts +0 -7
  439. package/src/network-error-thrower.tsx +0 -23
  440. package/src/router/middleware-cookies.ts +0 -55
@@ -4,13 +4,15 @@ import type {
4
4
  RscPayload,
5
5
  } from "./types.js";
6
6
  import { createPartialUpdater } from "./partial-update.js";
7
+ import { enterActionFence, exitActionFence } from "./action-fence.js";
8
+ import { KEEP_CACHE_HEADER } from "./cookie-name.js";
7
9
  import { createNavigationTransaction } from "./navigation-transaction.js";
8
10
  import {
9
11
  reconcileSegments,
10
12
  reconcileErrorSegments,
11
13
  } from "./segment-reconciler.js";
12
14
  import { startTransition } from "react";
13
- import type { EventController } from "./event-controller.js";
15
+ import type { EventController, ActionHandle } from "./event-controller.js";
14
16
  import {
15
17
  toNetworkError,
16
18
  emitNetworkError,
@@ -21,11 +23,16 @@ import {
21
23
  isBrowserDebugEnabled,
22
24
  startBrowserTransaction,
23
25
  } from "./logging.js";
24
- import { validateRedirectOrigin } from "./validate-redirect-origin.js";
26
+ import {
27
+ validateRedirectOrigin,
28
+ validateExternalRedirect,
29
+ } from "./validate-redirect-origin.js";
25
30
  import {
26
31
  extractRscHeaderUrl,
27
32
  emptyResponse,
33
+ handleReloadHeader,
28
34
  teeWithCompletion,
35
+ isForeignRouterId,
29
36
  } from "./response-adapter.js";
30
37
  import { mergeLocationState } from "./history-state.js";
31
38
  import { classifyActionOutcome } from "./action-coordinator.js";
@@ -51,6 +58,27 @@ export interface ServerActionBridgeConfigWithController extends ServerActionBrid
51
58
  ) => Promise<void>;
52
59
  }
53
60
 
61
+ /**
62
+ * Merge an action's location-state payload into history.state, restricted to
63
+ * the keys this action is entitled to write. claimLocationState enforces
64
+ * last-initiated-wins for same-key concurrent writes (a later-initiated sibling
65
+ * keeps its value even if this action's response settles afterward); distinct
66
+ * keys from every concurrent action survive. Arbitration is scoped to the
67
+ * action's cohort (captured at startAction), so a newer action on another entry
68
+ * cannot suppress this one. No-op when nothing was set or all keys were already
69
+ * claimed by a later-initiated sibling in the cohort.
70
+ */
71
+ function applyActionLocationState(
72
+ handle: ActionHandle,
73
+ locationState: Record<string, unknown> | undefined,
74
+ ): void {
75
+ if (!locationState) return;
76
+ const winning = handle.claimLocationState(locationState);
77
+ if (Object.keys(winning).length > 0) {
78
+ mergeLocationState(winning);
79
+ }
80
+ }
81
+
54
82
  /**
55
83
  * Create a server action bridge for handling RSC server actions
56
84
  *
@@ -77,6 +105,26 @@ export function createServerActionBridge(
77
105
  onNavigate,
78
106
  } = config;
79
107
 
108
+ // SPA-navigate when onNavigate is set, else hard-reload. state is omitted (not
109
+ // passed as undefined) to match the header path's prior call shape.
110
+ // Callers pass an already same-origin-validated url; the hard-reload fallback
111
+ // re-validates defensively so this leaf cannot become an open redirect if a
112
+ // future caller forgets (the SPA path validates inside the navigation bridge).
113
+ async function dispatchRedirect(url: string, state?: unknown): Promise<void> {
114
+ if (onNavigate) {
115
+ await onNavigate(url, {
116
+ ...(state !== undefined ? { state } : {}),
117
+ replace: true,
118
+ _skipCache: true,
119
+ });
120
+ } else {
121
+ const safe = validateRedirectOrigin(url, window.location.origin);
122
+ if (safe) {
123
+ window.location.href = safe;
124
+ }
125
+ }
126
+ }
127
+
80
128
  let isRegistered = false;
81
129
 
82
130
  const fetchPartialUpdate = createPartialUpdater({
@@ -138,14 +186,57 @@ export function createServerActionBridge(
138
186
  const locationKey = window.history.state?.key;
139
187
  log("action start", { id, argsCount: args.length });
140
188
 
141
- // Start action in event controller - handles lifecycle tracking
142
- const handle = eventController.startAction(id, args);
189
+ // Start action in event controller - handles lifecycle tracking. The
190
+ // current history key is the action's cohort: location-state arbitration is
191
+ // scoped to it so a later action on a different entry cannot suppress this
192
+ // one (and vice versa).
193
+ const handle = eventController.startAction(id, args, locationKey);
194
+ // Whether the action's response carried the keepClientCache() directive.
195
+ // Set when the response arrives; gates the deferred invalidation below.
196
+ let keepCache = false;
197
+ // Whether a Response actually settled from the network (the server saw the
198
+ // request). Set true as the first statement in the fetch .then() below.
199
+ // Gates the automatic invalidation: a pre-dispatch failure (encodeReply
200
+ // throw or a fetch rejection — server unreachable/DNS/connection refused)
201
+ // leaves this false, so finalizeAction() must NOT invalidate or broadcast —
202
+ // nothing reached the server, so nothing could have mutated. A failed Flight
203
+ // DECODE after the response arrived keeps it true (the mutation may have
204
+ // committed, so invalidating the now-possibly-stale client cache is correct).
205
+ let responseReceived = false;
206
+ // Single deferred invalidation + fence release, run exactly ONCE however the
207
+ // action terminates (normal, redirect, error, abort, intercept, concurrent).
208
+ // This replaces main's eager clear at action start: every directive-free
209
+ // action invalidates once; keepClientCache() suppresses only the automatic
210
+ // invalidation, so a concurrent directive-free action still invalidates via
211
+ // its own latch. Latched so the finally AND the early SPA-redirect returns
212
+ // (whose Flight stream never settles) can both call it safely.
213
+ let actionFinalized = false;
214
+ // skipInvalidation: the version-mismatch reload terminal released nothing
215
+ // server-side, so it releases the fence without invalidating.
216
+ const finalizeAction = (skipInvalidation = false): void => {
217
+ if (actionFinalized) return;
218
+ actionFinalized = true;
219
+ // finally so a throw in invalidation cannot leak the fence (latch is set).
220
+ try {
221
+ // responseReceived gates the automatic invalidation: a pre-dispatch
222
+ // failure (serialize throw / fetch reject) never reached the server, so
223
+ // marking the cache stale + broadcasting cross-tab would be spurious.
224
+ if (responseReceived && !keepCache && !skipInvalidation) {
225
+ store.markCacheAsStaleAndBroadcast();
226
+ }
227
+ } finally {
228
+ exitActionFence();
229
+ }
230
+ };
143
231
  try {
144
232
  const segmentState = store.getSegmentState();
145
233
 
146
- // Mark cache as stale immediately when action starts
147
- // This ensures SWR pattern kicks in if user navigates away during action
148
- store.markCacheAsStaleAndBroadcast();
234
+ // Raise the action fence (replaces the old eager clear). Nothing is wiped,
235
+ // rotated, or broadcast yet: navigations during the flight fetch fresh
236
+ // (no-store) and popstate is treated as SWR, but the decision to
237
+ // invalidate is deferred to the response so a no-op action (keepClientCache)
238
+ // can leave the caches and the jar untouched.
239
+ enterActionFence();
149
240
 
150
241
  // Create temporary references for serialization
151
242
  const temporaryReferences = deps.createTemporaryReferenceSet();
@@ -217,23 +308,34 @@ export function createServerActionBridge(
217
308
  body: encodedBody,
218
309
  signal: fetchAbort.signal,
219
310
  }).then(async (response) => {
311
+ // A settled fetch promise means the request reached the server and a
312
+ // Response came back (true for 2xx, 4xx, AND 5xx — fetch only rejects
313
+ // on network-layer failure, never on HTTP status). Record it as the
314
+ // first statement so every downstream terminal can invalidate; a
315
+ // pre-dispatch failure never gets here and stays gated out.
316
+ responseReceived = true;
220
317
  // Response arrived — disconnect fetch abort from handle abort so
221
318
  // abortAllActions() doesn't disrupt the in-progress Flight stream.
222
319
  handle.signal.removeEventListener("abort", onHandleAbort);
223
320
 
321
+ // Did the action call keepClientCache()? If so the deferred invalidation
322
+ // below is suppressed for THIS action (a concurrent directive-free
323
+ // action still invalidates via its own response).
324
+ keepCache = response.headers.get(KEEP_CACHE_HEADER) === "1";
325
+
224
326
  // Check for version mismatch - server wants us to reload
225
- const reload = extractRscHeaderUrl(response, "X-RSC-Reload");
226
- if (reload === "blocked") {
227
- resolveStreamComplete();
228
- return emptyResponse();
229
- }
230
- if (reload) {
231
- log("version mismatch on action, reloading", {
232
- reloadUrl: reload.url,
233
- });
234
- window.location.href = reload.url;
235
- return new Promise<Response>(() => {});
236
- }
327
+ const reloadResult = handleReloadHeader(response, {
328
+ onBlocked: resolveStreamComplete,
329
+ onReload: (url) => {
330
+ log("version mismatch on action, reloading", { reloadUrl: url });
331
+ // Never-settling terminal (navigates away), so the finally never
332
+ // runs: release the fence here. skipInvalidation — the mismatch
333
+ // short-circuits the action server-side, so nothing mutated and a
334
+ // broadcast would only risk hard-reloading a sibling mid-task.
335
+ finalizeAction(true);
336
+ },
337
+ });
338
+ if (reloadResult) return reloadResult;
237
339
 
238
340
  // Simple redirect from action (no state, no RSC payload).
239
341
  // Short-circuits before createFromFetch — no Flight deserialization needed.
@@ -243,14 +345,11 @@ export function createServerActionBridge(
243
345
  if (redirect && redirect !== "blocked" && !handle.signal.aborted) {
244
346
  log("action simple redirect", { url: redirect.url });
245
347
  handle.complete(undefined);
246
- if (onNavigate) {
247
- await onNavigate(redirect.url, {
248
- replace: true,
249
- _skipCache: true,
250
- });
251
- } else {
252
- window.location.href = redirect.url;
253
- }
348
+ // This path returns a never-settling promise, so the finally never
349
+ // runs: invalidate + release the fence here (the mutation committed
350
+ // and we're navigating away). Latched, so the finally is a no-op.
351
+ finalizeAction();
352
+ await dispatchRedirect(redirect.url);
254
353
  return new Promise<Response>(() => {});
255
354
  }
256
355
  if (redirect === "blocked") {
@@ -258,6 +357,29 @@ export function createServerActionBridge(
258
357
  return emptyResponse();
259
358
  }
260
359
 
360
+ // Integrity check (pre-decode): a foreign app's action response must
361
+ // not be decoded + applied here. This is the one decode-and-apply path
362
+ // the post-decode partial-update guard does NOT cover (the action
363
+ // bridge has its own createFromFetch -> onUpdate). Ordered after the
364
+ // reload/redirect handlers, which steer control responses first.
365
+ // Reloads via window.location.reload() rather than navigating to a
366
+ // target (as the navigation-client guard does): an action has no
367
+ // navigation target, so reloading the current URL re-syncs the
368
+ // document against the server-applied action effect.
369
+ if (
370
+ !handle.signal.aborted &&
371
+ isForeignRouterId(response, store.getRouterId?.())
372
+ ) {
373
+ log("action router id mismatch, reloading to re-sync");
374
+ handle.complete(undefined);
375
+ resolveStreamComplete();
376
+ // Never-settling return: release the fence before the reload (the
377
+ // reload resets module state anyway, but stay balanced). Latched.
378
+ finalizeAction();
379
+ window.location.reload();
380
+ return new Promise<Response>(() => {});
381
+ }
382
+
261
383
  // Start streaming immediately when response arrives
262
384
  if (!handle.signal.aborted) {
263
385
  streamingToken = handle.startStreaming();
@@ -328,6 +450,27 @@ export function createServerActionBridge(
328
450
  // Check handle.signal.aborted to avoid redirecting from a stale action
329
451
  // when the user has already navigated away.
330
452
  if (metadata?.redirect && !handle.signal.aborted) {
453
+ // Explicit off-host redirect (redirect(url, { external: true })):
454
+ // hard-navigate, but still scheme-validate (http/https only). external
455
+ // waives the same-origin check, NOT scheme safety, so a forged payload
456
+ // carrying a javascript:/data: URL cannot script via location.assign.
457
+ if (metadata.redirect.external) {
458
+ const externalUrl = validateExternalRedirect(
459
+ metadata.redirect.url,
460
+ window.location.origin,
461
+ );
462
+ if (!externalUrl) {
463
+ log("blocked external action redirect payload", {
464
+ url: metadata.redirect.url,
465
+ });
466
+ handle.complete(returnValue?.data);
467
+ return returnValue?.data;
468
+ }
469
+ log("external action redirect", { url: externalUrl });
470
+ handle.complete(returnValue?.data);
471
+ window.location.assign(externalUrl);
472
+ return returnValue?.data;
473
+ }
331
474
  const redirectUrl = validateRedirectOrigin(
332
475
  metadata.redirect.url,
333
476
  window.location.origin,
@@ -339,18 +482,9 @@ export function createServerActionBridge(
339
482
  handle.complete(returnValue?.data);
340
483
  return returnValue?.data;
341
484
  }
342
- const redirectState = metadata.locationState;
343
485
  log("action redirect", { url: redirectUrl });
344
486
  handle.complete(returnValue?.data);
345
- if (onNavigate) {
346
- await onNavigate(redirectUrl, {
347
- state: redirectState,
348
- replace: true,
349
- _skipCache: true,
350
- });
351
- } else {
352
- window.location.href = redirectUrl;
353
- }
487
+ await dispatchRedirect(redirectUrl, metadata.locationState);
354
488
  return returnValue?.data;
355
489
  }
356
490
 
@@ -424,18 +558,15 @@ export function createServerActionBridge(
424
558
  return undefined;
425
559
  }
426
560
 
427
- // Update UI with error boundary
428
- startTransition(() => {
429
- onUpdate({ root: errorTree, metadata: metadata! });
430
- });
431
-
432
561
  // Update segment tracking to exclude error segment IDs
433
562
  const errorSegmentIds = new Set(diff);
434
563
  const segmentIdsAfterError = segmentState.currentSegmentIds.filter(
435
564
  (id) => !errorSegmentIds.has(id),
436
565
  );
437
566
 
438
- // Update store state
567
+ // Cache (and bump the nav instance) BEFORE the UI update so a deferred
568
+ // handle pushed by the error-boundary render still applies — see the
569
+ // "normal" case below for why caching after onUpdate dropped it.
439
570
  store.setSegmentIds(segmentIdsAfterError);
440
571
  const currentHandleData = eventController.getHandleState().data;
441
572
  store.cacheSegmentsForHistory(
@@ -444,6 +575,11 @@ export function createServerActionBridge(
444
575
  currentHandleData,
445
576
  );
446
577
 
578
+ // Update UI with error boundary
579
+ startTransition(() => {
580
+ onUpdate({ root: errorTree, metadata: metadata! });
581
+ });
582
+
447
583
  // Throw the error so the action promise rejects
448
584
  if (returnValue && !returnValue.ok) {
449
585
  throw returnValue.data;
@@ -517,6 +653,20 @@ export function createServerActionBridge(
517
653
  currentInterceptSource: store.getInterceptSourceUrl(),
518
654
  });
519
655
 
656
+ // Apply server-set location state exhaustively here, as a successful-
657
+ // response effect — the terminal switch below decides rendering/refetch,
658
+ // not whether this metadata survives (every refetch path is storeOnly and
659
+ // never writes history.state). Gated on NOT navigated-away: the classifier
660
+ // treats either a pathname OR history-key change as diversion, so this
661
+ // both honors the "diverted state is dropped" contract AND prevents a
662
+ // cross-entry write (mergeLocationState writes the CURRENT entry, which a
663
+ // navigated-away action no longer owns). Done before the switch (and
664
+ // before the normal branch's async renderSegments) so a slow render racing
665
+ // a navigation cannot drop it.
666
+ if (scenario.type !== "navigated-away") {
667
+ applyActionLocationState(handle, metadata?.locationState);
668
+ }
669
+
520
670
  switch (scenario.type) {
521
671
  case "navigated-away": {
522
672
  log("user navigated away during action", {
@@ -528,8 +678,9 @@ export function createServerActionBridge(
528
678
  handle.clearConsolidation();
529
679
 
530
680
  if (scenario.historyKeyChanged) {
531
- if (!scenario.onInterceptRoute) {
532
- store.markCacheAsStaleAndBroadcast();
681
+ // Invalidation is deferred to finalizeAction(); here we only trigger
682
+ // the revalidation refetch of the new route (suppressed on keep).
683
+ if (!scenario.onInterceptRoute && !keepCache) {
533
684
  refetchRoute().catch((error) => {
534
685
  if (isBackgroundSuppressible(error)) return;
535
686
  console.error(
@@ -541,11 +692,14 @@ export function createServerActionBridge(
541
692
  break;
542
693
  }
543
694
 
544
- // Same history key but different pathname - safe to refetch current route
545
- store.markCacheAsStaleAndBroadcast();
546
- await refetchRoute({
547
- interceptSourceUrl: store.getInterceptSourceUrl(),
548
- });
695
+ // Same history key but different pathname - safe to refetch current
696
+ // route. Invalidation is deferred to finalizeAction(); here we only
697
+ // trigger the revalidation refetch (suppressed on keep).
698
+ if (!keepCache) {
699
+ await refetchRoute({
700
+ interceptSourceUrl: store.getInterceptSourceUrl(),
701
+ });
702
+ }
549
703
  break;
550
704
  }
551
705
 
@@ -553,8 +707,11 @@ export function createServerActionBridge(
553
707
  console.warn(
554
708
  `[Browser] Missing segments after action (HMR detected), refetching...`,
555
709
  );
710
+ // Repair (not revalidation), so ungated on keepCache: a keep action
711
+ // resolving last must discharge a directive-free sibling's repair.
712
+ // See the keep row in docs/design/rango-state-cookie.md (the all-keep
713
+ // edge, and the benign re-mark-stale-after-refetch end-state delta).
556
714
  await refetchRoute({ interceptSourceUrl });
557
- store.broadcastCacheInvalidation();
558
715
  break;
559
716
  }
560
717
 
@@ -562,7 +719,8 @@ export function createServerActionBridge(
562
719
  log("consolidation fetch needed", {
563
720
  segmentIds: scenario.segmentIds,
564
721
  });
565
- // Calculate segments to send (exclude the ones we want fresh)
722
+ // Location state already applied above (pre-switch). Calculate
723
+ // segments to send (exclude the ones we want fresh).
566
724
  const currentSegmentIds = store.getSegmentState().currentSegmentIds;
567
725
  const segmentsToSend = currentSegmentIds.filter(
568
726
  (sid) => !scenario.segmentIds.includes(sid),
@@ -571,11 +729,11 @@ export function createServerActionBridge(
571
729
  // Clear consolidation tracking before fetch
572
730
  handle.clearConsolidation();
573
731
 
732
+ // Ungated on keepCache, same as hmr-missing above (see the keep row).
574
733
  await refetchRoute({
575
734
  segments: segmentsToSend,
576
735
  interceptSourceUrl,
577
736
  });
578
- store.broadcastCacheInvalidation();
579
737
  break;
580
738
  }
581
739
 
@@ -586,6 +744,8 @@ export function createServerActionBridge(
586
744
  // Only update store if history key hasn't changed (user didn't navigate away)
587
745
  const currentKeyNow = store.getHistoryKey();
588
746
  if (currentKeyNow === currentKey) {
747
+ // Location state already applied above (pre-switch); this action's
748
+ // UI render is skipped because a later sibling consolidates.
589
749
  store.setSegmentIds(matched);
590
750
  const currentHandleData = eventController.getHandleState().data;
591
751
  store.cacheSegmentsForHistory(
@@ -621,17 +781,17 @@ export function createServerActionBridge(
621
781
  break;
622
782
  }
623
783
 
624
- startTransition(() => {
625
- onUpdate({ root: newTree, metadata: metadata! });
626
- });
627
-
628
- // Apply server-set location state to history.state (non-redirect flow)
629
- const actionLocationState = metadata?.locationState;
630
- if (actionLocationState) {
631
- mergeLocationState(actionLocationState);
632
- }
633
-
634
- // Update store state
784
+ // Cache (and bump the nav instance) BEFORE the UI update, matching the
785
+ // navigation commit order (navigation-transaction.ts:157). processHandles
786
+ // is spawned by onUpdate and captures the current nav instance up front;
787
+ // a deferred handle value resolves asynchronously and is only applied
788
+ // while stillLive() (its captured instance still owns the page). Caching
789
+ // AFTER onUpdate bumped the instance out from under that in-flight
790
+ // resolve, so stillLive() turned false and the resolved handle snapshot
791
+ // was dropped on the action-revalidation path (sync siblings sharing the
792
+ // yield are held atomically, so they were dropped too).
793
+ //
794
+ // Location state already applied above (pre-switch). Update store.
635
795
  store.setSegmentIds(matched);
636
796
  const currentHandleData = eventController.getHandleState().data;
637
797
  store.cacheSegmentsForHistory(
@@ -639,7 +799,13 @@ export function createServerActionBridge(
639
799
  fullSegments,
640
800
  currentHandleData,
641
801
  );
642
- store.markCacheAsStaleAndBroadcast();
802
+
803
+ startTransition(() => {
804
+ onUpdate({ root: newTree, metadata: metadata! });
805
+ });
806
+ // Invalidation deferred to finalizeAction() (runs after this caches
807
+ // the fresh segments), suppressed when the action called
808
+ // keepClientCache().
643
809
  break;
644
810
  }
645
811
  }
@@ -647,6 +813,11 @@ export function createServerActionBridge(
647
813
  handle.complete(returnData);
648
814
  return returnData;
649
815
  } finally {
816
+ // The single deferred invalidation + fence release for this action. Runs
817
+ // for every terminal that settles (normal, navigated-away, error, abort,
818
+ // intercept, concurrent); the SPA-redirect paths above already ran it.
819
+ // Latched, so it fires exactly once.
820
+ finalizeAction();
650
821
  handle[Symbol.dispose]();
651
822
  }
652
823
  }
@@ -14,7 +14,6 @@ import type { RenderSegmentsOptions } from "../segment-system.js";
14
14
  export interface RscPayload<TMetadata = RscMetadata> {
15
15
  metadata?: TMetadata;
16
16
  returnValue?: ActionResult;
17
- formState?: unknown;
18
17
  }
19
18
 
20
19
  /**
@@ -32,13 +31,21 @@ export type HandleData = Record<string, Record<string, unknown[]>>;
32
31
  export interface RscMetadata {
33
32
  pathname: string;
34
33
  segments: ResolvedSegment[];
35
- /** Router instance ID. When this changes between navigations, the client
36
- * forces a full tree replacement (app switch via host router). */
34
+ /** Router instance ID the current app's identity. A mismatch with the
35
+ * client's id (sent as _rsc_rid) is detected server-side and answered with
36
+ * X-RSC-Reload (full document load), so the client never swaps apps
37
+ * in-session; within a session this always equals the current app. */
37
38
  routerId?: string;
38
39
  isPartial?: boolean;
39
40
  isError?: boolean;
40
41
  matched?: string[];
41
42
  diff?: string[];
43
+ /**
44
+ * All segment ids re-resolved on the server, including null-component
45
+ * ones excluded from `segments`/`diff`. Drives client-side handle-bucket
46
+ * cleanup. Superset of `diff`. See MatchResult.resolvedIds.
47
+ */
48
+ resolvedIds?: string[];
42
49
  /** Merged route params from the matched route */
43
50
  params?: Record<string, string>;
44
51
  /**
@@ -63,6 +70,22 @@ export interface RscMetadata {
63
70
  * Sent on initial render so the browser can configure its cache duration.
64
71
  */
65
72
  prefetchCacheTTL?: number;
73
+ /**
74
+ * Max entries in the client-side in-memory prefetch cache (FIFO eviction).
75
+ * Sent on initial render so the browser can configure its cache capacity.
76
+ */
77
+ prefetchCacheSize?: number;
78
+ /**
79
+ * Max concurrent speculative prefetch requests on the client.
80
+ * Sent on initial render so the browser can configure its prefetch queue.
81
+ */
82
+ prefetchConcurrency?: number;
83
+ /**
84
+ * Server-resolved rango state cookie name (`{prefix}_{routerId}`). The client
85
+ * reads it verbatim and binds the rango state cookie to it; composition
86
+ * happens only server-side.
87
+ */
88
+ stateCookieName?: string;
66
89
  /**
67
90
  * Theme configuration from router.
68
91
  * Included when theme is enabled in router config.
@@ -77,8 +100,18 @@ export interface RscMetadata {
77
100
  basename?: string;
78
101
  /** Whether connection warmup is enabled */
79
102
  warmupEnabled?: boolean;
80
- /** Server-side redirect with optional state (for partial requests) */
81
- redirect?: { url: string };
103
+ /**
104
+ * Whether the client should hydrate inside React.StrictMode. Carried on the
105
+ * initial full-render payload only; the browser entry reads it once at
106
+ * hydration. Defaults to true on the client when omitted.
107
+ */
108
+ strictMode?: boolean;
109
+ /**
110
+ * Server-side redirect with optional state (for partial requests).
111
+ * `external: true` (from redirect(url, { external: true })) tells the client
112
+ * to hard-navigate to an off-host target instead of validating same-origin.
113
+ */
114
+ redirect?: { url: string; external?: boolean };
82
115
  /** Server-set location state to include in history.pushState */
83
116
  locationState?: Record<string, unknown>;
84
117
  }
@@ -179,6 +212,15 @@ export interface TrackedActionState {
179
212
  result: unknown | null;
180
213
  }
181
214
 
215
+ /**
216
+ * The value returned by {@link useAction} when called without a selector.
217
+ *
218
+ * This is the stable, public name for the action-state shape; consumers can
219
+ * name it in their own signatures (e.g. a wrapper hook). It aliases the
220
+ * internal {@link TrackedActionState}.
221
+ */
222
+ export type ActionState = TrackedActionState;
223
+
182
224
  /**
183
225
  * Listener for action state changes
184
226
  *
@@ -316,8 +358,14 @@ export interface RouterInstance {
316
358
  replace(url: string, options?: RouterNavigateOptions): Promise<void>;
317
359
  /** Refresh the current route (re-fetch server data, preserve client state) */
318
360
  refresh(): Promise<void>;
319
- /** Prefetch a URL for faster client-side transition */
320
- prefetch(url: string): void;
361
+ /**
362
+ * Prefetch a URL for faster client-side transition.
363
+ *
364
+ * Pass `{ key: ":source" }` to source-scope the prefetch cache entry (parity
365
+ * with `<Link prefetchKey=":source">`) when the target's response can differ
366
+ * by source page.
367
+ */
368
+ prefetch(url: string, options?: { key?: ":source" }): void;
321
369
  /** Go back in browser history */
322
370
  back(): void;
323
371
  /** Go forward in browser history */
@@ -409,6 +457,10 @@ export interface NavigationStore {
409
457
  // History-based segment cache (for back/forward navigation and partial merging)
410
458
  getHistoryKey(): string;
411
459
  setHistoryKey(key: string): void;
460
+ /** Monotonic token of the most recently committed navigation. */
461
+ getNavInstance(): number;
462
+ /** Nav-instance token recorded on a cache entry (undefined if absent). */
463
+ getCacheEntryInstance(historyKey: string): number | undefined;
412
464
  cacheSegmentsForHistory(
413
465
  historyKey: string,
414
466
  segments: ResolvedSegment[],
@@ -420,14 +472,44 @@ export interface NavigationStore {
420
472
  stale: boolean;
421
473
  handleData?: HandleData;
422
474
  routerId?: string;
475
+ /**
476
+ * True when the entry's handle data is incomplete (a deferred Meta was
477
+ * still pending at navigate-away). A popstate return must revalidate with
478
+ * a FULL re-render so the server re-streams handles.
479
+ */
480
+ handlesPending?: boolean;
423
481
  }
424
482
  | undefined;
425
483
  hasHistoryCache(historyKey: string): boolean;
426
- updateCacheHandleData(historyKey: string, handleData: HandleData): void;
484
+ /**
485
+ * Update only the handleData (and optionally the stale / handlesPending flags)
486
+ * of an existing cache entry. When a flag is omitted the entry's current value
487
+ * is preserved. `stale=true` marks a single entry stale so a popstate return
488
+ * revalidates it; `handlesPending=true` additionally forces that revalidation
489
+ * to be a full re-render (so a deferred Meta re-streams).
490
+ */
491
+ updateCacheHandleData(
492
+ historyKey: string,
493
+ handleData: HandleData,
494
+ stale?: boolean,
495
+ handlesPending?: boolean,
496
+ ): void;
497
+ /**
498
+ * Owner-guarded variant of updateCacheHandleData: writes only when the entry
499
+ * is still owned by `ownerInstance`. Folds the caller's separate ownership
500
+ * probe and write into one historyCache scan for the per-yield streaming path.
501
+ */
502
+ updateCacheHandleDataIfOwned(
503
+ historyKey: string,
504
+ handleData: HandleData,
505
+ ownerInstance: number,
506
+ stale?: boolean,
507
+ handlesPending?: boolean,
508
+ ): void;
427
509
  markCacheAsStale(): void;
510
+ markHistoryCacheStale(): void;
428
511
  markCacheAsStaleAndBroadcast(): void;
429
512
  clearHistoryCache(): void;
430
- broadcastCacheInvalidation(): void;
431
513
 
432
514
  // Cross-tab refresh callback (set by navigation bridge)
433
515
  setCrossTabRefreshCallback(callback: () => void): void;
@@ -483,6 +565,15 @@ export interface FetchPartialResult {
483
565
  payload: RscPayload;
484
566
  /** Promise that resolves when the response stream is fully consumed */
485
567
  streamComplete: Promise<void>;
568
+ /**
569
+ * True only when this payload came from a prefetch-cache hit whose stream had
570
+ * ALREADY fully drained at fetch time (the route was fully prefetched). The
571
+ * commit then runs in a startTransition so loading()/Suspense content — already
572
+ * resolved — swaps in directly without flashing a fallback. A partially-warmed
573
+ * (still-streaming) prefetch hit and a cold fetch leave this false so their
574
+ * fallbacks stream as usual.
575
+ */
576
+ fullyPrefetched?: boolean;
486
577
  }
487
578
 
488
579
  /**
@@ -540,6 +631,8 @@ export interface NavigationBridge {
540
631
  refresh(): Promise<void>;
541
632
  handlePopstate(): Promise<void>;
542
633
  registerLinkInterception(): () => void;
634
+ /** Current RSC version (live, reflects the latest updateVersion). */
635
+ getVersion(): string | undefined;
543
636
  /** Update the RSC version (e.g. after HMR). Clears prefetch cache. */
544
637
  updateVersion(newVersion: string): void;
545
638
  }