@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
@@ -37,16 +37,17 @@
37
37
  */
38
38
  import type { SegmentCacheStore } from "./types.js";
39
39
  import { type ExecutionStart } from "./invalidation-order.js";
40
- export { executionStart, invalidatedSince, type ExecutionStart, } from "./invalidation-order.js";
40
+ export { executionStart, invalidatedSince, markResponseStart, type ExecutionStart, } from "./invalidation-order.js";
41
41
  /**
42
42
  * The write gate of every cache execution (#977): whether one of `tags` was
43
43
  * invalidated after the execution that produced the value started.
44
44
  *
45
- * Every built-in store stamps an entry when it is written (CFCacheStore's
46
- * `taggedAt`, VercelCacheStore's `ta`) or checks nothing on write
47
- * (MemorySegmentCacheStore), so a value read before an invalidation and
48
- * written after it would be served as newer than the invalidation until it
49
- * expires. Two answers, either one skips the write:
45
+ * A store stamps an entry with the execution's start (CFCacheStore's
46
+ * `taggedAt`, VercelCacheStore's `ta`, #1068), but only CFCacheStore with KV
47
+ * markers rejects it on a later request's read, and MemorySegmentCacheStore
48
+ * refuses the write. The gate saves the write everywhere and is the only
49
+ * guard where a read compares nothing. Two answers, either one skips the
50
+ * write:
50
51
  *
51
52
  * - this isolate's order (invalidatedSince), which sees every request's
52
53
  * updateTag()/revalidateTag() here, the calling one included;
@@ -36,6 +36,17 @@ export interface CacheGetResult {
36
36
  */
37
37
  shouldRevalidate: boolean;
38
38
  }
39
+ /**
40
+ * Where an entry a store writes during one request can be read by a later
41
+ * request. `router.prerender()` warms a route only when the app store answers
42
+ * `"global"` or `"regional"`: a warm fills the store where it runs, which
43
+ * serves other traffic only when the store is shared beyond that place.
44
+ * - `"global"`: every location reads the same entries.
45
+ * - `"regional"`: each region has its own copy; a warm fills the region it
46
+ * runs in.
47
+ * - `"local"`: only the process, isolate or edge location that wrote it.
48
+ */
49
+ export type CacheStoreScope = "global" | "regional" | "local";
39
50
  /**
40
51
  * Low-level segment cache store interface.
41
52
  *
@@ -43,9 +54,21 @@ export interface CacheGetResult {
43
54
  * The store deals with serialized data - RSC serialization is handled
44
55
  * by the cache provider layer.
45
56
  *
57
+ * A store that outlives the process has to keep one build from reading what
58
+ * other code wrote. The built-in stores prefix their keys with the serving
59
+ * router's cache versions; a custom store reads the same pair with
60
+ * `getCacheVersions()` (`@rangojs/router/cache`): `data` for segment entries
61
+ * and items, `document` for responses and shells, and no version on its
62
+ * tag-invalidation records, so an invalidation reaches every version.
63
+ *
46
64
  * @typeParam TEnv - Platform bindings type (e.g., Cloudflare env)
47
65
  */
48
66
  export interface SegmentCacheStore<TEnv = unknown> {
67
+ /**
68
+ * Where an entry this store writes can be read ({@link CacheStoreScope}).
69
+ * Absent: unknown to the router, treated as `"local"`.
70
+ */
71
+ readonly scope?: CacheStoreScope;
49
72
  /**
50
73
  * The store honors getShell(..., { claimRevalidation: false }) without
51
74
  * claiming an SWR lock. Navigation replay requires this opt-in because it
@@ -140,6 +163,11 @@ export interface SegmentCacheStore<TEnv = unknown> {
140
163
  * @param ttl - Time-to-live in seconds
141
164
  * @param swr - Optional stale-while-revalidate window in seconds
142
165
  * @param tags - Optional cache tags for invalidation
166
+ *
167
+ * The built-in stores read the start of the render from the `Response`
168
+ * object itself (#1068). A custom store, or a wrapper that rebuilds the
169
+ * `Response` before delegating, stamps write time. The write gate
170
+ * (`predatesInvalidation`) runs before this call either way.
143
171
  */
144
172
  putResponse?(key: string, response: Response, ttl: number, swr?: number, tags?: string[]): Promise<void>;
145
173
  /**
@@ -361,13 +389,14 @@ export interface ShellCacheEntry {
361
389
  /** React.version captured at prerender time; the read-time invalidation gate. */
362
390
  reactVersion: string;
363
391
  /**
364
- * Build version captured at prerender time (the RSC handler's `version` —
365
- * the `@rangojs/router:version` build stamp by default, bumped per build and
366
- * on dev RSC-module edits). The second read-time gate: a persistent shared
392
+ * Build version captured at prerender time: the RSC handler's `version`,
393
+ * which is the router's DOCUMENT version (router-versions.ts) — a hash of
394
+ * its server code and the client asset names by default, a stamp bumped on
395
+ * dev RSC-module edits. The second read-time gate: a persistent shared
367
396
  * store (KV/runtime-cache) survives deploys, and an app-code change that
368
397
  * keeps the same React version would otherwise leave a stale-build
369
- * prelude+postponed live under the same key. A custom store returns it as
370
- * putShell received it.
398
+ * prelude+postponed live under the same key. A deploy that changes neither
399
+ * keeps the entry. A custom store returns it as putShell received it.
371
400
  */
372
401
  buildVersion: string;
373
402
  /**
@@ -389,9 +418,10 @@ export interface ShellCacheEntry {
389
418
  * bake-lane loader pins (pruned to what a HIT reads, issue #941). Replaying
390
419
  * them on a HIT keeps the freshly rendered hydration payload equal to the
391
420
  * frozen prelude after the underlying cache entries have drifted; every
392
- * other read on a HIT, a hole's included, reads the store. Empty when the capture
393
- * recorded nothing (a prerender-served capture without bake-lane loaders,
394
- * a tombstone). A custom store returns it as putShell received it. See
421
+ * other read on a HIT, a hole's included, reads the store. A
422
+ * prerender-served capture holds a `handles` record instead of a doc record
423
+ * (ShellSnapshotHandlesValue). Empty for a tombstone. A custom store
424
+ * returns it as putShell received it. See
395
425
  * docs/design/ppr-shell-resume.md ("the capture data snapshot").
396
426
  */
397
427
  snapshot: ShellSnapshotRecord[];
@@ -552,12 +582,22 @@ export interface ShellReadStats {
552
582
  }
553
583
  /**
554
584
  * The families a shell snapshot pins: the doc segment record (recorded by
555
- * RecordingShellStore) and the settled CONTAINER of each bake-lane loader
556
- * (lane rule: see resolveLoaderData, loader-cache.ts). No cache read is
557
- * pinned: a HIT's holes, and a bake-lane loader body that runs on the HIT,
558
- * read the store.
585
+ * RecordingShellStore), the settled CONTAINER of each bake-lane loader
586
+ * (lane rule: see resolveLoaderData, loader-cache.ts), and a prerender-served
587
+ * entry's `handles` record. No cache read is pinned: a HIT's holes, and a bake-lane
588
+ * loader body that runs on the HIT, read the store.
559
589
  */
560
- export type ShellSnapshotFamily = "segment" | "loader";
590
+ export type ShellSnapshotFamily = "segment" | "loader" | "handles";
591
+ /**
592
+ * The stored form of the `handles` family: the handle arrays of a
593
+ * prerender-served capture that hold a loader-owned push, in the doc record's
594
+ * format (CachedEntryData `handles` + `handleOwners`). A Prerender + ppr
595
+ * route's handler layer comes from the prerender store, whose build-time
596
+ * entry has no loader push, so the entry keeps these for the HIT to restore
597
+ * as a doc record's (issue #1057, yieldFromStore in cache-lookup.ts). An
598
+ * entry written before it (v0.21) has none and serves as it did.
599
+ */
600
+ export type ShellSnapshotHandlesValue = Pick<CachedEntryData, "handles" | "handleOwners">;
561
601
  /**
562
602
  * The stored form of a loader-family snapshot value: the bake-lane loader's
563
603
  * settled container, Flight-serialized AFTER eliding every still-pending nested
@@ -590,17 +630,18 @@ export interface ShellSnapshotLoaderValue {
590
630
  runs: 0 | 1;
591
631
  }
592
632
  /**
593
- * One snapshot record: the doc segment record or a bake-lane loader pin.
594
- * `value` carries it in its stored/serialized shape so it round-trips
595
- * through a JSON-serializing store (KV, CF, Vercel) with the rest of the
596
- * ShellCacheEntry:
633
+ * One snapshot record: the doc segment record, a bake-lane loader pin, or a
634
+ * prerender-served entry's handle record. `value` carries it in its
635
+ * stored/serialized shape so it round-trips through a JSON-serializing store
636
+ * (KV, CF, Vercel) with the rest of the ShellCacheEntry:
597
637
  * - `segment` -> {@link CachedEntryData} (already JSON-able)
598
638
  * - `loader` -> {@link ShellSnapshotLoaderValue}
639
+ * - `handles` -> {@link ShellSnapshotHandlesValue}
599
640
  */
600
641
  export interface ShellSnapshotRecord {
601
642
  family: ShellSnapshotFamily;
602
643
  key: string;
603
- value: CachedEntryData | ShellSnapshotLoaderValue;
644
+ value: CachedEntryData | ShellSnapshotLoaderValue | ShellSnapshotHandlesValue;
604
645
  }
605
646
  /**
606
647
  * Options for setItem() for function-level caching ("use cache").
@@ -614,6 +655,17 @@ export interface CacheItemOptions {
614
655
  swr?: number;
615
656
  /** Cache tags for invalidation */
616
657
  tags?: string[];
658
+ /**
659
+ * Ms since epoch when the execution that produced the value started (#1068).
660
+ * A store stamps a tagged entry with it instead of its write time. Later
661
+ * requests then reject an entry an invalidation landed under in
662
+ * CFCacheStore with KV markers; MemorySegmentCacheStore refuses the write.
663
+ * Not rejected: VercelCacheStore (a read compares the stamp with the
664
+ * invalidating request's mask only) and CFCacheStore L1 hits in purge mode
665
+ * or without KV. Absent or not positive: write time. A custom store may
666
+ * ignore it.
667
+ */
668
+ startedAt?: number;
617
669
  }
618
670
  /**
619
671
  * Serialized segment data stored in cache
@@ -656,7 +708,12 @@ export interface CachedEntryData {
656
708
  expiresAt: number;
657
709
  /** Cache tags for invalidation */
658
710
  tags?: string[];
659
- /** Timestamp (ms since epoch) when tags were attached, for distributed invalidation */
711
+ /**
712
+ * Timestamp (ms since epoch) the entry's tags are valid as of, for
713
+ * distributed invalidation. The writer sets it to the start of the render
714
+ * that produced the record; a store stamps write time when it is absent
715
+ * (#1068). Which reads reject on it: see `CacheItemOptions.startedAt`.
716
+ */
660
717
  taggedAt?: number;
661
718
  }
662
719
  /**
@@ -696,10 +753,13 @@ export type SegmentHandleData = Record<string, unknown[]>;
696
753
  * index (null: not owned). Only a PPR shell capture writes owners: the
697
754
  * settled pushes of the loader bodies it ran (an `ssr: false` loader's own,
698
755
  * the loaders it awaits, and its own cache() replays), which the record keeps
699
- * because the prelude rendered them. A document HIT restores a bake-lane
700
- * owner's value through HandleStore.pushRestored, and it stands against a run
701
- * of its loader there; a live-lane owner's (a hole), and every owner's on
702
- * any other replay of the record (a client navigation), go through
703
- * pushReplayed, so a run of the loader replaces it with its live push.
756
+ * because the prelude rendered them. A restore of the record
757
+ * (handle-snapshot.ts restoreHandles) keeps an owner's value where the
758
+ * request serves that loader from the pin stored with the record
759
+ * (HandleStore.pushRestored: it stands against a run of the loader), on a
760
+ * document HIT and on a navigation replay alike. Every other owner's value
761
+ * is a placeholder (pushPlaceholder): a live-lane loader's, and every
762
+ * owner's where the record has no pin for it. The loader's run, or its own
763
+ * cache() entry, replaces it.
704
764
  */
705
765
  export type HandleOwners = Record<string, Record<string, (string | null)[]>>;
@@ -23,7 +23,8 @@
23
23
  * 3. The platform guardrails: the 2 MB per-item ceiling (oversized writes
24
24
  * silently no-op on Vercel, so we skip + report), the per-item tag cap, and
25
25
  * cross-deploy non-reconciliation (TTL/tag updates are not reconciled across
26
- * deployments - bake a build id into `version` or the getCache namespace).
26
+ * deployments - every key carries the serving router's cache version, see
27
+ * toStoreKey).
27
28
  *
28
29
  * Dependency stance: this module imports NOTHING from `@vercel/functions`. The
29
30
  * runtime cache handle (and `waitUntil`) are injected through the constructor and
@@ -32,7 +33,7 @@
32
33
  * `getCache(...)` result, which satisfies the shape structurally. That keeps the
33
34
  * router free of a hard Vercel dependency.
34
35
  */
35
- import type { SegmentCacheStore, CachedEntryData, CacheDefaults, CacheGetResult, CacheItemResult, CacheItemOptions, ShellCacheEntry, ShellDocumentRead, ShellDocumentReadOptions, CacheReadError } from "../types.js";
36
+ import type { CacheStoreScope, SegmentCacheStore, CachedEntryData, CacheDefaults, CacheGetResult, CacheItemResult, CacheItemOptions, ShellCacheEntry, ShellDocumentRead, ShellDocumentReadOptions, CacheReadError } from "../types.js";
36
37
  import { type RequestContext } from "../../server/request-context.js";
37
38
  import { type StoreMemoOptions } from "../shell-memo.js";
38
39
  /**
@@ -92,14 +93,20 @@ export type VercelCacheDebug = boolean | ((event: VercelCacheReadDebugEvent) =>
92
93
  export interface VercelCacheStoreOptions<TEnv = unknown> {
93
94
  /**
94
95
  * The Vercel Runtime Cache handle - `getCache()` from `@vercel/functions`.
95
- * Required. Construct it with a build-hash namespace to bust stale-shaped
96
- * entries across deployments, since Vercel does not reconcile TTL/tags between
97
- * deploys:
96
+ * Required.
98
97
  *
99
98
  * ```ts
100
99
  * import { getCache } from "@vercel/functions";
101
- * new VercelCacheStore({ cache: getCache({ namespace: BUILD_ID }) });
100
+ * new VercelCacheStore({ cache: getCache() });
102
101
  * ```
102
+ *
103
+ * Vercel does not reconcile entries between deployments; the store does it
104
+ * through its keys, which carry the serving router's cache versions (see
105
+ * `version`). So a deploy that changes a router's code stops reading that
106
+ * router's old entries, and a deploy that does not keeps them. A namespace
107
+ * that changes per deployment (`getCache({ namespace: VERCEL_DEPLOYMENT_ID })`)
108
+ * still works and clears the cache on every deploy, which is what you want
109
+ * only if you need that.
103
110
  */
104
111
  cache: VercelRuntimeCache;
105
112
  /**
@@ -122,9 +129,15 @@ export interface VercelCacheStoreOptions<TEnv = unknown> {
122
129
  */
123
130
  keyGenerator?: (ctx: RequestContext<TEnv>, defaultKey: string) => string | Promise<string>;
124
131
  /**
125
- * Build/version id folded into every stored key as `v/{version}/...`. A second
126
- * cross-deploy busting layer in addition to (or instead of) the getCache
127
- * namespace. Changing it invalidates everything this store wrote previously.
132
+ * Version override, folded into every versioned key as `v/{version}/...`.
133
+ * When set, this exact value is used for cached data and stored HTML alike;
134
+ * changing it makes everything this store wrote before unreachable.
135
+ *
136
+ * Leave it unset for the default: the store keys with the versions of the
137
+ * router serving the request, as CFCacheStore does. Segments and `"use
138
+ * cache"` items use the router's data version; responses and PPR shells use
139
+ * its document version (see `createRouter({ version })`). Tag markers are
140
+ * never versioned, so an invalidation applies to every version.
128
141
  */
129
142
  version?: string;
130
143
  /**
@@ -181,8 +194,8 @@ export interface VercelCacheStoreOptions<TEnv = unknown> {
181
194
  * that propagates expireTag rejections does get the strict guarantee, which is
182
195
  * what the unit suite exercises.)
183
196
  *
184
- * Key hashing / family isolation. The store namespaces its three value tiers as
185
- * `rg:{s|i|r}:{key}` so segment, `"use cache"`, and response entries occupy
197
+ * Key hashing / family isolation. The store namespaces its value tiers as
198
+ * `rg:{s|i|r|h}:{key}` so segment, `"use cache"`, response and shell entries occupy
186
199
  * disjoint keyspaces. That separation is only as strong as the cache's
187
200
  * `keyHashFunction`: `getCache`'s default is djb2, which folds every key to 32
188
201
  * bits (8 hex chars), so at a large live-key count a collision can let one
@@ -198,11 +211,9 @@ export interface VercelCacheStoreOptions<TEnv = unknown> {
198
211
  *
199
212
  * // One handle per process: getCache() resolves the platform cache on every
200
213
  * // call, and the PPR shell and tag-marker memos are kept per handle.
201
- * // `process.env` (not `import.meta.env`, which needs a VITE_ prefix and
202
- * // would be undefined here) so cross-deploy busting via `version` works.
203
- * const runtimeCache = getCache({
204
- * namespace: process.env.VERCEL_DEPLOYMENT_ID,
205
- * });
214
+ * // No deployment id in the namespace: the store's keys carry the serving
215
+ * // router's cache versions, so a deploy keeps what its code did not change.
216
+ * const runtimeCache = getCache();
206
217
  *
207
218
  * export const router = createRouter({
208
219
  * cache: () => ({
@@ -216,12 +227,19 @@ export interface VercelCacheStoreOptions<TEnv = unknown> {
216
227
  * ```
217
228
  */
218
229
  export declare class VercelCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
230
+ /**
231
+ * SegmentCacheStore.scope: the Runtime Cache is per region. A
232
+ * router.prerender() warm fills the region it runs in, which is all traffic
233
+ * only when the project's functions run in one region.
234
+ */
235
+ readonly scope: CacheStoreScope;
219
236
  readonly supportsPassiveShellReads: true;
220
237
  readonly defaults?: CacheDefaults;
221
238
  readonly keyGenerator?: (ctx: RequestContext<TEnv>, defaultKey: string) => string | Promise<string>;
222
239
  private readonly cache;
223
240
  private readonly waitUntil?;
224
- private readonly version?;
241
+ /** The `version` option: used for every versioned family when set. */
242
+ private readonly explicitVersion?;
225
243
  private readonly maxItemBytes;
226
244
  private readonly memo;
227
245
  private readonly handleMemos;
@@ -7,6 +7,8 @@ export interface ClientUrlNavigationIntent {
7
7
  /** Absolute destination pathname (mount included) and search ("?..." or ""). */
8
8
  readonly pathname: string;
9
9
  readonly search: string;
10
+ /** `history.state` the navigation will push (OptimisticLocation.state). */
11
+ readonly state: unknown;
10
12
  /**
11
13
  * transition({ when }) gated this navigation off at the swap: the
12
14
  * destination presents urgently instead of in a transition lane.
@@ -26,6 +28,8 @@ export interface ClientUrlDestination {
26
28
  * params minus these are the mount's (include prefix) params.
27
29
  */
28
30
  readonly originLocalParamNames: readonly string[];
31
+ /** `history.state` the navigation will push (ClientUrlNavigationIntent.state). */
32
+ readonly state: unknown;
29
33
  }
30
34
  export declare function setActiveInterceptTargets(targets: readonly string[] | undefined): void;
31
35
  export interface ClientUrlNavigationPresentation {
@@ -39,7 +43,9 @@ export interface ClientUrlNavigationPresentation {
39
43
  clear(): void;
40
44
  }
41
45
  export declare function registerClientUrlGroup(definition: ClientUrlPatterns, mount: string, namePrefix: string, setIntent: (intent: ClientUrlNavigationIntent | null) => void, routeId?: string): () => void;
42
- export declare function beginClientUrlNavigation(targetUrl: URL, signal: AbortSignal, decideTransition?: (destination: ClientUrlDestination) => boolean): ClientUrlNavigationPresentation | null;
46
+ export declare function beginClientUrlNavigation(targetUrl: URL, signal: AbortSignal,
47
+ /** Builds the entry state; called only when the group presents the target. */
48
+ buildState?: () => unknown, decideTransition?: (destination: ClientUrlDestination) => boolean): ClientUrlNavigationPresentation | null;
43
49
  /**
44
50
  * Run the held clientUrls route's per-loader revalidate() predicates and
45
51
  * encode their decisions for the request header. Predicates are CLIENT code
@@ -3,15 +3,21 @@ import { type Context } from "react";
3
3
  * Route identity of an optimistically rendered clientUrls() destination:
4
4
  * the values the local trie match produced for the URL the user navigated
5
5
  * to. Provided by ClientUrlsRoot around the optimistic branch ONLY, so
6
- * useParams / usePathname / useSearchParams inside that branch describe the
7
- * route being rendered while the same hooks in chrome outside it keep the
8
- * committed location until the canonical response commits (or redirects, in
9
- * which case the branch — and these values — are discarded).
6
+ * useParams / usePathname / useSearchParams / useLocationState inside that
7
+ * branch describe the route being rendered while the same hooks in chrome
8
+ * outside it keep the committed location until the canonical response commits
9
+ * (or redirects, in which case the branch — and these values — are discarded).
10
10
  */
11
11
  export interface OptimisticLocation {
12
12
  readonly params: Readonly<Record<string, string>>;
13
13
  readonly pathname: string;
14
14
  /** Search string including the leading "?" (or ""). */
15
15
  readonly search: string;
16
+ /**
17
+ * `history.state` the navigation will push: its `state` option, without
18
+ * what the server adds (that arrives with the canonical commit). History
19
+ * still holds the entry being left, so useLocationState reads this instead.
20
+ */
21
+ readonly state: unknown;
16
22
  }
17
23
  export declare const OptimisticLocationContext: Context<OptimisticLocation | null>;
@@ -223,5 +223,11 @@ export declare function invariant(condition: any, message: string): asserts cond
223
223
  * @param error - Error to sanitize
224
224
  * @returns Response suitable for sending to client
225
225
  */
226
+ /**
227
+ * Vite replaces import.meta.env.DEV at compile time. The fallback covers
228
+ * non-Vite environments (plain Node, test runners without Vite transforms).
229
+ * SECURITY: fail closed — default to production when the environment is ambiguous.
230
+ */
231
+ export declare function isDevEnvironment(): boolean;
226
232
  export declare function sanitizeError(error: unknown): Response;
227
233
  export {};
@@ -39,8 +39,25 @@ export declare function resolveDeferredHandleValues(data: HandleData): Promise<H
39
39
  * A hung handle promise blocks the full render like any unresolved await (no
40
40
  * handle-specific timeout). Partial / action payloads keep streaming via
41
41
  * `handleStore.stream()`.
42
+ *
43
+ * `recordedOnly` is a PPR shell capture's render (full-payload.ts): the
44
+ * snapshot is cut down to the pushes the capture's record keeps
45
+ * (getDataForSegment's `excludeLoaderPushes`, the filter captureHandles
46
+ * writes the record with). A HIT restores that record and hydrates with it,
47
+ * so a push the record leaves out must not be in the prelude either: it
48
+ * rendered an element no HIT's hydration data matched (issue #1035). Which
49
+ * pushes a record keeps is the capture funnel's decision (shell-capture.ts
50
+ * deriveShellCaptureContext: today it leaves out a deferred loader push and
51
+ * one holding a masked promise), not this function's.
52
+ */
53
+ export declare function resolvedHandleStream(handleStore: HandleStore, recordedOnly?: boolean): AsyncGenerator<HandleData, void, unknown>;
54
+ /**
55
+ * `snapshot` without the pushes a record leaves out. A shell capture renders
56
+ * (resolvedHandleStream) and waits (settleCaptureRecord) on this cut only: a
57
+ * promise the record drops has no slot in the prelude, and waiting for it
58
+ * could refuse a capture for a value the shell does not hold.
42
59
  */
43
- export declare function resolvedHandleStream(handleStore: HandleStore): AsyncGenerator<HandleData, void, unknown>;
60
+ export declare function recordedHandleData(handleStore: HandleStore, snapshot: HandleData): HandleData;
44
61
  /**
45
62
  * Resolve the deferred (Promise) values in ONE segment's handle bucket
46
63
  * (`handleName -> entries[]`). Same shallow + drop-rejections rule as
@@ -0,0 +1 @@
1
+ export declare const INTERNAL_RANGO_SUSPENSE_AUDIT: boolean;
@@ -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
  /** The loader-segment shape announcePendingStreams reads (ResolvedSegment). */
37
38
  export interface PendingStreamSource {
@@ -237,9 +238,4 @@ export declare class LoaderStore {
237
238
  */
238
239
  reset(): void;
239
240
  }
240
- /**
241
- * Module-level singleton. Each browser tab gets its own; SSR never mutates it.
242
- * The hook falls through to `OutletContext.loaderData` during the server render.
243
- */
244
- export declare const loaderStore: LoaderStore;
245
241
  export declare const EMPTY_LOADER_SNAPSHOT: LoaderEntry;
@@ -1,4 +1,4 @@
1
- import { type ReactNode } from "react";
1
+ import { type ComponentProps, type ReactNode } from "react";
2
2
  import type { ResolvedSegment } from "./types.js";
3
3
  /**
4
4
  * Outlet content provider — stores parent context for useLoader chain walking.
@@ -13,3 +13,4 @@ export declare function OutletProvider({ content, parallel, segment, loaderData,
13
13
  pending?: boolean;
14
14
  children: ReactNode;
15
15
  }): ReactNode;
16
+ export declare function AuditedOutletProvider({ children, ...props }: ComponentProps<typeof OutletProvider>): ReactNode;
@@ -20,6 +20,7 @@
20
20
  * identity, the same eligibility rules as at runtime.
21
21
  */
22
22
  import type { ShellCacheEntry } from "../cache/types.js";
23
+ import type { RouterVersions } from "../router-versions.js";
23
24
  import type { SSRModule } from "../rsc/types.js";
24
25
  import { type ResolvedPprConfig } from "../rsc/shell-serve.js";
25
26
  /**
@@ -67,13 +68,14 @@ export interface BuildShellCaptureOptions {
67
68
  /** Build-time env bindings (rango plugin buildEnv), if configured. */
68
69
  buildEnv?: unknown;
69
70
  /**
70
- * The MAIN build's version (the version plugin's value folded into the
71
- * shipped worker) — NOT the temp server's own version-plugin value. The
72
- * serve-side isValidShellHit gate compares entry.buildVersion against the
73
- * running worker's ctx.version; stamping the temp server's would make every
74
- * build entry an eternal MISS.
71
+ * The versions the SHIPPED build serves this router with (the build table,
72
+ * or the router's consumer-set version) — NOT the temp server's own
73
+ * version-plugin stamp. The serve-side isValidShellHit gate compares
74
+ * entry.buildVersion against the running worker's ctx.version, which is the
75
+ * document version; stamping the temp server's would make every build entry
76
+ * an eternal MISS.
75
77
  */
76
- buildVersion: string;
78
+ versions: RouterVersions;
77
79
  /**
78
80
  * The SSR half, composed by the plugin from the temp server's SSR
79
81
  * environment runner (react-dom/static prerender + Flight client), with the
@@ -0,0 +1,49 @@
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
+ import type { KVNamespace } from "../cache/cf/cf-cache-types.js";
28
+ import { type WritablePrerenderStore } from "./writable-store.js";
29
+ export interface KVPrerenderStoreOptions {
30
+ /** Injectable clock for `markStale` markers in deterministic tests. Defaults to Date.now. */
31
+ now?: () => number;
32
+ }
33
+ /**
34
+ * Create a {@link WritablePrerenderStore} backed by a Cloudflare KV namespace.
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * import { createKVPrerenderStore } from "@rangojs/router/prerender/cloudflare";
39
+ *
40
+ * createRouter<Env>({
41
+ * prerender: (env) => ({
42
+ * store: createKVPrerenderStore(env.PRERENDER_KV),
43
+ * ttl: 3600,
44
+ * onRevalidate: (target, e) => e.PRERENDER_QUEUE.send({ target }),
45
+ * }),
46
+ * });
47
+ * ```
48
+ */
49
+ export declare function createKVPrerenderStore(kv: KVNamespace, options?: KVPrerenderStoreOptions): WritablePrerenderStore;