@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
@@ -88,9 +88,20 @@ async function resolveValues(values: unknown[]): Promise<unknown[]> {
88
88
  * A hung handle promise blocks the full render like any unresolved await (no
89
89
  * handle-specific timeout). Partial / action payloads keep streaming via
90
90
  * `handleStore.stream()`.
91
+ *
92
+ * `recordedOnly` is a PPR shell capture's render (full-payload.ts): the
93
+ * snapshot is cut down to the pushes the capture's record keeps
94
+ * (getDataForSegment's `excludeLoaderPushes`, the filter captureHandles
95
+ * writes the record with). A HIT restores that record and hydrates with it,
96
+ * so a push the record leaves out must not be in the prelude either: it
97
+ * rendered an element no HIT's hydration data matched (issue #1035). Which
98
+ * pushes a record keeps is the capture funnel's decision (shell-capture.ts
99
+ * deriveShellCaptureContext: today it leaves out a deferred loader push and
100
+ * one holding a masked promise), not this function's.
91
101
  */
92
102
  export async function* resolvedHandleStream(
93
103
  handleStore: HandleStore,
104
+ recordedOnly?: boolean,
94
105
  ): AsyncGenerator<HandleData, void, unknown> {
95
106
  // Drain stream() (NOT getData()) for the converged snapshot: consuming a
96
107
  // stream arms the store's late-push guard (LateHandlePushError for pushes
@@ -109,9 +120,34 @@ export async function* resolvedHandleStream(
109
120
  for await (const data of handleStore.stream("settled")) {
110
121
  snapshot = data;
111
122
  }
123
+ if (recordedOnly) snapshot = recordedHandleData(handleStore, snapshot);
112
124
  yield await resolveDeferredHandleValues(snapshot);
113
125
  }
114
126
 
127
+ /**
128
+ * `snapshot` without the pushes a record leaves out. A shell capture renders
129
+ * (resolvedHandleStream) and waits (settleCaptureRecord) on this cut only: a
130
+ * promise the record drops has no slot in the prelude, and waiting for it
131
+ * could refuse a capture for a value the shell does not hold.
132
+ */
133
+ export function recordedHandleData(
134
+ handleStore: HandleStore,
135
+ snapshot: HandleData,
136
+ ): HandleData {
137
+ const segmentIds = new Set<string>();
138
+ for (const handleName in snapshot) {
139
+ for (const segmentId in snapshot[handleName]) segmentIds.add(segmentId);
140
+ }
141
+ const recorded: HandleData = {};
142
+ for (const segmentId of segmentIds) {
143
+ const kept = handleStore.getDataForSegment(segmentId, true);
144
+ for (const handleName in kept) {
145
+ (recorded[handleName] ??= {})[segmentId] = kept[handleName];
146
+ }
147
+ }
148
+ return recorded;
149
+ }
150
+
115
151
  /**
116
152
  * Resolve the deferred (Promise) values in ONE segment's handle bucket
117
153
  * (`handleName -> entries[]`). Same shallow + drop-rejections rule as
@@ -0,0 +1,12 @@
1
+ // Turns on the suspense audit (suspense-audit.ts) in a dev build. Only this
2
+ // repo sets INTERNAL_RANGO_SUSPENSE_AUDIT: the e2e webServers and fixtures of
3
+ // the router test-app and cloudflare-basic, and the router's vitest config.
4
+ // A consumer's dev server and tests never print the audit's errors.
5
+ //
6
+ // Runtime fallback for non-Vite contexts (vitest, Node). In the Vite pipeline
7
+ // the discovery plugin replaces this module with the build-time-resolved flag
8
+ // (vite/inject-client-debug.ts), as it does for internal-debug.ts. Keep this
9
+ // module to the single export: the transform replaces the whole file.
10
+ export const INTERNAL_RANGO_SUSPENSE_AUDIT: boolean =
11
+ typeof process !== "undefined" &&
12
+ Boolean((process as any).env?.INTERNAL_RANGO_SUSPENSE_AUDIT);
@@ -29,9 +29,10 @@
29
29
  * reclaim a bucket that is about to be reused, and is held until any
30
30
  * in-flight load settles.
31
31
  *
32
- * The store is intentionally module-level: each browser tab is its own JS
33
- * realm, so there is no cross-request pollution. Server renders never mutate
34
- * the store — the hook falls back to `OutletContext.loaderData`.
32
+ * Each router's NavigationStore owns one (navigation-store.ts), and hooks reach
33
+ * it through NavigationStoreContext: the router store is the single source of
34
+ * truth. Server renders have no store — the hook falls back to
35
+ * `OutletContext.loaderData`.
35
36
  */
36
37
 
37
38
  import { unwrapsSynchronously } from "./thenable-status.js";
@@ -597,10 +598,4 @@ export class LoaderStore {
597
598
  }
598
599
  }
599
600
 
600
- /**
601
- * Module-level singleton. Each browser tab gets its own; SSR never mutates it.
602
- * The hook falls through to `OutletContext.loaderData` during the server render.
603
- */
604
- export const loaderStore: LoaderStore = new LoaderStore();
605
-
606
601
  export const EMPTY_LOADER_SNAPSHOT: LoaderEntry = EMPTY_SNAPSHOT;
@@ -1,8 +1,14 @@
1
1
  "use client";
2
2
 
3
- import { useContext, useMemo, type ReactNode } from "react";
3
+ import {
4
+ useContext,
5
+ useMemo,
6
+ type ComponentProps,
7
+ type ReactNode,
8
+ } from "react";
4
9
  import { OutletContext, type OutletContextValue } from "./outlet-context.js";
5
10
  import type { ResolvedSegment } from "./types.js";
11
+ import { AuditedOutlet } from "./suspense-audit-react.js";
6
12
 
7
13
  /**
8
14
  * Outlet content provider — stores parent context for useLoader chain walking.
@@ -57,3 +63,21 @@ export function OutletProvider({
57
63
  <OutletContext.Provider value={value}>{children}</OutletContext.Provider>
58
64
  );
59
65
  }
66
+
67
+ // Dev only (suspense-audit.ts): OutletProvider with the segment's mount
68
+ // tracked under it. Chosen where the element is created, inside a NODE_ENV
69
+ // test: see AuditedRouteContent (route-content-wrapper.tsx).
70
+ export function AuditedOutletProvider({
71
+ children,
72
+ ...props
73
+ }: ComponentProps<typeof OutletProvider>): ReactNode {
74
+ return (
75
+ <OutletProvider {...props}>
76
+ {props.segment ? (
77
+ <AuditedOutlet segmentId={props.segment.id}>{children}</AuditedOutlet>
78
+ ) : (
79
+ children
80
+ )}
81
+ </OutletProvider>
82
+ );
83
+ }
@@ -21,6 +21,7 @@
21
21
  */
22
22
 
23
23
  import type { ShellCacheEntry } from "../cache/types.js";
24
+ import type { RouterVersions } from "../router-versions.js";
24
25
  import type { MatchResult } from "../types.js";
25
26
  import { MemorySegmentCacheStore } from "../cache/memory-segment-store.js";
26
27
  import {
@@ -105,13 +106,14 @@ export interface BuildShellCaptureOptions {
105
106
  /** Build-time env bindings (rango plugin buildEnv), if configured. */
106
107
  buildEnv?: unknown;
107
108
  /**
108
- * The MAIN build's version (the version plugin's value folded into the
109
- * shipped worker) — NOT the temp server's own version-plugin value. The
110
- * serve-side isValidShellHit gate compares entry.buildVersion against the
111
- * running worker's ctx.version; stamping the temp server's would make every
112
- * build entry an eternal MISS.
109
+ * The versions the SHIPPED build serves this router with (the build table,
110
+ * or the router's consumer-set version) — NOT the temp server's own
111
+ * version-plugin stamp. The serve-side isValidShellHit gate compares
112
+ * entry.buildVersion against the running worker's ctx.version, which is the
113
+ * document version; stamping the temp server's would make every build entry
114
+ * an eternal MISS.
113
115
  */
114
- buildVersion: string;
116
+ versions: RouterVersions;
115
117
  /**
116
118
  * The SSR half, composed by the plugin from the temp server's SSR
117
119
  * environment runner (react-dom/static prerender + Flight client), with the
@@ -192,7 +194,7 @@ async function attemptBuildCapture(
192
194
  cacheStore: new MemorySegmentCacheStore(),
193
195
  themeConfig: router.themeConfig ?? null,
194
196
  stateCookieName: router.resolvedStateCookieName,
195
- version: opts.buildVersion,
197
+ versions: opts.versions,
196
198
  });
197
199
  // Scope registry lookups (root-scope/search-schema) per router during the
198
200
  // bake, mirroring rsc/handler.ts on the request path (#762).
@@ -215,7 +217,7 @@ async function attemptBuildCapture(
215
217
 
216
218
  const descriptor: ShellCaptureDescriptor = {
217
219
  key: opts.key,
218
- buildVersion: opts.buildVersion,
220
+ buildVersion: opts.versions.document,
219
221
  ttl: opts.ttl,
220
222
  swr: opts.swr,
221
223
  tags: opts.tags,
@@ -448,7 +450,7 @@ async function runBuildCaptureFinal(
448
450
  // buildFullPayload reads only ctx.router.* and ctx.version.
449
451
  {
450
452
  router,
451
- version: opts.buildVersion,
453
+ version: opts.versions.document,
452
454
  } as unknown as HandlerContext<any>,
453
455
  url,
454
456
  derivedCtx,
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Cloudflare KV writable prerender store (`@rangojs/router/prerender/cloudflare`).
3
+ *
4
+ * Backs the durable overlay for on-demand prerender on Cloudflare. Self-contained
5
+ * (no CFCacheStore construction constraints): entries are stored under the
6
+ * versioned key `prerender:{routerId}:{version}:{routeName}:{paramHash}`, and
7
+ * tag invalidation uses the same timestamp-marker technique as the runtime cache
8
+ * — a per-tag KV marker holding the ms epoch of the last invalidation; a read
9
+ * treats the entry as stale (never deleted) when any of its tags' markers is at
10
+ * or after the entry's storedAt.
11
+ *
12
+ * Key design constraints (see docs/design/ondemand-prerender.md, Store Model):
13
+ * - staleAt is SOFT metadata; entries are written with NO KV expirationTtl. Hard
14
+ * expiry would delete the entry SWR needs and re-expose older bundled-manifest
15
+ * content below the overlay.
16
+ * - Prerender keys are route + params + variant only — never host-namespaced, so
17
+ * a refresh from a different host than the serve request still hits.
18
+ * - Invalidation is mark-stale, not delete (deleting would re-expose the manifest
19
+ * entry as the result of an "invalidation").
20
+ * - Tag markers use a prerender-specific prefix, separate from the runtime cache's
21
+ * `updateTag()`/`revalidateTag()` namespace, and are scoped by router id
22
+ * (`{prefix}{encodeURIComponent(routerId)}/{tag}`): routers sharing one KV
23
+ * namespace (an app behind a host router) must not mark each other's entries.
24
+ * - Markers carry no expiry: an entry without a `ttl` never goes stale by
25
+ * itself, so an expired marker would silently drop an invalidation.
26
+ */
27
+
28
+ import type { KVNamespace } from "../cache/cf/cf-cache-types.js";
29
+ import {
30
+ lowerStoredEntryStaleAt,
31
+ serializePrerenderKey,
32
+ type PrerenderKey,
33
+ type PrerenderStoredEntry,
34
+ type WritablePrerenderStore,
35
+ } from "./writable-store.js";
36
+
37
+ /** Prerender tag markers live in their own namespace, separate from the runtime cache. */
38
+ const PRERENDER_TAG_MARKER_PREFIX = "__rango_pr_tag__/";
39
+
40
+ export interface KVPrerenderStoreOptions {
41
+ /** Injectable clock for `markStale` markers in deterministic tests. Defaults to Date.now. */
42
+ now?: () => number;
43
+ }
44
+
45
+ /**
46
+ * Create a {@link WritablePrerenderStore} backed by a Cloudflare KV namespace.
47
+ *
48
+ * @example
49
+ * ```ts
50
+ * import { createKVPrerenderStore } from "@rangojs/router/prerender/cloudflare";
51
+ *
52
+ * createRouter<Env>({
53
+ * prerender: (env) => ({
54
+ * store: createKVPrerenderStore(env.PRERENDER_KV),
55
+ * ttl: 3600,
56
+ * onRevalidate: (target, e) => e.PRERENDER_QUEUE.send({ target }),
57
+ * }),
58
+ * });
59
+ * ```
60
+ */
61
+ export function createKVPrerenderStore(
62
+ kv: KVNamespace,
63
+ options: KVPrerenderStoreOptions = {},
64
+ ): WritablePrerenderStore {
65
+ const now = options.now ?? (() => Date.now());
66
+ function markerKey(routerId: string, tag: string): string {
67
+ // An encoded id holds no "/", so the id/tag split is unambiguous.
68
+ return `${PRERENDER_TAG_MARKER_PREFIX}${encodeURIComponent(routerId)}/${tag}`;
69
+ }
70
+
71
+ async function readTagMarker(
72
+ routerId: string,
73
+ tag: string,
74
+ ): Promise<number | null> {
75
+ const raw = await kv.get(markerKey(routerId, tag));
76
+ if (!raw) return null;
77
+ const n = Number(raw);
78
+ return Number.isFinite(n) ? n : null;
79
+ }
80
+
81
+ return {
82
+ async get(key: PrerenderKey): Promise<PrerenderStoredEntry | null> {
83
+ const raw = await kv.get(serializePrerenderKey(key));
84
+ if (!raw) return null;
85
+
86
+ let stored: PrerenderStoredEntry;
87
+ try {
88
+ stored = JSON.parse(raw) as PrerenderStoredEntry;
89
+ } catch {
90
+ // Corrupt entry: treat as a miss rather than crash the serve path.
91
+ return null;
92
+ }
93
+
94
+ // Tag invalidation is mark-stale: a tag marked at or after the write
95
+ // forces the entry stale (it still serves). Shape is verified by the
96
+ // router; an already-stale entry skips the marker reads.
97
+ const meta = stored?.meta;
98
+ if (
99
+ meta?.tags?.length &&
100
+ !(meta.staleAt != null && meta.staleAt <= now())
101
+ ) {
102
+ const markers = await Promise.all(
103
+ meta.tags.map((tag) => readTagMarker(key.routerId, tag)),
104
+ );
105
+ if (markers.some((m) => m != null && m >= meta.storedAt)) {
106
+ lowerStoredEntryStaleAt(stored, meta.storedAt);
107
+ }
108
+ }
109
+
110
+ return stored;
111
+ },
112
+
113
+ async set(key: PrerenderKey, stored: PrerenderStoredEntry): Promise<void> {
114
+ // No expirationTtl: staleAt is soft metadata (see file header).
115
+ await kv.put(serializePrerenderKey(key), JSON.stringify(stored));
116
+ },
117
+
118
+ async delete(key: PrerenderKey): Promise<void> {
119
+ await kv.delete(serializePrerenderKey(key));
120
+ },
121
+
122
+ async markStale(routerId: string, tags: string[]): Promise<void> {
123
+ if (tags.length === 0) return;
124
+ const marker = String(now());
125
+ await Promise.all(
126
+ tags.map((tag) => kv.put(markerKey(routerId, tag), marker)),
127
+ );
128
+ },
129
+ };
130
+ }