@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
@@ -170,6 +170,21 @@ export async function classifyRequest<TEnv = any>(
170
170
  const willFullRender =
171
171
  !isAction && !isLoaderFetch && !isPartialReq && !isPeRender;
172
172
 
173
+ // Before the version check: each router has its own version, so a tab of
174
+ // another app always carries one this router does not have. That request is
175
+ // an app switch, not a stale client.
176
+ const clientRouterId = url.searchParams.get("_rsc_rid");
177
+ if (
178
+ clientRouterId &&
179
+ clientRouterId !== deps.routerId &&
180
+ url.searchParams.has("_rsc_partial")
181
+ ) {
182
+ return {
183
+ mode: "app-switch",
184
+ reloadUrl: stripInternalParams(url).toString(),
185
+ };
186
+ }
187
+
173
188
  const clientVersion = url.searchParams.get("_rsc_v");
174
189
  if (
175
190
  deps.routerVersion &&
@@ -195,18 +210,6 @@ export async function classifyRequest<TEnv = any>(
195
210
  };
196
211
  }
197
212
 
198
- const clientRouterId = url.searchParams.get("_rsc_rid");
199
- if (
200
- clientRouterId &&
201
- clientRouterId !== deps.routerId &&
202
- url.searchParams.has("_rsc_partial")
203
- ) {
204
- return {
205
- mode: "app-switch",
206
- reloadUrl: stripInternalParams(url).toString(),
207
- };
208
- }
209
-
210
213
  const result = await resolveRoute<TEnv>(pathname, {
211
214
  findMatch: deps.findMatch,
212
215
  lite: true,
@@ -37,6 +37,12 @@ export interface TrieLeaf {
37
37
  pr?: true;
38
38
  /** Passthrough: handler kept in bundle for live fallback on unknown params */
39
39
  pt?: true;
40
+ /**
41
+ * On-demand prerender eligible: the route opted into router.prerender()
42
+ * refresh. Distinct from `pr` — an od route may have no build-baked entry yet
43
+ * still needs a writable-overlay lookup at serve time.
44
+ */
45
+ od?: true;
40
46
  /** Response type for non-RSC routes (json, text, image, any) */
41
47
  rt?: string;
42
48
  /** Negotiate variants: response-type routes sharing this path */
@@ -72,6 +78,7 @@ export interface TrieNode {
72
78
  * @param prerenderRouteNames - Optional set of prerendered route names (sets leaf.pr)
73
79
  * @param passthroughRouteNames - Optional set of passthrough route names (sets leaf.pt)
74
80
  * @param responseTypeRoutes - Optional map of route name to response type (sets leaf.rt)
81
+ * @param onDemandRouteNames - Optional set of on-demand-eligible route names (sets leaf.od)
75
82
  */
76
83
  export function buildRouteTrie(
77
84
  routeManifest: Record<string, string>,
@@ -80,6 +87,7 @@ export function buildRouteTrie(
80
87
  prerenderRouteNames?: Set<string>,
81
88
  passthroughRouteNames?: Set<string>,
82
89
  responseTypeRoutes?: Record<string, string>,
90
+ onDemandRouteNames?: Set<string>,
83
91
  ): TrieNode {
84
92
  const root: TrieNode = {};
85
93
 
@@ -99,6 +107,7 @@ export function buildRouteTrie(
99
107
  ...(trailingSlash ? { ts: trailingSlash } : {}),
100
108
  ...(prerenderRouteNames?.has(routeName) ? { pr: true } : {}),
101
109
  ...(passthroughRouteNames?.has(routeName) ? { pt: true } : {}),
110
+ ...(onDemandRouteNames?.has(routeName) ? { od: true } : {}),
102
111
  ...(responseType ? { rt: responseType } : {}),
103
112
  });
104
113
  }
@@ -100,6 +100,7 @@ export interface RouterContext<TEnv = any> {
100
100
  parent: any,
101
101
  fn: () => T,
102
102
  ) => T;
103
+ runIsolated: <T>(forRoute: string, fn: () => T) => T;
103
104
  };
104
105
 
105
106
  getMetricsStore: () => MetricsStore | undefined;
@@ -225,6 +225,29 @@ export interface Rango<
225
225
  * ```
226
226
  */
227
227
  fetch(request: Request, input?: RouterRequestInput<TEnv>): Promise<Response>;
228
+
229
+ /**
230
+ * Bind `{ env, ctx }` and get the on-demand (ISR-style) prerender runner, to refresh from a running app —
231
+ * fetch handler, cron, queue, workflow, webhook, or server action.
232
+ *
233
+ * Requestless: the producer renders with route params and env only, never the
234
+ * caller's cookies/headers/session. Only routes that opted in via
235
+ * `Prerender(..., { onDemand })` are persistable; others return a skipped
236
+ * result. Configure the store via the `prerender` router option.
237
+ *
238
+ * @example
239
+ * ```ts
240
+ * const prerender = router.prerender({ env, ctx });
241
+ * await prerender("/products/42");
242
+ * await prerender(
243
+ * { route: "products.detail", params: { id: "42" } },
244
+ * { throwOnError: true },
245
+ * );
246
+ * await prerender.many(targets, { concurrency: 4 });
247
+ * await prerender.markStale(["product:42"]);
248
+ * ```
249
+ */
250
+ prerender: import("../prerender/on-demand.js").PrerenderFn<TEnv, TRoutes>;
228
251
  }
229
252
 
230
253
  /**
@@ -332,6 +355,13 @@ export interface RangoInternal<
332
355
  */
333
356
  readonly cache?: RangoOptions<TEnv>["cache"];
334
357
 
358
+ /**
359
+ * Prerender store configuration (the writable durable overlay for on-demand
360
+ * prerender). Stored separately from the `prerender` trigger method so the RSC
361
+ * handler can resolve it per request. @internal
362
+ */
363
+ readonly _prerenderConfig?: RangoOptions<TEnv>["prerender"];
364
+
335
365
  /**
336
366
  * Not found component to render when no route matches
337
367
  */
@@ -457,7 +487,8 @@ export interface RangoInternal<
457
487
  readonly nonce?: NonceProvider<TEnv>;
458
488
 
459
489
  /**
460
- * RSC version string
490
+ * The consumer-set version (createRouter({ version })), used for both of the
491
+ * router's versions. Undefined when the router uses its build versions.
461
492
  */
462
493
  readonly version?: string;
463
494
 
@@ -619,6 +650,9 @@ export interface RangoInternal<
619
650
  * Handle an RSC request.
620
651
  */
621
652
  fetch(request: Request, input?: RouterRequestInput<TEnv>): Promise<Response>;
653
+
654
+ /** On-demand prerender binder: router.prerender({ env, ctx }) returns the runner. */
655
+ prerender: import("../prerender/on-demand.js").PrerenderFn<TEnv, TRoutes>;
622
656
  }
623
657
 
624
658
  /**
@@ -341,6 +341,36 @@ export interface RangoOptions<TEnv = any> {
341
341
  searchParams?: CacheSearchParams;
342
342
  });
343
343
 
344
+ /**
345
+ * Writable prerender store for on-demand (ISR-style) prerender refresh.
346
+ *
347
+ * A durable overlay in front of the bundled build manifest. When provided,
348
+ * `router.prerender(target, { env, ctx })` renders a route requestlessly and
349
+ * writes a versioned payload here; the serve path reads the overlay before the
350
+ * bundled manifest. Same union shape as `cache`: a plain config object or a
351
+ * factory resolved per request/per call (never at `createRouter()` time), so
352
+ * multi-tenant apps never memoize one request's binding for another.
353
+ *
354
+ * @example Cloudflare KV overlay with SWR revalidation
355
+ * ```typescript
356
+ * import { createKVPrerenderStore } from "@rangojs/router/prerender/cloudflare";
357
+ *
358
+ * const router = createRouter<AppEnv>({
359
+ * prerender: (env) => ({
360
+ * store: createKVPrerenderStore(env.PRERENDER_KV),
361
+ * ttl: 3600,
362
+ * onRevalidate: (target, e) => e.PRERENDER_QUEUE.send({ target }),
363
+ * }),
364
+ * });
365
+ * ```
366
+ */
367
+ prerender?:
368
+ | import("../prerender/on-demand.js").PrerenderConfig<TEnv>
369
+ | ((
370
+ env: TEnv,
371
+ ctx?: ExecutionContext,
372
+ ) => import("../prerender/on-demand.js").PrerenderConfig<TEnv>);
373
+
344
374
  /**
345
375
  * Named cache profiles for "use cache" directive.
346
376
  * Profile names map to TTL/SWR configuration.
@@ -502,13 +532,23 @@ export interface RangoOptions<TEnv = any> {
502
532
  nonce?: NonceProvider<TEnv>;
503
533
 
504
534
  /**
505
- * RSC version string included in metadata.
506
- * The browser sends this back on partial requests to detect version mismatches.
535
+ * Version override. When set, this exact value is used for both of the
536
+ * router's versions: it keys the router's cached data (segments, `"use
537
+ * cache"` values, loader data) and its stored HTML (PPR shells,
538
+ * document-cache responses), is sent in payload metadata, and is what the
539
+ * browser's `_rsc_v` is compared with (a mismatch reloads the tab).
540
+ *
541
+ * Leave it unset for the default: rango computes two versions per router at
542
+ * build time, a data version from the router's server code and a document
543
+ * version that also covers the client assets. The same code builds to the
544
+ * same versions, so a deploy that does not change this router keeps its
545
+ * cache. In dev both are a stamp bumped on every RSC module edit.
507
546
  *
508
- * Defaults to the auto-generated VERSION from `@rangojs/router:version` virtual module.
509
- * Only set this if you need a custom versioning strategy.
547
+ * Set it to clear this router's cache on demand (change the value), or to
548
+ * tie the cache to your own release id (a per-deploy value clears the cache
549
+ * on every deploy).
510
550
  *
511
- * @default VERSION from @rangojs/router:version
551
+ * @default the router's build versions
512
552
  */
513
553
  version?: string;
514
554
 
@@ -36,7 +36,7 @@ import {
36
36
  import { applyViewTransitionDefault } from "./view-transition-default.js";
37
37
  import { _getRequestContext } from "../../server/request-context.js";
38
38
  import { getRouterContext } from "../router-context.js";
39
- import { observeStreamedHandler } from "./streamed-handler-telemetry.js";
39
+ import { trackStreamedHandler } from "./streamed-handler-boundary.js";
40
40
  import { observeSegmentHandler } from "../instrument.js";
41
41
  import {
42
42
  track,
@@ -280,6 +280,13 @@ export interface ResolveSegmentOptions {
280
280
  * #918: resolved twice on a MISS, once and discarded on a HIT).
281
281
  */
282
282
  chain?: readonly EntryData[];
283
+ /**
284
+ * The matched chain entry being resolved: where the sync path catches a
285
+ * handler error, so a streamed handler (or slot owned by a routeless layout)
286
+ * recovers into the same boundary. resolveSegment sets it for the entry it
287
+ * resolves; nested resolutions keep the outer value.
288
+ */
289
+ boundaryEntry?: EntryData;
283
290
  }
284
291
 
285
292
  /**
@@ -298,6 +305,8 @@ export async function resolveSegment<TEnv>(
298
305
  options?: ResolveSegmentOptions,
299
306
  ): Promise<ResolvedSegment[]> {
300
307
  const segments: ResolvedSegment[] = [];
308
+ const boundaryEntry = options?.boundaryEntry ?? entry;
309
+ if (!options?.boundaryEntry) options = { ...options, boundaryEntry };
301
310
 
302
311
  if (entry.type === "layout" || entry.type === "cache") {
303
312
  if (!options?.skipLoaders) {
@@ -398,19 +407,15 @@ export async function resolveSegment<TEnv>(
398
407
  if (result instanceof Promise) {
399
408
  warnOnStreamedResponse(result, entry.id);
400
409
  result.finally(doneRouteHandler).catch(() => {});
401
- const tracked = deps.trackHandler(result, {
410
+ component = trackStreamedHandler(deps, result, {
411
+ boundaryEntry,
402
412
  segmentId: entry.shortCode,
403
413
  segmentType: entry.type,
404
- });
405
- observeStreamedHandler(
406
- tracked,
407
- entry.shortCode,
408
- entry.type,
409
- context.pathname,
414
+ context,
410
415
  routeKey,
411
416
  params,
412
- );
413
- component = tracked;
417
+ recover: !options?.throwOnError,
418
+ });
414
419
  } else {
415
420
  doneRouteHandler();
416
421
  component = result;
@@ -679,19 +684,15 @@ export async function resolveParallelEntry<TEnv>(
679
684
  : handler;
680
685
  if (result instanceof Promise) {
681
686
  result.finally(doneParallelHandler).catch(() => {});
682
- const tracked = deps.trackHandler(result, {
687
+ component = trackStreamedHandler(deps, result, {
688
+ boundaryEntry: options?.boundaryEntry ?? parallelEntry,
683
689
  segmentId: `${parentShortCode}.${slot}`,
684
690
  segmentType: "parallel",
685
- });
686
- observeStreamedHandler(
687
- tracked,
688
- `${parentShortCode}.${slot}`,
689
- "parallel",
690
- context.pathname,
691
+ context,
691
692
  routeKey,
692
693
  params,
693
- );
694
- component = tracked as ReactNode;
694
+ recover: !options?.throwOnError,
695
+ });
695
696
  } else {
696
697
  doneParallelHandler();
697
698
  component = result as ReactNode;
@@ -9,7 +9,8 @@
9
9
  * 1. options.key(requestCtx) — full override, stored namespaced as
10
10
  * loader:{loaderId}:key:{encodeURIComponent(result)} (#1009)
11
11
  * 2. store.keyGenerator(requestCtx, defaultKey) — store-level modification
12
- * 3. loader:{loaderId}:{host}{pathname}:{sortedParams} — default
12
+ * 3. loader:{loaderId}:{routerId}@{host}{pathname}:{sortedParams} — default
13
+ * (a loader's ctx is one router's: cache-key-utils.ts, the router rule)
13
14
  *
14
15
  * Values are serialized via RSC Flight (serializeResult/deserializeResult),
15
16
  * supporting ReactNode, Promises, null, and all RSC-serializable types.
@@ -50,24 +51,34 @@ import {
50
51
  type LoaderIdentityRead,
51
52
  } from "../../server/context.js";
52
53
  import type { HandlerContext, InternalHandlerContext } from "../../types.js";
53
- import type { HandleStore } from "../../server/handle-store.js";
54
+ import type {
55
+ HandleStore,
56
+ RecordAuthority,
57
+ } from "../../server/handle-store.js";
54
58
  import type { CacheItemResult } from "../../cache/types.js";
55
59
  import {
56
60
  startHandleCapture,
57
61
  type HandleCapture,
58
62
  } from "../../cache/handle-capture.js";
59
- import { encodeHandles, decodeHandles } from "../../cache/handle-snapshot.js";
63
+ import {
64
+ appendHandles,
65
+ encodeHandles,
66
+ decodeHandles,
67
+ type OwnedPushDelivery,
68
+ } from "../../cache/handle-snapshot.js";
69
+ import type { ShellLoaderSeedEntry } from "../../cache/shell-snapshot.js";
60
70
  import { INTERNAL_RANGO_DEBUG } from "../../internal-debug.js";
61
71
  import { runIdentityExempt } from "../../cache/cache-exec-scope.js";
62
72
  import {
63
73
  getRequestContext,
64
74
  _getRequestContext,
65
75
  runWithRequestContext,
76
+ type RequestContext,
66
77
  } from "../../server/request-context.js";
67
78
  import { observePhase, PHASES } from "../instrument.js";
68
79
  import {
69
80
  KEY_PART_PREFIX,
70
- sortedRouteParams,
81
+ requestKeyBase,
71
82
  } from "../../cache/cache-key-utils.js";
72
83
  import {
73
84
  resolveTtl,
@@ -78,6 +89,7 @@ import {
78
89
  DEFAULT_ROUTE_TTL,
79
90
  } from "../../cache/cache-policy.js";
80
91
  import { readThroughItem } from "../../cache/read-through-swr.js";
92
+ import { isWarmReplace, noteWarmWrite } from "../../prerender/warm-request.js";
81
93
  import {
82
94
  executionStart,
83
95
  predatesInvalidation,
@@ -128,17 +140,6 @@ function debugLoaderCacheLog(message: string): void {
128
140
  }
129
141
  }
130
142
 
131
- function getDefaultLoaderCacheKey(
132
- loaderId: string,
133
- host: string,
134
- pathname: string,
135
- params: Record<string, string>,
136
- ): string {
137
- const paramStr = sortedRouteParams(params);
138
- const base = paramStr ? `${pathname}:${paramStr}` : pathname;
139
- return `loader:${loaderId}:${host}${base}`;
140
- }
141
-
142
143
  /**
143
144
  * A loader's own `key()` result as its entry key (#1009). The result is
144
145
  * often request input, and the item family (`getItem`/`setItem`) has no
@@ -149,7 +150,7 @@ function getDefaultLoaderCacheKey(
149
150
  * - an encoded result holds no `:`, so the key reads unambiguously from
150
151
  * the right (result, `key:`, loader id), and two namespaced keys are
151
152
  * equal only for the same loader and result;
152
- * - a default key `loader:<id>:<host><pathname>...` holds a `/` past its
153
+ * - a default key `loader:<id>:[<router>@]<host><pathname>...` holds a `/` past its
153
154
  * id, and an encoded result holds none, so the two are equal only if one
154
155
  * loader's id is literally `<other id>:<host>/<path>...`. Loader ids can
155
156
  * hold `:` (a dev id is a root-relative path, absolute `D:/...` on
@@ -178,8 +179,9 @@ async function resolveLoaderKey(
178
179
  // (cache-runtime pushes ctx.url.host). Without it, a multi-tenant host router
179
180
  // serving the same pathname for different hosts would leak one host's cached
180
181
  // loader data to another.
181
- const host = getRequestContext()?.url?.host ?? "localhost";
182
- const defaultKey = getDefaultLoaderCacheKey(loaderId, host, pathname, params);
182
+ const requestCtx = getRequestContext();
183
+ const host = requestCtx?.url?.host ?? "localhost";
184
+ const defaultKey = `loader:${loaderId}:${requestKeyBase(requestCtx, host, pathname, undefined, params)}`;
183
185
  if (options === false) return { key: defaultKey, declared: false };
184
186
  const keyFn = options.key;
185
187
  return {
@@ -256,26 +258,6 @@ function chainLoaderEntries(entry: EntryData): readonly LoaderEntry[] {
256
258
  return loaders;
257
259
  }
258
260
 
259
- /**
260
- * The ids of the loaders a request over the matched chain `entries` runs on
261
- * the live lane (no `ssr: false`), on a chain entry, its orphan layouts or
262
- * its parallel slots. A document HIT tail restores the record's pushes of
263
- * every other owner as authoritative (withCacheLookup, HandleStore
264
- * pushRestored); a live-lane loader is a hole, so its live run replaces its
265
- * recorded pushes instead. Memoized per entry.
266
- */
267
- export function liveLaneLoaderIds(
268
- entries: readonly EntryData[],
269
- ): ReadonlySet<string> {
270
- const ids = new Set<string>();
271
- for (const entry of entries) {
272
- for (const l of chainLoaderEntries(entry)) {
273
- if (!l.bake) ids.add(l.loader.$$id);
274
- }
275
- }
276
- return ids;
277
- }
278
-
279
261
  /**
280
262
  * The lane of each loader the matched chain `entries` registers: "live" (no
281
263
  * `ssr: false`) or "bake". A loader registered on both is "live". A PPR
@@ -295,6 +277,75 @@ export function routeLoaderLanes(
295
277
  return lanes;
296
278
  }
297
279
 
280
+ /**
281
+ * The pins this request serves loaders from, by loader id: the seed a
282
+ * document HIT tail (serveShellHit) or a navigation replay whose doc record
283
+ * hit (matchPartialWithPprReplay) armed from the shell entry's loader
284
+ * records. A capture serves none: its bake-lane loaders execute.
285
+ *
286
+ * The one answer to "is this loader served from the shell record on this
287
+ * request", for its value (resolveLoaderData) and for the record's copies of
288
+ * its handle pushes (loaderPins): both look the loader up here and nowhere
289
+ * else. A condition that makes pins, or one loader's pin, not apply belongs
290
+ * here or in the seed's decode (shell-snapshot.ts buildShellLoaderSeed),
291
+ * never after a lookup, or the two disagree: issues #1001 and #1003 were the
292
+ * pushes decided from the request's type while the value was decided from
293
+ * the pin.
294
+ */
295
+ function servedPins(
296
+ reqCtx: RequestContext<any> | undefined,
297
+ ): ReadonlyMap<string, ShellLoaderSeedEntry> | undefined {
298
+ return isShellCaptureActive(reqCtx) ? undefined : reqCtx?._shellLoaderSeed;
299
+ }
300
+
301
+ /**
302
+ * What a record restored on a request over the matched chain `entries` is to
303
+ * each loader's handle pushes (HandleStore RecordAuthority), from the pins
304
+ * the request serves (servedPins). Call it when the record hits: the pins are
305
+ * read here, once, because a navigation replay arms its seed for the match
306
+ * only and the store asks while loader bodies run.
307
+ *
308
+ * Each side applies its own lane to the pin. The value is per registration
309
+ * (resolveLoaderData: an `ssr: false` registration is served its pin). The
310
+ * pushes are per loader: one the route also registers without `ssr: false`
311
+ * is a hole whatever its other registration's pin says, because the capture
312
+ * credited its pushes to the live registration's run (routeLoaderLanes).
313
+ *
314
+ * A dependency the route does not register has no pin of its own, and the
315
+ * record does not name the loader that ran it at capture: its copies stand
316
+ * only while every `ssr: false` loader is pinned (exact when the pins are
317
+ * all there or all gone).
318
+ */
319
+ export function loaderPins(
320
+ entries: readonly EntryData[],
321
+ reqCtx: RequestContext<any> | undefined,
322
+ ): OwnedPushDelivery {
323
+ const pins = servedPins(reqCtx);
324
+ let lanes: ReadonlyMap<string, "live" | "bake"> | undefined;
325
+ let dependency: RecordAuthority | undefined;
326
+ return (loaderId) => {
327
+ lanes ??= routeLoaderLanes(entries);
328
+ const lane = lanes.get(loaderId);
329
+ if (lane === "live") return "hole";
330
+ if (lane === "bake") {
331
+ const pin = pins?.get(loaderId);
332
+ return pin ? (pin.complete ? "pin" : "copies") : "hole";
333
+ }
334
+ if (dependency === undefined) {
335
+ let bakeLane = 0;
336
+ let pinned = 0;
337
+ for (const [id, registered] of lanes) {
338
+ if (registered !== "bake") continue;
339
+ bakeLane++;
340
+ if (pins?.has(id)) pinned++;
341
+ }
342
+ dependency =
343
+ bakeLane > 0 && pinned === bakeLane ? "copies" : "placeholders";
344
+ }
345
+ return dependency;
346
+ };
347
+ }
348
+
298
349
  function getLoaderStore(
299
350
  loaderEntry: LoaderEntry,
300
351
  ): import("../../cache/types.js").SegmentCacheStore | null {
@@ -323,11 +374,12 @@ function getLoaderStore(
323
374
  * replay; and the store reads the body scope to tell a live push from a
324
375
  * replayed one.
325
376
  * A PPR shell capture records the replayed pushes under this loader (its
326
- * push funnel reads the pushReplayed owner, shell-capture.ts), so a shell
327
- * HIT restores them owned (HandleStore.pushRestored). This replay still
328
- * runs on that HIT: the store drops its settled values, which the record
329
- * already restored, and keeps the thenable ones the record could not keep
330
- * (a deferred push, the reason the loader's record carries `runs`).
377
+ * push funnel reads the pushReplayed owner, shell-capture.ts), so a replay
378
+ * of that shell that serves this loader from its pin restores them owned
379
+ * (HandleStore.pushRestored), on a document HIT and on a navigation alike.
380
+ * This replay still runs there: the store drops its settled values, which
381
+ * the record already restored, and keeps the thenable ones the record could
382
+ * not keep (a deferred push, the reason the loader's pin carries `runs`).
331
383
  */
332
384
  async function replayLoaderHandles(
333
385
  encoded: string | undefined,
@@ -338,34 +390,7 @@ async function replayLoaderHandles(
338
390
  ): Promise<void> {
339
391
  const recorded = encoded ? await decodeHandles(encoded) : {};
340
392
  if (!recorded) return;
341
- const delivers = new Map<string, boolean>();
342
- const claims = (owner: string): boolean => {
343
- let deliver = delivers.get(owner);
344
- if (deliver === undefined) {
345
- deliver = claim ? claim(owner) : true;
346
- delivers.set(owner, deliver);
347
- }
348
- return deliver;
349
- };
350
- const replay = (): void => {
351
- for (const key in recorded) {
352
- const owner = key.slice(key.indexOf(":") + 1);
353
- if (!claims(owner)) continue;
354
- for (const [handleName, values] of Object.entries(recorded[key])) {
355
- for (const value of values) {
356
- handleStore.pushReplayed(handleName, segmentId, value, owner);
357
- }
358
- }
359
- }
360
- };
361
- // A hole's entry, pushes or none, takes the place of the shell copies of
362
- // its pushes (HandleStore.redeliverReplays). Not once the hole delivered
363
- // in this request: a run a reader started first replaces them itself.
364
- if (claims(cachedLoaderId)) {
365
- handleStore.redeliverReplays(cachedLoaderId, replay);
366
- } else {
367
- replay();
368
- }
393
+ appendHandles(recorded, handleStore, segmentId, claim, cachedLoaderId);
369
394
  }
370
395
 
371
396
  /**
@@ -492,10 +517,11 @@ export function resolveLoaderData<TEnv>(
492
517
  return createMaskedLoaderPromise();
493
518
  }
494
519
 
495
- if (bakeSegmentKey) {
496
- const seed = reqCtx?._shellLoaderSeed;
497
- if (seed && seed.has(bakeSegmentKey)) {
498
- const recorded = seed.get(bakeSegmentKey)!;
520
+ // `bake`: the pin is the `ssr: false` registration's. The caller's key is
521
+ // passed for an unflagged loader of an entry without loading() too.
522
+ if (bakeSegmentKey && loaderEntry.bake) {
523
+ const recorded = servedPins(reqCtx)?.get(loaderEntry.loader.$$id);
524
+ if (recorded) {
499
525
  if (!recorded.holes) {
500
526
  // Pin-first (hole-free record): the pinned container is what the
501
527
  // payload serves (recorded paths win wholesale), resolved
@@ -734,7 +760,11 @@ function executeLoaderData<TEnv>(
734
760
  };
735
761
 
736
762
  const data = await readThroughItem({
737
- getItem: (k) => store.getItem!(k),
763
+ // A router.prerender() warm takes the miss path: the body runs and
764
+ // setItem replaces the entry.
765
+ getItem: isWarmReplace(requestCtxForExecute)
766
+ ? async () => null
767
+ : (k) => store.getItem!(k),
738
768
  // Handles ride the entry like "use cache" (encodeHandles: Flight, pending
739
769
  // pushes awaited up to its timeout, the whole blob dropped on a timeout
740
770
  // or a thrown encode). Encoded here, inside the deferred write, so a
@@ -761,7 +791,9 @@ function executeLoaderData<TEnv>(
761
791
  ...o,
762
792
  tags: entryTags.length > 0 ? entryTags : undefined,
763
793
  ...(handles ? { handles } : {}),
794
+ startedAt: start?.at,
764
795
  });
796
+ noteWarmWrite(requestCtxForExecute, "item");
765
797
  }),
766
798
  key,
767
799
  execute: async () => {
@@ -800,7 +832,8 @@ function executeLoaderData<TEnv>(
800
832
  });
801
833
 
802
834
  // An entry without handles (none recorded, or dropped by an encode
803
- // timeout) replays none, and still stands for a hole's shell copies.
835
+ // timeout) replays none, and still takes the place of the loader's
836
+ // placeholders (appendHandles): a claimed entry is the loader's source.
804
837
  if (cachedRead && handleStore && owningSegmentId) {
805
838
  await replayLoaderHandles(
806
839
  hitHandles,