@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
@@ -47,7 +47,7 @@ import { applyViewTransitionDefault } from "./view-transition-default.js";
47
47
  import { getRouterContext } from "../router-context.js";
48
48
  import { observeEvent, observeSegmentHandler } from "../instrument.js";
49
49
  import { runInSegmentTagScope } from "../../cache/cache-tag.js";
50
- import { observeStreamedHandler } from "./streamed-handler-telemetry.js";
50
+ import { trackStreamedHandler } from "./streamed-handler-boundary.js";
51
51
  import {
52
52
  track,
53
53
  RangoContext,
@@ -441,6 +441,8 @@ async function resolveParallelSlotComponent<TEnv>(args: {
441
441
  deps: SegmentResolutionDeps<TEnv>;
442
442
  routeKey: string;
443
443
  params: Record<string, string>;
444
+ /** Matched chain entry (where the sync path catches), else the slot's owner. */
445
+ boundaryEntry: EntryData;
444
446
  }): Promise<{ component: ReactNode | undefined; handlerRan: boolean }> {
445
447
  const {
446
448
  shouldResolve,
@@ -452,6 +454,7 @@ async function resolveParallelSlotComponent<TEnv>(args: {
452
454
  deps,
453
455
  routeKey,
454
456
  params,
457
+ boundaryEntry,
455
458
  } = args;
456
459
 
457
460
  let component: ReactNode | undefined;
@@ -484,19 +487,15 @@ async function resolveParallelSlotComponent<TEnv>(args: {
484
487
  : handler;
485
488
  if (result instanceof Promise) {
486
489
  warnOnStreamedResponse(result, parallelId);
487
- const tracked = deps.trackHandler(result, {
490
+ component = trackStreamedHandler(deps, result, {
491
+ boundaryEntry,
488
492
  segmentId: parallelId,
489
493
  segmentType: "parallel",
490
- });
491
- observeStreamedHandler(
492
- tracked,
493
- parallelId,
494
- "parallel",
495
- context.pathname,
494
+ context,
496
495
  routeKey,
497
496
  params,
498
- );
499
- component = tracked as ReactNode;
497
+ recover: true,
498
+ });
500
499
  } else {
501
500
  component = result as ReactNode;
502
501
  }
@@ -547,6 +546,8 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
547
546
  * to it: loader sub-ids are filtered out and slots are re-grouped by parent).
548
547
  */
549
548
  loaderOrder?: "after" | "before";
549
+ /** Matched chain entry being resolved; see ResolveSegmentOptions.boundaryEntry. */
550
+ boundaryEntry?: EntryData;
550
551
  },
551
552
  ): Promise<SegmentRevalidationResult> {
552
553
  const segments: ResolvedSegment[] = [];
@@ -696,6 +697,7 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
696
697
  deps,
697
698
  routeKey,
698
699
  params,
700
+ boundaryEntry: options?.boundaryEntry ?? entry,
699
701
  });
700
702
 
701
703
  segments.push({
@@ -865,19 +867,17 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
865
867
  if (result instanceof Promise) {
866
868
  warnOnStreamedResponse(result, routeEntry.id);
867
869
  result.finally(doneHandler).catch(() => {});
868
- const tracked = deps.trackHandler(result, {
869
- segmentId: entry.shortCode,
870
- segmentType: entry.type,
871
- });
872
- observeStreamedHandler(
873
- tracked,
874
- entry.shortCode,
875
- entry.type,
876
- context.pathname,
877
- routeKey,
878
- params,
879
- );
880
- return { content: tracked };
870
+ return {
871
+ content: trackStreamedHandler(deps, result, {
872
+ boundaryEntry: entry,
873
+ segmentId: entry.shortCode,
874
+ segmentType: entry.type,
875
+ context,
876
+ routeKey,
877
+ params,
878
+ recover: true,
879
+ }),
880
+ };
881
881
  }
882
882
  doneHandler();
883
883
  return { content: result };
@@ -1098,6 +1098,8 @@ export async function resolveSegmentWithRevalidation<TEnv>(
1098
1098
  deps,
1099
1099
  actionContext,
1100
1100
  stale,
1101
+ undefined,
1102
+ entry,
1101
1103
  );
1102
1104
  segments.push(...orphanResult.segments);
1103
1105
  matchedIds.push(...orphanResult.matchedIds);
@@ -1126,6 +1128,8 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
1126
1128
  stale?: boolean,
1127
1129
  /** Parent route entry — its loaders are inherited so parallel slots can access them. */
1128
1130
  parentRouteEntry?: EntryData,
1131
+ /** Matched chain entry being resolved (see ResolveSegmentOptions.boundaryEntry); defaults to parentRouteEntry. */
1132
+ boundaryEntry: EntryData | undefined = parentRouteEntry,
1129
1133
  ): Promise<SegmentRevalidationResult> {
1130
1134
  invariant(
1131
1135
  orphan.type === "layout" || orphan.type === "cache",
@@ -1286,7 +1290,11 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
1286
1290
  deps,
1287
1291
  actionContext,
1288
1292
  stale,
1289
- { parentChainDefault: "force-render", loaderOrder: "before" },
1293
+ {
1294
+ parentChainDefault: "force-render",
1295
+ loaderOrder: "before",
1296
+ boundaryEntry,
1297
+ },
1290
1298
  );
1291
1299
  segments.push(...parallelResult.segments);
1292
1300
  matchedIds.push(...parallelResult.matchedIds);
@@ -1307,6 +1315,8 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
1307
1315
  deps,
1308
1316
  actionContext,
1309
1317
  stale,
1318
+ undefined,
1319
+ boundaryEntry,
1310
1320
  );
1311
1321
  segments.push(...nestedResult.segments);
1312
1322
  matchedIds.push(...nestedResult.matchedIds);
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Streamed handlers (loading()): track, observe, and recover a rejection into
3
+ * the declared errorBoundary() / notFoundBoundary() fallback.
4
+ *
5
+ * Invariants:
6
+ * - No status write and no onError call: the status is committed at first
7
+ * flush, and trackHandler already reports each rejection once.
8
+ * - The recovered promise resolves, so Flight reports no error. The writers
9
+ * that refuse a failed render through Flight errors need the failure
10
+ * recorded here: `_renderErrors` for the document cache and a prerender
11
+ * warm, `_recoveredHandlerErrors` for cacheRoute (cache(), and the doc record
12
+ * a PPR capture needs). The shell capture and the SWR re-render own both,
13
+ * so a derived render never writes into (or reads) the foreground's.
14
+ * - Nothing to render to rethrows the original rejection: no boundary found, a
15
+ * thrown Response (redirect), a Skip, a throwing fallback.
16
+ * - No recovery when `recover` is false (prerender throwOnError, the intercept
17
+ * background re-render): those lanes must reject so nothing is baked/cached.
18
+ */
19
+
20
+ import type { ReactNode } from "react";
21
+ import { isDataNotFoundError } from "../../errors.js";
22
+ import {
23
+ createErrorInfo,
24
+ createNotFoundInfo,
25
+ renderErrorFallback,
26
+ renderNotFoundFallback,
27
+ resolveNotFoundFallback,
28
+ } from "../error-handling.js";
29
+ import { _getRequestContext } from "../../server/request-context.js";
30
+ import type { EntryData } from "../../server/context.js";
31
+ import type { ErrorInfo, HandlerContext } from "../../types/index.js";
32
+ import type { SegmentResolutionDeps } from "../types.js";
33
+ import { observeStreamedHandler } from "./streamed-handler-telemetry.js";
34
+
35
+ export interface StreamedHandlerOptions<TEnv> {
36
+ /**
37
+ * Entry to look boundaries up from: the matched chain entry being resolved
38
+ * (where the sync path catches, whatever routeless layout owns a slot), or
39
+ * the intercept's declaring entry.
40
+ */
41
+ boundaryEntry: EntryData;
42
+ segmentId: string;
43
+ segmentType: ErrorInfo["segmentType"];
44
+ context: HandlerContext<any, TEnv>;
45
+ routeKey?: string;
46
+ params: Record<string, string>;
47
+ /** False where the rejection must reach the caller (prerender, intercept background re-render). */
48
+ recover: boolean;
49
+ }
50
+
51
+ const NOOP = (): void => {};
52
+
53
+ /**
54
+ * Track a streamed handler promise and return the segment component: the
55
+ * tracked promise itself when there is nothing to recover to, else a promise
56
+ * that resolves to the declared fallback on rejection.
57
+ */
58
+ export function trackStreamedHandler<TEnv>(
59
+ deps: SegmentResolutionDeps<TEnv>,
60
+ result: Promise<unknown>,
61
+ options: StreamedHandlerOptions<TEnv>,
62
+ ): ReactNode {
63
+ const { segmentId, segmentType, context } = options;
64
+ const tracked = deps.trackHandler(result as Promise<ReactNode>, {
65
+ segmentId,
66
+ segmentType,
67
+ });
68
+ observeStreamedHandler(
69
+ tracked,
70
+ segmentId,
71
+ segmentType,
72
+ context.pathname,
73
+ options.routeKey,
74
+ options.params,
75
+ );
76
+
77
+ if (!options.recover) return tracked as ReactNode;
78
+ const { boundaryEntry } = options;
79
+ const hasBoundary =
80
+ deps.findNearestErrorBoundary(boundaryEntry) != null ||
81
+ deps.findNearestNotFoundBoundary(boundaryEntry) != null ||
82
+ deps.notFoundComponent !== undefined;
83
+ if (!hasBoundary) return tracked as ReactNode;
84
+
85
+ // Captured now: the rejection can settle outside the request's ALS frame.
86
+ const reqCtx = _getRequestContext();
87
+ const recovered = tracked.catch((error: unknown): ReactNode => {
88
+ const node = renderBoundary(error, deps, options, boundaryEntry);
89
+ if (node === undefined) throw error;
90
+ if (reqCtx)
91
+ (reqCtx._recoveredHandlerErrors ??= new Map()).set(segmentId, error);
92
+ reqCtx?._renderErrors?.push(error);
93
+ return node;
94
+ });
95
+ // A dropped segment (never rendered) must not become an unhandledRejection.
96
+ recovered.catch(NOOP);
97
+ return recovered as ReactNode;
98
+ }
99
+
100
+ function renderBoundary<TEnv>(
101
+ error: unknown,
102
+ deps: SegmentResolutionDeps<TEnv>,
103
+ options: StreamedHandlerOptions<TEnv>,
104
+ boundaryEntry: EntryData,
105
+ ): ReactNode | undefined {
106
+ if (error instanceof Response) return undefined;
107
+ if ((error as { name?: string } | null)?.name === "Skip") return undefined;
108
+ const { segmentId, segmentType, context } = options;
109
+ try {
110
+ if (isDataNotFoundError(error)) {
111
+ const boundary = deps.findNearestNotFoundBoundary(boundaryEntry);
112
+ if (boundary == null && deps.notFoundComponent === undefined) {
113
+ return undefined;
114
+ }
115
+ return renderNotFoundFallback(
116
+ resolveNotFoundFallback(
117
+ boundary,
118
+ deps.notFoundComponent,
119
+ context.pathname,
120
+ ),
121
+ createNotFoundInfo(error, segmentId, segmentType, context.pathname),
122
+ );
123
+ }
124
+ const fallback = deps.findNearestErrorBoundary(boundaryEntry);
125
+ if (fallback == null) return undefined;
126
+ return renderErrorFallback(
127
+ fallback,
128
+ createErrorInfo(error, segmentId, segmentType),
129
+ );
130
+ } catch {
131
+ return undefined;
132
+ }
133
+ }
@@ -25,6 +25,8 @@ export interface TrieMatchResult {
25
25
  pr?: true;
26
26
  /** Passthrough: handler kept for live fallback on unknown params */
27
27
  pt?: true;
28
+ /** On-demand prerender eligible (router.prerender() refreshable) */
29
+ od?: true;
28
30
  /** Response type for non-RSC routes (json, text, image, any) */
29
31
  responseType?: string;
30
32
  /** Negotiate variants: response-type routes sharing this path */
@@ -343,6 +345,7 @@ function validateAndBuild(
343
345
  if (redirectTo) result.redirectTo = redirectTo;
344
346
  if (leaf.pr) result.pr = true;
345
347
  if (leaf.pt) result.pt = true;
348
+ if (leaf.od) result.od = true;
346
349
  if (leaf.rt) result.responseType = leaf.rt;
347
350
  if (leaf.nv) result.negotiateVariants = leaf.nv;
348
351
  if (leaf.rf) result.rscFirst = true;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Per-router cache versions: the shapes shared by the build (which computes
3
+ * them, vite/discovery/build-versions.ts) and the runtime (which reads them,
4
+ * server/build-version-table.ts). No imports, so the Vite plugin bundle can
5
+ * use it without resolving the `@rangojs/router:version` virtual module.
6
+ *
7
+ * Design: docs/design/per-app-cache-version.md.
8
+ */
9
+
10
+ /**
11
+ * The two versions one `createRouter()` serves with.
12
+ *
13
+ * - `data` keys cached RSC data: segment entries, `"use cache"` values and
14
+ * loader data. It follows the router's server code only.
15
+ * - `document` keys stored HTML (PPR shells, document-cache responses), is the
16
+ * value the browser echoes as `_rsc_v`, and stamps `ShellCacheEntry.buildVersion`.
17
+ * It follows the server code and the client asset URLs.
18
+ */
19
+ export interface RouterVersions {
20
+ readonly data: string;
21
+ readonly document: string;
22
+ }
23
+
24
+ /**
25
+ * The build's table as the `@rangojs/router:version` module exports it:
26
+ * router id to `[data, document]`. {@link DEFAULT_ROUTER_VERSIONS_KEY} holds
27
+ * the whole-build pair, used for a router the build could not attribute.
28
+ */
29
+ export type RouterVersionsTable = Readonly<
30
+ Record<string, readonly [data: string, document: string]>
31
+ >;
32
+
33
+ /** Table key of the whole-build versions. Not a legal `$$id` (8 hex chars). */
34
+ export const DEFAULT_ROUTER_VERSIONS_KEY = "*";
35
+
36
+ /** One value for both versions: a consumer-set `version`, or the dev stamp. */
37
+ export function uniformVersions(version: string): RouterVersions {
38
+ return { data: version, document: version };
39
+ }
40
+
41
+ /**
42
+ * The versions a router serves with under `table`: a consumer-set `version`
43
+ * for both; else the router's own entry; else the whole-build pair, for a
44
+ * router the table does not list. `undefined` when there is no table (dev,
45
+ * tests) or it has neither entry. One function for the request handler and
46
+ * the build's shell capture, which must stamp what the handler will check.
47
+ */
48
+ export function resolveVersionsFrom(
49
+ table: RouterVersionsTable | undefined,
50
+ routerId: string | undefined,
51
+ override: string | undefined,
52
+ ): RouterVersions | undefined {
53
+ if (override !== undefined) return uniformVersions(override);
54
+ if (!table) return undefined;
55
+ const pair =
56
+ (routerId !== undefined && Object.hasOwn(table, routerId)
57
+ ? table[routerId]
58
+ : undefined) ?? table[DEFAULT_ROUTER_VERSIONS_KEY];
59
+ return pair ? { data: pair[0], document: pair[1] } : undefined;
60
+ }
package/src/router.ts CHANGED
@@ -14,6 +14,14 @@ import {
14
14
  isValidDevDiscoveryEpoch,
15
15
  } from "./dev-discovery-protocol.js";
16
16
  import { createReverse, type ReverseFunction } from "./reverse.js";
17
+ import {
18
+ resolvePrerenderVersion,
19
+ resolveRouterVersions,
20
+ } from "./server/build-version-table.js";
21
+ import { isDevEnvironment } from "./errors.js";
22
+ import { isViteDevServer } from "./cache/store-scope.js";
23
+ import { createPrerenderTrigger } from "./prerender/create-prerender-trigger.js";
24
+ import { createMemoryPrerenderStore } from "./prerender/memory-prerender-store.js";
17
25
  import {
18
26
  registerRouteMap,
19
27
  getRouterManifest,
@@ -155,6 +163,7 @@ export function createRouter<TEnv = any>(
155
163
  notFound,
156
164
  onError,
157
165
  cache,
166
+ prerender: prerenderConfigOption,
158
167
  cacheProfiles: cacheProfilesOption,
159
168
  theme: themeOption,
160
169
  urls: urlsOption,
@@ -181,6 +190,16 @@ export function createRouter<TEnv = any>(
181
190
  strictMode: strictModeOption = true,
182
191
  } = options;
183
192
 
193
+ const defaultDevPrerenderStore = prerenderConfigOption
194
+ ? undefined
195
+ : createMemoryPrerenderStore();
196
+ const effectivePrerenderConfigOption =
197
+ prerenderConfigOption ??
198
+ ((() =>
199
+ isViteDevServer() && defaultDevPrerenderStore
200
+ ? { store: defaultDevPrerenderStore }
201
+ : undefined) as RangoOptions<TEnv>["prerender"]);
202
+
184
203
  // Debug cache signal gate (DEVELOPMENT/TEST ONLY). Enabled by the
185
204
  // debugCacheSignal option OR the RANGO_TEST_SIGNALS=1 env flag. When off,
186
205
  // no X-Rango-Cache header is emitted and output is byte-identical.
@@ -744,6 +763,77 @@ export function createRouter<TEnv = any>(
744
763
  );
745
764
  }
746
765
 
766
+ // Shared reverse function (also the value behind router.reverse).
767
+ const reverseFn = createReverse(mergedRouteMap);
768
+ const reverseLoose = reverseFn as unknown as (
769
+ name: string,
770
+ params?: Record<string, string>,
771
+ ) => string;
772
+
773
+ // On-demand prerender binder (router.prerender({ env, ctx }) -> runner with .many / .markStale).
774
+ // Requestless: runProducer runs matchForPrerender with onDemand=true (arming
775
+ // the personalization guard); the store/config resolve per call from {env,ctx}.
776
+ // The key version is the router's data version, resolved by the same call the
777
+ // handler's overlay read makes (resolvePrerenderVersion), so the keys agree.
778
+ const prerenderTrigger = createPrerenderTrigger<TEnv, {}>({
779
+ routerId,
780
+ resolveVersion: () => resolvePrerenderVersion(routerId, version),
781
+ isDev: isDevEnvironment,
782
+ isViteDevServer,
783
+ ensureManifest: () => ensureRouterManifest(routerId),
784
+ resolveConfig: (env, ctx) => {
785
+ const opt = effectivePrerenderConfigOption;
786
+ if (!opt) return undefined;
787
+ return typeof opt === "function" ? opt(env, ctx) : opt;
788
+ },
789
+ reverse: (route, params) => {
790
+ try {
791
+ return reverseLoose(route, params);
792
+ } catch {
793
+ return undefined;
794
+ }
795
+ },
796
+ matchRoute: async (pathname) => {
797
+ const m = await findMatch(pathname);
798
+ if (!m) return null;
799
+ return {
800
+ routeName: m.routeKey,
801
+ params: m.params,
802
+ isOnDemand: m.od === true,
803
+ isPassthrough: m.pt === true,
804
+ };
805
+ },
806
+ runProducer: ({ pathname, isPassthrough, env, dev }) =>
807
+ _matchForPrerender<TEnv>(
808
+ pathname,
809
+ {},
810
+ {
811
+ ...prerenderDeps,
812
+ versions: resolveRouterVersions(routerId, version),
813
+ },
814
+ undefined,
815
+ isPassthrough,
816
+ env,
817
+ dev,
818
+ true,
819
+ ),
820
+ // A warm goes through router.fetch's handler: the one createRouter's own
821
+ // cache, nonce and version configure. An entry that passes them to
822
+ // createRSCHandler itself is not what a warm runs.
823
+ resolveCacheConfig: (env, ctx) =>
824
+ typeof cache === "function" ? cache(env, ctx) : cache,
825
+ // Called from inside a request, the warm leaves that request's scopes:
826
+ // it runs in the context the request entered the handler with
827
+ // (RequestContext._runAtRequestEntry).
828
+ fetch: (request, input) => {
829
+ const atRequestEntry = _getRequestContext()?._runAtRequestEntry;
830
+ return atRequestEntry
831
+ ? atRequestEntry(() => router.fetch(request, input))
832
+ : router.fetch(request, input);
833
+ },
834
+ ambientOrigin: () => _getRequestContext()?.url.origin,
835
+ });
836
+
747
837
  // Create match handler functions bound to router state
748
838
  const matchHandlers = createMatchHandlers<TEnv>({
749
839
  buildRouterContext,
@@ -866,6 +956,7 @@ export function createRouter<TEnv = any>(
866
956
  // Collect route keys that have prerender handlers (for non-trie match path)
867
957
  let prerenderRouteKeys: Set<string> | undefined;
868
958
  let passthroughRouteKeys: Set<string> | undefined;
959
+ let onDemandRouteKeys: Set<string> | undefined;
869
960
  for (const [name, entry] of manifest.entries()) {
870
961
  if (entry.type === "route" && entry.isPrerender) {
871
962
  if (!prerenderRouteKeys) prerenderRouteKeys = new Set();
@@ -874,6 +965,10 @@ export function createRouter<TEnv = any>(
874
965
  if (!passthroughRouteKeys) passthroughRouteKeys = new Set();
875
966
  passthroughRouteKeys.add(name);
876
967
  }
968
+ if (entry.isOnDemand === true) {
969
+ if (!onDemandRouteKeys) onDemandRouteKeys = new Set();
970
+ onDemandRouteKeys.add(name);
971
+ }
877
972
  }
878
973
  }
879
974
 
@@ -900,6 +995,7 @@ export function createRouter<TEnv = any>(
900
995
  cacheProfiles: resolvedCacheProfiles,
901
996
  ...(prerenderRouteKeys ? { prerenderRouteKeys } : {}),
902
997
  ...(passthroughRouteKeys ? { passthroughRouteKeys } : {}),
998
+ ...(onDemandRouteKeys ? { onDemandRouteKeys } : {}),
903
999
  });
904
1000
  }
905
1001
  } else {
@@ -920,6 +1016,7 @@ export function createRouter<TEnv = any>(
920
1016
  cacheProfiles: resolvedCacheProfiles,
921
1017
  ...(prerenderRouteKeys ? { prerenderRouteKeys } : {}),
922
1018
  ...(passthroughRouteKeys ? { passthroughRouteKeys } : {}),
1019
+ ...(onDemandRouteKeys ? { onDemandRouteKeys } : {}),
923
1020
  });
924
1021
  }
925
1022
 
@@ -1020,7 +1117,7 @@ export function createRouter<TEnv = any>(
1020
1117
  // Type-safe URL builder using merged route map
1021
1118
  // Types are tracked through the builder chain via TRoutes parameter
1022
1119
  // Seeded with static route names from the generated file (injected by Vite)
1023
- reverse: createReverse(mergedRouteMap),
1120
+ reverse: reverseFn,
1024
1121
 
1025
1122
  // Expose accumulated route map for typeof extraction
1026
1123
  // Returns {} initially, but builder chain accumulates specific route types
@@ -1037,6 +1134,15 @@ export function createRouter<TEnv = any>(
1037
1134
  // Expose cache configuration for RSC handler
1038
1135
  cache,
1039
1136
 
1137
+ // Expose the prerender store config (durable overlay) for the RSC handler to
1138
+ // resolve per request. Stored under _prerenderConfig because `prerender` on
1139
+ // the instance is the trigger method (router.prerender()).
1140
+ _prerenderConfig: effectivePrerenderConfigOption,
1141
+
1142
+ // On-demand prerender trigger: router.prerender({ env, ctx })(target),
1143
+ // plus .many() and .markStale() on the runner.
1144
+ prerender: prerenderTrigger,
1145
+
1040
1146
  // Expose notFound component for RSC handler
1041
1147
  notFound,
1042
1148
 
@@ -52,8 +52,12 @@ export function buildFullPayload(
52
52
  rootLayout: ctx.router.rootLayout,
53
53
  // Full render: resolve deferred handle values server-side so SSR markup and
54
54
  // the first sync useHandle read see resolved values. Partial payloads
55
- // (rsc-rendering.ts) keep streaming (handleStore.stream()).
56
- handles: resolvedHandleStream(handleStore),
55
+ // (rsc-rendering.ts) keep streaming (handleStore.stream()). A shell
56
+ // capture renders from what its record keeps (resolvedHandleStream).
57
+ handles: resolvedHandleStream(
58
+ handleStore,
59
+ reqCtx._shellCaptureRun === true,
60
+ ),
57
61
  // Post-handler-barrier pushes (streaming loader ctx.handle() writes)
58
62
  // stream here; the client applies them after hydration. Instantly
59
63
  // complete when the loader lane is idle — including PPR shell capture,
@@ -14,6 +14,11 @@ import type { SSRStreamMode } from "../router/router-options.js";
14
14
 
15
15
  export interface HandlerContext<TEnv = unknown> {
16
16
  router: RangoInternal<TEnv, any>;
17
+ /**
18
+ * The router's DOCUMENT version (router-versions.ts): payload metadata, the
19
+ * `_rsc_v` reload check, and PPR shell stamping and gating. The data version
20
+ * is not here; cache stores read it from the request context.
21
+ */
17
22
  version: string;
18
23
  devDiscoveryEpoch?: number;
19
24
  renderToReadableStream: RSCDependencies["renderToReadableStream"];