@rangojs/router 0.20.0 → 0.22.0

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 (331) hide show
  1. package/dist/bin/rango.js +39 -15
  2. package/dist/types/browser/event-controller.d.ts +49 -0
  3. package/dist/types/browser/history-state.d.ts +16 -4
  4. package/dist/types/browser/navigation-store.d.ts +9 -0
  5. package/dist/types/browser/navigation-transaction.d.ts +7 -0
  6. package/dist/types/browser/partial-update.d.ts +2 -1
  7. package/dist/types/browser/prefetch/default-strategy.d.ts +16 -20
  8. package/dist/types/browser/rango-state.d.ts +3 -1
  9. package/dist/types/browser/react/NavigationProvider.d.ts +14 -1
  10. package/dist/types/browser/react/context.d.ts +25 -0
  11. package/dist/types/browser/react/location-state-shared.d.ts +84 -10
  12. package/dist/types/browser/react/location-state.d.ts +7 -2
  13. package/dist/types/browser/rsc-router.d.ts +13 -2
  14. package/dist/types/browser/types.d.ts +10 -0
  15. package/dist/types/build/generate-manifest.d.ts +2 -0
  16. package/dist/types/cache/background-task.d.ts +22 -0
  17. package/dist/types/cache/cache-key-utils.d.ts +50 -5
  18. package/dist/types/cache/cache-policy.d.ts +17 -0
  19. package/dist/types/cache/cache-scope.d.ts +5 -6
  20. package/dist/types/cache/cf/cf-cache-store.d.ts +34 -7
  21. package/dist/types/cache/cf/cf-cache-types.d.ts +12 -4
  22. package/dist/types/cache/document-cache.d.ts +8 -5
  23. package/dist/types/cache/handle-snapshot.d.ts +59 -50
  24. package/dist/types/cache/index.d.ts +3 -1
  25. package/dist/types/cache/invalidation-order.d.ts +14 -0
  26. package/dist/types/cache/memory-segment-store.d.ts +33 -1
  27. package/dist/types/cache/shell-snapshot.d.ts +29 -4
  28. package/dist/types/cache/store-scope.d.ts +22 -0
  29. package/dist/types/cache/tag-invalidation.d.ts +7 -6
  30. package/dist/types/cache/types.d.ts +84 -24
  31. package/dist/types/cache/vercel/vercel-cache-store.d.ts +35 -17
  32. package/dist/types/client-urls/navigation.d.ts +7 -1
  33. package/dist/types/client-urls/optimistic-location.d.ts +10 -4
  34. package/dist/types/errors.d.ts +6 -0
  35. package/dist/types/handles/deferred-resolution.d.ts +18 -1
  36. package/dist/types/internal-suspense-audit.d.ts +1 -0
  37. package/dist/types/loader-store.d.ts +4 -8
  38. package/dist/types/outlet-provider.d.ts +2 -1
  39. package/dist/types/prerender/build-shell-capture.d.ts +8 -6
  40. package/dist/types/prerender/cloudflare.d.ts +49 -0
  41. package/dist/types/prerender/create-prerender-trigger.d.ts +121 -0
  42. package/dist/types/prerender/index.d.ts +12 -0
  43. package/dist/types/prerender/memory-prerender-store.d.ts +29 -0
  44. package/dist/types/prerender/on-demand.d.ts +272 -0
  45. package/dist/types/prerender/producer-guard.d.ts +33 -0
  46. package/dist/types/prerender/shell-manifest-key.d.ts +1 -18
  47. package/dist/types/prerender/warm-request.d.ts +65 -0
  48. package/dist/types/prerender/warm.d.ts +65 -0
  49. package/dist/types/prerender/writable-store.d.ts +170 -0
  50. package/dist/types/prerender.d.ts +27 -0
  51. package/dist/types/reverse.d.ts +1 -1
  52. package/dist/types/route-content-wrapper.d.ts +3 -1
  53. package/dist/types/router/error-handling.d.ts +2 -0
  54. package/dist/types/router/handler-context.d.ts +1 -1
  55. package/dist/types/router/match-middleware/cache-lookup.d.ts +15 -1
  56. package/dist/types/router/navigation-snapshot.d.ts +8 -1
  57. package/dist/types/router/pattern-matching.d.ts +2 -0
  58. package/dist/types/router/prefetch-default.d.ts +4 -4
  59. package/dist/types/router/prerender-match.d.ts +23 -1
  60. package/dist/types/router/route-trie-builder.d.ts +8 -1
  61. package/dist/types/router/router-context.d.ts +1 -0
  62. package/dist/types/router/router-interfaces.d.ts +32 -1
  63. package/dist/types/router/router-options.d.ts +39 -5
  64. package/dist/types/router/segment-resolution/fresh.d.ts +7 -0
  65. package/dist/types/router/segment-resolution/loader-cache.d.ts +23 -10
  66. package/dist/types/router/segment-resolution/revalidation.d.ts +5 -1
  67. package/dist/types/router/segment-resolution/streamed-handler-boundary.d.ts +43 -0
  68. package/dist/types/router/trie-matching.d.ts +2 -0
  69. package/dist/types/router-versions.d.ts +39 -0
  70. package/dist/types/rsc/handler-context.d.ts +5 -0
  71. package/dist/types/rsc/shell-build-manifest.d.ts +9 -6
  72. package/dist/types/rsc/shell-capture-constants.d.ts +20 -5
  73. package/dist/types/rsc/shell-capture.d.ts +14 -1
  74. package/dist/types/rsc/shell-serve.d.ts +2 -2
  75. package/dist/types/rsc/ssr-module-loader.d.ts +15 -0
  76. package/dist/types/rsc/types.d.ts +12 -6
  77. package/dist/types/segment-boundary-content.d.ts +17 -0
  78. package/dist/types/segment-loader-promise.d.ts +2 -6
  79. package/dist/types/server/build-version-table.d.ts +53 -0
  80. package/dist/types/server/context.d.ts +12 -4
  81. package/dist/types/server/handle-store.d.ts +89 -46
  82. package/dist/types/server/request-context.d.ts +84 -9
  83. package/dist/types/suspense-audit-react.d.ts +26 -0
  84. package/dist/types/suspense-audit.d.ts +157 -0
  85. package/dist/types/testing/action-form.d.ts +34 -0
  86. package/dist/types/testing/build-versions.d.ts +32 -0
  87. package/dist/types/testing/dom.entry.d.ts +1 -1
  88. package/dist/types/testing/flight.entry.d.ts +3 -1
  89. package/dist/types/testing/index.d.ts +6 -0
  90. package/dist/types/testing/internal/seed-vars.d.ts +7 -2
  91. package/dist/types/testing/internal/server-modules.d.ts +8 -0
  92. package/dist/types/testing/location-state-key.d.ts +5 -1
  93. package/dist/types/testing/render-route.d.ts +77 -6
  94. package/dist/types/testing/serve-shell-request.d.ts +55 -4
  95. package/dist/types/testing/shell-status.d.ts +14 -6
  96. package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +9 -6
  97. package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
  98. package/dist/types/thenable-status.d.ts +6 -0
  99. package/dist/types/types/handler-context.d.ts +15 -5
  100. package/dist/types/types/route-entry.d.ts +5 -0
  101. package/dist/types/urls/path-helper.d.ts +18 -0
  102. package/dist/types/vite/discovery/build-versions.d.ts +393 -0
  103. package/dist/types/vite/discovery/installed-packages.d.ts +26 -0
  104. package/dist/types/vite/discovery/router-versions-phase.d.ts +52 -0
  105. package/dist/types/vite/discovery/shell-prerender-phase.d.ts +7 -17
  106. package/dist/types/vite/discovery/state.d.ts +25 -0
  107. package/dist/types/vite/encryption-key.d.ts +18 -0
  108. package/dist/types/vite/inject-client-debug.d.ts +6 -25
  109. package/dist/types/vite/plugin-types.d.ts +27 -0
  110. package/dist/types/vite/plugins/{version-injector.d.ts → entry-bootstrap-injector.d.ts} +4 -6
  111. package/dist/types/vite/plugins/expose-ids/export-analysis.d.ts +1 -0
  112. package/dist/types/vite/plugins/expose-ids/router-transform.d.ts +14 -0
  113. package/dist/types/vite/plugins/stable-rsc-output.d.ts +51 -0
  114. package/dist/types/vite/plugins/version-plugin.d.ts +9 -11
  115. package/dist/types/vite/plugins/virtual-entries.d.ts +18 -1
  116. package/dist/types/vite/router-discovery.d.ts +6 -0
  117. package/dist/types/vite/utils/bundle-analysis.d.ts +6 -2
  118. package/dist/types/vite/utils/client-chunks.d.ts +2 -0
  119. package/dist/types/vite/utils/compare-strings.d.ts +7 -0
  120. package/dist/types/vite/utils/package-resolution.d.ts +8 -0
  121. package/dist/types/vite/utils/prerender-utils.d.ts +2 -0
  122. package/dist/vite/index.js +1707 -931
  123. package/package.json +12 -3
  124. package/skills/cache-guide/SKILL.md +102 -37
  125. package/skills/caching/SKILL.md +56 -9
  126. package/skills/catalog.json +1 -1
  127. package/skills/client-urls/SKILL.md +16 -0
  128. package/skills/cloudflare/SKILL.md +133 -1
  129. package/skills/comparison/references/framework-comparison.md +17 -13
  130. package/skills/deployment-caching/SKILL.md +33 -12
  131. package/skills/document-cache/SKILL.md +16 -14
  132. package/skills/hooks/SKILL.md +25 -0
  133. package/skills/hooks/handle-and-actions.md +5 -0
  134. package/skills/hooks/navigation.md +38 -0
  135. package/skills/hooks/state.md +213 -11
  136. package/skills/hooks/urls.md +40 -1
  137. package/skills/host-router/SKILL.md +31 -0
  138. package/skills/intercept/SKILL.md +6 -0
  139. package/skills/loader/SKILL.md +25 -5
  140. package/skills/middleware/SKILL.md +2 -0
  141. package/skills/migrate-nextjs/SKILL.md +82 -9
  142. package/skills/ppr/SKILL.md +150 -48
  143. package/skills/prerender/SKILL.md +651 -10
  144. package/skills/rango/SKILL.md +9 -7
  145. package/skills/response-routes/SKILL.md +5 -5
  146. package/skills/route/SKILL.md +29 -0
  147. package/skills/router-setup/SKILL.md +15 -1
  148. package/skills/testing/SKILL.md +32 -27
  149. package/skills/testing/cache-prerender.md +18 -17
  150. package/skills/testing/client-components.md +193 -8
  151. package/skills/testing/handles.md +2 -0
  152. package/skills/testing/loader.md +1 -1
  153. package/skills/testing/middleware.md +1 -1
  154. package/skills/testing/render-handler.md +18 -18
  155. package/skills/testing/serve-shell-request.md +167 -22
  156. package/skills/testing/server-actions.md +17 -17
  157. package/skills/testing/server-tree.md +1 -1
  158. package/skills/typesafety/generated-files-and-cli.md +1 -1
  159. package/skills/use-cache/SKILL.md +25 -10
  160. package/skills/vercel/SKILL.md +5 -3
  161. package/skills/view-transitions/SKILL.md +9 -4
  162. package/src/bin/rango.ts +1 -1
  163. package/src/browser/event-controller.ts +119 -2
  164. package/src/browser/history-state.ts +39 -17
  165. package/src/browser/link-interceptor.ts +2 -2
  166. package/src/browser/navigation-bridge.ts +104 -41
  167. package/src/browser/navigation-store.ts +26 -0
  168. package/src/browser/navigation-transaction.ts +24 -12
  169. package/src/browser/network-error-handler.ts +2 -0
  170. package/src/browser/partial-update.ts +58 -14
  171. package/src/browser/prefetch/default-strategy.ts +22 -42
  172. package/src/browser/rango-state.ts +3 -1
  173. package/src/browser/react/Link.tsx +37 -43
  174. package/src/browser/react/NavigationProvider.tsx +85 -1
  175. package/src/browser/react/context.ts +30 -0
  176. package/src/browser/react/location-state-shared.ts +200 -34
  177. package/src/browser/react/location-state.ts +28 -77
  178. package/src/browser/react/use-action.ts +4 -1
  179. package/src/browser/react/use-handle.ts +3 -0
  180. package/src/browser/react/use-link-status.ts +4 -3
  181. package/src/browser/react/use-navigation.ts +4 -1
  182. package/src/browser/react/use-params.ts +4 -1
  183. package/src/browser/react/use-pathname.ts +4 -1
  184. package/src/browser/react/use-search-params.ts +5 -3
  185. package/src/browser/react/use-segments.ts +36 -54
  186. package/src/browser/rsc-router.tsx +93 -31
  187. package/src/browser/segment-reconciler.ts +3 -3
  188. package/src/browser/server-action-bridge.ts +43 -7
  189. package/src/browser/types.ts +11 -0
  190. package/src/build/generate-manifest.ts +17 -0
  191. package/src/build/merge-full-manifests.ts +5 -0
  192. package/src/build/route-trie.ts +1 -0
  193. package/src/cache/background-task.ts +61 -0
  194. package/src/cache/cache-key-utils.ts +91 -12
  195. package/src/cache/cache-policy.ts +11 -2
  196. package/src/cache/cache-runtime.ts +23 -23
  197. package/src/cache/cache-scope.ts +48 -36
  198. package/src/cache/cf/cf-cache-store.ts +91 -66
  199. package/src/cache/cf/cf-cache-types.ts +12 -4
  200. package/src/cache/document-cache.ts +35 -14
  201. package/src/cache/handle-snapshot.ts +118 -86
  202. package/src/cache/index.ts +5 -0
  203. package/src/cache/invalidation-order.ts +53 -0
  204. package/src/cache/memory-segment-store.ts +52 -1
  205. package/src/cache/shell-snapshot.ts +52 -5
  206. package/src/cache/store-scope.ts +36 -0
  207. package/src/cache/tag-invalidation.ts +7 -5
  208. package/src/cache/types.ts +90 -24
  209. package/src/cache/vercel/vercel-cache-store.ts +68 -26
  210. package/src/client-urls/client-root.tsx +1 -0
  211. package/src/client-urls/navigation.ts +10 -0
  212. package/src/client-urls/optimistic-location.ts +10 -4
  213. package/src/client.tsx +40 -3
  214. package/src/errors.ts +13 -6
  215. package/src/handles/deferred-resolution.ts +36 -0
  216. package/src/internal-suspense-audit.ts +12 -0
  217. package/src/loader-store.ts +4 -9
  218. package/src/outlet-provider.tsx +25 -1
  219. package/src/prerender/build-shell-capture.ts +11 -9
  220. package/src/prerender/cloudflare.ts +130 -0
  221. package/src/prerender/create-prerender-trigger.ts +780 -0
  222. package/src/prerender/index.ts +39 -0
  223. package/src/prerender/memory-prerender-store.ts +115 -0
  224. package/src/prerender/on-demand.ts +339 -0
  225. package/src/prerender/producer-guard.ts +66 -0
  226. package/src/prerender/shell-manifest-key.ts +12 -17
  227. package/src/prerender/warm-request.ts +154 -0
  228. package/src/prerender/warm.ts +193 -0
  229. package/src/prerender/writable-store.ts +278 -0
  230. package/src/prerender.ts +29 -0
  231. package/src/reverse.ts +1 -1
  232. package/src/route-content-wrapper.tsx +107 -13
  233. package/src/router/error-handling.ts +12 -12
  234. package/src/router/find-match.ts +1 -0
  235. package/src/router/handler-context.ts +2 -0
  236. package/src/router/intercept-resolution.ts +15 -8
  237. package/src/router/loader-resolution.ts +2 -10
  238. package/src/router/manifest.ts +2 -0
  239. package/src/router/match-middleware/background-revalidation.ts +2 -0
  240. package/src/router/match-middleware/cache-lookup.ts +239 -54
  241. package/src/router/navigation-snapshot.ts +25 -6
  242. package/src/router/pattern-matching.ts +14 -0
  243. package/src/router/prefetch-default.ts +4 -4
  244. package/src/router/prerender-match.ts +460 -352
  245. package/src/router/request-classification.ts +15 -12
  246. package/src/router/route-trie-builder.ts +9 -0
  247. package/src/router/router-context.ts +1 -0
  248. package/src/router/router-interfaces.ts +35 -1
  249. package/src/router/router-options.ts +45 -5
  250. package/src/router/segment-resolution/fresh.ts +20 -19
  251. package/src/router/segment-resolution/loader-cache.ts +110 -77
  252. package/src/router/segment-resolution/revalidation.ts +34 -24
  253. package/src/router/segment-resolution/streamed-handler-boundary.ts +133 -0
  254. package/src/router/trie-matching.ts +3 -0
  255. package/src/router-versions.ts +60 -0
  256. package/src/router.ts +107 -1
  257. package/src/rsc/full-payload.ts +6 -2
  258. package/src/rsc/handler-context.ts +5 -0
  259. package/src/rsc/handler.ts +64 -7
  260. package/src/rsc/progressive-enhancement.ts +5 -1
  261. package/src/rsc/response-cache-serve.ts +10 -4
  262. package/src/rsc/rsc-rendering.ts +65 -12
  263. package/src/rsc/shell-build-manifest.ts +17 -12
  264. package/src/rsc/shell-capture-constants.ts +39 -7
  265. package/src/rsc/shell-capture.ts +144 -45
  266. package/src/rsc/shell-serve.ts +12 -6
  267. package/src/rsc/ssr-module-loader.ts +26 -0
  268. package/src/rsc/types.ts +12 -6
  269. package/src/segment-boundary-content.ts +25 -0
  270. package/src/segment-loader-promise.ts +28 -38
  271. package/src/segment-system.tsx +117 -57
  272. package/src/server/build-version-table.ts +130 -0
  273. package/src/server/context.ts +40 -17
  274. package/src/server/cookie-store.ts +5 -0
  275. package/src/server/handle-store.ts +357 -127
  276. package/src/server/request-context.ts +149 -12
  277. package/src/ssr/ssr-root.tsx +32 -13
  278. package/src/suspense-audit-react.tsx +98 -0
  279. package/src/suspense-audit.ts +1030 -0
  280. package/src/testing/action-form.ts +66 -0
  281. package/src/testing/build-versions.ts +64 -0
  282. package/src/testing/dispatch.ts +6 -0
  283. package/src/testing/dom.entry.ts +1 -0
  284. package/src/testing/flight-runtime.d.ts +27 -0
  285. package/src/testing/flight.entry.ts +4 -0
  286. package/src/testing/index.ts +22 -0
  287. package/src/testing/internal/context.ts +2 -1
  288. package/src/testing/internal/seed-vars.ts +14 -2
  289. package/src/testing/internal/server-modules.ts +32 -0
  290. package/src/testing/location-state-key.ts +18 -8
  291. package/src/testing/render-handler.ts +2 -1
  292. package/src/testing/render-route.tsx +293 -46
  293. package/src/testing/serve-shell-request.ts +262 -37
  294. package/src/testing/shell-status.ts +17 -6
  295. package/src/testing/vitest-stubs/plugin-rsc.ts +37 -11
  296. package/src/testing/vitest-stubs/version.ts +4 -1
  297. package/src/thenable-status.ts +10 -0
  298. package/src/types/handler-context.ts +15 -5
  299. package/src/types/route-entry.ts +6 -0
  300. package/src/urls/path-helper.ts +69 -9
  301. package/src/use-loader.tsx +57 -30
  302. package/src/vite/discovery/build-versions.ts +1281 -0
  303. package/src/vite/discovery/bundle-postprocess.ts +4 -0
  304. package/src/vite/discovery/discover-routers.ts +15 -0
  305. package/src/vite/discovery/installed-packages.ts +159 -0
  306. package/src/vite/discovery/prerender-collection.ts +38 -1
  307. package/src/vite/discovery/router-versions-phase.ts +336 -0
  308. package/src/vite/discovery/shell-prerender-phase.ts +33 -43
  309. package/src/vite/discovery/state.ts +36 -1
  310. package/src/vite/discovery/virtual-module-codegen.ts +7 -2
  311. package/src/vite/encryption-key.ts +101 -9
  312. package/src/vite/inject-client-debug.ts +42 -20
  313. package/src/vite/plugin-types.ts +28 -0
  314. package/src/vite/plugins/{version-injector.ts → entry-bootstrap-injector.ts} +7 -22
  315. package/src/vite/plugins/expose-ids/export-analysis.ts +1 -1
  316. package/src/vite/plugins/expose-ids/router-transform.ts +36 -15
  317. package/src/vite/plugins/expose-internal-ids.ts +12 -3
  318. package/src/vite/plugins/stable-rsc-output.ts +147 -0
  319. package/src/vite/plugins/version-plugin.ts +19 -24
  320. package/src/vite/plugins/version.d.ts +17 -2
  321. package/src/vite/plugins/virtual-entries.ts +39 -6
  322. package/src/vite/rango.ts +28 -3
  323. package/src/vite/router-discovery.ts +96 -13
  324. package/src/vite/utils/bundle-analysis.ts +39 -7
  325. package/src/vite/utils/client-chunks.ts +5 -1
  326. package/src/vite/utils/compare-strings.ts +9 -0
  327. package/src/vite/utils/package-resolution.ts +9 -0
  328. package/src/vite/utils/prerender-utils.ts +4 -1
  329. package/src/vite/utils/shared-utils.ts +1 -1
  330. package/dist/types/segment-content-promise.d.ts +0 -13
  331. package/src/segment-content-promise.ts +0 -67
@@ -27,6 +27,14 @@
27
27
  * capture refuses for a root postpone (a live loader read with no boundary
28
28
  * above it) or an SSR render error is captured here.
29
29
  *
30
+ * A `Prerender` route serves its handler layer from the artifact
31
+ * `router.matchForPrerender` bakes for the URL (on its first request, kept
32
+ * until resetShellTestState), through the production prerender store. Its
33
+ * first `ppr` request is a MISS with a runtime capture, as a URL without a
34
+ * build-time shell is in production; build-time shells are e2e-only. An
35
+ * on-demand route (`Prerender(..., { onDemand })`) bakes nothing here: it
36
+ * serves from its prerender store, as a refresh-only page does in production.
37
+ *
30
38
  * Must run under the `react-server` condition (the rsc Vitest project), with
31
39
  * `rangoTestAliases()` resolving `@vitejs/plugin-rsc/rsc/server` to the stub
32
40
  * that carries the Flight runtime.
@@ -36,14 +44,21 @@ import { AsyncLocalStorage } from "node:async_hooks";
36
44
  import { toInternal, type Rango } from "../router/router-interfaces.js";
37
45
  import { ensureRouterManifest } from "../route-map-builder.js";
38
46
  import type { SegmentCacheStore, ShellCacheEntry } from "../cache/types.js";
47
+ import {
48
+ awaitLaterMillisecond,
49
+ createCollectingExecutionContext,
50
+ } from "../cache/background-task.js";
39
51
  import type { ExecutionContext } from "../types/request-scope.js";
40
52
  import type { HandlerCacheConfig, SSRModule } from "../rsc/types.js";
41
53
  import type { createRSCHandler } from "../rsc/handler.js";
54
+ import { setDefaultSSRModuleLoaderForTests } from "../rsc/ssr-module-loader.js";
42
55
  import {
43
56
  POST_QUIESCE_TASK_HOPS,
44
57
  SHELL_CAPTURE_MAX_WAIT_MS,
45
58
  } from "../rsc/shell-capture-constants.js";
46
59
  import { SEGMENT_FRAGMENT_CAPABILITY_HEADER } from "../segment-fragments.js";
60
+ import { resolveDeferredHandleValues } from "../handles/deferred-resolution.js";
61
+ import type { HandleData } from "../server/handle-store.js";
47
62
  import { _getRequestContext } from "../server/request-context.js";
48
63
  import {
49
64
  parsePprReplayStatus,
@@ -75,6 +90,30 @@ export interface ServeShellRequestOptions<TEnv = any> {
75
90
  * `result.replayStatus`.
76
91
  */
77
92
  partial?: true | { from?: string; segments?: readonly string[] };
93
+ /**
94
+ * POST this form as a submit before hydration: the no-JS request the
95
+ * progressive-enhancement path runs the action for and re-renders the page
96
+ * after. Build it with `createActionForm`. The response is the document
97
+ * (`flight` is its payload, `formState` what the SSR render received).
98
+ * Not with `partial`.
99
+ */
100
+ form?: FormData;
101
+ }
102
+
103
+ /** A response's handle data, as the browser consumes it. */
104
+ export interface ShellRequestHandles {
105
+ /**
106
+ * What the document hydrates with: `metadata.handles`, read to its end
107
+ * before hydration starts. For a `partial` request, the last state the
108
+ * navigation streamed.
109
+ */
110
+ hydration: HandleData;
111
+ /**
112
+ * The states `metadata.handlesLate` delivered, in order. Each one replaces
113
+ * the client's handle data after hydration. Empty when nothing arrived
114
+ * late, and for a `partial` request.
115
+ */
116
+ late: HandleData[];
78
117
  }
79
118
 
80
119
  /** Result of {@link serveShellRequest}. */
@@ -113,6 +152,24 @@ export interface ServeShellRequestResult {
113
152
  * (CFCacheStore, VercelCacheStore) it warms the memo like any other read.
114
153
  */
115
154
  readEntry(): Promise<ShellCacheEntry | null>;
155
+ /**
156
+ * Decode the handle data of `flight` as the browser reads it, deferred
157
+ * values resolved. Undefined when no Flight was rendered.
158
+ */
159
+ readHandles(): Promise<ShellRequestHandles | undefined>;
160
+ /**
161
+ * The form state the SSR render received (the `formState` render option),
162
+ * or undefined when it received none.
163
+ */
164
+ formState: unknown;
165
+ /**
166
+ * The payload's `formState` as the browser reads it, the value the entry
167
+ * hands `hydrateRoot`. Undefined when the payload carries none (or no
168
+ * Flight was rendered). Rejects when the action's state is not
169
+ * Flight-serializable: the document still renders, and the entry
170
+ * hydrates without it.
171
+ */
172
+ readPayloadFormState(): Promise<unknown>;
116
173
  }
117
174
 
118
175
  type ShellHandler = ReturnType<typeof createRSCHandler>;
@@ -126,6 +183,8 @@ interface Recorder {
126
183
  rendered?: boolean;
127
184
  /** The HIT tail's Flight text. */
128
185
  tail?: string;
186
+ /** The formState render option of the document render. */
187
+ formState?: unknown;
129
188
  }
130
189
 
131
190
  const recorders = new AsyncLocalStorage<Recorder>();
@@ -145,9 +204,12 @@ function recordShellKey(recorder: Recorder | undefined): void {
145
204
  }
146
205
 
147
206
  const SSR_STUB: SSRModule = {
148
- async renderHTML(rscStream) {
207
+ async renderHTML(rscStream, options) {
149
208
  const recorder = recorders.getStore();
150
- if (recorder) recorder.rendered = true;
209
+ if (recorder) {
210
+ recorder.rendered = true;
211
+ recorder.formState = options?.formState ?? undefined;
212
+ }
151
213
  recordShellKey(recorder);
152
214
  return rscStream;
153
215
  },
@@ -203,8 +265,134 @@ const SSR_STUB: SSRModule = {
203
265
  },
204
266
  };
205
267
 
206
- /** Handlers per router, keyed by the `cacheStore` override or the router. */
207
- const handlers = new WeakMap<object, WeakMap<object, ShellHandler>>();
268
+ /**
269
+ * `router.prerender()` warms a route through `router.fetch`, whose handler is
270
+ * built with no `loadSSRModule` and so reaches for the Vite RSC module loader
271
+ * this project cannot run. It gets the stub the handlers below get:
272
+ * everything else on a warm's path (router.fetch, createRSCHandler, the match
273
+ * pipeline, the capture) is production code. A warm runs outside a
274
+ * serveShellRequest call, so the stub records nothing for it.
275
+ */
276
+ setDefaultSSRModuleLoaderForTests(async () => SSR_STUB);
277
+
278
+ type HandleChannel = AsyncIterable<HandleData> | undefined;
279
+
280
+ /** Decode a Flight payload as the browser does. */
281
+ async function decodePayload<T>(flight: string): Promise<T> {
282
+ const { deserializeResult } = await import("../cache/segment-codec.js");
283
+ return deserializeResult<T>(flight);
284
+ }
285
+
286
+ /**
287
+ * Read a payload's two handle channels as browser/rsc-router.tsx does:
288
+ * `handles` to its end, then every `handlesLate` state.
289
+ */
290
+ async function decodePayloadHandles(
291
+ flight: string,
292
+ ): Promise<ShellRequestHandles> {
293
+ const { metadata } = await decodePayload<{
294
+ metadata?: { handles?: HandleChannel; handlesLate?: HandleChannel };
295
+ }>(flight);
296
+ let hydration: HandleData = {};
297
+ for await (const data of metadata?.handles ?? []) hydration = data;
298
+ const late: HandleData[] = [];
299
+ for await (const data of metadata?.handlesLate ?? []) {
300
+ late.push(await resolveDeferredHandleValues(data));
301
+ }
302
+ return { hydration: await resolveDeferredHandleValues(hydration), late };
303
+ }
304
+
305
+ /**
306
+ * Each router's Prerender artifacts by manifest key, as `vite build` bakes
307
+ * them (router.matchForPrerender), baked on a URL's first request the way the
308
+ * dev server's /__rsc_prerender endpoint bakes them (`devMode`: a Passthrough
309
+ * route keeps getParams()). `baked` holds the pathnames already tried, one
310
+ * that baked nothing (ctx.passthrough()) included.
311
+ */
312
+ let prerenderArtifacts = new WeakMap<
313
+ object,
314
+ { payloads: Map<string, string>; baked: Set<string> }
315
+ >();
316
+
317
+ /**
318
+ * Serve a Prerender route's handler layer from its build-time artifact, as
319
+ * production does: through the store the built worker reads
320
+ * (createPrerenderStore over globalThis.__loadPrerenderManifestModule,
321
+ * installed as shell-prerender-phase.ts installs it for build-time shells).
322
+ * `env` stands in for the build's `buildEnv`. Returns the undo, or undefined
323
+ * when `pathname` matches no Prerender route.
324
+ */
325
+ async function servePrerenderArtifacts(
326
+ router: Rango<any, any>,
327
+ pathname: string,
328
+ env: unknown,
329
+ ): Promise<(() => void) | undefined> {
330
+ const internal = toInternal(router);
331
+ const matched = await internal.findMatch(pathname);
332
+ // A build bakes an on-demand route only for the params getParams() lists;
333
+ // a refresh-only page serves from its prerender store alone. A Passthrough
334
+ // route is baked here like a build bakes it: the `devMode` render below
335
+ // asks its getParams() and declines a param it does not list. A plain
336
+ // on-demand route is not: nothing here asks its getParams(), and baking
337
+ // every requested param would hide the 404 an unbaked one answers.
338
+ if (!matched?.pr || (matched.od && !matched.pt)) return undefined;
339
+ let artifacts = prerenderArtifacts.get(router);
340
+ if (!artifacts) {
341
+ prerenderArtifacts.set(
342
+ router,
343
+ (artifacts = { payloads: new Map(), baked: new Set() }),
344
+ );
345
+ }
346
+ const { payloads, baked } = artifacts;
347
+ if (!baked.has(pathname)) {
348
+ const result = await internal.matchForPrerender(
349
+ pathname,
350
+ {},
351
+ undefined,
352
+ matched.pt === true,
353
+ env,
354
+ true,
355
+ );
356
+ const { hashParams } = await import("../prerender/param-hash.js");
357
+ baked.add(pathname);
358
+ if (result && !result.passthrough) {
359
+ payloads.set(
360
+ `${result.routeName}/${hashParams(result.params)}`,
361
+ JSON.stringify({ segments: result.segments, handles: result.handles }),
362
+ );
363
+ }
364
+ }
365
+ const [{ createPrerenderStore }, { setPrerenderStoreForTests }] =
366
+ await Promise.all([
367
+ import("../prerender/store.js"),
368
+ import("../router/match-middleware/cache-lookup.js"),
369
+ ]);
370
+ const previousLoader = globalThis.__loadPrerenderManifestModule;
371
+ globalThis.__loadPrerenderManifestModule = async () => ({
372
+ default: Object.fromEntries([...payloads.keys()].map((key) => [key, key])),
373
+ loadPrerenderAsset: async (key) => ({
374
+ default: JSON.parse(payloads.get(key)!),
375
+ }),
376
+ });
377
+ const previousStore = setPrerenderStoreForTests(createPrerenderStore());
378
+ return () => {
379
+ globalThis.__loadPrerenderManifestModule = previousLoader;
380
+ setPrerenderStoreForTests(previousStore);
381
+ };
382
+ }
383
+
384
+ /**
385
+ * Handlers per router, keyed by the `cacheStore` override or the router. A
386
+ * handler binds its router's document version when it is created, so an entry
387
+ * is reused only while the router would still resolve that version: a
388
+ * setBuildVersions() call (a simulated deploy) that changes it gets a new
389
+ * handler. The data version is read per request, so a data-only change reuses
390
+ * the handler, as a long-lived worker would.
391
+ */
392
+ const handlers = new WeakMap<
393
+ object,
394
+ WeakMap<object, { handler: ShellHandler; versions: string }>
395
+ >();
208
396
 
209
397
  async function getHandler(
210
398
  router: Rango<any, any>,
@@ -212,10 +400,20 @@ async function getHandler(
212
400
  ): Promise<ShellHandler> {
213
401
  let byStore = handlers.get(router);
214
402
  if (!byStore) handlers.set(router, (byStore = new WeakMap()));
403
+ const internal = toInternal(router);
404
+ // Imported here, like the handler below: the module binds the build-only
405
+ // `@rangojs/router:version` virtual, which a flight-only test config (one
406
+ // that never serves a request) does not alias.
407
+ const { resolveRouterVersions } =
408
+ await import("../server/build-version-table.js");
409
+ // What createRSCHandler would resolve for this router now.
410
+ const { document: versions } = resolveRouterVersions(
411
+ internal.id,
412
+ internal.version,
413
+ );
215
414
  const cached = byStore.get(cacheStore ?? router);
216
- if (cached) return cached;
415
+ if (cached?.versions === versions) return cached.handler;
217
416
 
218
- const internal = toInternal(router);
219
417
  const routerCache = internal.cache;
220
418
  // Resolved per request, as the handler resolves the router's own config.
221
419
  const cache = (env: unknown, ctx?: ExecutionContext): HandlerCacheConfig => {
@@ -230,17 +428,28 @@ async function getHandler(
230
428
  const handler = createRSCHandler({
231
429
  router: internal,
232
430
  nonce: internal.nonce,
233
- version: internal.version,
234
431
  loadSSRModule: async () => SSR_STUB,
235
432
  cache: routerCache || cacheStore ? cache : undefined,
236
433
  });
237
- byStore.set(cacheStore ?? router, handler);
434
+ byStore.set(cacheStore ?? router, { handler, versions });
238
435
  return handler;
239
436
  }
240
437
 
241
438
  function buildRequest(url: URL, options: ServeShellRequestOptions): Request {
242
439
  const headers = new Headers(options.headers);
243
440
  const partial = options.partial;
441
+ if (options.form) {
442
+ if (partial) {
443
+ throw new Error(
444
+ "serveShellRequest: form cannot be combined with partial",
445
+ );
446
+ }
447
+ return new Request(url, {
448
+ method: "POST",
449
+ headers,
450
+ body: options.form,
451
+ });
452
+ }
244
453
  if (!partial) {
245
454
  if (!headers.has("accept")) headers.set("accept", "text/html");
246
455
  return new Request(url, { headers });
@@ -263,25 +472,30 @@ function buildRequest(url: URL, options: ServeShellRequestOptions): Request {
263
472
  * starts as a fresh worker would: the capture's stampede guard, its backoff
264
473
  * (a refused capture backs its URL off for later tests too), the shell path's
265
474
  * once-per-key warnings, the build-shell manifest memo, the "use cache"
266
- * in-flight leaders and warnings, and CFCacheStore's isolate memos (shells,
475
+ * in-flight leaders and warnings, CFCacheStore's isolate memos (shells,
267
476
  * tag markers, tag hints) and warnings, which every CFCacheStore in the
268
- * process shares by namespace and URL. Call it in `beforeEach`, never while
269
- * a request is in flight. VercelCacheStore's memos live on the `cache`
270
- * handle it is given: a new handle starts empty.
477
+ * process shares by namespace and URL, and the Prerender artifacts baked so
478
+ * far. Call it in `beforeEach`, never while a request is in flight.
479
+ * VercelCacheStore's memos live on the `cache` handle it is given: a new
480
+ * handle starts empty.
271
481
  */
272
482
  export async function resetShellTestState(): Promise<void> {
273
- const [capture, serve, buildShells, cf, cacheRuntime] = await Promise.all([
274
- import("../rsc/shell-capture.js"),
275
- import("../rsc/shell-serve.js"),
276
- import("../rsc/shell-build-manifest.js"),
277
- import("../cache/cf/cf-cache-store.js"),
278
- import("../cache/cache-runtime.js"),
279
- ]);
483
+ prerenderArtifacts = new WeakMap();
484
+ const [capture, serve, buildShells, cf, cacheRuntime, cacheLookup] =
485
+ await Promise.all([
486
+ import("../rsc/shell-capture.js"),
487
+ import("../rsc/shell-serve.js"),
488
+ import("../rsc/shell-build-manifest.js"),
489
+ import("../cache/cf/cf-cache-store.js"),
490
+ import("../cache/cache-runtime.js"),
491
+ import("../router/match-middleware/cache-lookup.js"),
492
+ ]);
280
493
  capture.resetShellCaptureStateForTests();
281
494
  serve.resetShellServeStateForTests();
282
495
  buildShells.resetBuildShellManifestForTests();
283
496
  cf.resetCFShellMemoForTests();
284
497
  cacheRuntime.resetCacheRuntimeForTests();
498
+ cacheLookup.resetOverlayRevalidationsForTests();
285
499
  }
286
500
 
287
501
  /** Settle background tasks, including ones scheduled while settling. */
@@ -323,19 +537,15 @@ export async function serveShellRequest<TEnv = any>(
323
537
  await ensureRouterManifest(router.id);
324
538
  const handler = await getHandler(router, options.cacheStore);
325
539
  const request = buildRequest(target, options);
326
- const tasks: Promise<unknown>[] = [];
327
- const executionContext: ExecutionContext = {
328
- waitUntil(promise) {
329
- tasks.push(
330
- Promise.resolve(promise).catch((error) =>
331
- console.error("[waitUntil] Background task failed:", error),
332
- ),
333
- );
334
- },
335
- passThroughOnException() {},
336
- };
540
+ const executionContext = createCollectingExecutionContext();
541
+ const { tasks } = executionContext;
337
542
  const recorder: Recorder = {};
338
- while (Date.now() === called) await macrotask();
543
+ const unservePrerender = await servePrerenderArtifacts(
544
+ router,
545
+ target.pathname,
546
+ options.env,
547
+ );
548
+ await awaitLaterMillisecond(called);
339
549
 
340
550
  const { response, body } = await recorders.run(recorder, async () => {
341
551
  try {
@@ -346,6 +556,7 @@ export async function serveShellRequest<TEnv = any>(
346
556
  return { response, body: await response.text() };
347
557
  } finally {
348
558
  await settle(tasks);
559
+ unservePrerender?.();
349
560
  }
350
561
  });
351
562
 
@@ -353,21 +564,24 @@ export async function serveShellRequest<TEnv = any>(
353
564
  recorder.cache && recorder.cache.enabled !== false
354
565
  ? recorder.cache
355
566
  : undefined;
356
- const key = recorder.shellKey ?? shellCacheKey(target, config?.searchParams);
567
+ const key =
568
+ recorder.shellKey ?? shellCacheKey(router, target, config?.searchParams);
357
569
  const tail = recorder.tail;
358
570
  const isFlight =
359
571
  recorder.rendered ||
360
572
  response.headers.get("content-type")?.includes("text/x-component");
573
+ const prelude =
574
+ tail !== undefined && body.endsWith(tail)
575
+ ? body.slice(0, body.length - tail.length)
576
+ : undefined;
577
+ const flight = tail ?? (isFlight && body ? body : undefined);
361
578
  return {
362
579
  response,
363
580
  body,
364
581
  shellStatus: parseShellStatus(response),
365
582
  replayStatus: parsePprReplayStatus(response),
366
- prelude:
367
- tail !== undefined && body.endsWith(tail)
368
- ? body.slice(0, body.length - tail.length)
369
- : undefined,
370
- flight: tail ?? (isFlight && body ? body : undefined),
583
+ prelude,
584
+ flight,
371
585
  key,
372
586
  async readEntry() {
373
587
  // Passive, like the serve path's own reads: a stale entry's SWR
@@ -377,5 +591,16 @@ export async function serveShellRequest<TEnv = any>(
377
591
  });
378
592
  return read?.entry ?? null;
379
593
  },
594
+ async readHandles() {
595
+ return flight ? decodePayloadHandles(flight) : undefined;
596
+ },
597
+ formState: recorder.formState,
598
+ async readPayloadFormState() {
599
+ if (!flight) return undefined;
600
+ const payload = await decodePayload<{
601
+ formState?: Promise<unknown>;
602
+ }>(flight);
603
+ return payload.formState;
604
+ },
380
605
  };
381
606
  }
@@ -15,7 +15,7 @@
15
15
  * 2. **Smallest status signals:** `x-rango-shell` (`HIT` | `MISS`) on document
16
16
  * GETs, and `x-rango-ppr-replay` on partial requests to ppr routes. A replay
17
17
  * HIT is reported only when matching consumes the captured segment record.
18
- * Secondary unit signal: `store.getShell(shellCacheKey(url))` after a real
18
+ * Secondary unit signal: `store.getShell(shellCacheKey(router, url))` after a real
19
19
  * capture flush (background `putShell`). There is no Flight flag for shell HIT.
20
20
  *
21
21
  * 3. **A real MISS → capture → HIT in unit tests:** `serveShellRequest`
@@ -82,10 +82,16 @@ export interface ShellCachePartition {
82
82
  }
83
83
 
84
84
  /**
85
- * Shell store key for a document URL: the production `buildShellKey` (host +
86
- * pathname + sorted search + `:shell`) and `partitionShellKey`, both from the
87
- * React-free leaf `rsc/shell-capture-constants.ts`, so the testing barrel never
88
- * imports the shell-serve module (it pulls React).
85
+ * Shell store key of `router` for a document URL: the production
86
+ * `buildShellKey` (router id + host + pathname + sorted search + `:shell`)
87
+ * and `partitionShellKey`, both from the React-free leaf
88
+ * `rsc/shell-capture-constants.ts`, so the testing barrel never imports the
89
+ * shell-serve module (it pulls React).
90
+ *
91
+ * `router` is the router that serves the URL, or anything carrying its `id`
92
+ * (`{ id }` where the router cannot be imported, as in a Playwright test). A
93
+ * shell belongs to one router: two routers serving one host and path keep
94
+ * two entries.
89
95
  *
90
96
  * Accepts a `URL` or an absolute/relative request URL string (relative strings
91
97
  * resolve against `http://localhost`). Pass the router's `cache.searchParams`
@@ -101,13 +107,18 @@ export interface ShellCachePartition {
101
107
  * `{ keys, generated }` (see {@link ShellCachePartition}).
102
108
  */
103
109
  export function shellCacheKey(
110
+ router: { readonly id: string },
104
111
  url: URL | string,
105
112
  searchParams?: CacheSearchParams,
106
113
  partition?: string | readonly string[] | ShellCachePartition,
107
114
  ): string {
108
115
  const resolved =
109
116
  typeof url === "string" ? new URL(url, "http://localhost") : url;
110
- const key = buildShellKey(resolved, compileSearchParamsFilter(searchParams));
117
+ const key = buildShellKey(
118
+ router.id,
119
+ resolved,
120
+ compileSearchParamsFilter(searchParams),
121
+ );
111
122
  if (partition === undefined) return key;
112
123
  const { keys = [], generated = [] }: ShellCachePartition =
113
124
  typeof partition === "string"
@@ -17,10 +17,15 @@
17
17
  *
18
18
  * Without the condition React's server build refuses to load, so the codec
19
19
  * functions throw and a "use cache" call runs uncached. Any other load failure
20
- * (a moved vendored path, a missing peer) throws. The action entries stay
21
- * inert either way: no primitive decodes an action request.
20
+ * (a moved vendored path, a missing peer) throws. decodeAction and
21
+ * decodeFormState decode a no-JS form POST under the condition, resolving its
22
+ * action from the modules createActionForm() registered, as do
23
+ * `loadServerAction` and `decodeReply` for a plain form action. Without the
24
+ * condition they stay inert.
22
25
  */
23
26
 
27
+ import { loadServerModule } from "../internal/server-modules.js";
28
+
24
29
  /** React's server build throws this on load without the react-server condition. */
25
30
  const MISSING_REACT_SERVER_CONDITION =
26
31
  /"react-server" condition must be enabled/;
@@ -52,11 +57,7 @@ async function loadFlightRuntime() {
52
57
  // Resolves the `$$decode-client:` / `$$preserve:` ids a cached value's client
53
58
  // and server references decode to; any other id would load a server module.
54
59
  core.setRequireModule({
55
- load: (id) => {
56
- throw new Error(
57
- `plugin-rsc stub: server reference module "${id}" is not loadable in a test`,
58
- );
59
- },
60
+ load: (id) => loadServerModule(id),
60
61
  });
61
62
  return { server, client, core };
62
63
  }
@@ -121,7 +122,32 @@ export const encodeReply = (
121
122
  : unavailable("encodeReply");
122
123
  export const createClientTemporaryReferenceSet = (): unknown =>
123
124
  runtime ? runtime.client.createTemporaryReferenceSet() : {};
124
- export const loadServerAction = (): undefined => undefined;
125
- export const decodeReply = (): undefined => undefined;
126
- export const decodeAction = (): undefined => undefined;
127
- export const decodeFormState = (): undefined => undefined;
125
+ export const loadServerAction = (id: string): Promise<unknown> | undefined => {
126
+ if (!runtime) return undefined;
127
+ const [file, name] = id.split("#");
128
+ return Promise.resolve(loadServerModule(file!)[name!]);
129
+ };
130
+ export const decodeReply = (
131
+ body: string | FormData,
132
+ options?: object,
133
+ ): Promise<unknown> | undefined =>
134
+ runtime
135
+ ? runtime.server.decodeReply(body, referenceManifest("$$server:"), options)
136
+ : undefined;
137
+ export const decodeAction = (
138
+ body: FormData,
139
+ ): Promise<unknown> | null | undefined =>
140
+ runtime
141
+ ? runtime.server.decodeAction(body, referenceManifest("$$server:"))
142
+ : undefined;
143
+ export const decodeFormState = (
144
+ actionResult: unknown,
145
+ body: FormData,
146
+ ): Promise<unknown> | undefined =>
147
+ runtime
148
+ ? runtime.server.decodeFormState(
149
+ actionResult,
150
+ body,
151
+ referenceManifest("$$server:"),
152
+ )
153
+ : undefined;
@@ -1,5 +1,8 @@
1
1
  // Stub for the build-only `@rangojs/router:version` virtual module, shipped so
2
2
  // consumers do not have to author it. The rango Vite plugin injects this at
3
3
  // build time; in a bare Vitest process it must be aliased to a stub. Empty
4
- // string keeps generated URLs free of a version path segment.
4
+ // string keeps generated URLs free of a version path segment. There is no
5
+ // build table here; setBuildVersions (@rangojs/router/testing) installs one
6
+ // for a test that needs per-router versions.
5
7
  export const VERSION = "";
8
+ export const ROUTER_VERSIONS: undefined = undefined;
@@ -14,3 +14,13 @@ export function unwrapsSynchronously(stream: Promise<unknown>): boolean {
14
14
  status === "resolved_module"
15
15
  );
16
16
  }
17
+
18
+ /**
19
+ * A resolved thenable for a use() whose result is ignored: React reads the
20
+ * "fulfilled" stamp and returns at once. `_debugInfo` keeps React DevTools from
21
+ * listing a 0 ms promise on every reader that calls it.
22
+ */
23
+ export const SETTLED_THENABLE: Promise<undefined> = Object.assign(
24
+ Promise.resolve(undefined),
25
+ { status: "fulfilled", value: undefined, _debugInfo: [] },
26
+ );
@@ -449,6 +449,13 @@ export type InternalHandlerContext<
449
449
  _responseType?: string;
450
450
  /** Route name for cache key scoping (prevents cross-route collisions). */
451
451
  _routeName?: string;
452
+ /**
453
+ * @internal The on-demand overlay holds a "removed" marker for this request
454
+ * (tryPrerenderLookup, cache-lookup.ts); gateOnDemandProducer reads it. On
455
+ * this context, not the request's: withCacheLookup documents that the
456
+ * request ALS can be lost mid-pipeline on workerd.
457
+ */
458
+ _prerenderRemoved?: true;
452
459
  /**
453
460
  * @internal Loader-cache override table: loaderId -> memoized data promise.
454
461
  * A single stable ctx.use interceptor consults this instead of chaining one
@@ -462,11 +469,14 @@ export type InternalHandlerContext<
462
469
  */
463
470
  _loaderCacheOriginalUse?: (item: any) => any;
464
471
  /**
465
- * @internal A loader-cache HIT asks before replaying a loader's recorded
466
- * pushes: true when that loader has not run in this request and no other
467
- * replay delivered it; its later run then replaces the replayed values
468
- * (HandleStore.pushReplayed). One copy per loader per request. See
469
- * setupLoaderAccess.
472
+ * @internal A loader-cache or "use cache" HIT asks before replaying a
473
+ * loader's recorded pushes: true when that loader has not run in this
474
+ * request and no other replay delivered it; its later run then replaces
475
+ * the replayed values (HandleStore.pushReplayed). One copy per loader per
476
+ * request. A record's restore does not ask (restoreHandles): its copies
477
+ * are the pinned loader's own, or placeholders the claimed replay
478
+ * replaces. Absent on a stale refresh's ctx, whose pushes are diverted
479
+ * (handle-snapshot.ts appendHandles). See setupLoaderAccess.
470
480
  */
471
481
  _claimLoaderPushes?: (loaderId: string) => boolean;
472
482
  /**
@@ -82,6 +82,12 @@ export interface RouteEntry<TEnv = any> {
82
82
  */
83
83
  passthroughRouteKeys?: Set<string>;
84
84
 
85
+ /**
86
+ * Route keys in this entry that opted into on-demand prerender.
87
+ * Used by the non-trie match path to set the `od` flag.
88
+ */
89
+ onDemandRouteKeys?: Set<string>;
90
+
85
91
  // === Lazy evaluation fields ===
86
92
 
87
93
  /**