@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
@@ -3,31 +3,17 @@ declare global {
3
3
  readonly default: Cache;
4
4
  }
5
5
  }
6
- /**
7
- * Cloudflare Edge Cache Store
8
- *
9
- * Production cache store using Cloudflare's Cache API (L1) with optional
10
- * KV persistence (L2).
11
- *
12
- * L1 (Cache API): Per-colo, fast, ephemeral. Handles SWR atomically.
13
- * L2 (KV): Global, persistent, ~50ms reads. Auto-warms cold colos.
14
- *
15
- * Read flow: L1 hit → serve | L1 miss → L2 hit → serve + promote to L1 | both miss → render
16
- * Write flow: L1 write + L2 write (both via waitUntil)
17
- *
18
- * Features:
19
- * - Extended TTL for SWR window (max-age = ttl + swr)
20
- * - Staleness via x-edge-cache-stale-at header
21
- * - Atomic REVALIDATING status for thundering herd prevention (L1 only)
22
- * - Non-blocking writes via waitUntil
23
- * - KV L2 for cross-colo cache persistence
24
- */
25
- import type { SegmentCacheStore, CachedEntryData, CacheDefaults, CacheGetResult, CacheItemResult, CacheItemOptions, ShellCacheEntry, CacheReadError } from "../types.js";
6
+ import type { SegmentCacheStore, CachedEntryData, CacheDefaults, CacheGetResult, CacheItemResult, CacheItemOptions, ShellCacheEntry, ShellDocumentRead, ShellDocumentReadOptions, CacheReadError } from "../types.js";
26
7
  import { type RequestContext } from "../../server/request-context.js";
27
8
  import { CACHE_STALE_AT_HEADER, CACHE_STATUS_HEADER, CACHE_TAGS_HEADER, CACHE_TAGGED_AT_HEADER, TAG_MARKER_PREFIX, CACHE_REVALIDATING_AT_HEADER, MAX_REVALIDATION_INTERVAL, EDGE_LOOKUP_TIMEOUT_MS, EDGE_READ_TIMEOUT_MS, KV_READ_TIMEOUT_MS } from "./cf-cache-constants.js";
28
9
  export { CACHE_STALE_AT_HEADER, CACHE_STATUS_HEADER, CACHE_TAGS_HEADER, CACHE_TAGGED_AT_HEADER, TAG_MARKER_PREFIX, CACHE_REVALIDATING_AT_HEADER, MAX_REVALIDATION_INTERVAL, EDGE_LOOKUP_TIMEOUT_MS, EDGE_READ_TIMEOUT_MS, KV_READ_TIMEOUT_MS, };
29
10
  import type { KVNamespace, CFCacheReadDebugEvent, CFCacheDebug, CFCacheStoreOptions } from "./cf-cache-types.js";
30
11
  export type { KVNamespace, CFCacheReadDebugEvent, CFCacheDebug, CFCacheStoreOptions, };
12
+ /**
13
+ * @internal Reset the per-isolate shell and marker memos and the once-per-
14
+ * isolate warnings (tests).
15
+ */
16
+ export declare function resetCFShellMemoForTests(): void;
31
17
  export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
32
18
  readonly supportsPassiveShellReads: true;
33
19
  readonly defaults?: CacheDefaults;
@@ -39,6 +25,19 @@ export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<T
39
25
  private readonly edgeLookupTimeoutMs;
40
26
  private readonly edgeReadTimeoutMs;
41
27
  private readonly kvReadTimeoutMs;
28
+ /**
29
+ * The shell snapshot read's budgets. `read` (getShell: partial replay, the
30
+ * testing helpers) has nothing committed, so a slow snapshot is a quick
31
+ * miss at kvReadTimeoutMs. `document` (readShellDocument, the serve path)
32
+ * has sent the prelude and cannot finish without the snapshot's doc
33
+ * record; a timeout there reloads the page, so it waits at least
34
+ * SHELL_SNAPSHOT_READ_MIN_TIMEOUT_MS. `kvReadTimeoutMs <= 0` keeps both
35
+ * unbounded.
36
+ */
37
+ private readonly snapshotBudgets;
38
+ private readonly memo;
39
+ /** @internal SegmentCacheStore.freshReadsWindowMs */
40
+ readonly freshReadsWindowMs: number;
42
41
  private readonly debug?;
43
42
  private readonly kv?;
44
43
  /** True when constructed without KV: no durable tag history (see ctor). */
@@ -72,7 +71,11 @@ export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<T
72
71
  * @internal
73
72
  */
74
73
  private emitDebug;
75
- /** Build-time-gated shell tier trace for deployed cross-colo diagnostics. */
74
+ /**
75
+ * Build-time-gated shell tier trace for deployed cross-colo diagnostics.
76
+ * A read's `stats` print as its timings, after the other details and before
77
+ * `expiresAt`.
78
+ */
76
79
  private debugShell;
77
80
  /**
78
81
  * Resolve the cache-key base URL for the current cache operation.
@@ -216,7 +219,9 @@ export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<T
216
219
  get(key: string): Promise<CacheGetResult | null | CacheReadError>;
217
220
  /**
218
221
  * Store entry data with TTL and optional SWR window.
219
- * Uses waitUntil for non-blocking write when available.
222
+ * Resolves once the L1 write lands, per the SegmentCacheStore.set contract
223
+ * (also held by waitUntil when available); the KV write stays in the
224
+ * background.
220
225
  * When KV is configured, also persists to L2.
221
226
  */
222
227
  set(key: string, data: CachedEntryData, ttl: number, swr?: number): Promise<void>;
@@ -269,7 +274,7 @@ export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<T
269
274
  /**
270
275
  * Generation gate for shell writes and the capture scheduler
271
276
  * (isTagsInvalidatedSince). With KV it is the durable marker cascade.
272
- * Without KV there are no markers: in purge mode the per-request memo is
277
+ * Without KV there are no markers: in purge mode the per-request mask is
273
278
  * the only signal — a capture racing THIS request's updateTag() is still
274
279
  * rejected (read-your-own-writes), while cross-request races are bounded
275
280
  * by ttl+swr exactly like the data families' purge-mode writes. Without
@@ -278,31 +283,121 @@ export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<T
278
283
  */
279
284
  private isShellGenerationInvalidated;
280
285
  /**
281
- * Get a cached PPR shell entry from Cache API, falling through to KV and
282
- * promoting a valid KV hit. Both tiers store one envelope so the prelude,
283
- * postponed state, snapshot, versions, and generation metadata cannot mix.
284
- * SWR remains a plain staleness flag; the capture scheduler's module-level
285
- * in-flight set is the recapture stampede guard.
286
+ * Get a cached PPR shell entry: the public custom-store contract, built on
287
+ * readShellDocument. It waits for the snapshot and re-encodes the prelude to
288
+ * the base64 `ShellCacheEntry.prelude` the contract carries (native
289
+ * `toBase64` in workerd). Partial replay and the testing helpers read
290
+ * through here; the document serve path reads readShellDocument directly.
291
+ * A snapshot that failed to read is a miss here: with no committed response
292
+ * to protect, an entry without its pins is worse than none.
286
293
  */
287
294
  getShell(key: string): Promise<{
288
295
  entry: ShellCacheEntry;
289
296
  shouldRevalidate?: boolean;
290
297
  } | null>;
291
298
  /**
292
- * Store a PPR shell envelope in Cache API and, when KV is configured and
293
- * the retention meets its 60-second floor, KV. The shared write is
294
- * registered with waitUntil and awaited so invalidation rejection can be
295
- * acknowledged to the capture scheduler. Short-lived shells remain useful
296
- * in L1 even though KV rejects them; a KV-less store is L1-only by design
297
- * (edge-only ppr — see the section comment).
299
+ * @internal Prelude-first read of a shell (SegmentCacheStore
300
+ * .readShellDocument). Cache API first, then KV; both tiers hold one
301
+ * cf-shell-frame body, so the prelude, postponed state, snapshot, versions,
302
+ * and generation metadata cannot mix. Before it resolves it reads only the
303
+ * head and the prelude's bytes, and the tag-marker read runs alongside the
304
+ * prelude read (it starts as soon as the head is parsed, and is awaited
305
+ * before this resolves, so an invalidated shell is never returned). The
306
+ * snapshot is the rest of the same body, read on its own promise, bounded
307
+ * by the document read's budget (`snapshotBudgets.document`; getShell uses
308
+ * `snapshotBudgets.read`). SWR is a plain staleness flag; the capture
309
+ * scheduler's in-flight set is the recapture stampede guard.
310
+ */
311
+ readShellDocument(key: string, options?: ShellDocumentReadOptions): Promise<ShellDocumentRead | null>;
312
+ private readShellDocumentWithin;
313
+ /** @internal SegmentCacheStore.dropShellMemo */
314
+ dropShellMemo(key: string): void;
315
+ /**
316
+ * Start the marker reads of the tags this shell key carried last time (and
317
+ * the route's static tags) alongside the entry read (readShellTagMarker),
318
+ * so the marker check after the head reuses them. With KV only: without it
319
+ * there are no markers to read. A wrong hint costs a wasted read; the check
320
+ * itself uses the head's tags.
321
+ */
322
+ private prefetchShellMarkers;
323
+ /**
324
+ * A HIT: remember the entry's tags for the next read's hints, and fill the
325
+ * marker row. The shell-read marker values stay in their own record: the
326
+ * request's data reads (the tail's, or a partial navigation's matchPartial
327
+ * on the same context) read their markers as before.
328
+ */
329
+ private recordShellHit;
330
+ /** The isolate marker memo's key for a tag: markers are per namespace and version. */
331
+ private markerMemoKey;
332
+ /** The memo key: one per namespace, build version, base URL, and shell key. */
333
+ private shellMemoKey;
334
+ /**
335
+ * Serve a memoized shell (shell-memo.ts). The tag-marker check is the only
336
+ * store I/O left, so it is started first and awaited before anything is
337
+ * returned: nothing of an invalidated shell reaches the response. A shell
338
+ * that went stale since it was memoized, or that predates a tag this
339
+ * isolate is invalidating or just invalidated (RecentTagInvalidations), is
340
+ * dropped and read from the store (SWR recapture scheduling and each mode's
341
+ * invalidation semantics stay with the store read). That read keeps the
342
+ * caller's `tagHints`: cfTagHints may have evicted the key while the memo
343
+ * still holds it.
344
+ */
345
+ private readMemoizedShell;
346
+ /**
347
+ * Stats for one shell read, allocated only when the request collects
348
+ * `debugPerformance` metrics or the build has the internal shell trace on,
349
+ * so a normal HIT takes no timestamps for them.
350
+ */
351
+ private shellReadStats;
352
+ /**
353
+ * Read a frame's head and prelude under one budget, starting the
354
+ * tag-marker read (`isInvalidated`) the moment the head is parsed so it
355
+ * overlaps the prelude read, then await it. Expired heads are rejected
356
+ * before the marker read starts.
357
+ */
358
+ private openShellFrame;
359
+ /**
360
+ * The ShellDocumentRead for an opened frame. Its snapshot promise starts
361
+ * reading the rest of the body now (off the commit path, not after it),
362
+ * bounded by `budget` ({@link snapshotReadBudget}), and is registered with
363
+ * waitUntil so the KV tier's L1 promotion and a corrupt entry's eviction
364
+ * still run when nothing awaits it (a read that is not served). A snapshot
365
+ * whose length differs from the head's `sl`, or that fails to parse, evicts
366
+ * the entry from both tiers and reports cache-corrupt. Every failure
367
+ * resolves undefined and names itself on `snapshotFailure` (`unavailable`
368
+ * for a timeout or read error, `corrupt` otherwise): a document HIT then
369
+ * degrades (rsc-rendering.ts serveShellHit), and getShell misses. Only
370
+ * a read whose snapshot parsed goes into the per-isolate memo (when still
371
+ * fresh), and `promote` receives its snapshot bytes (the KV tier's L1
372
+ * promotion). `headers` are the L1 response's, kept for the KV-less
373
+ * memo-hit marker check.
374
+ */
375
+ private shellDocumentRead;
376
+ /** Delete a shell from both tiers (non-blocking). */
377
+ private evictShell;
378
+ /**
379
+ * Store a PPR shell in Cache API and, when KV is configured, KV — the same
380
+ * cf-shell-frame bytes in both. The shared write is registered with
381
+ * waitUntil and awaited so invalidation rejection can be acknowledged to
382
+ * the capture scheduler. A KV-less store is L1-only by design (edge-only
383
+ * ppr — see the section comment).
384
+ *
385
+ * KV rejects an expirationTtl below KV_MIN_EXPIRATION_TTL, so a shorter
386
+ * retention (a `ppr` ttl + swr under 60, or a shell capped to a short or
387
+ * aging route cache() entry) is written with that floor: the frame head's deadlines (`s`, `e`) are the
388
+ * entry's, and every read checks `e` (openShellFrame), so the extra KV
389
+ * lifetime is never served, and the shell is readable from every colo, not
390
+ * only from the capturing colo's L1.
298
391
  */
299
392
  putShell(key: string, entry: ShellCacheEntry, ttlSeconds?: number, swrSeconds?: number, tags?: string[]): Promise<"stored" | "invalidated" | "uncacheable" | void>;
300
- /** Rebuild the public shell entry from its validated storage envelope. */
301
- private shellEnvelopeToEntry;
302
- /** Build the Cache API representation of the coupled shell envelope. */
303
- private shellEnvelopeResponse;
304
- /** KV shell fallback with corruption checks and background L1 promotion. */
305
- private kvGetShell;
393
+ /** The Cache API representation of a shell frame. */
394
+ private shellFrameResponse;
395
+ /**
396
+ * KV shell fallback: the same prelude-first read, then L1 promotion. After
397
+ * an L1 attempt, `stats` is that read's and gains the L1 attempt's time
398
+ * (`l1MissMs`) before its per-tier fields restart for KV.
399
+ */
400
+ private kvReadShellDocument;
306
401
  /** Promote a valid KV shell into the per-colo Cache API tier. */
307
402
  private promoteShellToL1;
308
403
  /**
@@ -442,15 +537,16 @@ export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<T
442
537
  * configured), invalidateTags() evicts L1 entries via a Cloudflare
443
538
  * purge-by-tag call, so a hit that SURVIVED is trusted without a per-read
444
539
  * marker lookup — that skipped lookup is the entire point of purge mode.
445
- * Only the per-request memo is consulted (synchronous, no KV read) so a
446
- * request that ran updateTag() still masks its own entries during the purge
447
- * propagation window (read-your-own-writes).
540
+ * Only the request's mask and its memo are consulted (synchronous, no KV
541
+ * read) so a request that ran updateTag() still misses its own entries
542
+ * during the purge propagation window (read-your-own-writes).
448
543
  *
449
544
  * The trust is conditional on the entry actually CARRYING this store's
450
545
  * entry Cache-Tag tokens (`headers`): an entry a purge cannot reach — one
451
546
  * written before the tokens existed, or whose tag set overflowed the
452
547
  * Cache-Tag header limit — keeps the full marker check, or purge mode
453
- * would serve it stale until TTL with no eviction path.
548
+ * would serve it stale until TTL with no eviction path. KV-less there are
549
+ * no markers, so it gets the per-request mask check like any hit (#973).
454
550
  *
455
551
  * Without tagPurge this is the full marker cascade. KV-tier reads and
456
552
  * shells always use isGloballyInvalidated directly: purge cannot reach KV,
@@ -463,9 +559,8 @@ export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<T
463
559
  /**
464
560
  * Read a tag's latest invalidation timestamp (or null if never invalidated)
465
561
  * through the cascade: per-request memo -> per-colo L1 cache (only when
466
- * tagCacheTtl > 0) -> KV (the global truth). The memo is always consulted
467
- * first so it stays authoritative within a request (read-your-own-writes),
468
- * and every KV/L1 result is written back into the memo. A Cache API miss
562
+ * tagCacheTtl > 0) -> KV (the global truth). Every KV/L1 result is written
563
+ * back into the memo, so the request reads one value per tag. A Cache API miss
469
564
  * always falls through to KV; absence is represented by a cached sentinel,
470
565
  * never by a miss.
471
566
  *
@@ -475,6 +570,18 @@ export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<T
475
570
  * @internal
476
571
  */
477
572
  private readTagMarker;
573
+ /**
574
+ * A PPR shell read's marker for `tag`: this request's own store read if it
575
+ * has one, else through the per-isolate marker memo (isolate-tag-memo.ts,
576
+ * stale-while-revalidate
577
+ * under `markerFreshMs` / `markerMaxStaleMs`; a request carrying the
578
+ * fresh-reads cookie reads L1/KV). The value is kept in the request's
579
+ * shell-read record, never in the per-request memo the data families read,
580
+ * HIT or MISS. A store read still lands in the per-request memo, as any
581
+ * marker read does; it is not stale. A timed-out read fails open for this
582
+ * request and is not memoized.
583
+ */
584
+ private readShellTagMarker;
478
585
  /**
479
586
  * Uncached body of readTagMarker: L1 (per-colo Cache API, opt-in via
480
587
  * tagCacheTtl) -> KV. Writes the resolved value back into the memo.
@@ -554,39 +661,84 @@ export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<T
554
661
  * @internal
555
662
  */
556
663
  private putTagMarkerL1;
664
+ /**
665
+ * SegmentCacheStore.isTagsInvalidatedSince: the SAME KV markers used by
666
+ * runtime envelopes also evict immutable build shells, and gate the writes
667
+ * of cache executions that started before an invalidation (#977,
668
+ * tag-invalidation.ts predatesInvalidation). Marker >= since. A marker read
669
+ * that fails or times out answers false (fail open), or true for the write
670
+ * gate (`failClosed`). With KV each marker is read past the per-request
671
+ * memo (isInvalidatedSincePastMemo); KV-less it degrades to the per-request
672
+ * mask in purge mode and to false otherwise — see
673
+ * isShellGenerationInvalidated.
674
+ */
675
+ isTagsInvalidatedSince(tags: string[], sinceMs: number, options?: {
676
+ failClosed?: boolean;
677
+ }): Promise<boolean>;
678
+ /**
679
+ * isTagsInvalidatedSince with KV, never answered from a marker value this
680
+ * request read before the asking gate. A write gate asks after an
681
+ * execution that can run for seconds, and the per-request memo can hold
682
+ * the marker the request read before it (a stale hit reads its entry's
683
+ * markers, then its refresh runs): answered from it, another isolate's
684
+ * invalidation in between let the refresh write.
685
+ *
686
+ * - This request's own invalidations answer from its mask, checked again
687
+ * once the reads are back: a read in flight when the request invalidated
688
+ * one of the tags resolved to the marker before it.
689
+ * - Each tag is read through gateMarkerRead (request-tag-mask.ts): gates
690
+ * asking while a read is in flight share it; a settled read is not
691
+ * reused.
692
+ * - The read returns its own value from L1 (tagCacheTtl) or KV, not one an
693
+ * older read of the tag memoized while it was in flight, and fills the
694
+ * memo only when it holds nothing, so a build shell's check still serves
695
+ * the request's later reads. A timed-out read fills nothing.
696
+ * - It publishes to L1 unless the request masked the tag
697
+ * (fetchTagMarker), and never without a request context (a detached
698
+ * waitUntil task), where a mask cannot be told apart.
699
+ * - A read that fails or times out answers `failClosed`: the write gate
700
+ * counts it as an invalidation, other callers as none.
701
+ * @internal
702
+ */
703
+ private isInvalidatedSincePastMemo;
557
704
  /**
558
705
  * Invalidate every entry tagged with any of `tags`. Receives the whole batch
559
706
  * from one updateTag()/revalidateTag() call so the eager-purge hook fires
560
- * ONCE (one CDN purge request, not one per tag). For each tag: records the KV
561
- * marker (the durable cross-colo truth that reads compare taggedAt against),
707
+ * ONCE (one CDN purge request, not one per tag). Before its first await it
708
+ * masks the tags for the rest of the request (maskTagsForRequest), which is
709
+ * all revalidateTag() waits for. For each tag it then records the KV marker
710
+ * (the durable cross-colo truth that reads compare taggedAt against) and
562
711
  * writes the fresh marker straight into this colo's L1 (write-through, NOT
563
712
  * delete - a delete would let the next read re-read a not-yet-converged KV
564
- * value and re-arm the stale window), and memoizes it for same-request
565
- * read-your-own-writes. In purge mode (tagPurge) it then AWAITS the
566
- * consumer's purge-by-tag call with the entry Cache-Tags — the eviction the
567
- * per-read marker skip on L1 hits relies on. Finally fires onRevalidateTag
568
- * with the namespaced lookup Cache-Tags so a consumer purge evicts the
569
- * cached lookups in other colos promptly (otherwise they converge within
570
- * tagCacheTtl).
713
+ * value and re-arm the stale window). In purge mode (tagPurge) it then
714
+ * AWAITS the consumer's purge-by-tag call with the entry Cache-Tags — the
715
+ * eviction the per-read marker skip on L1 hits relies on. Finally fires
716
+ * onRevalidateTag with the namespaced lookup Cache-Tags so a consumer purge
717
+ * evicts the cached lookups in other colos promptly (otherwise they converge
718
+ * within tagCacheTtl).
571
719
  *
572
- * Durable-write integrity: the in-memory write-through (memo + L1) for a tag
573
- * runs ONLY after that tag's KV marker write is confirmed. If any KV write
574
- * fails (transient error; over-limit keys are normalized by toKVKey rather
575
- * than rejected), this rejects with the
576
- * failed tags so an awaiting updateTag() surfaces the failure instead of
577
- * silently reporting success while other requests/colos serve stale data. The
578
- * eager purge still fires for the whole batch first (it is additive).
579
- */
580
- /**
581
- * Shell tag-generation gate (SegmentCacheStore.isTagsInvalidatedSince): the
582
- * SAME KV markers used by runtime envelopes also evict immutable build shells
583
- * and captures whose write races updateTag(). Thin public wrapper over the
584
- * shell generation check (marker >= since, fail open); KV-less it degrades
585
- * to the per-request memo in purge mode and to false otherwise — see
586
- * isShellGenerationInvalidated.
720
+ * Durable-write integrity: the write-through other requests read (the
721
+ * isolate marker memo and L1) for a tag runs ONLY after that tag's KV marker
722
+ * write is confirmed. If any KV write fails (transient error; over-limit
723
+ * keys are normalized by toKVKey rather than rejected), this rejects with
724
+ * the failed tags so an awaiting updateTag() surfaces the failure instead of
725
+ * silently reporting success while other requests/colos serve stale data.
726
+ * The purge and onRevalidateTag still fire for the whole batch once the
727
+ * puts settle, whether or not one failed (they are additive); the rejection
728
+ * follows them.
587
729
  */
588
- isTagsInvalidatedSince(tags: string[], sinceMs: number): Promise<boolean>;
589
730
  invalidateTags(tags: string[]): Promise<void>;
731
+ /**
732
+ * The request-local half of invalidateTags(), run before its first await
733
+ * (#973): the request masks the tags (request-tag-mask.ts), so every later
734
+ * read in the request (data families, purge-mode L1 hits, shell reads)
735
+ * treats them as invalidated at `invalidatedAt` without waiting for the KV
736
+ * put or the purge that revalidateTag() leaves to waitUntil. Skipped where
737
+ * no read consults the mask (neither KV nor tagPurge).
738
+ */
739
+ private maskTagsForRequest;
740
+ /** invalidateTags' marker writes, tag purge, and hooks. */
741
+ private writeTagInvalidation;
590
742
  /**
591
743
  * KV fallback for segment cache reads.
592
744
  * Returns null if KV is not configured, entry is missing, or expired.
@@ -2,6 +2,7 @@ import type { ExecutionContext } from "../../types/request-scope.js";
2
2
  import type { CacheDefaults } from "../types.js";
3
3
  import type { RequestContext } from "../../server/request-context.js";
4
4
  import type { CloudflareZonePurgeOptions } from "./cf-zone-purge.js";
5
+ import type { StoreMemoOptions } from "../shell-memo.js";
5
6
  /**
6
7
  * Minimal Cloudflare KV Namespace interface.
7
8
  * Avoids hard dependency on @cloudflare/workers-types.
@@ -294,9 +295,12 @@ export interface CFCacheStoreOptions<TEnv = unknown> {
294
295
  edgeLookupTimeoutMs?: number;
295
296
  /**
296
297
  * Latency budget (ms) for reading the BODY of a matched L1 entry
297
- * (response.json()). CF streams the cache body lazily, so the multi-second
298
- * tail can appear after `match` already resolved; this bounds it. On timeout
299
- * the read is treated as a miss and falls through to L2/KV or render.
298
+ * (response.json(); for a PPR shell, only its head and prelude — the
299
+ * snapshot behind them is read after the HIT commits, under
300
+ * {@link kvReadTimeoutMs}). CF streams the cache body lazily, so the
301
+ * multi-second tail can appear after `match` already resolved; this bounds
302
+ * it. On timeout the read is treated as a miss and falls through to L2/KV or
303
+ * render.
300
304
  *
301
305
  * Separate from {@link edgeLookupTimeoutMs} because a healthy body read
302
306
  * (fetch + JSON parse of a potentially large Flight payload) takes a little
@@ -309,7 +313,11 @@ export interface CFCacheStoreOptions<TEnv = unknown> {
309
313
  * Latency budget (ms) for an L2 (KV) read. KV is the last cache tier before a
310
314
  * full render and is a global store (~50ms healthy, seconds when degraded);
311
315
  * this bounds it so a slow namespace cannot pin the request. On timeout the
312
- * read is treated as a miss (no L1 promote) and falls through to render.
316
+ * read is treated as a miss (no L1 promote) and falls through to render. For
317
+ * a PPR shell it covers opening the KV value and reading its head and
318
+ * prelude; separately, it bounds the shell's snapshot read (either tier),
319
+ * which runs off the commit path: a timeout there lets the tail run without
320
+ * the snapshot's pins.
313
321
  *
314
322
  * Defaults to {@link KV_READ_TIMEOUT_MS} (170) -- a few multiples above the
315
323
  * ~50ms healthy read, with headroom for legitimate tails (large payloads / far
@@ -319,6 +327,25 @@ export interface CFCacheStoreOptions<TEnv = unknown> {
319
327
  * <= 0) to disable and always await KV.
320
328
  */
321
329
  kvReadTimeoutMs?: number;
330
+ /**
331
+ * Per-isolate memos ({@link StoreMemoOptions}), one set per isolate and
332
+ * shared by every CFCacheStore in it. `shellMs` (default 2000) and
333
+ * `shellMaxBytes` (default 16 MiB) size the PPR shell memo;
334
+ * `markerFreshMs` (default 1000) and `markerMaxStaleMs` (default 10000)
335
+ * the tag-marker memo PPR shell reads use with KV bound (served
336
+ * stale-while-revalidate). A memo HIT still runs the tag-marker check; the
337
+ * isolate that invalidates drops its own shell copies (and keeps reads in
338
+ * flight from memoizing them again) and writes the new marker into its
339
+ * marker memo, and its own `putShell` replaces its copy. Another isolate
340
+ * rejects a memoized shell once its marker memo sees the invalidation (up
341
+ * to `markerMaxStaleMs`). Without KV, in purge mode, the purge does not
342
+ * reach other isolates' memos: they serve the purged shell until their
343
+ * window passes. The response of a request that ran `updateTag()` /
344
+ * `revalidateTag()` sets the fresh-reads cookie, and the same user's
345
+ * requests carrying it skip both memos. `{ shellMs: 0 }` turns the shell
346
+ * memo off, `{ markerFreshMs: 0 }` the marker memo.
347
+ */
348
+ memo?: StoreMemoOptions;
322
349
  /**
323
350
  * Emit a {@link CFCacheReadDebugEvent} per L1 read. `true` logs to console
324
351
  * (visible via `wrangler tail`); pass a function to capture events directly.
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Prelude-first binary layout of a CFCacheStore PPR shell entry, shared by the
3
+ * Cache API body and the KV value (issue #941; docs/design/shell-entry-layout.md):
4
+ *
5
+ * "RSH1" | head length, 8 hex digits | head JSON | prelude bytes | snapshot JSON
6
+ *
7
+ * A document HIT needs the head (versions, freshness, tags, postponed) and the
8
+ * raw prelude before its first byte, and the capture snapshot only in the
9
+ * tail. Laying them out in that order lets the serve path read the head,
10
+ * start the tag-marker read, read exactly `pl` prelude bytes, and commit while
11
+ * the snapshot bytes are still arriving. One record keeps the prelude and the
12
+ * snapshot generation-coupled by construction: a Cache API entry and a KV
13
+ * value are written and read whole, so a split read can never pair one
14
+ * capture's prelude with another's pins.
15
+ *
16
+ * Every byte is UTF-8 text when the prelude is (React's HTML through
17
+ * TextEncoder always is), so the KV value is stored as a string (the
18
+ * KVNamespace contract's `put(key, string)`) and read back as the same bytes
19
+ * with `{ type: "stream" }`.
20
+ */
21
+ import type { ShellSnapshotRecord } from "../types.js";
22
+ /** The frame head: every entry field except the prelude and the snapshot. */
23
+ export interface ShellFrameHead {
24
+ /** ShellCacheEntry.reactVersion */
25
+ rv: string;
26
+ /** ShellCacheEntry.buildVersion */
27
+ bv: string;
28
+ /** Capture-generation start time (ms epoch), used by tag marker checks. */
29
+ c: number;
30
+ /** When the entry becomes stale (ms epoch). */
31
+ s: number;
32
+ /** When the entry hard-expires (ms epoch). */
33
+ e: number;
34
+ /** Cache tags (for distributed tag invalidation). */
35
+ t?: string[];
36
+ /** Timestamp when tags were attached (ms epoch). */
37
+ ta?: number;
38
+ /** ShellCacheEntry.initialTheme */
39
+ i?: string;
40
+ /** ShellCacheEntry.docKey; navigation replay needs it after any round trip. */
41
+ dk?: string;
42
+ /** ShellCacheEntry.prunedRecords (diagnostic). */
43
+ pr?: string;
44
+ /** ShellCacheEntry.navigationOnly; its partial-context prelude is not document-safe. */
45
+ no?: true;
46
+ /** ShellCacheEntry.postponed. Absent iff `no` (no document half is stored). */
47
+ po?: string | null;
48
+ /** Prelude byte length (0 for a navigationOnly entry). */
49
+ pl: number;
50
+ /**
51
+ * Snapshot byte length (0 when the entry has none). A body whose snapshot
52
+ * part is any other length was truncated or corrupted.
53
+ */
54
+ sl: number;
55
+ }
56
+ /**
57
+ * Serialize a shell entry into one prelude-first frame. Throws when the head's
58
+ * byte lengths (`pl`, `sl`) do not describe `prelude` and `snapshot`.
59
+ */
60
+ export declare function encodeShellFrame(head: ShellFrameHead, prelude: Uint8Array, snapshot?: Uint8Array): Uint8Array<ArrayBuffer>;
61
+ /**
62
+ * The frame as the string KV stores. Throws when the prelude is not valid
63
+ * UTF-8, which would not round-trip through a string.
64
+ */
65
+ export declare function shellFrameToText(frame: Uint8Array): string;
66
+ /** UTF-8 JSON of a snapshot, or undefined when there is none to store. */
67
+ export declare function encodeShellSnapshot(snapshot: ShellSnapshotRecord[] | undefined): Uint8Array | undefined;
68
+ /**
69
+ * Parse the snapshot bytes that follow the prelude. Zero bytes means the entry
70
+ * carries no snapshot. Throws on anything else that is not a JSON array: the
71
+ * caller treats that as corruption.
72
+ */
73
+ export declare function parseShellSnapshot(bytes: Uint8Array): ShellSnapshotRecord[] | undefined;
74
+ /**
75
+ * Incremental reader over a streamed frame (a Cache API response body or a KV
76
+ * `{ type: "stream" }` value). The serve path reads the head and exactly the
77
+ * prelude's bytes before it commits; the snapshot is whatever remains.
78
+ */
79
+ export declare class ShellFrameReader {
80
+ private readonly reader;
81
+ private chunks;
82
+ private buffered;
83
+ private done;
84
+ /** Bytes of the prefix and head, once readHead has read them. */
85
+ headBytes: number;
86
+ constructor(body: ReadableStream<Uint8Array>);
87
+ /** Exactly `n` bytes, or null when the stream ends first. */
88
+ take(n: number): Promise<Uint8Array | null>;
89
+ /** The head, or null when the magic, length, or JSON is malformed or short. */
90
+ readHead(): Promise<ShellFrameHead | null>;
91
+ /** Everything after what was taken, to the end of the stream. */
92
+ readRest(): Promise<Uint8Array>;
93
+ /** Stop reading and release the body. */
94
+ cancel(): void;
95
+ }
@@ -1,3 +1,4 @@
1
+ import type { MarkerMemoOutcome } from "../isolate-tag-memo.js";
1
2
  /**
2
3
  * Cache-API path prefix for the optional per-colo L1 cache of tag-invalidation
3
4
  * markers (enabled by tagCacheTtl). Distinct from data keys (doc:/fn:/segment)
@@ -12,4 +13,22 @@ export declare const TAG_MARKER_CACHE_PREFIX = "__tagmarker__/";
12
13
  */
13
14
  export declare const TAG_MARKER_ABSENT = "none";
14
15
  export declare function getTagMarkerMemo(ctx: object, store: object): Map<string, number | null>;
16
+ /**
17
+ * A request's PPR shell-read marker reads (issue #941), kept apart from the
18
+ * per-request memo above, HIT or MISS. A shell read may answer a marker from
19
+ * the isolate marker memo (isolate-tag-memo.ts), up to `markerMaxStaleMs`
20
+ * old; the data families must never see that value. Scar tissue: first the
21
+ * shell read wrote into the per-request memo, so a MISS request rendered and
22
+ * stored its `"use cache"` data under a stale marker; then a HIT copied its
23
+ * entry's tags across, which a partial navigation's matchPartial (on the same
24
+ * context as its replay gate's getShell) read, and a `cache()` segment it
25
+ * wrote kept the stale item past `markerMaxStaleMs` under a fresh taggedAt.
26
+ */
27
+ export interface ShellMarkerReads {
28
+ /** The read of each tag, settled or in flight (collapses concurrent reads). */
29
+ reads: Map<string, Promise<number | null>>;
30
+ /** How the isolate memo answered each tag (the marker row's `memo=`). */
31
+ outcomes: Map<string, MarkerMemoOutcome>;
32
+ }
33
+ export declare function getShellMarkerReads(ctx: object, store: object): ShellMarkerReads;
15
34
  export declare function getTagMarkerInflight(ctx: object, store: object): Map<string, Promise<number | null>>;
@@ -13,6 +13,16 @@
13
13
  * 4. If miss → run handler, cache if response has cache headers
14
14
  */
15
15
  import type { MiddlewareFn, MiddlewareContext } from "../router/middleware.js";
16
+ import { type RequestContext } from "../server/request-context.js";
17
+ /**
18
+ * Whether this cache will store the response the current render produces, as
19
+ * far as the render can tell: the middleware runs it (a MISS or a stale
20
+ * refresh) and the response stub already opts in. payloadInitialTheme
21
+ * (rsc/full-payload.ts) then renders the no-cookie default theme (#978). A
22
+ * Cache-Control written after next() is not on the stub yet; for that render
23
+ * shouldCacheResponse refuses a payload marked `_payloadVisitorTheme`.
24
+ */
25
+ export declare function documentCacheStoresRender(reqCtx: Pick<RequestContext, "_documentCacheRender" | "res">): boolean;
16
26
  export interface DocumentCacheOptions<TEnv = any> {
17
27
  /**
18
28
  * Skip caching for specific paths (e.g., API routes)