@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
@@ -1,5 +1,3 @@
1
- /// <reference path="../../vite/plugins/version.d.ts" />
2
-
3
1
  // Extend CacheStorage with Cloudflare's default cache property
4
2
  declare global {
5
3
  interface CacheStorage {
@@ -29,6 +27,7 @@ declare global {
29
27
 
30
28
  import { requestHeaders } from "../../server/request-headers.js";
31
29
  import type {
30
+ CacheStoreScope,
32
31
  SegmentCacheStore,
33
32
  CachedEntryData,
34
33
  CacheDefaults,
@@ -50,7 +49,10 @@ import {
50
49
  type RequestContext,
51
50
  } from "../../server/request-context.js";
52
51
  import { INTERNAL_RANGO_DEBUG } from "../../internal-debug.js";
53
- import { VERSION } from "@rangojs/router:version";
52
+ import {
53
+ versionKeyPrefix,
54
+ type KeyVersion,
55
+ } from "../../server/build-version-table.js";
54
56
  import {
55
57
  isPerClientSignalHeader,
56
58
  stripPerClientSignals,
@@ -117,6 +119,7 @@ import {
117
119
  DEFAULT_CF_MARKER_MAX_STALE_MS,
118
120
  } from "../isolate-tag-memo.js";
119
121
  import { createCloudflareZonePurge } from "./cf-zone-purge.js";
122
+ import { responseStartedAt, taggedStamp } from "../invalidation-order.js";
120
123
 
121
124
  // ============================================================================
122
125
  // Constants
@@ -314,10 +317,9 @@ interface KVItemEnvelope {
314
317
 
315
318
  /**
316
319
  * Key namespace of the shell family. `shell2:` holds the prelude-first frame
317
- * (cf-shell-frame.ts); `shell:` held the JSON envelope. Every deploy retires
318
- * the old entries anyway (buildVersion gate); the new namespace also keeps
319
- * new code from reporting an old body as corrupt and a rollback from parsing
320
- * a frame. Old keys age out by their TTL.
320
+ * (cf-shell-frame.ts); `shell:` held the JSON envelope. The separate
321
+ * namespace keeps new code from reporting an old body as corrupt and a
322
+ * rollback from parsing a frame. Old keys age out by their TTL.
321
323
  */
322
324
  const SHELL_KEY_PREFIX = "shell2:";
323
325
 
@@ -494,7 +496,8 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
494
496
  private readonly namespace?: string;
495
497
  private readonly explicitBaseUrl?: string;
496
498
  private readonly waitUntil?: (fn: () => Promise<void>) => void;
497
- private readonly version?: string;
499
+ /** The `version` option: used for every versioned family when set. */
500
+ private readonly explicitVersion?: string;
498
501
  private readonly edgeLookupTimeoutMs: number;
499
502
  private readonly edgeReadTimeoutMs: number;
500
503
  private readonly kvReadTimeoutMs: number;
@@ -518,6 +521,12 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
518
521
  private readonly kv?: KVNamespace;
519
522
  /** True when constructed without KV: no durable tag history (see ctor). */
520
523
  readonly tagHistoryInert?: boolean;
524
+ /**
525
+ * SegmentCacheStore.scope: with KV every colo's L1 miss reads the same L2
526
+ * entry ("global"); without it entries live in one colo's Cache API
527
+ * ("local"), purge mode included (a purge evicts, it stores nothing).
528
+ */
529
+ readonly scope: CacheStoreScope;
521
530
  private readonly onRevalidateTag?: (tags: string[]) => Promise<void>;
522
531
  private readonly tagPurge?: (cacheTags: string[]) => Promise<void>;
523
532
  private readonly tagInvalidationTtl?: number;
@@ -540,7 +549,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
540
549
  // the internal host. Only the explicit override can be captured eagerly.
541
550
  this.explicitBaseUrl = options.baseUrl;
542
551
  this.defaults = options.defaults;
543
- this.version = options.version ?? VERSION;
552
+ this.explicitVersion = options.version;
544
553
  // Coalesce only finite numbers to the override; a non-finite value (NaN from
545
554
  // `Number(env.UNSET)`, or Infinity) would otherwise sail past `?? DEFAULT`
546
555
  // (which only replaces null/undefined) into setTimeout, where NaN/Infinity
@@ -603,6 +612,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
603
612
  // evict it (purge cannot delete a build asset), so the manifest gate
604
613
  // declines on this flag (shell-build-manifest.ts).
605
614
  this.tagHistoryInert = options.kv ? undefined : true;
615
+ this.scope = options.kv ? "global" : "local";
606
616
  this.onRevalidateTag = options.onRevalidateTag;
607
617
  // tagPurge accepts a ready purge function or a credentials object; the
608
618
  // object form is normalized through the built-in zone purge client, which
@@ -1134,7 +1144,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
1134
1144
  if (this.isReservedSegmentKey(key, "cache-read")) return null;
1135
1145
  try {
1136
1146
  const cache = await this.getCache();
1137
- const request = this.keyToRequest(key);
1147
+ const request = this.keyToRequest(key, "data");
1138
1148
  const matchStart = Date.now();
1139
1149
  const {
1140
1150
  response,
@@ -1381,20 +1391,18 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
1381
1391
  if (this.skipUncacheableTagSet(data.tags)) return;
1382
1392
  try {
1383
1393
  const cache = await this.getCache();
1384
- const request = this.keyToRequest(key);
1394
+ const request = this.keyToRequest(key, "data");
1385
1395
 
1386
1396
  // Extended TTL covers SWR window
1387
1397
  const swrWindow = resolveSwrWindow(swr, this.defaults);
1388
1398
  const totalTtl = ttl + swrWindow;
1389
1399
  const staleAt = Date.now() + ttl * 1000;
1390
1400
 
1391
- // Stamp the tag timestamp at write time and carry it (with the tags)
1392
- // into both the L1 body and the KV envelope so reads can run the
1393
- // invalidation check.
1394
- const taggedAt =
1395
- Array.isArray(data.tags) && data.tags.length > 0
1396
- ? Date.now()
1397
- : undefined;
1401
+ // Stamp the tag timestamp and carry it (with the tags) into both the
1402
+ // L1 body and the KV envelope so reads can run the invalidation check.
1403
+ // It is the render's start when the writer passed it (#1068), else the
1404
+ // write time: a marker written while the render ran then rejects it.
1405
+ const taggedAt = taggedStamp(data.tags, data.taggedAt);
1398
1406
  const dataToStore: CachedEntryData = taggedAt
1399
1407
  ? { ...data, taggedAt }
1400
1408
  : data;
@@ -1457,11 +1465,11 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
1457
1465
  if (this.isReservedSegmentKey(key, "cache-delete")) return false;
1458
1466
  try {
1459
1467
  const cache = await this.getCache();
1460
- const result = await cache.delete(this.keyToRequest(key));
1468
+ const result = await cache.delete(this.keyToRequest(key, "data"));
1461
1469
 
1462
1470
  // L2: delete from KV
1463
1471
  if (this.kv && this.waitUntil) {
1464
- const kvKey = await this.toKVKey(key);
1472
+ const kvKey = await this.toKVKey(key, "data");
1465
1473
  this.waitUntil(() =>
1466
1474
  reportingAsync(
1467
1475
  () => this.kv!.delete(kvKey),
@@ -1492,7 +1500,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
1492
1500
  ): Promise<{ response: Response; shouldRevalidate: boolean } | null> {
1493
1501
  try {
1494
1502
  const cache = await this.getCache();
1495
- const request = this.keyToRequest(`doc:${key}`);
1503
+ const request = this.keyToRequest(`doc:${key}`, "document");
1496
1504
  // The document path is outside the debug surface (op is only get/getItem),
1497
1505
  // so the match-timeout flag is not surfaced as an event here -- though
1498
1506
  // matchWithTimeout still warns on a slow match. A miss or timeout falls
@@ -1633,14 +1641,13 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
1633
1641
  if (this.skipUncacheableTagSet(tags)) return;
1634
1642
  try {
1635
1643
  const cache = await this.getCache();
1636
- const request = this.keyToRequest(`doc:${key}`);
1644
+ const request = this.keyToRequest(`doc:${key}`, "document");
1637
1645
 
1638
1646
  // Extended TTL covers SWR window
1639
1647
  const swrWindow = resolveSwrWindow(swr, this.defaults);
1640
1648
  const totalTtl = ttl + swrWindow;
1641
1649
  const staleAt = Date.now() + ttl * 1000;
1642
- const taggedAt =
1643
- Array.isArray(tags) && tags.length > 0 ? Date.now() : undefined;
1650
+ const taggedAt = taggedStamp(tags, responseStartedAt(response));
1644
1651
 
1645
1652
  // Clone body for potential KV write before consuming it for L1
1646
1653
  const [l1Body, kvBody] = this.kv
@@ -1747,7 +1754,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
1747
1754
  async getItem(key: string): Promise<CacheItemResult | null> {
1748
1755
  try {
1749
1756
  const cache = await this.getCache();
1750
- const request = this.keyToRequest(`fn:${key}`);
1757
+ const request = this.keyToRequest(`fn:${key}`, "data");
1751
1758
  const matchStart = Date.now();
1752
1759
  const {
1753
1760
  response,
@@ -1932,7 +1939,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
1932
1939
  if (this.skipUncacheableTagSet(options?.tags)) return;
1933
1940
  try {
1934
1941
  const cache = await this.getCache();
1935
- const request = this.keyToRequest(`fn:${key}`);
1942
+ const request = this.keyToRequest(`fn:${key}`, "data");
1936
1943
 
1937
1944
  const ttl = resolveTtl(options?.ttl, this.defaults, DEFAULT_FUNCTION_TTL);
1938
1945
  const swrWindow = resolveSwrWindow(options?.swr, this.defaults);
@@ -1940,8 +1947,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
1940
1947
  const staleAt = Date.now() + ttl * 1000;
1941
1948
 
1942
1949
  const tags = options?.tags;
1943
- const taggedAt =
1944
- Array.isArray(tags) && tags.length > 0 ? Date.now() : undefined;
1950
+ const taggedAt = taggedStamp(tags, options?.startedAt);
1945
1951
 
1946
1952
  // Serialize value/handles once; L1 body and KV envelope.v/h share the
1947
1953
  // escaped strings so a large RSC payload is not re-escaped for L2.
@@ -1984,7 +1990,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
1984
1990
  // JSON.stringify(KVItemEnvelope); field names differ from L1 so we assemble
1985
1991
  // from the pre-escaped value/handles pieces rather than re-stringifying.
1986
1992
  if (this.kv && this.waitUntil && totalTtl >= 60) {
1987
- const kvKey = await this.toKVKey(`fn:${key}`);
1993
+ const kvKey = await this.toKVKey(`fn:${key}`, "data");
1988
1994
  const expiresAt = staleAt + swrWindow * 1000;
1989
1995
  let envelopeJson = `{"v":${valueJson}`;
1990
1996
  if (handlesJson !== undefined) envelopeJson += `,"h":${handlesJson}`;
@@ -2154,7 +2160,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
2154
2160
  }
2155
2161
  try {
2156
2162
  const cache = await this.getCache();
2157
- const request = this.keyToRequest(`${SHELL_KEY_PREFIX}${key}`);
2163
+ const request = this.shellRequest(key);
2158
2164
  const matchStartedAt = stats ? performance.now() : 0;
2159
2165
  const {
2160
2166
  response,
@@ -2297,14 +2303,17 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
2297
2303
  }
2298
2304
  }
2299
2305
 
2300
- /** The isolate marker memo's key for a tag: markers are per namespace and version. */
2306
+ /**
2307
+ * The isolate marker memo's key for a tag: markers are per namespace, and
2308
+ * shared by every version (tagMarkerKey).
2309
+ */
2301
2310
  private markerMemoKey(tag: string): string {
2302
- return `${this.namespace ?? ""}\u0000${this.version ?? ""}\u0000${tag}`;
2311
+ return `${this.namespace ?? ""}\u0000${tag}`;
2303
2312
  }
2304
2313
 
2305
- /** The memo key: one per namespace, build version, base URL, and shell key. */
2314
+ /** The memo key: one per namespace, document version, base URL, and shell key. */
2306
2315
  private shellMemoKey(key: string): string {
2307
- return `${this.namespace ?? ""}\u0000${this.keyToRequest(`${SHELL_KEY_PREFIX}${key}`).url}`;
2316
+ return `${this.namespace ?? ""}\u0000${this.shellRequest(key).url}`;
2308
2317
  }
2309
2318
 
2310
2319
  /**
@@ -2571,15 +2580,12 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
2571
2580
  const evict = async (): Promise<void> => {
2572
2581
  const cache = await this.getCache();
2573
2582
  await reportingAsync(
2574
- () => cache.delete(this.keyToRequest(`${SHELL_KEY_PREFIX}${key}`)),
2583
+ () => cache.delete(this.shellRequest(key)),
2575
2584
  "cache-delete",
2576
2585
  "[CFCacheStore] getShell: evict corrupt L1",
2577
2586
  );
2578
2587
  if (this.kv) {
2579
- await this.evictKvKey(
2580
- await this.toKVKey(`${SHELL_KEY_PREFIX}${key}`),
2581
- "getShell",
2582
- );
2588
+ await this.evictKvKey(await this.shellKVKey(key), "getShell");
2583
2589
  }
2584
2590
  };
2585
2591
  if (this.waitUntil) this.waitUntil(evict);
@@ -2641,9 +2647,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
2641
2647
  Array.isArray(tags) && tags.length > 0 ? entry.createdAt : undefined;
2642
2648
 
2643
2649
  const writeKv = !!this.kv && retentionTtl > 0;
2644
- const kvKey = writeKv
2645
- ? await this.toKVKey(`${SHELL_KEY_PREFIX}${key}`)
2646
- : null;
2650
+ const kvKey = writeKv ? await this.shellKVKey(key) : null;
2647
2651
 
2648
2652
  const write = (async (): Promise<"stored" | "invalidated" | void> => {
2649
2653
  if (
@@ -2684,7 +2688,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
2684
2688
  try {
2685
2689
  const cache = await this.getCache();
2686
2690
  await cache.put(
2687
- this.keyToRequest(`${SHELL_KEY_PREFIX}${key}`),
2691
+ this.shellRequest(key),
2688
2692
  this.shellFrameResponse(frame, head),
2689
2693
  );
2690
2694
  this.debugShell(key, "l1-stored", { expiresAt: head.e });
@@ -2781,7 +2785,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
2781
2785
  // One kvReadTimeoutMs budget covers opening the value and reading its
2782
2786
  // head and prelude; the remainder after the open bounds the frame read.
2783
2787
  const deadline = Date.now() + this.kvReadTimeoutMs;
2784
- const kvKey = await this.toKVKey(`${SHELL_KEY_PREFIX}${key}`);
2788
+ const kvKey = await this.shellKVKey(key);
2785
2789
  // A transient read REJECTION propagates to the catch below (reported
2786
2790
  // cache-read, the entry left intact); only a malformed frame on a body
2787
2791
  // that WAS read is corruption that evicts.
@@ -2884,7 +2888,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
2884
2888
  if (Date.now() > head.e) return;
2885
2889
  const cache = await this.getCache();
2886
2890
  await cache.put(
2887
- this.keyToRequest(`${SHELL_KEY_PREFIX}${key}`),
2891
+ this.shellRequest(key),
2888
2892
  this.shellFrameResponse(
2889
2893
  encodeShellFrame(head, prelude, snapshotBytes),
2890
2894
  head,
@@ -2905,18 +2909,30 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
2905
2909
  // Key Helpers
2906
2910
  // ============================================================================
2907
2911
 
2912
+ private shellRequest(key: string): Request {
2913
+ return this.keyToRequest(`${SHELL_KEY_PREFIX}${key}`, "document");
2914
+ }
2915
+
2916
+ private shellKVKey(key: string): Promise<string> {
2917
+ return this.toKVKey(`${SHELL_KEY_PREFIX}${key}`, "document");
2918
+ }
2919
+
2908
2920
  /**
2909
2921
  * Convert string key to Request object for CF Cache API.
2910
- * Includes version in URL if specified (for cache invalidation on code changes).
2922
+ *
2923
+ * `family` picks the version in the URL path, so a version change misses:
2924
+ * `"data"` for segment entries and `fn:` items (cached RSC data, valid while
2925
+ * the router's server code is unchanged), `"document"` for `doc:` responses
2926
+ * and `shell2:` shells (their bytes carry asset URLs and the handler
2927
+ * version), `null` for tag markers, which apply to every version.
2911
2928
  * @internal
2912
2929
  */
2913
- private keyToRequest(key: string): Request {
2930
+ private keyToRequest(key: string, family: KeyVersion): Request {
2914
2931
  const encodedKey = encodeURIComponent(key);
2915
- // Include version in URL path to invalidate cache when version changes
2916
- const versionPath = this.version ? `v/${this.version}/` : "";
2917
- return new Request(`${this.resolveBaseUrl()}${versionPath}${encodedKey}`, {
2918
- method: "GET",
2919
- });
2932
+ return new Request(
2933
+ `${this.resolveBaseUrl()}${versionKeyPrefix(this.explicitVersion, family)}${encodedKey}`,
2934
+ { method: "GET" },
2935
+ );
2920
2936
  }
2921
2937
 
2922
2938
  /**
@@ -2937,9 +2953,8 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
2937
2953
  * and delete paths agree on the stored key.
2938
2954
  * @internal
2939
2955
  */
2940
- private async toKVKey(key: string): Promise<string> {
2941
- const versionPath = this.version ? `v/${this.version}/` : "";
2942
- const composed = `${versionPath}${key}`;
2956
+ private async toKVKey(key: string, family: KeyVersion): Promise<string> {
2957
+ const composed = `${versionKeyPrefix(this.explicitVersion, family)}${key}`;
2943
2958
  if (kvKeyByteLength(composed) <= KV_MAX_KEY_BYTES) return composed;
2944
2959
  const prefix = truncateToBytes(composed, KV_KEY_PRESERVED_PREFIX_BYTES);
2945
2960
  return `${prefix}~${await kvKeyDigest(composed)}`;
@@ -2973,7 +2988,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
2973
2988
  * @internal
2974
2989
  */
2975
2990
  private toDocKVKey(key: string): Promise<string> {
2976
- return this.toKVKey(`h/${this.docKVHost()}/doc:${key}`);
2991
+ return this.toKVKey(`h/${this.docKVHost()}/doc:${key}`, "document");
2977
2992
  }
2978
2993
 
2979
2994
  /**
@@ -3087,9 +3102,15 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
3087
3102
  // Tag Invalidation (single-store: markers live in this.kv)
3088
3103
  // ============================================================================
3089
3104
 
3090
- /** KV key for a tag's invalidation marker. */
3105
+ /**
3106
+ * KV key for a tag's invalidation marker. Unversioned: a content-hash
3107
+ * version can come back (deploy A, deploy B, roll back to A), and a marker
3108
+ * written while B was live has to be visible to A's entries. The staleness
3109
+ * check compares the marker time with the entry's taggedAt and nothing else
3110
+ * (isGloballyInvalidated), so one marker per tag serves every version.
3111
+ */
3091
3112
  private tagMarkerKey(tag: string): Promise<string> {
3092
- return this.toKVKey(`${TAG_MARKER_PREFIX}${tag}`);
3113
+ return this.toKVKey(`${TAG_MARKER_PREFIX}${tag}`, null);
3093
3114
  }
3094
3115
 
3095
3116
  /**
@@ -3320,9 +3341,13 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
3320
3341
  return false;
3321
3342
  }
3322
3343
 
3323
- /** Synthetic Cache API request for a tag's L1-cached invalidation marker. */
3344
+ /**
3345
+ * Synthetic Cache API request for a tag's L1-cached invalidation marker.
3346
+ * Unversioned for the same reason as tagMarkerKey; the cache namespace
3347
+ * (getCache) still scopes it.
3348
+ */
3324
3349
  private tagMarkerRequest(tag: string): Request {
3325
- return this.keyToRequest(`${TAG_MARKER_CACHE_PREFIX}${tag}`);
3350
+ return this.keyToRequest(`${TAG_MARKER_CACHE_PREFIX}${tag}`, null);
3326
3351
  }
3327
3352
 
3328
3353
  /**
@@ -3974,7 +3999,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
3974
3999
  if (!this.kv) return null;
3975
4000
 
3976
4001
  try {
3977
- const kvKey = await this.toKVKey(key);
4002
+ const kvKey = await this.toKVKey(key, "data");
3978
4003
  const { value: envelope, timedOut } =
3979
4004
  await this.kvGetOrEvict<KVSegmentEnvelope>(
3980
4005
  kvKey,
@@ -4076,7 +4101,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
4076
4101
  this.waitUntil(() =>
4077
4102
  reportingAsync(
4078
4103
  async () =>
4079
- this.kv!.put(await this.toKVKey(key), envelopeJson, {
4104
+ this.kv!.put(await this.toKVKey(key, "data"), envelopeJson, {
4080
4105
  expirationTtl: totalTtl,
4081
4106
  }),
4082
4107
  "cache-write",
@@ -4101,7 +4126,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
4101
4126
  Math.floor((envelope.e - now) / 1000),
4102
4127
  );
4103
4128
  const cache = await this.getCache();
4104
- const request = this.keyToRequest(key);
4129
+ const request = this.keyToRequest(key, "data");
4105
4130
 
4106
4131
  const response = new Response(JSON.stringify(envelope.d), {
4107
4132
  headers: {
@@ -4137,7 +4162,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
4137
4162
  if (!this.kv) return null;
4138
4163
 
4139
4164
  try {
4140
- const kvKey = await this.toKVKey(`fn:${key}`);
4165
+ const kvKey = await this.toKVKey(`fn:${key}`, "data");
4141
4166
  const { value: envelope, timedOut } =
4142
4167
  await this.kvGetOrEvict<KVItemEnvelope>(
4143
4168
  kvKey,
@@ -4221,7 +4246,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
4221
4246
  Math.floor((envelope.e - now) / 1000),
4222
4247
  );
4223
4248
  const cache = await this.getCache();
4224
- const request = this.keyToRequest(`fn:${key}`);
4249
+ const request = this.keyToRequest(`fn:${key}`, "data");
4225
4250
 
4226
4251
  const body = JSON.stringify({
4227
4252
  value: envelope.v,
@@ -4355,7 +4380,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
4355
4380
  Math.floor((envelope.e - now) / 1000),
4356
4381
  );
4357
4382
  const cache = await this.getCache();
4358
- const request = this.keyToRequest(`doc:${key}`);
4383
+ const request = this.keyToRequest(`doc:${key}`, "document");
4359
4384
 
4360
4385
  const headers = new Headers(envelope.hd);
4361
4386
  const originalCacheControl = headers.get("Cache-Control");
@@ -320,11 +320,19 @@ export interface CFCacheStoreOptions<TEnv = unknown> {
320
320
  tagCacheTtl?: number;
321
321
 
322
322
  /**
323
- * Cache version string override. When this changes, all cached entries are
324
- * effectively invalidated (new keys won't match old entries).
323
+ * Cache version string override. When set, this exact value prefixes every
324
+ * versioned key the store writes (`v/{version}/...`), for cached data and
325
+ * stored HTML alike; changing it makes every earlier entry unreachable.
325
326
  *
326
- * Defaults to the auto-generated VERSION from the `@rangojs/router:version` virtual module.
327
- * Only set this if you need a custom versioning strategy.
327
+ * Leave it unset for the default: the store keys with the versions of the
328
+ * router serving the request. Segment entries and `"use cache"` items use
329
+ * the router's data version; document responses and PPR shells use its
330
+ * document version (see `createRouter({ version })`). Those are computed
331
+ * from the router's built code, so a deploy that does not change the router
332
+ * keeps its entries.
333
+ *
334
+ * Tag invalidation markers are never versioned: an invalidation applies to
335
+ * the entries of every version, including one that comes back in a rollback.
328
336
  */
329
337
  version?: string;
330
338
 
@@ -22,10 +22,19 @@ import {
22
22
  type RequestContext,
23
23
  } from "../server/request-context.js";
24
24
  import { mayNeedSSR } from "../rsc/ssr-setup.js";
25
- import { cacheKeyBase } from "./cache-key-utils.js";
25
+ import { requestKeyBase } from "./cache-key-utils.js";
26
26
  import { runBackground } from "./background-task.js";
27
+ import {
28
+ isWarmReplace,
29
+ noteWarmDocument,
30
+ noteWarmWrite,
31
+ } from "../prerender/warm-request.js";
27
32
  import { reportCacheError } from "./cache-error.js";
28
- import { executionStart, predatesInvalidation } from "./tag-invalidation.js";
33
+ import {
34
+ executionStart,
35
+ markResponseStart,
36
+ predatesInvalidation,
37
+ } from "./tag-invalidation.js";
29
38
  import { observePhase, PHASES } from "../router/instrument.js";
30
39
  import {
31
40
  SEGMENT_FRAGMENT_CAPABILITY_HEADER,
@@ -239,11 +248,14 @@ export interface DocumentCacheOptions<TEnv = any> {
239
248
  /**
240
249
  * Custom cache key generator.
241
250
  *
242
- * Replaces the default `host + pathname + search` key entirely. On a
243
- * multi-domain deployment served by one function you MUST include `url.host`
244
- * (or an equivalent tenant discriminator) yourself — the default key is
245
- * host-namespaced, but a custom generator's output is used verbatim, so
246
- * omitting host bleeds one hostname's cached response to another.
251
+ * Replaces the default `router id + host + pathname + search` key entirely.
252
+ * On a multi-domain deployment served by one function you MUST include
253
+ * `url.host` (or an equivalent tenant discriminator) yourself — the default
254
+ * key is host-namespaced, but a custom generator's output is used verbatim,
255
+ * so omitting host bleeds one hostname's cached response to another. The
256
+ * same goes for the router: two routers that share a cache store and can
257
+ * serve one host and path (a `hostOverride` cookie) need a generator that
258
+ * tells them apart.
247
259
  */
248
260
  keyGenerator?: (url: URL) => string;
249
261
 
@@ -347,7 +359,8 @@ export function createDocumentCacheMiddleware<TEnv = any>(
347
359
  return next();
348
360
  }
349
361
 
350
- // The write gate's start (#977): the request's, not this middleware's. A
362
+ // The write gate's start (#977) and the entry's stamp (#1068): the
363
+ // request's, not this middleware's, also for a stale refresh. A
351
364
  // middleware ahead of this one can read tagged data the document bakes,
352
365
  // and a stale refresh re-runs the handler over what they set.
353
366
  const start = requestCtx._requestStart ?? executionStart();
@@ -392,7 +405,8 @@ export function createDocumentCacheMiddleware<TEnv = any>(
392
405
  // existing keys and double any host they already include).
393
406
  const cacheKey = keyGenerator
394
407
  ? keyGenerator(url) + segmentHash + fragmentSuffix + typeSuffix
395
- : cacheKeyBase(
408
+ : requestKeyBase(
409
+ requestCtx,
396
410
  url.host,
397
411
  url.pathname,
398
412
  url.searchParams,
@@ -406,9 +420,11 @@ export function createDocumentCacheMiddleware<TEnv = any>(
406
420
  // Recovery must reach CacheScope's server decoder so it can evict the bad
407
421
  // segment. Treat it as a miss, then let the ordinary write path replace
408
422
  // the corrupt fragment-capable response with the valid fallback bytes.
409
- const cached = isFragmentRecovery
410
- ? null
411
- : await store.getResponse(cacheKey);
423
+ // A router.prerender() warm is a miss too: it renders and replaces.
424
+ const cached =
425
+ isFragmentRecovery || isWarmReplace(requestCtx)
426
+ ? null
427
+ : await store.getResponse(cacheKey);
412
428
  // Every path past the lookup either returns a fresh HIT, which renders
413
429
  // nothing, or renders a response this cache may store.
414
430
  requestCtx._documentCacheRender = true;
@@ -462,11 +478,13 @@ export function createDocumentCacheMiddleware<TEnv = any>(
462
478
  if (await predatesInvalidation(store, tags, start)) return;
463
479
  await store.putResponse!(
464
480
  cacheKey,
465
- new Response(body, fresh),
481
+ markResponseStart(new Response(body, fresh), start),
466
482
  directives.sMaxAge!,
467
483
  directives.staleWhileRevalidate,
468
484
  tags,
469
485
  );
486
+ noteWarmWrite(requestCtx, "response");
487
+ noteWarmDocument(requestCtx, "stored");
470
488
  log(
471
489
  `[DocumentCache] REVALIDATED ${typeLabel}: ${url.pathname}`,
472
490
  );
@@ -531,11 +549,13 @@ export function createDocumentCacheMiddleware<TEnv = any>(
531
549
  if (await predatesInvalidation(store, tags, start)) return;
532
550
  await store.putResponse!(
533
551
  cacheKey,
534
- new Response(body, originalResponse),
552
+ markResponseStart(new Response(body, originalResponse), start),
535
553
  directives.sMaxAge!,
536
554
  directives.staleWhileRevalidate,
537
555
  tags,
538
556
  );
557
+ noteWarmWrite(requestCtx, "response");
558
+ noteWarmDocument(requestCtx, "stored");
539
559
  } catch (error) {
540
560
  // Detached waitUntil task — pass the captured requestCtx so onError
541
561
  // fires even though the ALS context is gone (see the revalidation
@@ -556,6 +576,7 @@ export function createDocumentCacheMiddleware<TEnv = any>(
556
576
  }
557
577
 
558
578
  // No cache headers - pass through
579
+ noteWarmDocument(requestCtx, "not-cacheable");
559
580
  return originalResponse;
560
581
  } catch (error) {
561
582
  reportCacheError(error, "cache-read", "[DocumentCache] middleware");