@rangojs/router 0.17.0 → 0.19.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 (293) hide show
  1. package/README.md +3 -2
  2. package/dist/types/browser/cookie-name.d.ts +6 -0
  3. package/dist/types/browser/event-controller.d.ts +3 -3
  4. package/dist/types/browser/history-state.d.ts +18 -0
  5. package/dist/types/browser/navigation-transaction.d.ts +20 -2
  6. package/dist/types/browser/partial-update.d.ts +16 -1
  7. package/dist/types/browser/prefetch/cache.d.ts +6 -6
  8. package/dist/types/browser/react/Link.d.ts +19 -9
  9. package/dist/types/browser/react/context.d.ts +2 -2
  10. package/dist/types/browser/react/location-state-shared.d.ts +95 -56
  11. package/dist/types/browser/react/location-state.d.ts +1 -1
  12. package/dist/types/browser/transition-when.d.ts +80 -0
  13. package/dist/types/browser/types.d.ts +104 -9
  14. package/dist/types/build/generate-manifest.d.ts +8 -0
  15. package/dist/types/cache/background-task.d.ts +24 -2
  16. package/dist/types/cache/cache-exec-scope.d.ts +31 -0
  17. package/dist/types/cache/cache-key-utils.d.ts +48 -2
  18. package/dist/types/cache/cache-policy.d.ts +18 -1
  19. package/dist/types/cache/cache-runtime.d.ts +7 -0
  20. package/dist/types/cache/cache-scope.d.ts +192 -26
  21. package/dist/types/cache/cache-tag.d.ts +116 -11
  22. package/dist/types/cache/cf/cf-base64.d.ts +6 -0
  23. package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
  24. package/dist/types/cache/cf/cf-cache-store.d.ts +224 -72
  25. package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
  26. package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
  27. package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +19 -0
  28. package/dist/types/cache/document-cache.d.ts +10 -0
  29. package/dist/types/cache/handle-snapshot.d.ts +41 -8
  30. package/dist/types/cache/index.d.ts +1 -0
  31. package/dist/types/cache/invalidation-order.d.ts +23 -0
  32. package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
  33. package/dist/types/cache/request-tag-mask.d.ts +54 -0
  34. package/dist/types/cache/shell-memo.d.ts +178 -0
  35. package/dist/types/cache/shell-snapshot.d.ts +141 -105
  36. package/dist/types/cache/tag-invalidation.d.ts +54 -3
  37. package/dist/types/cache/types.d.ts +267 -96
  38. package/dist/types/cache/vercel/vercel-cache-store.d.ts +123 -18
  39. package/dist/types/client-urls/navigation.d.ts +28 -2
  40. package/dist/types/client-urls/server-projection.d.ts +5 -2
  41. package/dist/types/client-urls/types.d.ts +5 -5
  42. package/dist/types/client.d.ts +1 -1
  43. package/dist/types/index.d.ts +1 -1
  44. package/dist/types/index.rsc.d.ts +1 -1
  45. package/dist/types/prerender/build-shell-capture.d.ts +11 -4
  46. package/dist/types/route-definition/helpers-types.d.ts +11 -11
  47. package/dist/types/route-name.d.ts +7 -0
  48. package/dist/types/router/error-handling.d.ts +1 -1
  49. package/dist/types/router/instrument.d.ts +8 -0
  50. package/dist/types/router/intercept-resolution.d.ts +2 -0
  51. package/dist/types/router/match-context.d.ts +5 -0
  52. package/dist/types/router/match-middleware/background-revalidation.d.ts +0 -4
  53. package/dist/types/router/middleware-types.d.ts +6 -1
  54. package/dist/types/router/router-options.d.ts +4 -1
  55. package/dist/types/router/segment-resolution/loader-cache.d.ts +58 -12
  56. package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -4
  57. package/dist/types/router/segment-resolution/loader-snapshot.d.ts +33 -48
  58. package/dist/types/router/segment-resolution/mask-nested.d.ts +73 -24
  59. package/dist/types/router/segment-resolution/view-transition-default.d.ts +7 -5
  60. package/dist/types/router/transition-when.d.ts +24 -10
  61. package/dist/types/rsc/attach-transition-when.d.ts +18 -0
  62. package/dist/types/rsc/full-payload.d.ts +21 -0
  63. package/dist/types/rsc/handler.d.ts +1 -1
  64. package/dist/types/rsc/response-cache-serve.d.ts +15 -12
  65. package/dist/types/rsc/shell-build-manifest.d.ts +12 -3
  66. package/dist/types/rsc/shell-capture-constants.d.ts +116 -12
  67. package/dist/types/rsc/shell-capture.d.ts +202 -74
  68. package/dist/types/rsc/shell-serve.d.ts +144 -50
  69. package/dist/types/rsc/types.d.ts +9 -0
  70. package/dist/types/segment-system.d.ts +7 -0
  71. package/dist/types/server/context.d.ts +155 -33
  72. package/dist/types/server/cookie-store.d.ts +33 -0
  73. package/dist/types/server/handle-store.d.ts +52 -1
  74. package/dist/types/server/request-context.d.ts +210 -63
  75. package/dist/types/server/request-headers.d.ts +44 -0
  76. package/dist/types/server.d.ts +1 -0
  77. package/dist/types/ssr/index.d.ts +9 -0
  78. package/dist/types/ssr/ssr-root.d.ts +9 -8
  79. package/dist/types/testing/e2e/index.d.ts +1 -1
  80. package/dist/types/testing/flight.entry.d.ts +7 -0
  81. package/dist/types/testing/index.d.ts +3 -2
  82. package/dist/types/testing/internal/action-id.d.ts +7 -0
  83. package/dist/types/testing/location-state-key.d.ts +24 -0
  84. package/dist/types/testing/render-route.d.ts +17 -1
  85. package/dist/types/testing/run-loader.d.ts +18 -3
  86. package/dist/types/testing/run-transition-when.d.ts +44 -69
  87. package/dist/types/testing/serve-shell-request.d.ts +133 -0
  88. package/dist/types/testing/shell-status.d.ts +44 -19
  89. package/dist/types/transition-when-ref.d.ts +79 -0
  90. package/dist/types/types/cache-types.d.ts +15 -3
  91. package/dist/types/types/handler-context.d.ts +14 -1
  92. package/dist/types/types/index.d.ts +1 -1
  93. package/dist/types/types/segments.d.ts +86 -71
  94. package/dist/types/urls/path-helper-types.d.ts +3 -4
  95. package/dist/types/urls/pattern-types.d.ts +21 -18
  96. package/dist/types/vite/plugins/transition-when-hoist.d.ts +45 -0
  97. package/dist/types/vite/router-discovery.d.ts +29 -2
  98. package/dist/vite/index.js +1005 -105
  99. package/package.json +1 -1
  100. package/skills/breadcrumbs/SKILL.md +17 -7
  101. package/skills/cache-guide/SKILL.md +175 -76
  102. package/skills/caching/SKILL.md +361 -33
  103. package/skills/catalog.json +1 -1
  104. package/skills/client-urls/SKILL.md +12 -7
  105. package/skills/cloudflare/SKILL.md +15 -0
  106. package/skills/comparison/references/framework-comparison.md +52 -37
  107. package/skills/defer-hydration/SKILL.md +50 -30
  108. package/skills/deployment-caching/SKILL.md +5 -2
  109. package/skills/document-cache/SKILL.md +32 -2
  110. package/skills/handler-use/SKILL.md +2 -0
  111. package/skills/hooks/SKILL.md +3 -1
  112. package/skills/hooks/handle-and-actions.md +3 -1
  113. package/skills/hooks/navigation.md +23 -2
  114. package/skills/hooks/state.md +101 -7
  115. package/skills/intercept/SKILL.md +29 -12
  116. package/skills/loader/SKILL.md +197 -44
  117. package/skills/middleware/SKILL.md +34 -9
  118. package/skills/migrate-nextjs/SKILL.md +68 -37
  119. package/skills/migrate-react-router/SKILL.md +2 -1
  120. package/skills/migrate-react-router/component-migration.md +3 -1
  121. package/skills/migrate-react-router/data-and-actions.md +3 -1
  122. package/skills/migrate-react-router/route-mapping.md +7 -4
  123. package/skills/observability/SKILL.md +65 -0
  124. package/skills/parallel/SKILL.md +13 -8
  125. package/skills/ppr/SKILL.md +715 -371
  126. package/skills/prerender/SKILL.md +30 -17
  127. package/skills/rango/SKILL.md +72 -34
  128. package/skills/route/SKILL.md +14 -7
  129. package/skills/router-setup/SKILL.md +4 -2
  130. package/skills/scripts/SKILL.md +8 -1
  131. package/skills/server-actions/SKILL.md +11 -0
  132. package/skills/shell-manifest/SKILL.md +21 -12
  133. package/skills/testing/SKILL.md +49 -43
  134. package/skills/testing/cache-prerender.md +15 -14
  135. package/skills/testing/client-components.md +45 -3
  136. package/skills/testing/loader.md +19 -1
  137. package/skills/testing/middleware.md +1 -0
  138. package/skills/testing/predicates.md +49 -49
  139. package/skills/testing/render-handler.md +1 -0
  140. package/skills/testing/serve-shell-request.md +202 -0
  141. package/skills/testing/server-actions.md +9 -3
  142. package/skills/theme/SKILL.md +56 -4
  143. package/skills/typesafety/env-and-bindings.md +18 -2
  144. package/skills/use-cache/SKILL.md +91 -23
  145. package/skills/vercel/SKILL.md +14 -2
  146. package/skills/view-transitions/SKILL.md +150 -29
  147. package/src/__augment-tests__/augmented.check.ts +23 -0
  148. package/src/browser/cookie-name.ts +23 -0
  149. package/src/browser/event-controller.ts +4 -4
  150. package/src/browser/history-state.ts +75 -1
  151. package/src/browser/link-interceptor.ts +4 -0
  152. package/src/browser/navigation-bridge.ts +77 -18
  153. package/src/browser/navigation-client.ts +4 -3
  154. package/src/browser/navigation-store.ts +43 -1
  155. package/src/browser/navigation-transaction.ts +31 -11
  156. package/src/browser/partial-update.ts +146 -35
  157. package/src/browser/prefetch/cache.ts +6 -6
  158. package/src/browser/prefetch/fetch.ts +2 -2
  159. package/src/browser/react/Link.tsx +38 -33
  160. package/src/browser/react/NavigationProvider.tsx +2 -2
  161. package/src/browser/react/context.ts +2 -2
  162. package/src/browser/react/location-state-shared.ts +241 -49
  163. package/src/browser/react/location-state.ts +5 -2
  164. package/src/browser/rsc-router.tsx +9 -0
  165. package/src/browser/server-action-bridge.ts +84 -15
  166. package/src/browser/transition-when.ts +205 -0
  167. package/src/browser/types.ts +124 -10
  168. package/src/build/generate-manifest.ts +77 -1
  169. package/src/cache/background-task.ts +55 -2
  170. package/src/cache/cache-exec-scope.ts +48 -1
  171. package/src/cache/cache-key-utils.ts +62 -2
  172. package/src/cache/cache-policy.ts +31 -4
  173. package/src/cache/cache-runtime.ts +209 -129
  174. package/src/cache/cache-scope.ts +573 -69
  175. package/src/cache/cache-tag.ts +475 -19
  176. package/src/cache/cf/cf-base64.ts +36 -4
  177. package/src/cache/cf/cf-cache-constants.ts +12 -0
  178. package/src/cache/cf/cf-cache-store.ts +1094 -376
  179. package/src/cache/cf/cf-cache-types.ts +32 -4
  180. package/src/cache/cf/cf-shell-frame.ts +258 -0
  181. package/src/cache/cf/cf-tag-marker-memo.ts +55 -40
  182. package/src/cache/document-cache.ts +53 -7
  183. package/src/cache/handle-snapshot.ts +53 -9
  184. package/src/cache/index.ts +2 -0
  185. package/src/cache/invalidation-order.ts +84 -0
  186. package/src/cache/isolate-tag-memo.ts +308 -0
  187. package/src/cache/request-tag-mask.ts +147 -0
  188. package/src/cache/shell-memo.ts +341 -0
  189. package/src/cache/shell-snapshot.ts +249 -277
  190. package/src/cache/tag-invalidation.ts +109 -18
  191. package/src/cache/types.ts +274 -102
  192. package/src/cache/vercel/vercel-cache-store.ts +561 -74
  193. package/src/client-urls/client-root.tsx +8 -4
  194. package/src/client-urls/client-urls.ts +8 -6
  195. package/src/client-urls/navigation.ts +55 -4
  196. package/src/client-urls/server-projection.ts +31 -10
  197. package/src/client-urls/types.ts +12 -5
  198. package/src/client.tsx +2 -0
  199. package/src/host/cookie-handler.ts +2 -1
  200. package/src/index.rsc.ts +3 -0
  201. package/src/index.ts +3 -0
  202. package/src/loader-redirect.tsx +7 -3
  203. package/src/prerender/build-shell-capture.ts +173 -110
  204. package/src/route-definition/dsl-helpers.ts +6 -3
  205. package/src/route-definition/helpers-types.ts +11 -11
  206. package/src/route-name.ts +12 -0
  207. package/src/router/content-negotiation.ts +4 -1
  208. package/src/router/error-handling.ts +5 -2
  209. package/src/router/handler-context.ts +42 -5
  210. package/src/router/instrument.ts +54 -2
  211. package/src/router/intercept-resolution.ts +30 -8
  212. package/src/router/loader-resolution.ts +89 -33
  213. package/src/router/logging.ts +5 -3
  214. package/src/router/match-api.ts +89 -42
  215. package/src/router/match-context.ts +5 -0
  216. package/src/router/match-handlers.ts +14 -29
  217. package/src/router/match-middleware/background-revalidation.ts +3 -5
  218. package/src/router/match-middleware/cache-lookup.ts +52 -19
  219. package/src/router/match-middleware/cache-store.ts +36 -10
  220. package/src/router/match-result.ts +1 -0
  221. package/src/router/middleware-types.ts +6 -1
  222. package/src/router/middleware.ts +20 -4
  223. package/src/router/navigation-snapshot.ts +6 -5
  224. package/src/router/prerender-match.ts +10 -0
  225. package/src/router/request-classification.ts +7 -4
  226. package/src/router/router-options.ts +4 -1
  227. package/src/router/segment-resolution/fresh.ts +18 -13
  228. package/src/router/segment-resolution/helpers.ts +4 -2
  229. package/src/router/segment-resolution/loader-cache.ts +366 -70
  230. package/src/router/segment-resolution/loader-mask.ts +3 -4
  231. package/src/router/segment-resolution/loader-snapshot.ts +215 -126
  232. package/src/router/segment-resolution/mask-nested.ts +161 -42
  233. package/src/router/segment-resolution/revalidation.ts +27 -13
  234. package/src/router/segment-resolution/view-transition-default.ts +6 -21
  235. package/src/router/telemetry.ts +6 -3
  236. package/src/router/transition-when.ts +34 -52
  237. package/src/router.ts +2 -1
  238. package/src/rsc/attach-transition-when.ts +47 -0
  239. package/src/rsc/full-payload.ts +47 -3
  240. package/src/rsc/handler.ts +24 -10
  241. package/src/rsc/loader-fetch.ts +2 -1
  242. package/src/rsc/manifest-init.ts +4 -0
  243. package/src/rsc/origin-guard.ts +8 -5
  244. package/src/rsc/progressive-enhancement.ts +10 -32
  245. package/src/rsc/response-cache-serve.ts +69 -78
  246. package/src/rsc/response-route-handler.ts +2 -2
  247. package/src/rsc/rsc-rendering.ts +712 -338
  248. package/src/rsc/server-action.ts +8 -20
  249. package/src/rsc/shell-build-manifest.ts +49 -29
  250. package/src/rsc/shell-capture-constants.ts +182 -12
  251. package/src/rsc/shell-capture.ts +1126 -612
  252. package/src/rsc/shell-serve.ts +331 -85
  253. package/src/rsc/ssr-setup.ts +3 -2
  254. package/src/rsc/types.ts +9 -0
  255. package/src/segment-system.tsx +40 -7
  256. package/src/server/context.ts +328 -72
  257. package/src/server/cookie-store.ts +184 -133
  258. package/src/server/handle-store.ts +245 -27
  259. package/src/server/request-context.ts +364 -100
  260. package/src/server/request-headers.ts +134 -0
  261. package/src/server.ts +4 -0
  262. package/src/ssr/index.tsx +96 -37
  263. package/src/ssr/ssr-root.tsx +9 -8
  264. package/src/testing/dispatch.ts +4 -7
  265. package/src/testing/e2e/index.ts +1 -0
  266. package/src/testing/flight.entry.ts +14 -0
  267. package/src/testing/index.ts +5 -0
  268. package/src/testing/internal/action-id.ts +25 -0
  269. package/src/testing/internal/context.ts +1 -1
  270. package/src/testing/location-state-key.ts +45 -0
  271. package/src/testing/render-handler.ts +1 -1
  272. package/src/testing/render-route.tsx +109 -16
  273. package/src/testing/run-client-revalidate.ts +3 -19
  274. package/src/testing/run-loader.ts +67 -5
  275. package/src/testing/run-transition-when.ts +109 -172
  276. package/src/testing/serve-shell-request.ts +381 -0
  277. package/src/testing/shell-status.ts +73 -47
  278. package/src/theme/ThemeProvider.tsx +7 -6
  279. package/src/transition-when-ref.ts +220 -0
  280. package/src/types/cache-types.ts +15 -3
  281. package/src/types/handler-context.ts +14 -1
  282. package/src/types/index.ts +3 -0
  283. package/src/types/segments.ts +94 -91
  284. package/src/urls/path-helper-types.ts +3 -4
  285. package/src/urls/pattern-types.ts +21 -18
  286. package/src/vite/discovery/discover-routers.ts +3 -0
  287. package/src/vite/discovery/shell-prerender-phase.ts +9 -2
  288. package/src/vite/discovery/virtual-module-codegen.ts +16 -6
  289. package/src/vite/plugins/transition-when-hoist.ts +1064 -0
  290. package/src/vite/rango.ts +2 -0
  291. package/src/vite/router-discovery.ts +89 -9
  292. package/dist/types/rsc/transition-gate.d.ts +0 -23
  293. package/src/rsc/transition-gate.ts +0 -86
@@ -67,6 +67,12 @@ export interface SegmentCacheStore<TEnv = unknown> {
67
67
  * 2. Store-level `keyGenerator` (modifies default key)
68
68
  * 3. Default key generation (prefix:pathname:params)
69
69
  *
70
+ * Return `defaultKey` unchanged to leave a request unpartitioned. An empty
71
+ * string is not a key: where the result partitions a nested cache() on
72
+ * another store, or a ppr shell, it fails key resolution, so the request
73
+ * renders uncached (as when the keyGenerator throws), and the router warns
74
+ * once naming the store.
75
+ *
70
76
  * @example Using headers for cache segmentation
71
77
  * ```typescript
72
78
  * keyGenerator: (ctx, defaultKey) => {
@@ -99,7 +105,11 @@ export interface SegmentCacheStore<TEnv = unknown> {
99
105
  */
100
106
  get(key: string): Promise<CacheGetResult | null | CacheReadError>;
101
107
  /**
102
- * Store entry data with TTL
108
+ * Store entry data with TTL. Resolve once a later `get(key)` from the same
109
+ * location observes the entry: the route cache() write runs in a background
110
+ * task, and a PPR shell capture awaits that task before it reads the entry
111
+ * back (#957). A store that resolves earlier makes the capture re-render
112
+ * the page instead of replaying the entry.
103
113
  * @param key - Cache key
104
114
  * @param data - Serialized entry data
105
115
  * @param ttl - Time-to-live in seconds
@@ -156,8 +166,14 @@ export interface SegmentCacheStore<TEnv = unknown> {
156
166
  * upgrade — the reactVersion field on the entry gates that at read time).
157
167
  * @param key - Cache key
158
168
  * @param entry - The shell prelude/postponed/version/createdAt bundle
159
- * @param ttlSeconds - Time-to-live in seconds
160
- * @param swrSeconds - Optional stale-while-revalidate window in seconds
169
+ * @param ttlSeconds - Time-to-live in seconds: the route's `ppr.ttl`
170
+ * (undefined: the store's default), capped to the route cache() entry
171
+ * the capture replayed or wrote. A capped window arrives in whole
172
+ * seconds, rounded up, with ttl + swr at least 1; its ttl can be 0 (a
173
+ * shell stale from the start, captured from a record inside its swr
174
+ * window).
175
+ * @param swrSeconds - Optional stale-while-revalidate window in seconds,
176
+ * under the same cap
161
177
  * @param tags - Optional cache tags for invalidation (participates in
162
178
  * invalidateTags via the same tag machinery as the item family)
163
179
  * @returns `invalidated` when a generation marker rejected the write,
@@ -169,6 +185,40 @@ export interface SegmentCacheStore<TEnv = unknown> {
169
185
  * without acknowledgements.
170
186
  */
171
187
  putShell?(key: string, entry: ShellCacheEntry, ttlSeconds?: number, swrSeconds?: number, tags?: string[]): Promise<"stored" | "invalidated" | "uncacheable" | void>;
188
+ /**
189
+ * @internal The document serve path's shell read, for built-in stores. The
190
+ * serve path prefers it over getShell: it resolves with the entry and the
191
+ * raw (decoded) prelude, the snapshot on its own promise, and the read's
192
+ * `debugPerformance` stats. CFCacheStore's layout keeps the snapshot behind
193
+ * the prelude, so its HIT can commit before the snapshot is read;
194
+ * VercelCacheStore reads the whole entry and decodes the prelude once per
195
+ * memoized shell (issue #941, docs/design/shell-entry-layout.md). Not part
196
+ * of the custom-store contract.
197
+ */
198
+ readShellDocument?(key: string, options?: ShellDocumentReadOptions): Promise<ShellDocumentRead | null>;
199
+ /**
200
+ * @internal How long (ms) this store's isolate memos (shell, tag markers)
201
+ * can serve a read that predates a tag invalidation made elsewhere. After
202
+ * updateTag()/revalidateTag(), the response's fresh-reads cookie lasts the
203
+ * longest of these, and the same user's requests skip the memos meanwhile.
204
+ * Absent or 0: nothing to cover.
205
+ */
206
+ readonly freshReadsWindowMs?: number;
207
+ /**
208
+ * @internal Drop this isolate's memoized copy of a shell (shell-memo.ts) so
209
+ * the next read goes to the store. A HIT whose doc record failed to decode
210
+ * calls it after replacing the entry with a tombstone: the memoized copy
211
+ * holds the same record. Built-in stores only; not part of the custom-store
212
+ * contract.
213
+ */
214
+ dropShellMemo?(key: string): void;
215
+ /**
216
+ * @internal The largest shell entry (prelude, postponed state, and snapshot
217
+ * bytes) this store can hold in one value. The capture refuses a bigger
218
+ * entry instead of letting the write fail inside waitUntil. Absent: the
219
+ * capture applies DEFAULT_SHELL_ENTRY_MAX_BYTES (Cloudflare KV's 25 MiB).
220
+ */
221
+ readonly maxShellEntryBytes?: number;
172
222
  /**
173
223
  * Declares the shell family present-but-inert: getShell/putShell exist but
174
224
  * no-op (a custom store whose backing tier is conditionally unavailable).
@@ -210,6 +260,20 @@ export interface SegmentCacheStore<TEnv = unknown> {
210
260
  * APIs delegate to. Receives ALL of one invalidation call's tags at once so
211
261
  * stores can batch their work (e.g. a single CDN purge request rather than
212
262
  * one per tag). Stores that do not support tags simply omit this method.
263
+ *
264
+ * Both verbs call it synchronously, inside the request that invalidates
265
+ * (#973); revalidateTag() then hands the returned promise to waitUntil
266
+ * without awaiting it. Read-your-own-writes for that request comes from
267
+ * what the store does before its first await: record the tags as
268
+ * invalidated in state its reads consult for the rest of the request (the
269
+ * built-in stores key a map by the request's root context and the store,
270
+ * so contexts derived from the request share it, and compare each hit's
271
+ * tags and write time against it), then start the
272
+ * durable write. Such a mask must only ever turn hits into misses, and only
273
+ * in that request: a durable write that later fails then costs extra
274
+ * misses, never a stale read. A store that records nothing before its
275
+ * first await still works; the request that ran revalidateTag() can read
276
+ * entries the invalidation covers until the durable write lands.
213
277
  * @param tags - The cache tags to invalidate
214
278
  */
215
279
  invalidateTags?(tags: string[]): Promise<void>;
@@ -219,12 +283,25 @@ export interface SegmentCacheStore<TEnv = unknown> {
219
283
  * freshness). Consulted by build-shell read-through and runtime shell stores:
220
284
  * "was it evicted" is answered by tag markers against the entry's createdAt,
221
285
  * including when invalidation races a capture that has not been written yet.
286
+ * Also the store's half of every cache write's gate (#977,
287
+ * tag-invalidation.ts predatesInvalidation): a "use cache", loader cache(),
288
+ * route cache() or document-cache write whose execution started before an
289
+ * invalidation another isolate made is skipped when this answers true, so
290
+ * it must reflect invalidations made after this request started reading
291
+ * (not a value memoized earlier in the request).
222
292
  * Optional: without it, TAGGED build entries are not served (untagged ones
223
- * are unaffected — they are evictable only by deploy/buildVersion anyway).
293
+ * are unaffected — they are evictable only by deploy/buildVersion anyway),
294
+ * and writes are gated by this isolate's invalidations only.
224
295
  * Fail open to `false` on marker-read errors: a transient store fault must
225
296
  * degrade to "still valid", the same posture as the envelope tag checks.
226
- */
227
- isTagsInvalidatedSince?(tags: string[], sinceMs: number): Promise<boolean>;
297
+ * The write gate passes `{ failClosed: true }` instead: a marker it cannot
298
+ * read (an error, a timed-out read) answers `true`, since a skipped write
299
+ * only costs a later miss. A store that ignores the option still works;
300
+ * its fault then lets that write through.
301
+ */
302
+ isTagsInvalidatedSince?(tags: string[], sinceMs: number, options?: {
303
+ failClosed?: boolean;
304
+ }): Promise<boolean>;
228
305
  }
229
306
  /**
230
307
  * Result from getItem() for function-level caching ("use cache").
@@ -289,49 +366,51 @@ export interface ShellCacheEntry {
289
366
  * on dev RSC-module edits). The second read-time gate: a persistent shared
290
367
  * store (KV/runtime-cache) survives deploys, and an app-code change that
291
368
  * keeps the same React version would otherwise leave a stale-build
292
- * prelude+postponed live under the same key. Optional only for entries
293
- * stored before the field existed — those are treated as a miss and the
294
- * recapture re-stamps them (pre-release, no compat shim).
369
+ * prelude+postponed live under the same key. A custom store returns it as
370
+ * putShell received it.
295
371
  */
296
- buildVersion?: string;
372
+ buildVersion: string;
297
373
  /**
298
- * The initialTheme the CAPTURE render was built with (the derived context's
299
- * reqCtx.theme). The resume tail must render ThemeProvider with the SAME
374
+ * The initialTheme the CAPTURE render was built with: the no-cookie default
375
+ * (payloadInitialTheme, rsc/full-payload.ts), never the capturing visitor's
376
+ * theme (#971). The resume tail must render ThemeProvider with the SAME
300
377
  * initialTheme the frozen prelude was rendered with: React resume requires the
301
378
  * tree above the holes to match the prerendered tree, and initialTheme is
302
379
  * per-request METADATA, not part of the cached segments — a visitor whose
303
- * theme differs from the capturer's would otherwise produce a divergent resume
380
+ * theme differs from the capture's would otherwise produce a divergent resume
304
381
  * tree (broken stitching/hydration). The visitor's real theme is applied
305
382
  * pre-paint by the FOUC script and re-synced from the cookie post-mount by
306
383
  * ThemeProvider.
307
384
  */
308
385
  initialTheme?: string;
309
386
  /**
310
- * The CAPTURE DATA SNAPSHOT: every cache-store read-hit and write the capture
311
- * render performed, in stored/serialized form. Replaying these on a HIT (via
312
- * the SeededShellStore overlay, for the tail render only) reproduces the
313
- * shell's cached content byte-identically, so the freshly rendered hydration
314
- * payload matches the frozen prelude even after the underlying cache entries
315
- * have drifted (expired, been recomputed, or been tag-invalidated).
316
- *
317
- * Optional: an entry captured before this field existed simply has no
318
- * snapshot and keeps the pre-snapshot behavior (the tail reads live, so any
319
- * shell-baked cached value that drifted mismatches the prelude). Recapture
320
- * heals it. See docs/design/ppr-shell-resume.md ("the capture data snapshot").
321
- */
322
- snapshot?: ShellSnapshotRecord[];
323
- /**
324
- * The key of the CANONICAL document segment record inside `snapshot` — the
325
- * one navigation replay can actually consume (resolved under the implicit
326
- * doc namespace at capture; see CacheScope.cacheRoute). Replay eligibility
327
- * requires this exact record: the snapshot also carries incidentally
328
- * recorded explicit-tier records (RecordingShellStore passthroughs) whose
329
- * keys a partial lookup can never resolve, and counting those declared
330
- * entries "replayable" that always missed (`snapshot-miss` flip-flop).
331
- * Absent on entries captured before the field existed OR when the capture
332
- * recorded no doc record (cache(false)/condition-false routes, prerender
333
- * short-circuit) — both read as `no-segment-snapshot`; recapture heals the
334
- * former.
387
+ * The CAPTURE DATA SNAPSHOT, in stored/serialized form: the doc segment
388
+ * record every HIT replays the handler layer from (`docKey`) and the
389
+ * bake-lane loader pins (pruned to what a HIT reads, issue #941). Replaying
390
+ * them on a HIT keeps the freshly rendered hydration payload equal to the
391
+ * 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
395
+ * docs/design/ppr-shell-resume.md ("the capture data snapshot").
396
+ */
397
+ snapshot: ShellSnapshotRecord[];
398
+ /**
399
+ * Records the capture dropped from `snapshot` because no reader of this
400
+ * entry consumes them, by family (`loader:1`). Diagnostic only: the
401
+ * HIT tail timing reports it next to the kept records. See
402
+ * docs/design/shell-entry-layout.md ("Record only what a HIT reads").
403
+ */
404
+ prunedRecords?: string;
405
+ /**
406
+ * The key of the document segment record inside `snapshot`: the handler
407
+ * layer every HIT replays (a document HIT tail looks the record up by this
408
+ * key, so a store keyGenerator or a build-time capture host cannot send it
409
+ * elsewhere) and the record navigation replay consumes (resolved under the
410
+ * implicit doc namespace at capture; see CacheScope.cacheRoute). A capture
411
+ * that ran handlers always stores it; only a prerender-served entry (the
412
+ * prerender store supplies the handler layer) has none. A document entry
413
+ * without it is served as a MISS.
335
414
  */
336
415
  docKey?: string;
337
416
  /**
@@ -340,29 +419,15 @@ export interface ShellCacheEntry {
340
419
  * the partial request's headers and middleware state are not document state.
341
420
  */
342
421
  navigationOnly?: true;
343
- /**
344
- * True when the capture's HANDLER layer declared per-request liveness: a
345
- * handle pushed OUTSIDE a DSL loader scope carried a nested thenable (the
346
- * capture mask turns it into a never-filling hole), such a push was still
347
- * pending when the entry was written, or a handler-invoked loader
348
- * (ctx.use(loader) from a handler body — the consumption lane, #672)
349
- * executed during the capture. The serve tail then must NOT take the
350
- * handler-free fast path (the implicit doc-cache hit): only a handler
351
- * re-run can mint that hole's live promise or refresh that consumed value.
352
- * DSL-loader pushes never set this — loaders re-run fresh on every HIT, so
353
- * their holes always fill.
354
- */
355
- handlerLiveHoles?: boolean;
356
- /**
357
- * Legacy/fallback marker for a transition({ when }) predicate that was not
358
- * evaluated by the PPR pre-handler gate. Such an entry stays conservatively
359
- * ineligible for handler-free replay. New PPR matches evaluate known segment
360
- * predicates before the pipeline and do not set this marker.
361
- */
362
- transitionWhen?: true;
363
422
  /** Capture-generation start time; tag invalidations at or after it win. */
364
423
  createdAt: number;
365
424
  }
425
+ /**
426
+ * @internal A shell entry without its prelude and snapshot: what a
427
+ * prelude-first read (SegmentCacheStore.readShellDocument) resolves with
428
+ * before the snapshot bytes arrive.
429
+ */
430
+ export type ShellEntryHead = Omit<ShellCacheEntry, "prelude" | "snapshot">;
366
431
  /**
367
432
  * A shell entry whose document half is present — what the document HIT path
368
433
  * (serveShellHit / lookupBuildShell) consumes. hasIntactShellPayload
@@ -374,15 +439,125 @@ export type DocumentShellCacheEntry = ShellCacheEntry & {
374
439
  prelude: string;
375
440
  postponed: string | null;
376
441
  };
442
+ /** @internal Options of {@link SegmentCacheStore.readShellDocument}. */
443
+ export interface ShellDocumentReadOptions {
444
+ /**
445
+ * Tag names the route declares for its shell (`ppr.tags`), known before the
446
+ * entry is read: their marker reads can start alongside the entry read.
447
+ */
448
+ tagHints?: readonly string[];
449
+ }
450
+ /**
451
+ * @internal Why a prelude-first read's snapshot is missing although the entry
452
+ * stored one: `unavailable` (the read timed out or failed; the entry may be
453
+ * sound) or `corrupt` (the bytes arrived and do not parse; the store evicted
454
+ * the entry).
455
+ */
456
+ export type ShellSnapshotFailure = "unavailable" | "corrupt";
377
457
  /**
378
- * The families a shell snapshot pins. The item/segment/response families are
379
- * cache-store reads/writes (recorded by RecordingShellStore); the loader family
380
- * pins the settled CONTAINER of a bake-lane loader (lane rule: see
381
- * resolveLoaderData, loader-cache.ts). Excludes the shell family itself
382
- * (getShell/putShell) — the snapshot rides INSIDE a shell entry, so recording
383
- * it would be self-referential.
458
+ * @internal Result of {@link SegmentCacheStore.readShellDocument}.
384
459
  */
385
- export type ShellSnapshotFamily = "item" | "segment" | "response" | "loader";
460
+ export interface ShellDocumentRead {
461
+ /** The entry without its prelude and snapshot (delivered separately). */
462
+ entry: ShellEntryHead;
463
+ /** Raw prelude bytes (empty for a navigationOnly entry). */
464
+ prelude: Uint8Array;
465
+ shouldRevalidate?: boolean;
466
+ /**
467
+ * The capture snapshot, read after the prelude. Resolves undefined when the
468
+ * entry has none or it could not be read (`snapshotFailure` says which); a
469
+ * document HIT then cannot replay its doc record and degrades
470
+ * (rsc-rendering.ts serveShellHit).
471
+ * Never rejects.
472
+ */
473
+ snapshot: Promise<ShellSnapshotRecord[] | undefined>;
474
+ /**
475
+ * Why `snapshot` resolved undefined although the entry stored one, or
476
+ * undefined when it did not fail. A document HIT that cannot replay its
477
+ * doc record replaces the entry only when it is broken, not when its read
478
+ * was merely slow (rsc-rendering.ts serveShellHit). Never rejects.
479
+ */
480
+ snapshotFailure?: Promise<ShellSnapshotFailure | undefined>;
481
+ /**
482
+ * Where the read's time and bytes went, for the `debugPerformance` metrics
483
+ * (rsc-rendering.ts) and the store's own debug trace. Present only when one
484
+ * of them is on for the request.
485
+ */
486
+ stats?: ShellReadStats;
487
+ }
488
+ /**
489
+ * @internal Sub-steps of a prelude-first shell read. Times are ms. workerd
490
+ * advances its clock only on I/O, so a CPU-only field
491
+ * (`snapshot.parseMs`) reads 0 on a deployed worker: the byte counts are the
492
+ * cost signal there.
493
+ */
494
+ export interface ShellReadStats {
495
+ /**
496
+ * The tier that answered: `memo` (the store's shell memo), `l1`/`kv`
497
+ * (CFCacheStore), or `store` (a single-tier store, VercelCacheStore).
498
+ */
499
+ tier: "memo" | "l1" | "kv" | "store";
500
+ /** The per-isolate memo's outcome for this read and its size after it. */
501
+ memo?: {
502
+ hit: boolean;
503
+ bytes: number;
504
+ };
505
+ /**
506
+ * A KV read after an L1 attempt: how long the L1 attempt took and why it
507
+ * missed. The KV fields below then restart from the KV read.
508
+ */
509
+ l1MissMs?: number;
510
+ l1MissReason?: string;
511
+ /** Cache API match (L1) or KV get (KV) until the body was available. */
512
+ matchMs?: number;
513
+ /** Frame head read (I/O) and parse. */
514
+ headMs?: number;
515
+ /** Prelude bytes read. */
516
+ preludeMs?: number;
517
+ /** Tag-marker read, start to resolve (runs alongside the prelude read). */
518
+ markerMs?: number;
519
+ /** How long the read waited for the marker after the prelude was read. */
520
+ markerWaitMs?: number;
521
+ /**
522
+ * The marker check was awaited after the entry read (VercelCacheStore), not
523
+ * alongside the prelude read; hinted reads still start with the entry read.
524
+ */
525
+ markerSerial?: true;
526
+ /**
527
+ * How the isolate marker memo answered the shell's tags (the most
528
+ * store-bound outcome across them): `fresh`, `stale` (served, refreshing in
529
+ * the background), `read` (from the store), `bypass` (fresh-reads cookie).
530
+ */
531
+ markerMemo?: "fresh" | "stale" | "read" | "bypass";
532
+ /** Tags whose marker reads were started before the entry's head (hints). */
533
+ markerHinted?: readonly string[];
534
+ /** When the hinted marker reads started (performance.now()). */
535
+ markerHintStartedAt?: number;
536
+ /** How many of the shell's tags were hinted. */
537
+ markerHintHits?: number;
538
+ /** How long the hinted marker reads ran before the entry named its tags. */
539
+ markerLeadMs?: number;
540
+ /** The request carried the fresh-reads cookie: no isolate memo was used. */
541
+ freshReads?: true;
542
+ headBytes?: number;
543
+ preludeBytes?: number;
544
+ /** Number of the shell's tags the marker read covered. */
545
+ tags?: number;
546
+ /** Filled after the commit, once the snapshot has been read. */
547
+ snapshot?: {
548
+ readMs: number;
549
+ parseMs: number;
550
+ bytes: number;
551
+ };
552
+ }
553
+ /**
554
+ * 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.
559
+ */
560
+ export type ShellSnapshotFamily = "segment" | "loader";
386
561
  /**
387
562
  * The stored form of a loader-family snapshot value: the bake-lane loader's
388
563
  * settled container, Flight-serialized AFTER eliding every still-pending nested
@@ -398,42 +573,34 @@ export interface ShellSnapshotLoaderValue {
398
573
  * container carries hole markers, so a HIT must gate the overlay on the
399
574
  * fresh run (only the loader body can mint the live nested promises);
400
575
  * 0 = fully pinned, so a HIT resolves the payload promise immediately from
401
- * the pin while the fresh run proceeds ungated (side effects and cache
402
- * read-through writes preserved; its values were discarded either way —
403
- * recorded paths win wholesale). Absent on pre-bit snapshots, which keep
404
- * the gated path.
576
+ * the pin and does not run the loader body (unless `runs`).
405
577
  */
406
- holes?: 0 | 1;
407
- }
408
- /** A serialized cached Response for the response family of a shell snapshot. */
409
- export interface ShellSnapshotResponseValue {
410
- status: number;
411
- /** Client-facing header pairs (per-client signal headers excluded at record). */
412
- headers: [string, string][];
413
- /** base64-encoded response body (binary-safe, JSON-serializable). */
414
- body: string;
415
- }
416
- /** The stored form of an item-family (use cache / loader cache) snapshot value. */
417
- export interface ShellSnapshotItemValue {
418
- /** RSC-serialized return value. */
419
- value: string;
420
- /** RSC-encoded handle data, if any. */
421
- handles?: string;
422
- /** The entry's cache tags. */
423
- tags?: string[];
578
+ holes: 0 | 1;
579
+ /**
580
+ * 1 when the capture saw a loader push it could not record (a deferred
581
+ * push, one with masked nested promises, or one made outside any loader
582
+ * body): a HIT then still runs the loader body in the background
583
+ * (pin-first) so those pushes reach the page. 0: a hole-free record is
584
+ * served from the pin alone and its body does not run on a HIT.
585
+ *
586
+ * Both bits are always written. A record stored before they existed (v0.17)
587
+ * reads each missing bit as 1 (buildShellLoaderSeed): its snapshot lacks
588
+ * the loader-owned pushes, so the body supplies them.
589
+ */
590
+ runs: 0 | 1;
424
591
  }
425
592
  /**
426
- * One recorded cache-store read-hit or write from the capture render. `value`
427
- * carries the entry in its stored/serialized shape so it round-trips through a
428
- * JSON-serializing store (KV, CF, Vercel) with the rest of the ShellCacheEntry:
429
- * - `item` -> {@link ShellSnapshotItemValue}
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:
430
597
  * - `segment` -> {@link CachedEntryData} (already JSON-able)
431
- * - `response`-> {@link ShellSnapshotResponseValue}
598
+ * - `loader` -> {@link ShellSnapshotLoaderValue}
432
599
  */
433
600
  export interface ShellSnapshotRecord {
434
601
  family: ShellSnapshotFamily;
435
602
  key: string;
436
- value: ShellSnapshotItemValue | CachedEntryData | ShellSnapshotResponseValue | ShellSnapshotLoaderValue;
603
+ value: CachedEntryData | ShellSnapshotLoaderValue;
437
604
  }
438
605
  /**
439
606
  * Options for setItem() for function-level caching ("use cache").
@@ -527,8 +694,12 @@ export type SegmentHandleData = Record<string, unknown[]>;
527
694
  /**
528
695
  * segmentId -> handleName -> the owning loader id of each recorded value, by
529
696
  * index (null: not owned). Only a PPR shell capture writes owners: the
530
- * settled pushes of an `ssr: false` loader's own body, which the record keeps
531
- * because the prelude rendered them. restoreHandles replays an owned value
532
- * through HandleStore.pushReplayed, so that loader's live re-run replaces it.
697
+ * settled pushes of the loader bodies it ran (an `ssr: false` loader's own,
698
+ * 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.
533
704
  */
534
705
  export type HandleOwners = Record<string, Record<string, (string | null)[]>>;