@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
@@ -1,78 +1,76 @@
1
1
  /**
2
2
  * Capture data snapshot: recording + seeding stores for PPR shell parity.
3
3
  *
4
- * The scar tissue this fixes: a PPR HIT serves frozen prelude bytes, then a
5
- * FULL FRESH Flight render for hydration. Any shell-baked (non-hole) content
6
- * that drifts between capture time and hit time — a cache() segment with a
7
- * shorter ttl than the shell, a tag-invalidated item — makes the fresh payload
8
- * disagree with the prelude, so React throws a hydration text mismatch and
9
- * regenerates the tree client-side (wiping the FOUC theme class, flashing
10
- * content). See docs/design/ppr-shell-resume.md.
4
+ * A PPR HIT serves frozen prelude bytes, then a Flight payload for hydration
5
+ * that must agree with them byte for byte. The handler layer's part of that
6
+ * payload is the capture's own doc segment record: the capture writes it
7
+ * first, renders its prelude from it, and every HIT replays it (no handler
8
+ * runs on a HIT). The loader layer's part is the bake-lane (`ssr: false`)
9
+ * loader pins: a promise-free bake-lane loader is served from its pin and
10
+ * does not run; one whose return carries promises (`holes`), or whose pushes
11
+ * the record could not keep (`runs`), runs on the HIT. See
12
+ * docs/design/ppr-shell-resume.md.
11
13
  *
12
- * The fix (Next.js resume-data-cache analog, adapted to Rango's cache rings):
13
- * the CAPTURE render records every cache-store read-hit and write it performed
14
- * (the {@link RecordingShellStore}); the record rides inside the ShellCacheEntry
15
- * as its `snapshot`; on a HIT the tail render reads through a
16
- * {@link SeededShellStore} overlay that serves those recorded values AS FRESH,
17
- * so the shell region reproduces byte-identically while everything NOT recorded
18
- * (the holes — masked loaders were never executed at capture, so their reads
19
- * were never recorded) stays live.
20
- *
21
- * The invariant, verbatim: the snapshot is exactly the set of cache-store reads
22
- * the capture render performed; replaying them on a HIT reproduces the shell
23
- * content byte-identically; everything not recorded stays live.
14
+ * The invariant, verbatim: the snapshot is the doc record and the bake-lane
15
+ * loader pins; replaying them on a HIT reproduces the shell content
16
+ * byte-identically; everything else a HIT reads comes from the store. A hole
17
+ * (a live-lane loader, or a promise nested in a bake-lane return) never reads
18
+ * the capture's copy of a value: it is dynamic or has its own cache. A
19
+ * bake-lane loader body that runs on a HIT reads the store too; its pinned
20
+ * container paths stay the pin's (overlayLoaderContainer), and its settled
21
+ * pushes stay the record's (HandleStore.pushRestored).
24
22
  */
25
23
  import type { SegmentCacheStore, CacheGetResult, CacheItemResult, CacheItemOptions, CachedEntryData, ShellCacheEntry, ShellSnapshotRecord, CacheReadError } from "./types.js";
26
24
  /**
27
- * A store wrapper the CAPTURE render reads through. Every call passes through to
28
- * the underlying store unchanged; for the item/segment/response families it also
29
- * RECORDS, last-write-wins per (family, key):
30
- * - read-hits (get/getItem/getResponse returning non-null) — the value that
31
- * fed the shell,
32
- * - writes (set/setItem/putResponse) — the value a MISS computed and baked.
33
- * The shell family (getShell/putShell) is never recorded (the snapshot rides
34
- * inside a shell entry). Reads that MISS are not recorded (a miss produced no
35
- * shell content; if the render then computed and wrote, that write is recorded).
25
+ * A store wrapper the CAPTURE render reads through. Every call passes through
26
+ * to the underlying store unchanged; the one thing it records is the doc
27
+ * segment record ({@link recordSegmentWrite}). No cache read is recorded: a
28
+ * HIT replays the handler layer from the doc record and serves a promise-free
29
+ * bake-lane loader from its pin, and everything else a HIT reads (a hole, a
30
+ * bake-lane loader body that runs there) comes from the store.
36
31
  *
37
32
  * Deferred writes: cache writes run under waitUntil (fire-and-forget on Node,
38
- * executionContext on workerd), so their setItem/set calls — hence their records
39
- * — may land after the shell has quiesced. The capture collects those write
40
- * promises via {@link trackWrite} and awaits them ({@link settleWrites}) before
41
- * draining, so a MISS-at-capture value is still pinned.
33
+ * executionContext on workerd), so the doc record's write may land after the
34
+ * capture's match returns. The capture collects those write promises via
35
+ * {@link trackWrite} and awaits them ({@link settleWrites}) before reading the
36
+ * record and draining.
42
37
  */
43
38
  export declare class RecordingShellStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
44
39
  private readonly inner;
40
+ /** The recorded segment records by key, last write wins. */
45
41
  private readonly records;
46
42
  private readonly writes;
47
43
  constructor(inner: SegmentCacheStore<TEnv>);
48
44
  get defaults(): SegmentCacheStore<TEnv>["defaults"];
49
45
  get keyGenerator(): SegmentCacheStore<TEnv>["keyGenerator"];
50
46
  get supportsPassiveShellReads(): true | undefined;
51
- private record;
47
+ get isTagsInvalidatedSince(): SegmentCacheStore<TEnv>["isTagsInvalidatedSince"];
52
48
  /** Track a deferred cache-write promise so the capture can await it pre-drain. */
53
49
  trackWrite(p: Promise<unknown>): void;
54
50
  /**
55
- * Record a segment-family write into the snapshot WITHOUT touching the inner
56
- * store. The shell fast path's implicit doc-cache scope writes through this
57
- * (via {@link SnapshotOnlySegmentStore}): the recorded doc entry must ride
58
- * ONLY inside the shell entry — a passthrough write would leave a doc-keyed
59
- * entry in the real store that the NEXT capture's lookup would hit, replaying
60
- * the previous generation's segments instead of re-running handlers (breaking
51
+ * Record the doc segment record into the snapshot WITHOUT touching the
52
+ * inner store. The capture's implicit doc-cache scope writes through this
53
+ * (via {@link SnapshotOnlySegmentStore}): the record must ride ONLY inside
54
+ * the shell entry — a passthrough write would leave a doc-keyed entry in the
55
+ * real store that the NEXT capture's lookup would hit, replaying the
56
+ * previous generation's segments instead of re-running handlers (breaking
61
57
  * SWR recapture freshness).
62
58
  */
63
59
  recordSegmentWrite(key: string, data: CachedEntryData): void;
64
60
  /**
65
- * Await the tracked deferred writes so their records are present before drain.
66
- * Drains ITERATIVELY: a write task can schedule a NESTED write (the ring-3
67
- * cacheRoute path schedules its actual store.set in a second waitUntil while the
68
- * first is running), so each awaited batch may enqueue more. Loop until the
69
- * queue empties or the deadline passes. Bounded: a pathologically slow write
70
- * must never stall the capture task, so a key that does not settle in time is
71
- * left unpinned (it drifts, the pre-snapshot behavior) rather than hanging.
61
+ * Await the tracked deferred writes so their records are present before
62
+ * drain, including a NESTED write a settled one scheduled (the ring-3
63
+ * cacheRoute path schedules its actual store.set in a second waitUntil).
64
+ * Bounded: a pathologically slow write must never stall the capture task.
65
+ * Resolves true when every tracked write settled, false at the deadline
66
+ * (settleCaptureRecord then fails the capture when the doc record is among
67
+ * the writes still pending); a later call still waits for what is pending.
72
68
  */
73
- settleWrites(timeoutMs: number): Promise<void>;
74
- /** The recorded snapshot (last-write-wins per family+key), or undefined if empty. */
69
+ settleWrites(timeoutMs: number): Promise<boolean>;
70
+ /** The recorded segment records, or undefined if none. */
75
71
  drainSnapshot(): ShellSnapshotRecord[] | undefined;
72
+ /** One recorded segment record, by key. */
73
+ getRecord(key: string): CachedEntryData | undefined;
76
74
  get(key: string): Promise<CacheGetResult | null | CacheReadError>;
77
75
  set(key: string, data: CachedEntryData, ttl: number, swr?: number): Promise<void>;
78
76
  delete(key: string): Promise<boolean>;
@@ -93,16 +91,61 @@ export declare class RecordingShellStore<TEnv = unknown> implements SegmentCache
93
91
  putShell(key: string, entry: ShellCacheEntry, ttlSeconds?: number, swrSeconds?: number, tags?: string[]): Promise<"stored" | "invalidated" | "uncacheable" | void>;
94
92
  invalidateTags(tags: string[]): Promise<void>;
95
93
  }
96
- /** True iff `store` is a RecordingShellStore (duck-typed across module copies). */
94
+ /** `store` when it is a RecordingShellStore (an instanceof check). */
97
95
  export declare function getRecordingStore<TEnv>(store: SegmentCacheStore<TEnv> | undefined): RecordingShellStore<TEnv> | undefined;
98
96
  /**
99
- * The store the shell fast path's IMPLICIT doc-cache scope resolves during a
100
- * capture: reads pass through the recording store (a real-store hit is
101
- * recorded, exactly like any capture read), but segment WRITES are recorded
102
- * into the snapshot only — see {@link RecordingShellStore.recordSegmentWrite}
103
- * for why passthrough would break SWR recapture. Routes with their OWN
104
- * cache() config never see this store (their scope resolves the app-level
105
- * recording store and keeps today's record-and-write behavior).
97
+ * True when `snapshot` carries the canonical doc segment record `docKey`
98
+ * names, with at least one segment: the record partial replay and a document
99
+ * HIT tail consume.
100
+ */
101
+ export declare function hasDocRecord(snapshot: readonly ShellSnapshotRecord[] | undefined, docKey: string | undefined): boolean;
102
+ /**
103
+ * Split a capture's snapshot into what a HIT or a partial replay reads and
104
+ * the rest (docs/design/shell-entry-layout.md §2). Kept: the doc record
105
+ * (`docKey`), as a copy inside the entry (a separately stored record could be
106
+ * evicted on its own, and the HIT would have nothing to replay), and the
107
+ * loader-family pins unless the entry is navigation-only (partial replay of
108
+ * such an entry seeds the doc record alone). Every other record goes.
109
+ */
110
+ export declare function pruneShellSnapshot(snapshot: readonly ShellSnapshotRecord[], navigationOnly: boolean, docKey: string | undefined): {
111
+ kept: ShellSnapshotRecord[];
112
+ pruned: ShellSnapshotRecord[];
113
+ };
114
+ /**
115
+ * The shell entry head's fields estimateShellEntryBytes does not measure
116
+ * (versions, timestamps, theme, lengths, flags; about 200 bytes in a
117
+ * CFCacheStore frame) and the frame's 12-byte prefix, with room to spare.
118
+ */
119
+ export declare const SHELL_ENTRY_HEAD_ALLOWANCE_BYTES: number;
120
+ /**
121
+ * Bytes (UTF-8) a stored shell entry takes, as the built-in stores write it:
122
+ * the prelude, the snapshot's JSON, and a JSON head that carries the
123
+ * postponed state as an escaped string next to the tags and doc key
124
+ * (CFCacheStore's frame head, VercelCacheStore's envelope), plus
125
+ * SHELL_ENTRY_HEAD_ALLOWANCE_BYTES for the head's fixed fields and the
126
+ * frame prefix. Measuring the raw postponed bytes undercounted: every quote
127
+ * in it is escaped in the head (a /ppr-large/holes entry measured 1,098
128
+ * bytes under its KV value). The capture's whole-entry guard compares this
129
+ * with the store's value limit.
130
+ */
131
+ export declare function estimateShellEntryBytes(parts: {
132
+ preludeBytes: number;
133
+ postponed: string | null | undefined;
134
+ snapshot: readonly ShellSnapshotRecord[] | undefined;
135
+ tags: readonly string[] | undefined;
136
+ docKey: string | undefined;
137
+ prunedRecords: string | undefined;
138
+ }): number;
139
+ /** Snapshot records by family, e.g. `segment:1/loader:2` (no commas: it rides a Server-Timing desc). */
140
+ export declare function countSnapshotFamilies(snapshot: readonly ShellSnapshotRecord[]): string;
141
+ /**
142
+ * The store the capture's IMPLICIT doc-cache scope resolves: reads pass
143
+ * through the recording store to the real one, but segment WRITES are
144
+ * recorded into the snapshot only — see
145
+ * {@link RecordingShellStore.recordSegmentWrite} for why passthrough would
146
+ * break SWR recapture. A route with its OWN cache() scope keeps that scope's
147
+ * real-store reads and writes; recordShellCaptureDocRecord (cache-store.ts)
148
+ * writes its doc record through this store too.
106
149
  */
107
150
  export declare class SnapshotOnlySegmentStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
108
151
  private readonly recording;
@@ -113,74 +156,67 @@ export declare class SnapshotOnlySegmentStore<TEnv = unknown> implements Segment
113
156
  set(key: string, data: CachedEntryData): Promise<void>;
114
157
  delete(key: string): Promise<boolean>;
115
158
  }
159
+ /** One decoded loader-family pin (buildShellLoaderSeed). */
160
+ export interface ShellLoaderSeedEntry {
161
+ /** The Flight-deserialized elided container (recorded paths + markers). */
162
+ container: unknown;
163
+ /**
164
+ * True when the record carries hole markers: the HIT overlay must gate on
165
+ * the fresh run. False = fully pinned; the payload promise resolves
166
+ * immediately from the pin (loader-cache.ts pin-first path).
167
+ */
168
+ holes: boolean;
169
+ /**
170
+ * The record needs its loader body to run on a HIT for pushes the capture
171
+ * could not record (ShellSnapshotLoaderValue.runs). Only read for a
172
+ * hole-free record: a hole-carrying one always runs.
173
+ */
174
+ runs: boolean;
175
+ }
116
176
  /**
117
177
  * Materialize the loader-family seed from a shell snapshot for a HIT's tail
118
178
  * render: Flight-deserialize each recorded (promise-elided) bake-lane
119
179
  * container into a segment-key -> container Map, which serveShellHit assigns
120
180
  * to the tail context's `_shellLoaderSeed` for the resolveLoaderData overlay.
121
- * Lives here so every snapshot family is decoded in this module (the
122
- * item/segment/response families via {@link SeededShellStore}); the loader
123
- * family is not a store read, so it seeds the context instead of a store.
181
+ * Lives here so every snapshot family is decoded in this module (the segment
182
+ * family via {@link SeededShellStore}); the loader family is not a store
183
+ * read, so it seeds the context instead of a store.
124
184
  *
125
185
  * Deserializations run in parallel; a record that fails to decode is skipped
126
186
  * (that loader drifts — the pre-snapshot behavior — instead of failing the
127
187
  * HIT). Returns undefined when the snapshot carries no loader records, without
128
188
  * touching the Flight codec (kept lazy for cold paths and non-RSC configs).
129
189
  */
130
- export interface ShellLoaderSeedEntry {
131
- /** The Flight-deserialized elided container (recorded paths + markers). */
132
- container: unknown;
133
- /**
134
- * True when the record carries hole markers (or predates the capture-side
135
- * hole bit, where hole-ness is unknown): the HIT overlay must gate on the
136
- * fresh run. False = fully pinned; the payload promise resolves immediately
137
- * from the pin (loader-cache.ts pin-first path).
138
- */
139
- holes: boolean;
140
- }
141
190
  export declare function buildShellLoaderSeed(snapshot: ShellSnapshotRecord[]): Promise<Map<string, ShellLoaderSeedEntry> | undefined>;
142
191
  /**
143
- * A read-through overlay the HIT tail render reads through. For a key present in
144
- * the snapshot it serves the recorded value AS FRESH (shouldRevalidate: false —
145
- * a pinned key must NOT kick SWR background revalidation) so the tail's payload
146
- * matches the frozen prelude. Every other read falls through to the real store
147
- * (the holes — masked loaders were never recorded — stay live). Writes pass
148
- * through so a live hole may legitimately write, except that `segmentsOnly`
149
- * fully isolates the segment family: misses do not fall through, and writes or
150
- * deletes stay local to the navigation overlay. The shell family always passes
151
- * through. With `segmentsOnly`, item and response reads also pass through so a
152
- * partial navigation cannot freeze captured data or write a partial result into
153
- * the canonical document namespace.
192
+ * The implicit doc scope's store on a HIT tail and on a partial replay: it
193
+ * serves the snapshot's segment records, the doc record among them, and
194
+ * fully isolates the segment family. Misses do not fall through, and writes
195
+ * or deletes stay local to the overlay, so a partial navigation cannot write
196
+ * a partial result into the canonical document namespace. It carries nothing
197
+ * else: every other read on a HIT (a hole, a bake-lane loader body that runs
198
+ * there) resolves the request's own store. Loader-family records are not
199
+ * store reads: serveShellHit seeds them onto the tail context
200
+ * (_shellLoaderSeed) for the resolveLoaderData overlay instead.
154
201
  */
155
202
  export declare class SeededShellStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
156
203
  private readonly inner;
157
- private readonly items;
158
204
  private readonly segments;
159
- private readonly responses;
160
- private readonly segmentsOnly;
161
- constructor(inner: SegmentCacheStore<TEnv>, snapshot: ShellSnapshotRecord[], options?: {
162
- segmentsOnly?: boolean;
163
- });
205
+ constructor(inner: SegmentCacheStore<TEnv>, snapshot: readonly ShellSnapshotRecord[]);
164
206
  get defaults(): SegmentCacheStore<TEnv>["defaults"];
165
207
  get keyGenerator(): SegmentCacheStore<TEnv>["keyGenerator"];
166
- get supportsPassiveShellReads(): true | undefined;
167
208
  get(key: string): Promise<CacheGetResult | null | CacheReadError>;
168
- set(key: string, data: CachedEntryData, ttl: number, swr?: number): Promise<void>;
209
+ set(key: string, data: CachedEntryData): Promise<void>;
169
210
  delete(key: string): Promise<boolean>;
170
- clear(): Promise<void>;
171
- getResponse(key: string): Promise<{
172
- response: Response;
173
- shouldRevalidate: boolean;
174
- } | null>;
175
- putResponse(key: string, response: Response, ttl: number, swr?: number, tags?: string[]): Promise<void>;
176
- getItem(key: string): Promise<CacheItemResult | null>;
177
- setItem(key: string, value: string, options?: CacheItemOptions): Promise<void>;
178
- getShell(key: string, options?: {
179
- claimRevalidation?: boolean;
180
- }): Promise<{
181
- entry: ShellCacheEntry;
182
- shouldRevalidate?: boolean;
183
- } | null>;
184
- putShell(key: string, entry: ShellCacheEntry, ttlSeconds?: number, swrSeconds?: number, tags?: string[]): Promise<"stored" | "invalidated" | "uncacheable" | void>;
185
- invalidateTags(tags: string[]): Promise<void>;
211
+ }
212
+ /**
213
+ * Thrown by a document HIT tail's match when its doc record cannot supply the
214
+ * handler layer (it failed to decode, or the entry lost it). The tail must not
215
+ * run handlers after the prelude committed, so withCacheLookup throws this
216
+ * instead of resolving segments, and serveShellHit degrades the response:
217
+ * the entry is replaced with a tombstone, the memo is dropped, a recapture is
218
+ * scheduled, and the browser reloads into a MISS.
219
+ */
220
+ export declare class ShellRecordUnavailableError extends Error {
221
+ constructor(docKey: string | undefined);
186
222
  }
@@ -21,7 +21,56 @@
21
21
  * primitive for each tag. A single configured store (the common case) owns its
22
22
  * own tag index and distributed invalidation - there is no separate
23
23
  * tag-invalidation store.
24
+ *
25
+ * Both call invalidateTags() synchronously, inside the request (#973). A store
26
+ * masks the tags for the rest of the request before its first await (the
27
+ * built-in stores write their per-request marker state there), so the request
28
+ * that ran either verb reads its own writes; revalidateTag() leaves only the
29
+ * durable remainder (KV marker put, tag purge, expireTag) to waitUntil.
30
+ * Invariant: the mask only turns the invalidating request's hits into misses.
31
+ * If the durable write then fails, that request paid extra misses, never a
32
+ * stale read, and other requests read the durable state. Both also record
33
+ * the tags in the isolate's invalidation order (markInvalidated), so a cache
34
+ * execution that started before one of its tags was invalidated, in any
35
+ * request, is neither joined nor written (#973, #977): see
36
+ * predatesInvalidation, and docs/design/caching.md "Read-your-own-writes".
37
+ */
38
+ import type { SegmentCacheStore } from "./types.js";
39
+ import { type ExecutionStart } from "./invalidation-order.js";
40
+ export { executionStart, invalidatedSince, type ExecutionStart, } from "./invalidation-order.js";
41
+ /**
42
+ * The write gate of every cache execution (#977): whether one of `tags` was
43
+ * invalidated after the execution that produced the value started.
44
+ *
45
+ * Every built-in store stamps an entry when it is written (CFCacheStore's
46
+ * `taggedAt`, VercelCacheStore's `ta`) or checks nothing on write
47
+ * (MemorySegmentCacheStore), so a value read before an invalidation and
48
+ * written after it would be served as newer than the invalidation until it
49
+ * expires. Two answers, either one skips the write:
50
+ *
51
+ * - this isolate's order (invalidatedSince), which sees every request's
52
+ * updateTag()/revalidateTag() here, the calling one included;
53
+ * - the store's markers (isTagsInvalidatedSince), for another isolate's
54
+ * invalidation, as far as the store can tell: KV markers on CFCacheStore
55
+ * (KV-less, only this request's own), `tm` markers on VercelCacheStore,
56
+ * the process markers on MemorySegmentCacheStore. Asked about the
57
+ * milliseconds after the start's: this isolate's own invalidation that
58
+ * preceded the start usually shares its millisecond (Workers freeze
59
+ * Date.now() between I/O), and `>=` would skip every write started right
60
+ * after it. Another isolate's invalidation in the start's own millisecond
61
+ * is not caught. Asked fail-closed: a marker the store cannot read
62
+ * counts as an invalidation.
63
+ *
64
+ * The order is read again once the store answers: its read can be in flight
65
+ * while this isolate invalidates (an action awaits a "use cache" miss, whose
66
+ * write waits on the read, then runs updateTag()), and it then answers with
67
+ * the markers from before. markInvalidated is synchronous, so this second
68
+ * look sees every invalidation made before the answer.
69
+ *
70
+ * Invariant: a skipped write only costs a later miss, never a stale read.
71
+ * The caller still returns the value it computed.
24
72
  */
73
+ export declare function predatesInvalidation(store: SegmentCacheStore, tags: readonly string[] | undefined, start: ExecutionStart): Promise<boolean>;
25
74
  /**
26
75
  * Immediately expire every cache entry tagged with any of `tags`, resolving
27
76
  * once in-process invalidation across all configured stores completes.
@@ -47,10 +96,12 @@ export declare function updateTag(...tags: string[]): Promise<void>;
47
96
  * This is NOT stale-while-revalidate: like updateTag() it hard-purges, so the
48
97
  * next read after the invalidation lands is a miss that re-renders fresh. The
49
98
  * only difference from updateTag() is awaitability - revalidateTag() defers the
50
- * purge off the response path and is not awaited.
99
+ * durable write off the response path and is not awaited.
51
100
  *
52
- * Use in Route Handlers / webhooks. For read-your-own-writes inside a Server
53
- * Action, use updateTag() instead so the action's own response is fresh.
101
+ * Read-your-own-writes (#973): each built-in store masks the tags for the rest
102
+ * of this request before revalidateTag() returns, so a Server Action's own
103
+ * re-render after it is fresh, as after `await updateTag()`. Other requests see
104
+ * the invalidation once the durable write lands.
54
105
  *
55
106
  * Fire-and-forget: because this returns void and runs in the background, a
56
107
  * failed durable marker write (e.g. a transient KV outage) is NOT surfaced to