@rangojs/router 0.27.0 → 0.28.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 (212) hide show
  1. package/dist/bin/rango.js +1 -1
  2. package/dist/types/__internal.d.ts +7 -1
  3. package/dist/types/browser/event-controller.d.ts +2 -1
  4. package/dist/types/browser/history-state.d.ts +3 -9
  5. package/dist/types/browser/navigation-store.d.ts +8 -6
  6. package/dist/types/browser/react/filter-segment-order.d.ts +2 -2
  7. package/dist/types/browser/segment-reconciler.d.ts +6 -4
  8. package/dist/types/browser/types.d.ts +2 -1
  9. package/dist/types/browser/wire-protocol.d.ts +25 -0
  10. package/dist/types/cache/cache-key-utils.d.ts +11 -5
  11. package/dist/types/cache/cache-scope.d.ts +34 -22
  12. package/dist/types/cache/cf/cf-cache-constants.d.ts +2 -2
  13. package/dist/types/cache/cf/cf-cache-store.d.ts +47 -65
  14. package/dist/types/cache/cf/cf-kv-utils.d.ts +1 -1
  15. package/dist/types/cache/handle-snapshot.d.ts +9 -4
  16. package/dist/types/cache/segment-codec.d.ts +14 -0
  17. package/dist/types/cache/shell-snapshot.d.ts +1 -1
  18. package/dist/types/cache/tag-invalidation.d.ts +10 -0
  19. package/dist/types/cache/taint.d.ts +5 -3
  20. package/dist/types/cache/types.d.ts +7 -3
  21. package/dist/types/cache/vercel/vercel-cache-store.d.ts +8 -6
  22. package/dist/types/prefetch-markers.d.ts +5 -14
  23. package/dist/types/prerender/store.d.ts +31 -0
  24. package/dist/types/prerender/warm-request.d.ts +3 -3
  25. package/dist/types/prerender.d.ts +2 -2
  26. package/dist/types/route-name.d.ts +5 -0
  27. package/dist/types/router/error-handling.d.ts +4 -4
  28. package/dist/types/router/instrument.d.ts +4 -3
  29. package/dist/types/router/intercept-resolution.d.ts +25 -9
  30. package/dist/types/router/match-context.d.ts +4 -11
  31. package/dist/types/router/match-handlers.d.ts +2 -6
  32. package/dist/types/router/match-middleware/background-revalidation.d.ts +10 -20
  33. package/dist/types/router/match-middleware/cache-lookup.d.ts +34 -86
  34. package/dist/types/router/match-middleware/segment-resolution.d.ts +8 -6
  35. package/dist/types/router/prerender-match.d.ts +21 -6
  36. package/dist/types/router/preview-match.d.ts +2 -7
  37. package/dist/types/router/request-classification.d.ts +5 -0
  38. package/dist/types/router/router-context.d.ts +7 -46
  39. package/dist/types/router/router-interfaces.d.ts +8 -14
  40. package/dist/types/router/segment-resolution/loader-cache.d.ts +3 -3
  41. package/dist/types/router/segment-resolution/loader-mask.d.ts +4 -4
  42. package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +20 -18
  43. package/dist/types/router/segment-resolution/resolution-pass.d.ts +93 -0
  44. package/dist/types/router/segment-resolution/revalidation.d.ts +82 -31
  45. package/dist/types/router/segment-resolution.d.ts +1 -2
  46. package/dist/types/router/segment-wrappers.d.ts +11 -48
  47. package/dist/types/router/telemetry.d.ts +2 -0
  48. package/dist/types/router/types.d.ts +10 -0
  49. package/dist/types/rsc/full-payload.d.ts +2 -2
  50. package/dist/types/rsc/handler-context.d.ts +6 -6
  51. package/dist/types/rsc/json-route-result.d.ts +5 -9
  52. package/dist/types/rsc/request-stages.d.ts +203 -0
  53. package/dist/types/rsc/response-cache-serve.d.ts +7 -12
  54. package/dist/types/rsc/response-route-handler.d.ts +2 -2
  55. package/dist/types/rsc/routine-plan.d.ts +1 -1
  56. package/dist/types/rsc/server-action.d.ts +2 -2
  57. package/dist/types/rsc/shell-capture-constants.d.ts +1 -1
  58. package/dist/types/rsc/shell-capture.d.ts +132 -15
  59. package/dist/types/rsc/shell-serve.d.ts +37 -12
  60. package/dist/types/server/context.d.ts +9 -8
  61. package/dist/types/server/handle-store.d.ts +16 -0
  62. package/dist/types/server/render-pass.d.ts +72 -0
  63. package/dist/types/server/request-context.d.ts +24 -104
  64. package/dist/types/server/rewrite-trail.d.ts +2 -2
  65. package/dist/types/testing/dispatch.d.ts +35 -18
  66. package/dist/types/types/cache-types.d.ts +6 -3
  67. package/dist/types/types/error-types.d.ts +9 -1
  68. package/dist/types/types/handler-context.d.ts +9 -1
  69. package/dist/types/types/segments.d.ts +1 -1
  70. package/dist/types/vite/discovery/build-discovery-phase.d.ts +40 -0
  71. package/dist/types/vite/discovery/build-env.d.ts +22 -0
  72. package/dist/types/vite/discovery/bundle-scan-phase.d.ts +12 -0
  73. package/dist/types/vite/discovery/dev-prerender-cache.d.ts +3 -2
  74. package/dist/types/vite/discovery/dev-server-phase.d.ts +26 -0
  75. package/dist/types/vite/discovery/discovery-errors.d.ts +1 -1
  76. package/dist/types/vite/discovery/state.d.ts +12 -0
  77. package/dist/types/vite/plugins/version-plugin.d.ts +1 -1
  78. package/dist/types/vite/plugins/virtual-entries.d.ts +1 -1
  79. package/dist/types/vite/router-discovery.d.ts +8 -47
  80. package/dist/types/vite/temp-rsc-server.d.ts +39 -0
  81. package/dist/types/vite/utils/prerender-utils.d.ts +9 -0
  82. package/dist/types/wire-params.d.ts +34 -0
  83. package/dist/vite/index.js +3809 -3710
  84. package/package.json +1 -1
  85. package/skills/cache-guide/SKILL.md +8 -5
  86. package/skills/caching/SKILL.md +37 -18
  87. package/skills/document-cache/SKILL.md +13 -1
  88. package/skills/handler-use/SKILL.md +1 -1
  89. package/skills/intercept/SKILL.md +17 -7
  90. package/skills/middleware/SKILL.md +18 -0
  91. package/skills/ppr/SKILL.md +18 -3
  92. package/skills/prerender/SKILL.md +39 -15
  93. package/skills/rango/SKILL.md +5 -1
  94. package/skills/server-actions/SKILL.md +8 -1
  95. package/skills/testing/response-routes.md +6 -4
  96. package/skills/use-cache/SKILL.md +46 -8
  97. package/src/__internal.ts +7 -1
  98. package/src/browser/connection-warmup.ts +3 -1
  99. package/src/browser/event-controller.ts +5 -3
  100. package/src/browser/history-state.ts +6 -12
  101. package/src/browser/navigation-bridge.ts +14 -13
  102. package/src/browser/navigation-client.ts +18 -25
  103. package/src/browser/navigation-store.ts +25 -27
  104. package/src/browser/navigation-transaction.ts +4 -1
  105. package/src/browser/prefetch/cache.ts +2 -1
  106. package/src/browser/prefetch/fetch.ts +8 -16
  107. package/src/browser/prefetch/queue.ts +4 -4
  108. package/src/browser/react/NavigationProvider.tsx +2 -1
  109. package/src/browser/react/filter-segment-order.ts +2 -2
  110. package/src/browser/rsc-router.tsx +1 -0
  111. package/src/browser/segment-reconciler.ts +66 -53
  112. package/src/browser/server-action-bridge.ts +50 -57
  113. package/src/browser/types.ts +2 -1
  114. package/src/browser/wire-protocol.ts +41 -0
  115. package/src/cache/cache-key-utils.ts +11 -5
  116. package/src/cache/cache-runtime.ts +110 -50
  117. package/src/cache/cache-scope.ts +110 -64
  118. package/src/cache/cf/cf-cache-constants.ts +2 -2
  119. package/src/cache/cf/cf-cache-store.ts +486 -874
  120. package/src/cache/cf/cf-kv-utils.ts +1 -1
  121. package/src/cache/document-cache.ts +34 -13
  122. package/src/cache/handle-snapshot.ts +19 -7
  123. package/src/cache/segment-codec.ts +55 -0
  124. package/src/cache/shell-snapshot.ts +2 -2
  125. package/src/cache/tag-invalidation.ts +21 -2
  126. package/src/cache/taint.ts +5 -3
  127. package/src/cache/types.ts +7 -3
  128. package/src/cache/vercel/vercel-cache-store.ts +243 -310
  129. package/src/handles/pushed-by.ts +1 -1
  130. package/src/prefetch-markers.ts +5 -16
  131. package/src/prerender/store.ts +76 -0
  132. package/src/prerender/warm-request.ts +8 -4
  133. package/src/prerender.ts +2 -2
  134. package/src/route-name.ts +8 -0
  135. package/src/router/error-handling.ts +4 -4
  136. package/src/router/instrument.ts +4 -3
  137. package/src/router/intercept-resolution.ts +77 -75
  138. package/src/router/loader-resolution.ts +5 -4
  139. package/src/router/match-api.ts +4 -5
  140. package/src/router/match-context.ts +20 -12
  141. package/src/router/match-handlers.ts +8 -20
  142. package/src/router/match-middleware/background-revalidation.ts +41 -53
  143. package/src/router/match-middleware/cache-lookup.ts +675 -726
  144. package/src/router/match-middleware/cache-store.ts +4 -8
  145. package/src/router/match-middleware/intercept-resolution.ts +3 -23
  146. package/src/router/match-middleware/segment-resolution.ts +34 -61
  147. package/src/router/middleware.ts +69 -11
  148. package/src/router/navigation-snapshot.ts +4 -4
  149. package/src/router/prerender-match.ts +100 -44
  150. package/src/router/preview-match.ts +6 -41
  151. package/src/router/request-classification.ts +13 -7
  152. package/src/router/route-snapshot.ts +2 -5
  153. package/src/router/router-context.ts +10 -95
  154. package/src/router/router-interfaces.ts +8 -14
  155. package/src/router/segment-resolution/loader-cache.ts +14 -9
  156. package/src/router/segment-resolution/loader-mask.ts +5 -5
  157. package/src/router/segment-resolution/prefetch-deferral.ts +30 -27
  158. package/src/router/segment-resolution/resolution-pass.ts +137 -0
  159. package/src/router/segment-resolution/revalidation.ts +897 -776
  160. package/src/router/segment-resolution.ts +4 -13
  161. package/src/router/segment-wrappers.ts +51 -269
  162. package/src/router/telemetry.ts +5 -0
  163. package/src/router/types.ts +10 -0
  164. package/src/router.ts +12 -13
  165. package/src/rsc/full-payload.ts +6 -5
  166. package/src/rsc/handler-context.ts +8 -14
  167. package/src/rsc/handler.ts +126 -507
  168. package/src/rsc/json-route-result.ts +5 -9
  169. package/src/rsc/loader-fetch.ts +6 -5
  170. package/src/rsc/progressive-enhancement.ts +5 -0
  171. package/src/rsc/request-stages.ts +757 -0
  172. package/src/rsc/response-cache-serve.ts +10 -14
  173. package/src/rsc/response-route-handler.ts +13 -5
  174. package/src/rsc/rewrite-passes.ts +2 -1
  175. package/src/rsc/routine-plan.ts +1 -1
  176. package/src/rsc/rsc-rendering.ts +164 -349
  177. package/src/rsc/server-action.ts +87 -113
  178. package/src/rsc/shell-capture-constants.ts +1 -1
  179. package/src/rsc/shell-capture.ts +212 -169
  180. package/src/rsc/shell-serve.ts +121 -18
  181. package/src/rsc/ssr-setup.ts +5 -4
  182. package/src/segment-system.tsx +3 -3
  183. package/src/server/context.ts +22 -25
  184. package/src/server/handle-store.ts +55 -0
  185. package/src/server/render-pass.ts +291 -0
  186. package/src/server/request-context.ts +26 -115
  187. package/src/server/rewrite-trail.ts +6 -4
  188. package/src/ssr/index.tsx +1 -1
  189. package/src/testing/dispatch.ts +223 -710
  190. package/src/testing/e2e/page-helpers.ts +2 -2
  191. package/src/testing/serve-shell-request.ts +23 -36
  192. package/src/types/cache-types.ts +6 -3
  193. package/src/types/error-types.ts +9 -1
  194. package/src/types/handler-context.ts +12 -1
  195. package/src/types/segments.ts +1 -1
  196. package/src/vite/discovery/build-discovery-phase.ts +204 -0
  197. package/src/vite/discovery/build-env.ts +148 -0
  198. package/src/vite/discovery/bundle-scan-phase.ts +128 -0
  199. package/src/vite/discovery/dev-prerender-cache.ts +8 -3
  200. package/src/vite/discovery/dev-server-phase.ts +1776 -0
  201. package/src/vite/discovery/discover-routers.ts +1 -1
  202. package/src/vite/discovery/discovery-errors.ts +1 -1
  203. package/src/vite/discovery/prerender-collection.ts +24 -39
  204. package/src/vite/discovery/state.ts +15 -0
  205. package/src/vite/plugins/version-plugin.ts +1 -1
  206. package/src/vite/plugins/virtual-entries.ts +1 -1
  207. package/src/vite/router-discovery.ts +29 -2428
  208. package/src/vite/temp-rsc-server.ts +223 -0
  209. package/src/vite/utils/prerender-utils.ts +47 -0
  210. package/src/wire-params.ts +35 -0
  211. package/dist/types/router/segment-resolution/fresh.d.ts +0 -72
  212. package/src/router/segment-resolution/fresh.ts +0 -945
package/dist/bin/rango.js CHANGED
@@ -1644,7 +1644,7 @@ var init_package = __esm({
1644
1644
  "package.json"() {
1645
1645
  package_default = {
1646
1646
  name: "@rangojs/router",
1647
- version: "0.27.0",
1647
+ version: "0.28.0",
1648
1648
  description: "Django-inspired RSC router with composable URL patterns",
1649
1649
  keywords: [
1650
1650
  "react",
@@ -53,7 +53,13 @@ export type { CachedEntryData, CacheGetResult, SerializedSegmentData, CacheDefau
53
53
  * @internal
54
54
  * Router context for AsyncLocalStorage.
55
55
  */
56
- export type { RouterContext, RevalidationContext, InterceptResult, } from "./router/router-context.js";
56
+ export type { RouterContext, InterceptResult, } from "./router/router-context.js";
57
+ /**
58
+ * @internal
59
+ * The request side of a revalidating resolution pass (RouterContext's
60
+ * revalidating resolvers take it).
61
+ */
62
+ export type { ResolutionPassInput } from "./router/segment-resolution/resolution-pass.js";
57
63
  /**
58
64
  * @internal
59
65
  * Route match context during pipeline processing.
@@ -207,7 +207,8 @@ export interface EventController {
207
207
  startAction(actionId: string, args: unknown[],
208
208
  /** Originating history entry key; scopes location-state arbitration. */
209
209
  cohort?: string): ActionHandle;
210
- abortAllActions(): void;
210
+ /** Abort every action in flight except `except` (an ActionHandle id). */
211
+ abortAllActions(except?: string): void;
211
212
  getState(): DerivedNavigationState;
212
213
  getActionState(actionId: string): TrackedActionState;
213
214
  getLocation(): NavigationLocation;
@@ -21,15 +21,9 @@ export declare function hasLocationState(state: unknown): boolean;
21
21
  */
22
22
  export declare function pushHistoryWithIdx(state: Record<string, unknown> | null, url: string, replace: boolean): void;
23
23
  /**
24
- * The forced-MISS reload marker (`SHELL_MISS_PARAM` in rsc/shell-serve.ts,
25
- * which the browser bundle must not import): a degraded PPR shell HIT
26
- * reloads the page with it, and the server renders a marked request like a
27
- * cache miss.
28
- */
29
- export declare const SHELL_MISS_MARKER: string;
30
- /**
31
- * Drop the forced-MISS marker from the address bar at boot, keeping the
32
- * history entry's state (initBrowserApp calls it before any location read).
24
+ * Drop the forced-MISS marker (`WIRE.shellMiss`, rsc/shell-serve.ts) from the
25
+ * address bar at boot, keeping the history entry's state (initBrowserApp
26
+ * calls it before any location read).
33
27
  * Left in place, a refresh or a shared link would stay off the shell and the
34
28
  * app's search params would carry it. The server strips the same marker from
35
29
  * the request before rendering (shell-serve.ts withoutShellMissMarker), so
@@ -24,16 +24,18 @@ export declare function liveHandleDataForCache(eventController: Pick<EventContro
24
24
  * Options for generating a history key
25
25
  */
26
26
  export interface HistoryKeyOptions {
27
- /** If true, append :intercept suffix to differentiate intercept entries */
27
+ /** Key an intercept entry (apart from the plain entry for the URL). */
28
28
  intercept?: boolean;
29
+ /** The page the intercept was opened over (history.state.sourceUrl). */
30
+ interceptSource?: string;
29
31
  }
30
32
  /**
31
- * Generate a cache key from a URL.
32
- * Uses pathname + search (query params) directly as the key.
33
- * Hash fragments (#) are excluded since they don't affect server data.
33
+ * Generate a cache key from a URL: pathname + search. Hash fragments (#) are
34
+ * excluded since they don't affect server data.
34
35
  *
35
- * For intercept routes, append `:intercept` suffix to cache them separately
36
- * from non-intercept versions of the same URL.
36
+ * An intercept entry is keyed `<url>:intercept:<source>`: its cached tree
37
+ * includes the page under the intercept. hasInterceptCache relies on the
38
+ * `:intercept:` separator.
37
39
  */
38
40
  export declare function generateHistoryKey(url?: string, options?: HistoryKeyOptions): string;
39
41
  /**
@@ -26,8 +26,8 @@ export declare function loaderParentId(id: string): string;
26
26
  * 2. Place each parallel slot id (contains ".@") immediately after its
27
27
  * parent layout/route id. Raw segment-resolution emission order does NOT
28
28
  * guarantee this: route-mounted parallels are resolved/pushed BEFORE the
29
- * route handler's segment is appended (see fresh.ts:resolveSegment for
30
- * routes, and revalidation.ts ~915-919), so matched can read
29
+ * route handler's segment is appended (see resolveSegment in
30
+ * segment-resolution/revalidation.ts), so matched can read
31
31
  * `[..., R0.@panel, R0]`. collectHandleData consumes segmentOrder verbatim
32
32
  * with later-wins semantics, so without normalization the route handler's
33
33
  * Meta would override the slot's more-specific Meta — backwards.
@@ -62,10 +62,12 @@ export interface ReconcileResult {
62
62
  */
63
63
  export declare function reconcileSegments(input: ReconcileInput): ReconcileResult;
64
64
  /**
65
- * Reconcile error segments with cached segments.
65
+ * Reconcile an error-boundary response with cached segments.
66
66
  *
67
- * For error responses, the server returns the error boundary segment.
68
- * This function overlays error segments onto the full cached tree,
69
- * preserving sibling layouts that aren't in the error parent chain.
67
+ * The server returns the error boundary segment and, above it, what the
68
+ * action's revalidation re-ran (rsc/server-action.ts). This function overlays
69
+ * them onto the full cached tree, preserving sibling layouts that aren't in
70
+ * the error parent chain. The error segment replaces its cached copy; every
71
+ * other segment merges as an action's does (mergeCached).
70
72
  */
71
73
  export declare function reconcileErrorSegments(cachedSegments: ResolvedSegment[], errorSegments: ResolvedSegment[]): ReconcileResult;
@@ -558,7 +558,8 @@ export interface NavigationStore {
558
558
  */
559
559
  handlesPending?: boolean;
560
560
  } | undefined;
561
- hasHistoryCache(historyKey: string): boolean;
561
+ /** Whether an intercept entry is cached for the plain `historyKey`, from any source page. */
562
+ hasInterceptCache(historyKey: string): boolean;
562
563
  /**
563
564
  * Update only the handleData (and optionally the stale / handlesPending flags)
564
565
  * of an existing cache entry. When a flag is omitted the entry's current value
@@ -0,0 +1,25 @@
1
+ /** The parameters of a partial request: a navigation, a prefetch or a JS server action. */
2
+ export interface PartialRequestInit {
3
+ /** A JS server action's id: written as `_rsc_action` in place of `_rsc_partial`. */
4
+ action?: string;
5
+ /** The mounted segment ids the server diffs against. */
6
+ segmentIds: readonly string[];
7
+ /**
8
+ * An empty `segmentIds`: "send" writes `_rsc_segments=` (navigation,
9
+ * action), "omit" leaves it out (prefetch). The prefetch cache keys carry
10
+ * the difference.
11
+ */
12
+ emptySegments: "send" | "omit";
13
+ stale?: boolean;
14
+ version?: string;
15
+ routerId?: string;
16
+ rewriteTarget?: string;
17
+ fill?: boolean;
18
+ }
19
+ /**
20
+ * Writes the partial-request parameters onto `url` in the one order every
21
+ * sender uses. Navigation and prefetch cache keys are built from the result
22
+ * (prefetch/cache.ts), so a navigation adopts a prefetch only while both
23
+ * write identically: browser/__tests__/wire-urls.test.ts pins the strings.
24
+ */
25
+ export declare function writePartialParams(url: URL, init: PartialRequestInit): URL;
@@ -97,8 +97,10 @@ export declare const KEY_PART_PREFIX = "key:";
97
97
  * (CacheScope.resolveKeyFrom, CacheScope.resolvePartition): the `key()`
98
98
  * results of the scope's cache() chain, outermost first, then its default
99
99
  * parts: the keyGenerator results of enclosing scopes on another store
100
- * (#974) and, for a record key under a scope without its own `key()`, its
101
- * default key (or its store's keyGenerator result, #970).
100
+ * (#974) and, for a route record key, the scope's own part: without a
101
+ * `key()` of its own, its default key (or its store's keyGenerator result,
102
+ * #970); with one, the default key's request kind (`doc`, `partial` or
103
+ * `intercept:<intercept>`, routeRecordKind in cache-scope.ts).
102
104
  *
103
105
  * Key scheme invariant (#975). A `key()` result is often request input,
104
106
  * and a record key has no route or scope discriminator (the store reads
@@ -124,9 +126,13 @@ export declare const KEY_PART_PREFIX = "key:";
124
126
  * A namespaced `key()` result starts with `key:` and holds no `|`. A
125
127
  * composed key holds a `|` and either starts with `key:` or holds no `:`.
126
128
  * Among composed keys, splitting on `|` recovers each part, a part's kind
127
- * (a `key:` prefix, or no `:` at all) and its value. Pinned by the collision
128
- * probe in src/cache/__tests__/cache-scope-chain.test.ts. A keyGenerator
129
- * result is the store's own namespace, as before.
129
+ * (a `key:` prefix, or no `:` at all) and its value. A request kind part is
130
+ * a default key's prefix without the URL after it, so it never equals a
131
+ * default key part; a keyGenerator part can equal it (`doc`) only when the
132
+ * keyGenerator returns a constant, which already gives every URL one record.
133
+ * Pinned by the collision probe in
134
+ * src/cache/__tests__/cache-scope-chain.test.ts. A keyGenerator result is
135
+ * the store's own namespace, as before.
130
136
  *
131
137
  * Shared with the testing helper `shellCacheKey`, so a test builds the
132
138
  * production partition.
@@ -14,16 +14,23 @@ import { type ExecutionStart } from "./tag-invalidation.js";
14
14
  import { type OwnedPushDelivery } from "./handle-snapshot.js";
15
15
  import type { RequestContext } from "../server/request-context.js";
16
16
  type CacheKeyFn = NonNullable<PartialCacheOptions["key"]>;
17
+ /**
18
+ * A route record that answered. `restoreHandles` is set when the lookup left
19
+ * the handle restore to its caller (lookupRouteDetailed `deferHandles`),
20
+ * which names the segments it sends (handle-snapshot.ts restoreHandles).
21
+ */
22
+ export interface CachedRouteHit {
23
+ segments: ResolvedSegment[];
24
+ shouldRevalidate: boolean;
25
+ restoreHandles?: (sentIds: ReadonlySet<string>) => Promise<void>;
26
+ }
17
27
  /**
18
28
  * Discriminated outcome of a route cache lookup — see
19
29
  * {@link CacheScope.lookupRouteDetailed} for what each status licenses.
20
30
  */
21
31
  export type CacheRouteLookupOutcome = {
22
32
  status: "hit";
23
- result: {
24
- segments: ResolvedSegment[];
25
- shouldRevalidate: boolean;
26
- };
33
+ result: CachedRouteHit;
27
34
  } | {
28
35
  status: "miss" | "bypass" | "error";
29
36
  };
@@ -187,14 +194,18 @@ export declare class CacheScope {
187
194
  * enclosing partition (#970, #974). A scope whose own config sets no
188
195
  * `key()` appends its own default key (resolveDefaultKey). Reusing the
189
196
  * outer `key()` result alone would give every route under the inner
190
- * cache() one record: a `key()` replaces the whole default key, so an
191
- * outer key that names no route (`tier:gold`) would make `/a` HIT `/b`'s
192
- * record. A scope with its own `key()` adds no default key: a `key()` is a
193
- * full override, and its store's keyGenerator does not run. With no
194
- * partition part it is resolveDefaultKey alone. With a `key()` on the
195
- * chain but no request context it rejects (keyWithoutContextError).
196
- * `partitionedPrefix` prefixes a key built from partition parts only (the
197
- * response family's `response:`).
197
+ * cache() one record: a `key()` replaces the default key's URL part, so
198
+ * an outer key that names no route (`tier:gold`) would make `/a` HIT
199
+ * `/b`'s record. A scope with its own `key()` adds no default key: its `key()`
200
+ * replaces the URL part, and its store's keyGenerator does not run. It
201
+ * appends `kind` instead (routeRecordKind): the `key()` decides which
202
+ * requests of one kind share a record, never that a document, a
203
+ * navigation and an intercept navigation do. With no partition part it is
204
+ * resolveDefaultKey alone. With a `key()` on the chain but no request
205
+ * context it rejects (keyWithoutContextError). `partitionedPrefix`
206
+ * prefixes a key built from partition parts only (the response family's
207
+ * `response:`, which passes no `kind`: a response route answers every
208
+ * request alike).
198
209
  *
199
210
  * Each `key()` result lives on the request context (`_resolvedCacheKeys`),
200
211
  * by function: it runs once per request whatever the default key
@@ -205,7 +216,7 @@ export declare class CacheScope {
205
216
  * itself: a `key()` calling `cookies()` would trip the capture guard there,
206
217
  * and a capture must store the partition its request resolved.
207
218
  */
208
- resolveKeyFrom(defaultKey: string, partitionedPrefix?: string): Promise<string>;
219
+ resolveKeyFrom(defaultKey: string, partitionedPrefix?: string, kind?: string): Promise<string>;
209
220
  /**
210
221
  * @internal The request partition of a ppr route's shell under this scope
211
222
  * (resolveShellPartition): its partition parts (resolvePartitionParts),
@@ -285,14 +296,14 @@ export declare class CacheScope {
285
296
  *
286
297
  * @param pathname - URL pathname for cache key generation
287
298
  * @param params - Route params for cache key generation
288
- * @param isIntercept - Whether this is an intercept navigation (uses different cache key)
299
+ * @param intercept - The intercept an intercept navigation selected
300
+ * (interceptRecordKey): its record has its own key
289
301
  * @param ownedPushes - What the record is to each loader's handle pushes
290
302
  * (withCacheLookup: loader-cache.ts loaderPins), called on a hit
303
+ * @param deferHandles - Restore no handle value here: the caller calls the
304
+ * hit's `restoreHandles` once it decided which segments it sends
291
305
  */
292
- lookupRoute(pathname: string, params: Record<string, string>, isIntercept?: boolean, ownedPushes?: () => OwnedPushDelivery): Promise<{
293
- segments: ResolvedSegment[];
294
- shouldRevalidate: boolean;
295
- } | null>;
306
+ lookupRoute(pathname: string, params: Record<string, string>, intercept?: string, ownedPushes?: () => OwnedPushDelivery, deferHandles?: boolean): Promise<CachedRouteHit | null>;
296
307
  /**
297
308
  * @internal lookupRoute with a discriminated outcome. The PPR replay
298
309
  * composition (withCacheLookup) may substitute the seeded doc record ONLY on
@@ -311,7 +322,7 @@ export declare class CacheScope {
311
322
  * A corrupt entry that was evicted reads as `miss`: the store was consulted
312
323
  * and holds nothing servable, which is the post-eviction truth.
313
324
  */
314
- lookupRouteDetailed(pathname: string, params: Record<string, string>, isIntercept?: boolean, ownedPushes?: () => OwnedPushDelivery): Promise<CacheRouteLookupOutcome>;
325
+ lookupRouteDetailed(pathname: string, params: Record<string, string>, intercept?: string, ownedPushes?: () => OwnedPushDelivery, deferHandles?: boolean): Promise<CacheRouteLookupOutcome>;
315
326
  /**
316
327
  * Record this scope's segment-DSL cache({ tags }) into the request tag union
317
328
  * synchronously, under the same gate cacheRoute() uses for a write.
@@ -341,13 +352,14 @@ export declare class CacheScope {
341
352
  * @param pathname - URL pathname for cache key generation
342
353
  * @param params - Route params for cache key generation
343
354
  * @param segments - All resolved segments to cache
344
- * @param isIntercept - Whether this is an intercept navigation (uses different cache key)
355
+ * @param intercept - The intercept an intercept navigation selected
356
+ * (interceptRecordKey): its record has its own key
345
357
  * @param start - Where the render of `segments` started: the record is not
346
358
  * written when one of its tags was invalidated since (#977,
347
359
  * predatesInvalidation). A shell capture's doc record passes none: it
348
360
  * lives only in the shell entry, which putShell gates by its own start.
349
361
  */
350
- cacheRoute(pathname: string, params: Record<string, string>, segments: ResolvedSegment[], isIntercept?: boolean, start?: ExecutionStart): Promise<void>;
362
+ cacheRoute(pathname: string, params: Record<string, string>, segments: ResolvedSegment[], intercept?: string, start?: ExecutionStart): Promise<void>;
351
363
  }
352
364
  /**
353
365
  * @internal The request partition of a ppr route's shell: with a route
@@ -392,7 +404,7 @@ export declare function createShellImplicitDocScope(marker: ShellImplicitCacheMa
392
404
  * normal partial navigation replay), substitute an implicit doc-level scope
393
405
  * so withCacheLookup/withCacheStore treat the WHOLE matched route as a
394
406
  * cache() boundary — the shell entry IS a cache() of the handler layer, with
395
- * loaders as the live carve-outs (resolveFreshLoadersAndYield).
407
+ * loaders as the live carve-outs (serveStoredSegments).
396
408
  *
397
409
  * A document HIT tail (`docTail`) always gets the implicit scope: it replays
398
410
  * the shell's own record and never consults a route-derived scope, whose
@@ -38,13 +38,13 @@ export declare const CACHE_REVALIDATING_AT_HEADER = "x-edge-cache-revalidating-a
38
38
  * re-arm re-puts the full window and restarts CF's retention clock, pinning a
39
39
  * perpetually-stale entry (one whose background revalidation keeps failing) past
40
40
  * its intended hard-expiry indefinitely. Mirrors the KVSegmentEnvelope `e`
41
- * field and the remaining-ttl math in promoteSegmentToL1/promoteItemToL1.
41
+ * field and the remaining-ttl math in promoteToL1.
42
42
  * @internal
43
43
  */
44
44
  export declare const CACHE_EXPIRES_AT_HEADER = "x-edge-cache-expires-at";
45
45
  /**
46
46
  * Header stashing the route author's original Cache-Control on L1 document
47
- * entries. putResponse/promoteResponseToL1 overwrite Cache-Control with a long
47
+ * entries. putResponse/promoteToL1 overwrite Cache-Control with a long
48
48
  * `max-age` so the CF Cache API retains the entry across the whole SWR window;
49
49
  * getResponse restores this original value before serving so the client and any
50
50
  * upstream CDN see the author's intended directive, not the internal edge TTL.
@@ -145,7 +145,7 @@ export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<T
145
145
  * the faulty per-colo entry ONLY when that fall-through found no good copy.
146
146
  *
147
147
  * The conditional evict is the load-bearing detail: when KV DOES serve a copy,
148
- * kvGet* has already scheduled a same-key promote (`cache.put`); an eager
148
+ * readKv has already scheduled a same-key promote (`cache.put`); an eager
149
149
  * `cache.delete` here would race that put with no CF Cache API ordering
150
150
  * guarantee and could clobber the freshly-restored entry. So in that case we
151
151
  * lean on #558's heal-by-overwrite (the non-suppressed fall-through promotes /
@@ -164,7 +164,10 @@ export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<T
164
164
  private healCorruptL1;
165
165
  /**
166
166
  * Re-put a stale L1 entry marked REVALIDATING, so concurrent requests serve it
167
- * without each triggering a revalidation. Shared by get()/getItem().
167
+ * without each triggering a revalidation (readL1). A JSON family re-puts its
168
+ * parsed body as a string, without a status text as set() writes it; the
169
+ * document re-puts a clone (`source`) whose original body still streams to
170
+ * the client.
168
171
  *
169
172
  * The write is NON-BLOCKING (waitUntil) and best-effort by design:
170
173
  * - It runs in waitUntil, so it never adds the put latency to the served stale
@@ -185,20 +188,6 @@ export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<T
185
188
  * @internal
186
189
  */
187
190
  private markRevalidating;
188
- /**
189
- * Document-tier counterpart of markRevalidating for getResponse's herd guard.
190
- * The segment/item tiers JSON-parse the body, so they re-put with a string
191
- * body; document bodies are streamed verbatim, so we re-put with a CLONED
192
- * response body (`response.clone()`) supplied by the caller -- the original
193
- * body still streams to the client while the marker carries the clone. Same
194
- * REVALIDATING status header, same revalidating-at stamp, same
195
- * remainingCacheControl re-put math as markRevalidating, so the document tier
196
- * suppresses concurrent revalidation for the identical MAX_REVALIDATION_INTERVAL
197
- * window the segment tier does. Best-effort and non-blocking: a failed marker
198
- * write must not affect the served stale read.
199
- * @internal
200
- */
201
- private markResponseRevalidating;
202
191
  /**
203
192
  * Guard the segment tier against a `keyGenerator` that returns a key colliding
204
193
  * with a reserved tag-marker namespace: `__tag__/` (the KV marker key) or
@@ -212,17 +201,29 @@ export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<T
212
201
  */
213
202
  private isReservedSegmentKey;
214
203
  /**
215
- * Get cached entry data by key.
216
- *
217
- * Handles SWR atomically:
204
+ * Get cached entry data by key (readL1). On L1 miss, falls back to KV (L2)
205
+ * if configured; KV hits are promoted to L1 in the background.
206
+ */
207
+ get(key: string): Promise<CacheGetResult | null | CacheReadError>;
208
+ /**
209
+ * The family's debug sink for one read; the document family is outside the
210
+ * debug surface (op is only get/getItem). Undefined when debug is off, so
211
+ * the hot path allocates no event.
212
+ * @internal
213
+ */
214
+ private debugSink;
215
+ /**
216
+ * The L1 read of a family, which handles SWR atomically:
218
217
  * - If stale and not already revalidating, marks as REVALIDATING and returns shouldRevalidate: true
219
218
  * - If already REVALIDATING (and recent), returns shouldRevalidate: false
220
219
  * - If fresh, returns shouldRevalidate: false
221
220
  *
222
- * On L1 miss, falls back to KV (L2) if configured.
223
- * KV hits are promoted to L1 in the background.
221
+ * Every degraded L1 read falls through to KV (readKv). The family methods
222
+ * return it unwrapped: an async wrapper adds two promises and two microtask
223
+ * ticks to every read.
224
+ * @internal
224
225
  */
225
- get(key: string): Promise<CacheGetResult | null | CacheReadError>;
226
+ private readL1;
226
227
  /**
227
228
  * Store entry data with TTL and optional SWR window.
228
229
  * Resolves once the L1 write lands, per the SegmentCacheStore.set contract
@@ -236,9 +237,9 @@ export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<T
236
237
  */
237
238
  delete(key: string): Promise<boolean>;
238
239
  /**
239
- * Get a cached Response by key (for document-level caching).
240
- * Returns the response and whether it should be revalidated (SWR).
241
- * Falls back to KV (L2) on L1 miss.
240
+ * Get a cached Response by key (for document-level caching): see readL1.
241
+ * Its body streams to the client unread, outside the debug surface. Falls
242
+ * back to KV (L2) on L1 miss.
242
243
  */
243
244
  getResponse(key: string): Promise<{
244
245
  response: Response;
@@ -258,7 +259,7 @@ export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<T
258
259
  putResponse(key: string, response: Response, ttl: number, swr?: number, tags?: string[]): Promise<void>;
259
260
  /**
260
261
  * Get a cached function result by key.
261
- * Follows the same SWR pattern as get() for segment caching.
262
+ * Follows the same SWR pattern as get() for segment caching (readL1).
262
263
  * Falls back to KV (L2) on L1 miss.
263
264
  */
264
265
  getItem(key: string): Promise<CacheItemResult | null>;
@@ -351,7 +352,7 @@ export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<T
351
352
  * caller's `tagHints`: cfTagHints may have evicted the key while the memo
352
353
  * still holds it. A shell a tag marker rejects is dropped and read from
353
354
  * the store too, which may hold a capture from after the marker (see
354
- * get(), "Tag invalidation"), unless the request's own mask rejected it
355
+ * readL1(), "Tag invalidation"), unless the request's own mask rejected it
355
356
  * (declinesKvFallThrough).
356
357
  */
357
358
  private readMemoizedShell;
@@ -538,15 +539,6 @@ export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<T
538
539
  * @internal
539
540
  */
540
541
  private skipUncacheableTagSet;
541
- /**
542
- * Merge the internal tag headers onto an existing Headers instance. The
543
- * from-scratch paths spread tagHeaderEntries() into an object-literal init;
544
- * the document put/promote paths build a Headers first, so they .set() each
545
- * entry instead — except `Cache-Tag`, which is APPENDED: a document author
546
- * may have set their own Cache-Tag, and clobbering it would break their CDN
547
- * purging. Append produces the comma-merged list CF expects.
548
- */
549
- private setTagHeaders;
550
542
  /** Read an entry's tags/taggedAt back from its headers. */
551
543
  private readTagInfo;
552
544
  /**
@@ -780,12 +772,27 @@ export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<T
780
772
  /** invalidateTags' marker writes, tag purge, and hooks. */
781
773
  private writeTagInvalidation;
782
774
  /**
783
- * KV fallback for segment cache reads.
784
- * Returns null if KV is not configured, entry is missing, or expired.
785
- * Promotes hits to L1 via waitUntil.
775
+ * The KV (L2) read behind an L1 miss, for every family: a missing,
776
+ * timed-out, corrupt (kvGetOrEvict reports and evicts it), expired or
777
+ * tag-invalidated envelope is a miss, a read failure is the family's read
778
+ * error, and a hit is promoted to L1 in the background.
779
+ *
780
+ * A degraded L1 fall-through (body-timeout / non-200) passes
781
+ * `suppressRevalidate`: KV has no REVALIDATING herd guard, so N concurrent
782
+ * degraded reads would otherwise each spawn a render exactly when the colo
783
+ * is already struggling. The stale data is still served and promoted; only
784
+ * the revalidation is withheld.
785
+ * @internal
786
+ */
787
+ private readKv;
788
+ /**
789
+ * Promote a KV hit to L1 in the background, under its remaining ttl, with
790
+ * its deadlines (so a promoted entry that later goes stale re-puts with the
791
+ * correct remaining ttl, see set()) and its tags (so it stays
792
+ * tag-invalidatable). The family builds the entry (toL1).
786
793
  * @internal
787
794
  */
788
- private kvGetSegment;
795
+ private promoteToL1;
789
796
  /**
790
797
  * Write segment data to KV.
791
798
  *
@@ -794,29 +801,4 @@ export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<T
794
801
  * @internal
795
802
  */
796
803
  private kvSetSegment;
797
- /**
798
- * Promote segment data from KV to L1 Cache API.
799
- * @internal
800
- */
801
- private promoteSegmentToL1;
802
- /**
803
- * KV fallback for function cache reads.
804
- * @internal
805
- */
806
- private kvGetItem;
807
- /**
808
- * Promote function cache data from KV to L1.
809
- * @internal
810
- */
811
- private promoteItemToL1;
812
- /**
813
- * KV fallback for document cache reads.
814
- * @internal
815
- */
816
- private kvGetResponse;
817
- /**
818
- * Promote document cache data from KV to L1.
819
- * @internal
820
- */
821
- private promoteResponseToL1;
822
804
  }
@@ -37,7 +37,7 @@ export declare function kvKeyDigest(value: string): Promise<string>;
37
37
  * of restarting it -- copying set()'s original full-window max-age would reset
38
38
  * CF's retention clock on every re-arm and pin a perpetually-stale entry forever.
39
39
  * An entry lacking a valid deadline (legacy/tampered) floors to max-age=1, so it
40
- * hard-expires in ~1s and self-heals via KV. Mirrors promoteSegmentToL1's math.
40
+ * hard-expires in ~1s and self-heals via KV. Mirrors promoteToL1's math.
41
41
  * @internal
42
42
  */
43
43
  export declare function remainingCacheControl(headers: Headers, now: number): string;
@@ -31,7 +31,7 @@ export declare function decodeHandleValue<T>(encoded: string): Promise<T | null>
31
31
  *
32
32
  * DSL-loader pushes stay out of the CACHE WRITE: a HIT runs loaders exactly
33
33
  * as an uncached render of the same request would (cache-lookup.ts
34
- * resolveFreshLoadersAndYield), so replaying a recorded copy would duplicate
34
+ * serveStoredSegments), so replaying a recorded copy would duplicate
35
35
  * the live push. Handler pushes, including
36
36
  * handler-invoked ctx.use(Loader) bodies (skipped with their handler on a
37
37
  * HIT), are kept. The store tags loader pushes by array position at push
@@ -75,19 +75,24 @@ export type OwnedPushDelivery = (loaderId: string) => RecordAuthority;
75
75
  * owner. Neither claims its loader, so the loader's own cache() HIT still
76
76
  * delivers (the pushes holding a promise a restored copy lets through, or its
77
77
  * entry's pushes in a placeholder's place).
78
+ *
79
+ * `sent` (a partial request) names the segments the response sends. Any
80
+ * other segment is kept with its handler's values, as on a live walk: only
81
+ * its loaders' copies are restored, which follow their loader as above.
78
82
  */
79
- export declare function restoreHandles(handles: Record<string, SegmentHandleData>, handleStore: HandleStore, owners?: HandleOwners, owned?: OwnedPushDelivery): void;
83
+ export declare function restoreHandles(handles: Record<string, SegmentHandleData>, handleStore: HandleStore, owners?: HandleOwners, owned?: OwnedPushDelivery, sent?: ReadonlySet<string>): void;
80
84
  /**
81
85
  * Serve a doc record's handles: set its authority over the loaders' pushes
82
86
  * (with or without a handles blob: a pinned loader whose capture pushed
83
87
  * nothing has no copy, and its run must still add no settled push), then
84
88
  * decode and restoreHandles. `keepIds` limits the restore to the segments a
85
- * boundary-scoped scope serves. A decode failure skips the restore.
89
+ * boundary-scoped scope serves; `sent` is restoreHandles'. A decode failure
90
+ * skips the restore.
86
91
  */
87
92
  export declare function restoreRecordHandles(handleStore: HandleStore, record: {
88
93
  handles?: string;
89
94
  handleOwners?: HandleOwners;
90
- }, owned: OwnedPushDelivery | undefined, keepIds?: ReadonlySet<string>): Promise<void>;
95
+ }, owned: OwnedPushDelivery | undefined, keepIds?: ReadonlySet<string>, sent?: ReadonlySet<string>): Promise<void>;
91
96
  /**
92
97
  * Append recorded handle pushes to the store, in recorded order, via push()
93
98
  * so active captures and loader-scope tagging see them like live pushes. For
@@ -37,6 +37,20 @@ export declare function rscDeserialize<T>(encoded: string | undefined): Promise<
37
37
  * Returns null only on serialization failure.
38
38
  */
39
39
  export declare function serializeResult(value: unknown, onError?: (error: unknown) => void): Promise<string | null>;
40
+ /** The Flight encode of a value and a decoded copy of it (teeResult). */
41
+ export interface TeedResult {
42
+ /** The copy: settles when the root row arrives; later rows stream in. */
43
+ decoded: Promise<unknown>;
44
+ /** The whole encoded text, or null when the stream failed. */
45
+ serialized: Promise<string | null>;
46
+ }
47
+ /**
48
+ * Encode `value` once and decode a copy of it as the encode streams, so a
49
+ * "use cache" miss renders the copy as a hit renders a stored entry, and
50
+ * stores the same bytes. Null when the encode could not start.
51
+ */
52
+ export declare function teeResult(value: unknown, onError?: (error: unknown) => void): TeedResult | null;
53
+ export declare function isUnsupportedValueError(error: unknown): boolean;
40
54
  /**
41
55
  * RSC-deserialize a cached result string.
42
56
  * Counterpart to serializeResult() — always receives a non-empty string.
@@ -210,7 +210,7 @@ export declare function buildShellLoaderSeed(snapshot: ShellSnapshotRecord[], on
210
210
  export declare const SHELL_HANDLES_RECORD_KEY = "handles";
211
211
  /**
212
212
  * The `handles` record of a prerender-served entry, which a HIT tail restores
213
- * after the prerender store's handles (cache-lookup.ts yieldFromStore), or
213
+ * after the prerender store's handles (cache-lookup.ts decodeStoredEntry), or
214
214
  * undefined: an entry with a doc record, or one written before the record
215
215
  * existed (v0.21), which serves as it did.
216
216
  */
@@ -35,9 +35,19 @@
35
35
  * request, is neither joined nor written (#973, #977): see
36
36
  * predatesInvalidation, and docs/design/caching.md "Read-your-own-writes".
37
37
  */
38
+ import { type RequestContext } from "../server/request-context.js";
38
39
  import type { SegmentCacheStore } from "./types.js";
39
40
  import { type ExecutionStart } from "./invalidation-order.js";
40
41
  export { executionStart, invalidatedSince, markResponseStart, type ExecutionStart, } from "./invalidation-order.js";
42
+ /**
43
+ * Run a stale entry's background refresh on the visitor's request context: a
44
+ * "use cache" function's (cache-runtime.ts) or a cached loader's
45
+ * (loader-cache.ts). No visitor made the invalidations in it, so they set no
46
+ * fresh-reads cookie, as in a stale cache() HIT's re-render or a document
47
+ * cache entry's re-run, whose passes take a sink's no-op writer
48
+ * (server/render-pass.ts).
49
+ */
50
+ export declare function runAsBackgroundRefresh<T>(ctx: RequestContext, fn: () => T): T;
41
51
  /**
42
52
  * The write gate of every cache execution (#977): whether one of `tags` was
43
53
  * invalidated after the execution that produced the value started.
@@ -11,9 +11,11 @@ export declare const NOCACHE_SYMBOL: unique symbol;
11
11
  */
12
12
  export declare function isTainted(value: unknown): boolean;
13
13
  /**
14
- * Symbol stamped on tainted ctx objects PASSED AS ARGUMENTS to a "use cache"
15
- * function during its execution. ctx.set(), ctx.header(), etc. check this
16
- * flag and throw if present — those side effects are lost on cache hit.
14
+ * Symbol stamped on tainted handler and middleware ctx objects PASSED AS
15
+ * ARGUMENTS to a "use cache" function during its execution. ctx.set(),
16
+ * ctx.header(), etc. check this flag and throw if present — those side
17
+ * effects are lost on cache hit. A request context passed as an argument is
18
+ * not stamped (registerCachedFunction in cache-runtime.ts).
17
19
  *
18
20
  * Argument objects only. Ambient access (cookies()/headers() and ctx methods
19
21
  * reached via getRequestContext()) is guarded by the AsyncLocalStorage scope