@rangojs/router 0.32.0 → 0.33.1

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 (181) hide show
  1. package/README.md +3 -2
  2. package/dist/bin/browser-7KIJ5HSV.js +15 -0
  3. package/dist/bin/chunk-3B7CKBRO.js +463 -0
  4. package/dist/bin/chunk-524VACCG.js +71 -0
  5. package/dist/bin/chunk-LD7UBHT4.js +196 -0
  6. package/dist/bin/chunk-TO6CRWLN.js +1429 -0
  7. package/dist/bin/chunk-TTXC2U3G.js +2477 -0
  8. package/dist/bin/client-urls-projection-DQLWPDAT.js +28 -0
  9. package/dist/bin/forward-user-plugins-URBZKJP4.js +62 -0
  10. package/dist/bin/rango.js +42 -10173
  11. package/dist/bin/runtime-EPUDEHO4.js +596 -0
  12. package/dist/bin/runtime-discovery-3HH6BP3K.js +352 -0
  13. package/dist/bin/state-ACD2MGKJ.js +16 -0
  14. package/dist/bin/temp-rsc-server-JALTIWLT.js +3190 -0
  15. package/dist/types/bin/rango.d.ts +1 -0
  16. package/dist/types/browser/action-fence.d.ts +20 -0
  17. package/dist/types/browser/cookie-name.d.ts +51 -10
  18. package/dist/types/browser/invalidate-client-cache.d.ts +8 -6
  19. package/dist/types/browser/navigation-bridge.d.ts +36 -1
  20. package/dist/types/browser/navigation-client.d.ts +19 -0
  21. package/dist/types/browser/navigation-store-handle.d.ts +37 -4
  22. package/dist/types/browser/navigation-store.d.ts +2 -2
  23. package/dist/types/browser/navigation-transaction.d.ts +0 -1
  24. package/dist/types/browser/partial-update.d.ts +13 -0
  25. package/dist/types/browser/prefetch/cache.d.ts +96 -8
  26. package/dist/types/browser/prefetch/fetch.d.ts +26 -2
  27. package/dist/types/browser/prefetch/queue.d.ts +3 -2
  28. package/dist/types/browser/rango-state.d.ts +34 -5
  29. package/dist/types/browser/react/Link.d.ts +15 -6
  30. package/dist/types/browser/types.d.ts +25 -0
  31. package/dist/types/build/generate-route-types.d.ts +1 -1
  32. package/dist/types/build/route-types/include-resolution.d.ts +9 -1
  33. package/dist/types/build/route-types/router-processing.d.ts +3 -1
  34. package/dist/types/cache/cache-key-utils.d.ts +12 -8
  35. package/dist/types/cache/cache-scope.d.ts +4 -2
  36. package/dist/types/cache/cf/cf-cache-types.d.ts +9 -5
  37. package/dist/types/cache/document-cache-policy.d.ts +9 -1
  38. package/dist/types/cache/memory-segment-store.d.ts +3 -2
  39. package/dist/types/cache/segment-codec.d.ts +2 -2
  40. package/dist/types/cache/shell-snapshot.d.ts +1 -1
  41. package/dist/types/cache/types.d.ts +12 -7
  42. package/dist/types/cache/vercel/vercel-cache-store.d.ts +2 -1
  43. package/dist/types/client-urls/types.d.ts +7 -5
  44. package/dist/types/prefetch-markers.d.ts +9 -0
  45. package/dist/types/prefetch-source-scope.d.ts +66 -0
  46. package/dist/types/route-definition/helpers-types.d.ts +8 -5
  47. package/dist/types/router/intercept-resolution.d.ts +25 -11
  48. package/dist/types/router/match-context.d.ts +1 -0
  49. package/dist/types/router/match-handlers.d.ts +1 -1
  50. package/dist/types/router/prefetch-cache-ttl.d.ts +11 -3
  51. package/dist/types/router/router-context.d.ts +1 -1
  52. package/dist/types/router/router-options.d.ts +6 -3
  53. package/dist/types/router/segment-resolution/loader-cache.d.ts +8 -0
  54. package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +23 -7
  55. package/dist/types/router/segment-resolution/revalidation.d.ts +19 -0
  56. package/dist/types/router/source-reads.d.ts +53 -0
  57. package/dist/types/router/types.d.ts +1 -1
  58. package/dist/types/rsc/helpers.d.ts +5 -2
  59. package/dist/types/rsc/prefetch-render-error.d.ts +7 -2
  60. package/dist/types/rsc/request-stages.d.ts +2 -0
  61. package/dist/types/rsc/shell-serve.d.ts +6 -1
  62. package/dist/types/server/context.d.ts +23 -6
  63. package/dist/types/server/cookie-store.d.ts +15 -1
  64. package/dist/types/server/document-location-state.d.ts +5 -2
  65. package/dist/types/server/handle-store.d.ts +2 -0
  66. package/dist/types/server/request-context.d.ts +43 -7
  67. package/dist/types/testing/e2e/server.d.ts +16 -1
  68. package/dist/types/testing/render-route.d.ts +66 -2
  69. package/dist/types/testing/serve-shell-request.d.ts +6 -3
  70. package/dist/types/types/handler-context.d.ts +7 -5
  71. package/dist/types/types/loader-types.d.ts +7 -4
  72. package/dist/types/types/segments.d.ts +15 -4
  73. package/dist/types/urls/path-helper-types.d.ts +7 -5
  74. package/dist/types/vite/temp-rsc-server.d.ts +2 -2
  75. package/dist/vite/index.js +449 -189
  76. package/package.json +2 -2
  77. package/skills/app-structure/SKILL.md +81 -0
  78. package/skills/app-structure/e-commerce.md +10 -9
  79. package/skills/app-structure/large-app.md +18 -2
  80. package/skills/caching/SKILL.md +10 -5
  81. package/skills/client-urls/SKILL.md +29 -11
  82. package/skills/comparison/references/framework-comparison.md +13 -8
  83. package/skills/document-cache/SKILL.md +8 -0
  84. package/skills/hooks/navigation.md +8 -8
  85. package/skills/hooks/outlets.md +4 -0
  86. package/skills/hooks/state.md +24 -8
  87. package/skills/intercept/SKILL.md +33 -12
  88. package/skills/layout/SKILL.md +60 -0
  89. package/skills/links/SKILL.md +101 -4
  90. package/skills/loader/SKILL.md +49 -22
  91. package/skills/migrate-nextjs/SKILL.md +8 -2
  92. package/skills/migrate-react-router/data-and-actions.md +5 -0
  93. package/skills/ppr/SKILL.md +26 -5
  94. package/skills/prerender/SKILL.md +4 -1
  95. package/skills/route/SKILL.md +6 -6
  96. package/skills/router-setup/SKILL.md +2 -1
  97. package/skills/server-actions/SKILL.md +41 -8
  98. package/skills/testing/client-components.md +29 -13
  99. package/skills/testing/serve-shell-request.md +7 -7
  100. package/skills/testing/server-actions.md +8 -0
  101. package/skills/ux-architecture/SKILL.md +70 -19
  102. package/src/bin/rango.ts +39 -13
  103. package/src/browser/action-fence.ts +72 -0
  104. package/src/browser/cookie-name.ts +82 -14
  105. package/src/browser/invalidate-client-cache.ts +9 -7
  106. package/src/browser/navigation-bridge.ts +164 -32
  107. package/src/browser/navigation-client.ts +95 -20
  108. package/src/browser/navigation-store-handle.ts +67 -5
  109. package/src/browser/navigation-store.ts +86 -17
  110. package/src/browser/navigation-transaction.ts +0 -6
  111. package/src/browser/partial-update.ts +31 -12
  112. package/src/browser/prefetch/cache.ts +188 -11
  113. package/src/browser/prefetch/fetch.ts +211 -51
  114. package/src/browser/prefetch/queue.ts +3 -2
  115. package/src/browser/rango-state.ts +50 -8
  116. package/src/browser/react/Link.tsx +20 -9
  117. package/src/browser/react/use-navigation.ts +1 -1
  118. package/src/browser/rsc-router.tsx +8 -1
  119. package/src/browser/server-action-bridge.ts +16 -11
  120. package/src/browser/types.ts +25 -0
  121. package/src/build/generate-route-types.ts +1 -0
  122. package/src/build/route-types/include-resolution.ts +40 -3
  123. package/src/build/route-types/router-processing.ts +170 -25
  124. package/src/build/route-types/scan-filter.ts +1 -0
  125. package/src/cache/cache-key-utils.ts +12 -8
  126. package/src/cache/cache-runtime.ts +5 -1
  127. package/src/cache/cache-scope.ts +28 -7
  128. package/src/cache/cf/cf-cache-types.ts +9 -5
  129. package/src/cache/document-cache-policy.ts +27 -0
  130. package/src/cache/document-cache.ts +5 -0
  131. package/src/cache/memory-segment-store.ts +3 -2
  132. package/src/cache/segment-codec.ts +2 -2
  133. package/src/cache/shell-snapshot.ts +1 -1
  134. package/src/cache/types.ts +12 -7
  135. package/src/cache/vercel/vercel-cache-store.ts +2 -1
  136. package/src/client-urls/types.ts +7 -5
  137. package/src/prefetch-markers.ts +10 -0
  138. package/src/prefetch-source-scope.ts +137 -0
  139. package/src/route-definition/helpers-types.ts +8 -5
  140. package/src/router/intercept-resolution.ts +94 -50
  141. package/src/router/loader-resolution.ts +8 -6
  142. package/src/router/match-api.ts +33 -29
  143. package/src/router/match-context.ts +5 -0
  144. package/src/router/match-handlers.ts +2 -1
  145. package/src/router/match-middleware/cache-lookup.ts +21 -13
  146. package/src/router/match-result.ts +39 -87
  147. package/src/router/navigation-snapshot.ts +18 -3
  148. package/src/router/prefetch-cache-ttl.ts +15 -3
  149. package/src/router/revalidation.ts +53 -23
  150. package/src/router/router-context.ts +2 -1
  151. package/src/router/router-options.ts +6 -3
  152. package/src/router/segment-resolution/loader-cache.ts +21 -3
  153. package/src/router/segment-resolution/prefetch-deferral.ts +38 -31
  154. package/src/router/segment-resolution/revalidation.ts +40 -12
  155. package/src/router/source-reads.ts +122 -0
  156. package/src/router/types.ts +2 -1
  157. package/src/router.ts +11 -12
  158. package/src/rsc/helpers.ts +8 -3
  159. package/src/rsc/prefetch-render-error.ts +7 -2
  160. package/src/rsc/progressive-enhancement.ts +3 -2
  161. package/src/rsc/request-stages.ts +59 -0
  162. package/src/rsc/rsc-rendering.ts +49 -33
  163. package/src/rsc/server-action.ts +2 -1
  164. package/src/rsc/shell-capture.ts +4 -5
  165. package/src/rsc/shell-serve.ts +12 -1
  166. package/src/segment-system.tsx +1 -1
  167. package/src/server/context.ts +37 -6
  168. package/src/server/cookie-store.ts +22 -1
  169. package/src/server/document-location-state.ts +23 -2
  170. package/src/server/handle-store.ts +2 -0
  171. package/src/server/request-context.ts +113 -28
  172. package/src/testing/dispatch.ts +3 -0
  173. package/src/testing/e2e/fixture.ts +4 -12
  174. package/src/testing/e2e/server.ts +91 -4
  175. package/src/testing/render-route.tsx +269 -4
  176. package/src/testing/serve-shell-request.ts +6 -3
  177. package/src/types/handler-context.ts +7 -5
  178. package/src/types/loader-types.ts +7 -4
  179. package/src/types/segments.ts +15 -4
  180. package/src/urls/path-helper-types.ts +7 -5
  181. package/src/vite/temp-rsc-server.ts +2 -2
@@ -493,9 +493,12 @@ export interface RangoOptions<TEnv = any> {
493
493
  * TTL (in seconds) for the client-side in-memory prefetch cache.
494
494
  *
495
495
  * Controls how long prefetch responses are kept in the client-side
496
- * in-memory cache. Prefetch responses carry no `Cache-Control`, so the
497
- * browser's HTTP cache does not keep them: a prefetch whose render failed
498
- * after its 200 went out would otherwise answer later requests.
496
+ * in-memory cache. A prefetch with nothing live in it (a document cache
497
+ * HIT, or every sent segment from a `cache()` record or a Prerender entry,
498
+ * with no loader) also carries `Cache-Control: private, max-age=<ttl>`,
499
+ * so the browser's HTTP cache keeps it as long. Any other prefetch carries
500
+ * none: a prefetch whose render failed after its 200 went out would
501
+ * otherwise answer later requests.
499
502
  *
500
503
  * The cache is automatically invalidated on server actions regardless
501
504
  * of TTL, so this is primarily a staleness safety net.
@@ -61,6 +61,14 @@ export declare function bindsLoaderCache(entries: readonly EntryData[]): boolean
61
61
  * one is live (shell-capture.ts deriveShellCaptureContext).
62
62
  */
63
63
  export declare function routeLoaderLanes(entries: readonly EntryData[]): ReadonlyMap<string, "live" | "bake">;
64
+ /**
65
+ * This request serves `loaderEntry` from a pin (servedPins), so its body does
66
+ * not decide the value: resolveLoaderData serves a pin to an `ssr: false`
67
+ * registration only. A prefetch does not defer such a loader
68
+ * (prefetch-deferral.ts defersLoader): nothing would run, and the pin is the
69
+ * value the document shows.
70
+ */
71
+ export declare function servesLoaderPin(loaderEntry: LoaderEntry, reqCtx?: RequestContext<any> | undefined): boolean;
64
72
  /**
65
73
  * What a record restored on a request over the matched chain `entries` is to
66
74
  * each loader's handle pushes (HandleStore RecordAuthority), from the pins
@@ -30,11 +30,18 @@ import type { CacheScope } from "../../cache/cache-scope.js";
30
30
  /**
31
31
  * What a request says it is, read once when its context is created
32
32
  * (RequestContext._requestKind). A fill is marked on the raw URL and wins
33
- * over the prefetch header. Whether a prefetch defers anything is decided per
34
- * match, by partialDeferralMode: a shell capture inherits the context of the
35
- * request that scheduled it and is never a prefetch.
33
+ * over the prefetch header. `"prefetch"` is a speculative prefetch (viewport,
34
+ * render) and `"intent"` a hover or an explicit `prefetch()` call
35
+ * (PREFETCH_INTENT): both are prefetches, and only the first defers. Whether
36
+ * a prefetch defers anything is decided per match, by partialDeferralMode: a
37
+ * shell capture inherits the context of the request that scheduled it and is
38
+ * never a prefetch.
36
39
  */
37
- export declare function requestKind(request: Request, rawUrl: URL): "prefetch" | "fill" | undefined;
40
+ export declare function requestKind(request: Request, rawUrl: URL): RequestKind | undefined;
41
+ /** requestKind's answer for a marked request. */
42
+ export type RequestKind = "prefetch" | "intent" | "fill";
43
+ /** A prefetch of either kind: speculative or intent. */
44
+ export declare function isPrefetchKind(kind: RequestKind | undefined): boolean;
38
45
  /** Static, per matched chain. */
39
46
  interface DeferralScope {
40
47
  /**
@@ -158,7 +165,9 @@ export declare function resolveDeferralScope(entries: readonly EntryData[]): Def
158
165
  export declare function resolveInterceptDeferralScope(intercept: InterceptEntry): DeferralScope;
159
166
  /**
160
167
  * Which half of the feature a partial request is, if either (`fill` and
161
- * `prefetch` are requestKind's answer). Both halves are GETs: a request of
168
+ * `prefetch` are requestKind's answer). `prefetch` is a speculative prefetch
169
+ * only: an intent prefetch defers nothing, like the navigation it starts
170
+ * early (R16). Both halves are GETs: a request of
162
171
  * another method that carries either marker is a normal request. A prefetch
163
172
  * defers only on a plain GET navigation: an action re-renders what it
164
173
  * changed, and a shell capture is never a prefetch whatever the request that
@@ -234,7 +243,7 @@ export declare function startDeferredLoader(ctx: HandlerContext<any, any>, loade
234
243
  /**
235
244
  * The segment a run of `loaderId` files its handle pushes under: the one
236
245
  * that registers it when it is registered (registerDeferredLoader), else the
237
- * segment being resolved.
246
+ * segment whose code starts it (segmentPinOf).
238
247
  */
239
248
  export declare function loaderOwner(ctx: HandlerContext<any, any>, loaderId: string): string | undefined;
240
249
  /**
@@ -242,8 +251,15 @@ export declare function loaderOwner(ctx: HandlerContext<any, any>, loaderId: str
242
251
  * does not hold that segment, and the loader is flagged itself or sits behind
243
252
  * the fallback of a flagged entry the client does not hold either. Under a
244
253
  * flagged layout the client holds, a new route's loaders run.
254
+ *
255
+ * `servesPin` (loader-cache.ts servesLoaderPin) answers whether this request
256
+ * serves the loader from a pin of a document-captured shell. Such a loader
257
+ * is not deferred: its body does not decide the value, and the pin is what
258
+ * the document shows. On every other request it would run, so it is
259
+ * deferred as on a route without ppr. Asked when loaders resolve, after a
260
+ * replay HIT has armed the pins (rsc-rendering.ts matchPartialWithPprReplay).
245
261
  */
246
- export declare function defersLoader(ctx: HandlerContext<any, any>, loaderEntry: LoaderEntry, segmentId: string): boolean;
262
+ export declare function defersLoader(ctx: HandlerContext<any, any>, loaderEntry: LoaderEntry, segmentId: string, servesPin: (loaderEntry: LoaderEntry) => boolean): boolean;
247
263
  /**
248
264
  * This prefetch skips the handler of the entry whose segment is `segmentId`:
249
265
  * a unit the client does not hold, or anything the client does not hold
@@ -53,6 +53,25 @@ export interface LoaderOwner {
53
53
  }
54
54
  /** A route entry as a LoaderOwner, under `shortCodeOverride` when inherited. */
55
55
  export declare function ownerOf(entry: EntryData, shortCodeOverride?: string): LoaderOwner;
56
+ /**
57
+ * A route's loader copies inherited into a child layout with parallel slots
58
+ * are redundant beside the route's own loader segments: true when the route
59
+ * renders no loading() fallback on any request, so nothing hides the route's
60
+ * copy from useLoader() in the slots. A redundant copy is never resolved, on
61
+ * any request: the walkers check this before they inherit.
62
+ *
63
+ * Decided from the entry, never from the route segment's `loading`, which
64
+ * differs between requests: `loading(fallback, { ssr: false })` is `null` on
65
+ * a document (applyDocumentModeLoading) and the fallback on partials, and the
66
+ * walkers emit `loading(false)` as `null` too. A copy sent on one request and
67
+ * not on the next gives the layout a loader entry it did not have, so
68
+ * renderSegments wraps it in StreamedLoaderErrorBoundary and React remounts
69
+ * it with its Outlet (docs/tree-structure.md). Resolving a redundant copy and
70
+ * dropping it from the payload later is not enough: when the route's own
71
+ * loader is held (its revalidate() declines), the copy is new to the client,
72
+ * so it re-runs and is sent without the route's copy beside it.
73
+ */
74
+ export declare function inheritedCopyRedundant(route: EntryData): boolean;
56
75
  /**
57
76
  * The walker's one loader funnel: each loader is a segment, run when the
58
77
  * client lacks it, asked its revalidate() when held, kept outright in a fill
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Recording what revalidate() rules read of the source page, so a prefetch
3
+ * answer a rule kept is shared between the pages with equal values of what
4
+ * was read (prefetch-source-scope.ts).
5
+ */
6
+ import { type SourcePage, type SourceReads } from "../prefetch-source-scope.js";
7
+ /**
8
+ * The source group of a match whose diff a source read decided: the reads
9
+ * every kept decision made, unioned, and the source page they read.
10
+ */
11
+ export interface SourceHeld extends SourcePage {
12
+ reads: SourceReads;
13
+ }
14
+ /**
15
+ * A copy of `params` whose reads land in `reads`: an enumerable getter per
16
+ * key, so a spread, Object.entries, JSON.stringify or structuredClone reads
17
+ * every key. Not a Proxy: structuredClone refuses one. A read of an absent
18
+ * key is not recorded and needs no record: the key set is part of the group
19
+ * (`reads.keys`), so a page in the group lacks that key too.
20
+ */
21
+ export declare function recordingParams(params: Readonly<Record<string, string>>, reads: SourceReads): Record<string, string>;
22
+ /**
23
+ * Add `reads` to the source group of the match `context` decides for. All
24
+ * of one match's decisions read the same source page; a caller that reads
25
+ * no route name passes none.
26
+ */
27
+ export declare function markSourceHeld(context: {
28
+ _sourceHeld?: SourceHeld;
29
+ }, reads: SourceReads, page: SourcePage): void;
30
+ /**
31
+ * The `x-rsc-prefetch-scope` of a partial response, or undefined when every
32
+ * source page may share it.
33
+ *
34
+ * - `source`: the exact source page's. A route an intercept targets renders
35
+ * the modal or the full page depending on the source, so a full page
36
+ * prefetched where the intercept does not apply would otherwise serve a
37
+ * later click where it does (#1007); a response with deferred units
38
+ * answers one page's prefetch (`sourceOnly`); a kept rule that read
39
+ * `currentUrl` can depend on anything in it; and a source-held answer that
40
+ * sends nothing (`sentNothing`) commits treeless on the page that adopts
41
+ * it, which keeps its whole tree without checking it holds the ids the
42
+ * answer kept (partial-update.ts): only the source is known to hold them.
43
+ * - `params; <label>`: a revalidate() rule read the source's params or route
44
+ * name and kept a segment. A page that adopted the answer would keep its
45
+ * own copy of that segment (#1331), which is right only where the rule
46
+ * decides the same: on pages with equal values of what it read. The label
47
+ * names them (prefetch-source-scope.ts); the browser stores the answer
48
+ * under it. A page that lacks a kept id refetches (the commit's
49
+ * missing-segment check), as for a shared answer.
50
+ */
51
+ export declare function prefetchScopeOf(sourceOnly: boolean, held: SourceHeld | undefined, sentNothing: boolean): string | undefined;
52
+ /** Every param of the source read: its params must equal the page's. */
53
+ export declare function readsOfAllParams(params: Readonly<Record<string, string>>): SourceReads;
@@ -60,7 +60,7 @@ export interface SegmentResolutionDeps<TEnv = any> {
60
60
  export interface MatchApiDeps<TEnv = any> {
61
61
  findMatch: (pathname: string, ms?: any) => any;
62
62
  getMetricsStore: () => any;
63
- findInterceptForRoute: (routeKey: string, parentEntry: EntryData | null, selectorContext: InterceptSelectorContext | null, isAction: boolean) => {
63
+ findInterceptForRoute: (routeKeys: string | readonly string[], parentEntry: EntryData | null, selectorContext: InterceptSelectorContext | null, isAction: boolean, held?: ReadonlySet<string>) => {
64
64
  intercept: InterceptEntry;
65
65
  entry: EntryData;
66
66
  } | null;
@@ -4,6 +4,7 @@
4
4
  * Utility functions for RSC request handling.
5
5
  */
6
6
  import type { RequestContext } from "../server/request-context.js";
7
+ import { type LocationStateEntry } from "../browser/react/location-state-shared.js";
7
8
  import type { MiddlewareEntry, MiddlewareFn } from "../router/middleware.js";
8
9
  import type { RscPayload } from "./types.js";
9
10
  import type { HandlerContext } from "./handler-context.js";
@@ -98,9 +99,11 @@ export declare function interceptRedirectForPartial(response: Response, createRe
98
99
  * is populated. A `document` payload (a page load's or a no-JS form post's
99
100
  * bootstrap payload) that carries state marks the request
100
101
  * `_documentLocationState`: it is the visitor's, so no shared cache keeps it
101
- * (server/document-location-state.ts).
102
+ * (server/document-location-state.ts). `locationState` defaults to the
103
+ * current request context's; a shell HIT's tail passes the state set before
104
+ * its render (rsc/rsc-rendering.ts serveShellHit).
102
105
  */
103
- export declare function attachLocationStateIfPresent(payload: RscPayload, document?: boolean): void;
106
+ export declare function attachLocationStateIfPresent(payload: RscPayload, document?: boolean, locationState?: LocationStateEntry[] | undefined): void;
104
107
  /**
105
108
  * Only cache successful responses. Non-200 statuses (errors, redirects) are
106
109
  * not cached -- notFound() produces 500 in response routes, and explicit
@@ -11,8 +11,13 @@
11
11
  * So the body ends cleanly with one more row, PREFETCH_FAILED_ROW, which the
12
12
  * Flight decoder skips and teeWithCompletion (browser/response-adapter.ts)
13
13
  * reads as a stream that did not end cleanly: the client evicts the prefetch
14
- * and never respawns it. Prefetch responses carry no Cache-Control, so the
15
- * browser HTTP cache keeps none of it either.
14
+ * and never respawns it. The router puts no Cache-Control of its own on a
15
+ * prefetch with a live part, the only kind whose render can report an error
16
+ * here, so the browser HTTP cache keeps none of it unless the route sets one;
17
+ * a prefetch with nothing live in it carries `private, max-age`
18
+ * (request-stages.ts applyStoredPrefetchCacheControl); a prefetch
19
+ * scoped to its source page or carrying deferred units is sent
20
+ * `private, no-cache` (rsc-rendering.ts NOT_REUSED).
16
21
  *
17
22
  * The errors are the render's `_renderErrors`: a Flight error row, a streamed
18
23
  * handler recovered into its boundary, a loader that threw (redirect() and
@@ -38,6 +38,8 @@ export interface StageRouter<TEnv = unknown> {
38
38
  readonly onTimeout?: OnTimeoutCallback<TEnv>;
39
39
  /** Names the cookie location state crosses a document redirect in. */
40
40
  readonly resolvedStateCookieName: string;
41
+ /** The in-memory prefetch TTL in ms; also a stored prefetch's max-age. */
42
+ readonly prefetchCacheTTL: number;
41
43
  }
42
44
  /** Report an error through the router's onError. */
43
45
  export type ReportError<TEnv = unknown> = (error: unknown, phase: ErrorPhase, context: InvokeOnErrorContext<TEnv>) => void;
@@ -99,8 +99,13 @@ export declare function withoutShellMissMarker(request: Request): Request | unde
99
99
  * the page. window.stop() runs only when it reloads: it keeps the half-sent
100
100
  * Flight stream from being closed by DOMContentLoaded, which would throw
101
101
  * React's "Connection closed" (#412) before the reload lands.
102
+ *
103
+ * `restoreCookie` is the request's `_carriedLocationStateCookie`: the HIT's
104
+ * headers already cleared the location state cookie a document redirect left
105
+ * (server/document-location-state.ts), so the script sets it again before it
106
+ * reloads, and the reload carries the state.
102
107
  */
103
- export declare function shellReloadScript(): string;
108
+ export declare function shellReloadScript(restoreCookie?: string): string;
104
109
  /**
105
110
  * An inline script a shell HIT's tail appends. It carries no CSP nonce: a
106
111
  * request with an active per-request nonce never reaches a HIT
@@ -92,9 +92,10 @@ export type EntryPropRender = {
92
92
  loading?: ReactNode | false;
93
93
  /**
94
94
  * loading(fallback, { prefetch: false }) with a renderable fallback: a
95
- * prefetch defers what the fallback covers while the entry's segment is
96
- * new to the client (router/segment-resolution/prefetch-deferral.ts). Set
97
- * on both DSL evaluations, whatever `ssr` did to `loading`.
95
+ * speculative prefetch defers what the fallback covers while the entry's
96
+ * segment is new to the client
97
+ * (router/segment-resolution/prefetch-deferral.ts). Set on both DSL
98
+ * evaluations, whatever `ssr` did to `loading`.
98
99
  */
99
100
  loadingPrefetch?: false;
100
101
  /**
@@ -136,8 +137,8 @@ export type LoaderEntry = {
136
137
  */
137
138
  bake?: true;
138
139
  /**
139
- * loader(Def, { prefetch: false }): a prefetch defers this registration
140
- * while its segment is new to the client
140
+ * loader(Def, { prefetch: false }): a speculative prefetch defers this
141
+ * registration while its segment is new to the client
141
142
  * (router/segment-resolution/prefetch-deferral.ts).
142
143
  */
143
144
  prefetch?: false;
@@ -174,7 +175,10 @@ export type InterceptSegmentsState = {
174
175
  * @internal This type is an implementation detail and may change without notice.
175
176
  */
176
177
  export type InterceptSelectorContext<TEnv = any> = {
177
- /** Where the navigation comes from: the intercept source while one is open. */
178
+ /**
179
+ * Where the navigation comes from: the intercept source while one is open.
180
+ * Its `url` never carries a #fragment.
181
+ */
178
182
  from: ServerRouteLocation;
179
183
  /** The navigation target. */
180
184
  to: ServerRouteLocation;
@@ -818,6 +822,19 @@ export declare function findEnclosingLoaderBody(match: (loaderId: string) => boo
818
822
  * reader runs in that request only reaches the page through the replay.
819
823
  */
820
824
  export declare function isInsideLoaderBody(loaderId: string): boolean;
825
+ /**
826
+ * Run `fn` as segment `segmentId`'s code: a handle push, a loader kickoff or
827
+ * a "use cache" call it makes, before or after an await, files under
828
+ * `segmentId` (segmentPinOf).
829
+ */
830
+ export declare function runAsSegment<R>(segmentId: string | undefined, fn: () => R): R;
831
+ /**
832
+ * The segment code here runs for: the innermost runAsSegment scope, else
833
+ * `ctx`'s pin (a handler the walker awaits).
834
+ */
835
+ export declare function segmentPinOf(ctx: {
836
+ _currentSegmentId?: string;
837
+ }): string | undefined;
821
838
  /**
822
839
  * Check if the current execution is inside a handle push callback (sync or an
823
840
  * async callback's continuation). Used by the handler-to-loader deadlock guard
@@ -134,7 +134,21 @@ export declare function invalidateClientCache(): void;
134
134
  *
135
135
  * Sets an internal response header the bridge reads. Idempotent within a
136
136
  * request. Inert (a dev warning) outside a request context — there is no
137
- * automatic invalidation to suppress.
137
+ * automatic invalidation to suppress. It also keeps the state's write part
138
+ * (runActionBody).
138
139
  */
139
140
  export declare function keepClientCache(): void;
141
+ /**
142
+ * Run a server action's body (the JS call or a no-JS form post), then bump the
143
+ * rango state's write part for the client that called it: the response's
144
+ * state `Set-Cookie` keeps the client's timestamp and carries a new write part
145
+ * (browser/cookie-name.ts). Every later request of every tab and reload sends
146
+ * the new `X-Rango-State`, so a prefetch or navigation the browser HTTP cache
147
+ * holds from before the write (a response's own `Cache-Control: max-age`)
148
+ * misses, while the client's in-memory caches only go stale, as the action
149
+ * bridge marks them. Bumped however the body ends (returned, threw, or
150
+ * redirected), since the write may have committed either way, and not after
151
+ * keepClientCache() or invalidateClientCache() (whose rotation covers it).
152
+ */
153
+ export declare function runActionBody<T>(body: () => T | Promise<T>): Promise<T>;
140
154
  export {};
@@ -14,7 +14,10 @@
14
14
  * reads it on the next GET document request that navigates to the target
15
15
  * pathname (`Sec-Fetch-Dest` a browsing context, where sent), seeds
16
16
  * `_locationState` with its entries (so the render attaches them to the
17
- * bootstrap payload, rsc/rsc-rendering.ts) and clears it.
17
+ * bootstrap payload, rsc/rsc-rendering.ts) and clears it. A shell HIT that
18
+ * degrades into a reload after its headers cleared the cookie sets it
19
+ * again from script first (`_carriedLocationStateCookie`), so the reload
20
+ * carries the state.
18
21
  *
19
22
  * A document that carries location state is per visitor: it is marked
20
23
  * `_documentLocationState`, never stored by the document cache or as a PPR
@@ -38,7 +41,7 @@ export declare function documentLocationStateCookieName(stateCookieName: string,
38
41
  * expiry. A malformed value is cleared and ignored, and only `__rsc_ls_*`
39
42
  * slots are taken from it (the value comes from the browser).
40
43
  */
41
- export declare function consumeDocumentLocationState(reqCtx: Pick<RequestContext, "_locationState" | "setCookie">, request: Request, url: URL, stateCookieName: string): void;
44
+ export declare function consumeDocumentLocationState(reqCtx: Pick<RequestContext, "_locationState" | "_carriedLocationStateCookie" | "setCookie">, request: Request, url: URL, stateCookieName: string): void;
42
45
  /**
43
46
  * Write the cookie on a document request's redirect that set location state,
44
47
  * and mark the redirect private. Not for a partial or action request (their
@@ -158,6 +158,8 @@ export interface HandleStore {
158
158
  * so a loader that throws or skips its push shows none rather than the
159
159
  * record's. A hole's replays go with them. Any other loader's replays stay
160
160
  * (a dependency another entry's HIT replayed), and so do restored copies.
161
+ * A prefetch that defers the loader calls it too (revalidation.ts
162
+ * resolveOwnerLoaders): no run is made, so none of its placeholders stand.
161
163
  */
162
164
  settleLoaderRun(loaderId: string): void;
163
165
  /**
@@ -31,6 +31,7 @@ import type { RoutineTrace } from "../rsc/routine-plan.js";
31
31
  import type { ActionContinuation } from "../rsc/server-action.js";
32
32
  import type { ShellCaptureState } from "../rsc/shell-capture.js";
33
33
  import type { FormActionOutcome } from "../rsc/progressive-enhancement.js";
34
+ import { type RequestKind } from "../router/segment-resolution/prefetch-deferral.js";
34
35
  import { type ExecutionStart } from "../cache/invalidation-order.js";
35
36
  export interface RenderForegroundCursor {
36
37
  mode: RenderMode;
@@ -119,8 +120,12 @@ export interface RequestContext<TEnv = DefaultEnv, TParams = Record<string, stri
119
120
  setStatus(status: number): void;
120
121
  /** @internal Set status bypassing cache-exec guard (for framework error handling) */
121
122
  _setStatus(status: number): void;
122
- /** @internal Rotate the rango state cookie (server seat of invalidateClientCache). */
123
- _rotateStateCookie(): void;
123
+ /**
124
+ * @internal Rotate the rango state cookie: its timestamp (the server seat of
125
+ * invalidateClientCache), or with `"write"` only its write part (a server
126
+ * action without a directive, cookie-store.ts runActionBody).
127
+ */
128
+ _rotateStateCookie(part?: "write"): void;
124
129
  /** @internal Set the keepClientCache() directive header on the response. */
125
130
  _setKeepCacheDirective(): void;
126
131
  /**
@@ -532,11 +537,28 @@ export interface RequestContext<TEnv = DefaultEnv, TParams = Record<string, stri
532
537
  */
533
538
  _payloadDeferred?: boolean;
534
539
  /**
535
- * @internal What the request says it is: a `<Link>` prefetch or a fill
536
- * (`prefetch: false`), read once at creation
537
- * (segment-resolution/prefetch-deferral.ts requestKind).
540
+ * @internal Set by the RSC handler when a partial response carries an
541
+ * `x-rsc-prefetch-scope` (router/source-reads.ts prefetchScopeOf): its diff
542
+ * is right for its source page, or a group of them, only. The document
543
+ * cache keys a partial without its source, so it does not store that
544
+ * response, whatever Cache-Control it ends up with.
538
545
  */
539
- _requestKind?: "prefetch" | "fill";
546
+ _payloadSourceScoped?: boolean;
547
+ /**
548
+ * @internal Set when the answer has nothing live in it, so nothing in it can
549
+ * fail after the 200's headers left: the document cache middleware on a
550
+ * fresh HIT, and the RSC handler when the partial match came from storage
551
+ * (MatchResult.fromStorage). finalizeRequestResponse (rsc/request-stages.ts)
552
+ * sends such a prefetch the router's prefetch Cache-Control.
553
+ */
554
+ _servedFromStorage?: boolean;
555
+ /**
556
+ * @internal What the request says it is: a speculative `<Link>` prefetch,
557
+ * an intent prefetch (hover, `prefetch()`) or a fill (`prefetch: false`),
558
+ * read once at creation (segment-resolution/prefetch-deferral.ts
559
+ * requestKind).
560
+ */
561
+ _requestKind?: RequestKind;
540
562
  /**
541
563
  * @internal Set when a render shows the visitor's URL of a rewritten
542
564
  * request: the payload or SSR seed (renderedLocation, rsc/full-payload.ts)
@@ -558,6 +580,11 @@ export interface RequestContext<TEnv = DefaultEnv, TParams = Record<string, stri
558
580
  * `Cache-Control: private, no-store` and never stored by the document cache
559
581
  * or as a PPR shell.
560
582
  *
583
+ * Call it from middleware, a handler or an action. A loader's call
584
+ * (through `getRequestContext()`) is not supported: the payload takes the
585
+ * state before loaders settle, so the write may be dropped, and a PPR
586
+ * shell HIT never sends it.
587
+ *
561
588
  * Multiple calls accumulate entries across definitions. For one definition
562
589
  * the last write wins: two entries of the same definition share its slot
563
590
  * in history.state, so a later call (or a later entry in one array)
@@ -579,6 +606,15 @@ export interface RequestContext<TEnv = DefaultEnv, TParams = Record<string, stri
579
606
  * and it leaves `private, no-store` (server/document-location-state.ts).
580
607
  */
581
608
  _documentLocationState?: boolean;
609
+ /**
610
+ * @internal The `document.cookie` line that re-creates the location state
611
+ * cookie this request consumed (server/document-location-state.ts). The
612
+ * consuming response clears the cookie in its headers; a shell HIT that
613
+ * degrades into a reload after those headers left writes this line first
614
+ * (rsc/shell-serve.ts shellReloadScript), so the reload reads the state
615
+ * again.
616
+ */
617
+ _carriedLocationStateCookie?: string;
582
618
  /**
583
619
  * The matched route name, if the route has an explicit name.
584
620
  * Undefined before route matching or for unnamed routes.
@@ -812,7 +848,7 @@ export interface RequestContext<TEnv = DefaultEnv, TParams = Record<string, stri
812
848
  * This is the type exported to library consumers. Internal code should
813
849
  * use the full RequestContext interface directly.
814
850
  */
815
- export type PublicRequestContext<TEnv = DefaultEnv, TParams = Record<string, string>> = Omit<RequestContext<TEnv, TParams>, "cookie" | "cookies" | "_readCookie" | "_readCookies" | "setCookie" | "deleteCookie" | "_handleStore" | "_transitionWhenRefs" | "_pprReplayPostMatchReason" | "_cacheStore" | "_prerender" | "_onDemandProducer" | "_searchParamsFilter" | "_shellCaptureState" | "_shellImplicitCache" | "_shellLoaderSeed" | "_resolvedCacheKeys" | "_shellKey" | "_shellForcedMiss" | "_prerenderWarm" | "_runAtRequestEntry" | "_shellFragmentPayload" | "_explicitTaggedStores" | "_requestTags" | "_recordTagOwners" | "_recordLoaderTags" | "_cacheProfiles" | "_onResponseCallbacks" | "_pendingBackgroundTasks" | "_themeConfig" | "_readTheme" | "_documentCacheRender" | "_payloadVisitorTheme" | "_payloadDeferred" | "_requestKind" | "_payloadVisitorLocation" | "_locationState" | "_documentLocationState" | "_routeName" | "_prevRouteKey" | "_visitorMoved" | "_inActionRevalidation" | "_actionRan" | "_reportedErrors" | "_throwers" | "_printedErrors" | "_renderErrors" | "_recoveredHandlerErrors" | "_renderBarrier" | "_resolveRenderBarrier" | "_renderBarrierSegmentOrder" | "_treeHasStreaming" | "_renderBarrierWaiters" | "_handlerLoaderDeps" | "_awaitBeforeFlushLoaderIds" | "_renderBarrierHandleSnapshot" | "_renderBarrierGuardClosed" | "_reportBackgroundError" | "_debugPerformance" | "_metricsStore" | "_renderForeground" | "_activeRoutine" | "_renderDiagnosticsEnabled" | "_handlerStart" | "_handlerTiming" | "_tracing" | "_basename" | "_routerId" | "_versions" | "_setStatus" | "_rotateStateCookie" | "_setKeepCacheDirective" | "_freshReads" | "_setFreshReadsCookie" | "_requestRoot" | "_requestStart" | "_responseSent" | "_variables" | "_classifiedRoute" | "_requestMode" | "_cacheSignal" | "_dynamic" | "res">;
851
+ export type PublicRequestContext<TEnv = DefaultEnv, TParams = Record<string, string>> = Omit<RequestContext<TEnv, TParams>, "cookie" | "cookies" | "_readCookie" | "_readCookies" | "setCookie" | "deleteCookie" | "_handleStore" | "_transitionWhenRefs" | "_pprReplayPostMatchReason" | "_cacheStore" | "_prerender" | "_onDemandProducer" | "_searchParamsFilter" | "_shellCaptureState" | "_shellImplicitCache" | "_shellLoaderSeed" | "_resolvedCacheKeys" | "_shellKey" | "_shellForcedMiss" | "_prerenderWarm" | "_runAtRequestEntry" | "_shellFragmentPayload" | "_explicitTaggedStores" | "_requestTags" | "_recordTagOwners" | "_recordLoaderTags" | "_cacheProfiles" | "_onResponseCallbacks" | "_pendingBackgroundTasks" | "_themeConfig" | "_readTheme" | "_documentCacheRender" | "_payloadVisitorTheme" | "_payloadDeferred" | "_payloadSourceScoped" | "_servedFromStorage" | "_requestKind" | "_payloadVisitorLocation" | "_locationState" | "_documentLocationState" | "_carriedLocationStateCookie" | "_routeName" | "_prevRouteKey" | "_visitorMoved" | "_inActionRevalidation" | "_actionRan" | "_reportedErrors" | "_throwers" | "_printedErrors" | "_renderErrors" | "_recoveredHandlerErrors" | "_renderBarrier" | "_resolveRenderBarrier" | "_renderBarrierSegmentOrder" | "_treeHasStreaming" | "_renderBarrierWaiters" | "_handlerLoaderDeps" | "_awaitBeforeFlushLoaderIds" | "_renderBarrierHandleSnapshot" | "_renderBarrierGuardClosed" | "_reportBackgroundError" | "_debugPerformance" | "_metricsStore" | "_renderForeground" | "_activeRoutine" | "_renderDiagnosticsEnabled" | "_handlerStart" | "_handlerTiming" | "_tracing" | "_basename" | "_routerId" | "_versions" | "_setStatus" | "_rotateStateCookie" | "_setKeepCacheDirective" | "_freshReads" | "_setFreshReadsCookie" | "_requestRoot" | "_requestStart" | "_responseSent" | "_variables" | "_classifiedRoute" | "_requestMode" | "_cacheSignal" | "_dynamic" | "res">;
816
852
  /**
817
853
  * Marker for a waitUntil-scheduled fn whose task promise must NOT enter
818
854
  * _pendingBackgroundTasks. Used by the PPR shell capture for its own task:
@@ -11,7 +11,13 @@ export interface RunCliHandle {
11
11
  */
12
12
  exitCode: Promise<number | null>;
13
13
  findPort: (timeoutMs?: number) => Promise<number>;
14
- kill: () => void;
14
+ /**
15
+ * Stop the process and what it spawned (the whole process group). SIGTERM by
16
+ * default; `stopServer` escalates to SIGKILL for a process that outlives the
17
+ * wait.
18
+ */
19
+ kill: (signal?: NodeJS.Signals) => void;
20
+ readonly pid: number | undefined;
15
21
  stdout: () => string;
16
22
  stderr: () => string;
17
23
  }
@@ -19,6 +25,15 @@ export declare function runCli(options: {
19
25
  command: string;
20
26
  label?: string;
21
27
  } & SpawnOptions): RunCliHandle;
28
+ /**
29
+ * Stop a server started by `runCli` and wait for its whole process group to
30
+ * exit: SIGTERM to the group, then SIGKILL (and a log naming the pid) for a
31
+ * process that outlives the wait, so a leaked server is visible rather than
32
+ * left holding its port.
33
+ */
34
+ export declare function stopServer(server: RunCliHandle, options?: {
35
+ exitWaitMs?: number;
36
+ }): Promise<void>;
22
37
  export declare function tailOutput(text: string, maxChars?: number): string;
23
38
  export declare function createIsolatedViteCacheDir(cwd: string, projectName: string, mode: "dev" | "build" | undefined): string;
24
39
  export declare function waitForReady(url: string, getOutput?: () => {
@@ -35,6 +35,16 @@
35
35
  * not modeled). navigate(url, { streamUntil }) gives the navigation a
36
36
  * response stream that stays open past the commit (isStreaming).
37
37
  * useAction().state stays idle: no action runs.
38
+ * - prefetch(url) keeps a page as a prefetch made now carries it, and
39
+ * action() lands a server action's response: the page on screen
40
+ * re-renders with its post-write loader data, and, without
41
+ * `keepClientCache`, production's store marks every history entry and
42
+ * prefetch stale (browser/navigation-store.ts). A navigate(url) to a
43
+ * prefetched page commits the prefetch's data; when an action made it
44
+ * stale, the page then revalidates once the navigation settled, in the
45
+ * lane production's Back uses (useNavigation() idle), with the loader
46
+ * data current then. The prefetch request, its decode and the HTTP cache
47
+ * are e2e territory.
38
48
  * - A spec's transition({ when }) decides every commit with production's
39
49
  * browser decision: navigate() and useRouter().push model kind "push",
40
50
  * useRouter().replace kind "replace", refresh() kind "revalidate", and a
@@ -49,8 +59,8 @@
49
59
  * written there, and another page is on screen, its page is restored
50
60
  * from the history cache, with the handle values it had, as production's
51
61
  * restore is; otherwise the tree keeps its location. A stale entry's
52
- * revalidation, a restore that fetches, a server action's state, a
53
- * redirect's and an intercept's are e2e territory.
62
+ * revalidation on Back, a restore that fetches, a server action's
63
+ * location state, a redirect's and an intercept's are e2e territory.
54
64
  * What it DOES cover: client hooks that read NavigationProvider /
55
65
  * OutletContext — useParams, useReverse, useHref, useMount, useNavigation,
56
66
  * useRouter, usePathname, useSearchParams, Outlet/useOutlet nesting and seeded
@@ -420,6 +430,60 @@ export interface TestRouterHandle {
420
430
  * transition({ when }) decides it with kind "revalidate" (`to` is `from`).
421
431
  */
422
432
  refresh(): Promise<void>;
433
+ /**
434
+ * Prefetch `url`, as a `<Link>` hover or viewport trigger does: the page is
435
+ * kept with the loader data its response would carry now (the render-time
436
+ * seeds, `options.loaders` merged over). A later `navigate(url)` without
437
+ * `loaders` commits that data, whatever the seeds are by then.
438
+ *
439
+ * The prefetch is stamped with production's stale epoch
440
+ * (browser/prefetch/cache.ts): an `action()` without `keepClientCache`
441
+ * makes it stale. A click on a stale prefetch commits it at once, then the
442
+ * page revalidates once the navigation settled (its `streamUntil` ended),
443
+ * in Back's stale-revalidation lane: `useNavigation().state` stays "idle"
444
+ * and `isNavigating` reads true while its request is open, `useLinkStatus`
445
+ * shows no pending state, and the commit runs through commitInTransition
446
+ * with kind "revalidate", reading the loader data current then. A newer
447
+ * navigation aborts it.
448
+ *
449
+ * @example
450
+ * router.prefetch("/cart"); // carries count 2
451
+ * await router.action({ loaders: [[BasketLoader, { count: 3 }]] });
452
+ * await router.navigate("/cart"); // count 2, at once
453
+ * await waitFor(() => expect(count()).toBe("3")); // revalidated
454
+ */
455
+ prefetch(url: string, options?: Pick<RenderRouteOptions, "loaders">): void;
456
+ /**
457
+ * Land a server action's response on the page on screen. `loaders` are the
458
+ * loader values after the action's write: they become the data every
459
+ * later response carries, and the page on screen re-renders with them in
460
+ * the action lane (commitInTransition, kind "action", unless
461
+ * transition({ when }) gates it off). Without `keepClientCache`,
462
+ * production's store then marks every history entry and every prefetch
463
+ * stale (`markCacheAsStaleAndBroadcast()`), as the action bridge does:
464
+ * nothing is flushed and the state cookie is not rotated.
465
+ * `keepClientCache: true` is the `keepClientCache()` directive: caches stay
466
+ * fresh. The action's own lifecycle (`useAction()`), its follow-up and its
467
+ * redirect are e2e territory.
468
+ */
469
+ action(options?: Pick<RenderRouteOptions, "loaders"> & {
470
+ keepClientCache?: boolean;
471
+ }): Promise<void>;
472
+ /**
473
+ * The background revalidations a click on a stale prefetch started, in
474
+ * order: the page's pathname and search, and the segment ids the request
475
+ * holds, as production's bridge plans them
476
+ * (navigation-bridge.ts scheduleStalePrefetchRevalidation). A click on a
477
+ * fresh prefetch, or after a `keepClientCache` action, starts none.
478
+ *
479
+ * @example
480
+ * await router.navigate("/cart"); // a stale prefetch
481
+ * await waitFor(() => expect(router.revalidations()).toHaveLength(1));
482
+ */
483
+ revalidations(): ReadonlyArray<{
484
+ url: string;
485
+ segmentIds: string[];
486
+ }>;
423
487
  /** The current committed pathname. */
424
488
  pathname(): string;
425
489
  /** The current committed params. */
@@ -65,9 +65,12 @@ export interface ServeShellRequestOptions<TEnv = any> {
65
65
  * (default none), advertising fragment passthrough. The replay decision is
66
66
  * `result.replayStatus`.
67
67
  *
68
- * `prefetch: true` sends it as a `<Link>` prefetch does (the
69
- * `X-Rango-Prefetch` header), so `prefetch: false` loaders and `loading()`
70
- * entries are deferred: `result.readDeferred()` lists them. `fill: true`
68
+ * `prefetch: true` sends the speculative kind a viewport or render
69
+ * `<Link>` prefetch sends (`X-Rango-Prefetch: 1`), so `prefetch: false`
70
+ * loaders and `loading()` entries are deferred: `result.readDeferred()`
71
+ * lists them. A hover's or `prefetch()`'s intent prefetch defers nothing;
72
+ * serve it with `prefetch: true` and `headers: { "X-Rango-Prefetch":
73
+ * "intent" }`, which `prefetch: true` leaves as given. `fill: true`
71
74
  * sends the request the browser follows an adopted prefetch with
72
75
  * (`_rsc_fill`): pass the ids the prefetch delivered, without the deferred
73
76
  * ones, as `segments`, and the same URL as `from`. `interceptSource` sends
@@ -400,11 +400,13 @@ export type InternalHandlerContext<TParams = {}, TEnv = DefaultEnv, TSearch exte
400
400
  /**
401
401
  * @internal A revalidate() predicate that read the source page
402
402
  * (`currentParams`, `currentUrl`, `fromRouteName`) kept a held segment, so
403
- * this match's diff is right for that source only (#1331). Set by
404
- * evaluateRevalidation (router/revalidation.ts); match-result.ts turns it
405
- * into `sourceHeld`, which scopes a prefetch of the response to its source.
403
+ * this match's diff is right only for pages with equal values of what the
404
+ * kept decisions read (#1331). Set by evaluateRevalidation
405
+ * (router/revalidation.ts) and the stored-entry keep
406
+ * (match-middleware/cache-lookup.ts); match-result.ts turns it into
407
+ * `sourceHeld`, which scopes a prefetch of the response to that group.
406
408
  */
407
- _sourceHeld?: true;
409
+ _sourceHeld?: import("../router/source-reads.js").SourceHeld;
408
410
  /**
409
411
  * @internal DSL loaders registered and not started, by `$$id`: a prefetch
410
412
  * deferred them, or a pass that skips loaders (`skipLoaders`) did not run
@@ -549,7 +551,7 @@ export type IsActionFn = (...actions: ActionRef[]) => boolean;
549
551
  export type ShouldRevalidateFn<TParams = GenericParams, TEnv = any> = (args: {
550
552
  /** Route params from the page being navigated away from. */
551
553
  currentParams: TParams;
552
- /** Full URL of the page being navigated away from. */
554
+ /** Full URL of the page being navigated away from, without its #fragment. */
553
555
  currentUrl: URL;
554
556
  /** Route params for the navigation target. */
555
557
  nextParams: TParams;