@rangojs/router 0.31.0 → 0.33.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 (192) 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 -10143
  11. package/dist/bin/runtime-EPUDEHO4.js +596 -0
  12. package/dist/bin/runtime-discovery-CZ2QHT4Q.js +352 -0
  13. package/dist/bin/state-ACD2MGKJ.js +16 -0
  14. package/dist/bin/temp-rsc-server-QRR6HD6T.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/history-state.d.ts +13 -2
  19. package/dist/types/browser/invalidate-client-cache.d.ts +8 -6
  20. package/dist/types/browser/navigation-bridge.d.ts +36 -1
  21. package/dist/types/browser/navigation-client.d.ts +19 -0
  22. package/dist/types/browser/navigation-store-handle.d.ts +37 -4
  23. package/dist/types/browser/navigation-store.d.ts +2 -2
  24. package/dist/types/browser/navigation-transaction.d.ts +0 -1
  25. package/dist/types/browser/partial-update.d.ts +13 -0
  26. package/dist/types/browser/prefetch/cache.d.ts +96 -8
  27. package/dist/types/browser/prefetch/fetch.d.ts +26 -2
  28. package/dist/types/browser/prefetch/queue.d.ts +3 -2
  29. package/dist/types/browser/rango-state.d.ts +34 -5
  30. package/dist/types/browser/react/Link.d.ts +19 -13
  31. package/dist/types/browser/react/location-state-shared.d.ts +2 -1
  32. package/dist/types/browser/types.d.ts +25 -0
  33. package/dist/types/build/generate-route-types.d.ts +1 -1
  34. package/dist/types/build/route-types/include-resolution.d.ts +9 -1
  35. package/dist/types/build/route-types/router-processing.d.ts +3 -1
  36. package/dist/types/cache/cache-key-utils.d.ts +12 -8
  37. package/dist/types/cache/cache-scope.d.ts +4 -2
  38. package/dist/types/cache/cf/cf-cache-types.d.ts +9 -5
  39. package/dist/types/cache/document-cache-policy.d.ts +9 -1
  40. package/dist/types/cache/memory-segment-store.d.ts +3 -2
  41. package/dist/types/cache/segment-codec.d.ts +2 -2
  42. package/dist/types/cache/shell-snapshot.d.ts +1 -1
  43. package/dist/types/cache/types.d.ts +12 -7
  44. package/dist/types/cache/vercel/vercel-cache-store.d.ts +2 -1
  45. package/dist/types/client-urls/types.d.ts +7 -5
  46. package/dist/types/prefetch-markers.d.ts +9 -0
  47. package/dist/types/prefetch-source-scope.d.ts +66 -0
  48. package/dist/types/route-definition/helpers-types.d.ts +8 -5
  49. package/dist/types/route-definition/redirect.d.ts +5 -0
  50. package/dist/types/router/intercept-resolution.d.ts +25 -11
  51. package/dist/types/router/match-context.d.ts +1 -0
  52. package/dist/types/router/match-handlers.d.ts +1 -1
  53. package/dist/types/router/middleware-types.d.ts +6 -0
  54. package/dist/types/router/prefetch-cache-ttl.d.ts +11 -3
  55. package/dist/types/router/router-context.d.ts +1 -1
  56. package/dist/types/router/router-options.d.ts +6 -3
  57. package/dist/types/router/segment-resolution/loader-cache.d.ts +8 -0
  58. package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +23 -7
  59. package/dist/types/router/segment-resolution/revalidation.d.ts +19 -0
  60. package/dist/types/router/source-reads.d.ts +53 -0
  61. package/dist/types/router/types.d.ts +1 -1
  62. package/dist/types/rsc/helpers.d.ts +8 -3
  63. package/dist/types/rsc/prefetch-render-error.d.ts +7 -2
  64. package/dist/types/rsc/request-stages.d.ts +4 -0
  65. package/dist/types/rsc/shell-serve.d.ts +6 -1
  66. package/dist/types/server/context.d.ts +23 -6
  67. package/dist/types/server/cookie-store.d.ts +15 -1
  68. package/dist/types/server/document-location-state.d.ts +59 -0
  69. package/dist/types/server/handle-store.d.ts +2 -0
  70. package/dist/types/server/request-context.d.ts +65 -12
  71. package/dist/types/testing/e2e/server.d.ts +16 -1
  72. package/dist/types/testing/render-route.d.ts +83 -2
  73. package/dist/types/testing/serve-shell-request.d.ts +15 -3
  74. package/dist/types/types/handler-context.d.ts +21 -4
  75. package/dist/types/types/loader-types.d.ts +7 -4
  76. package/dist/types/types/segments.d.ts +18 -1
  77. package/dist/types/urls/path-helper-types.d.ts +7 -5
  78. package/dist/types/vite/temp-rsc-server.d.ts +2 -2
  79. package/dist/types/vite/utils/forward-user-plugins.d.ts +32 -2
  80. package/dist/vite/index.js +451 -191
  81. package/package.json +2 -2
  82. package/skills/app-structure/SKILL.md +74 -0
  83. package/skills/app-structure/e-commerce.md +10 -9
  84. package/skills/app-structure/large-app.md +15 -2
  85. package/skills/caching/SKILL.md +10 -5
  86. package/skills/client-urls/SKILL.md +18 -11
  87. package/skills/comparison/references/framework-comparison.md +16 -10
  88. package/skills/document-cache/SKILL.md +13 -0
  89. package/skills/hooks/navigation.md +8 -8
  90. package/skills/hooks/state.md +132 -12
  91. package/skills/intercept/SKILL.md +33 -11
  92. package/skills/layout/SKILL.md +11 -0
  93. package/skills/links/SKILL.md +101 -4
  94. package/skills/loader/SKILL.md +49 -22
  95. package/skills/migrate-nextjs/SKILL.md +8 -2
  96. package/skills/migrate-react-router/data-and-actions.md +5 -0
  97. package/skills/ppr/SKILL.md +38 -5
  98. package/skills/prerender/SKILL.md +4 -1
  99. package/skills/route/SKILL.md +6 -6
  100. package/skills/router-setup/SKILL.md +2 -1
  101. package/skills/server-actions/SKILL.md +49 -8
  102. package/skills/testing/client-components.md +73 -32
  103. package/skills/testing/serve-shell-request.md +45 -20
  104. package/skills/testing/server-actions.md +8 -0
  105. package/skills/ux-architecture/SKILL.md +67 -15
  106. package/src/bin/rango.ts +39 -13
  107. package/src/browser/action-fence.ts +72 -0
  108. package/src/browser/cookie-name.ts +82 -14
  109. package/src/browser/history-state.ts +24 -2
  110. package/src/browser/invalidate-client-cache.ts +9 -7
  111. package/src/browser/navigation-bridge.ts +164 -32
  112. package/src/browser/navigation-client.ts +95 -20
  113. package/src/browser/navigation-store-handle.ts +67 -5
  114. package/src/browser/navigation-store.ts +86 -17
  115. package/src/browser/navigation-transaction.ts +0 -6
  116. package/src/browser/partial-update.ts +31 -12
  117. package/src/browser/prefetch/cache.ts +188 -11
  118. package/src/browser/prefetch/fetch.ts +211 -51
  119. package/src/browser/prefetch/queue.ts +3 -2
  120. package/src/browser/rango-state.ts +50 -8
  121. package/src/browser/react/Link.tsx +24 -16
  122. package/src/browser/react/location-state-shared.ts +8 -1
  123. package/src/browser/react/use-navigation.ts +1 -1
  124. package/src/browser/rsc-router.tsx +13 -3
  125. package/src/browser/server-action-bridge.ts +3 -3
  126. package/src/browser/types.ts +25 -0
  127. package/src/build/generate-route-types.ts +1 -0
  128. package/src/build/route-types/include-resolution.ts +40 -3
  129. package/src/build/route-types/router-processing.ts +170 -25
  130. package/src/build/route-types/scan-filter.ts +1 -0
  131. package/src/build/runtime-discovery.ts +46 -8
  132. package/src/cache/cache-key-utils.ts +12 -8
  133. package/src/cache/cache-runtime.ts +5 -1
  134. package/src/cache/cache-scope.ts +28 -7
  135. package/src/cache/cf/cf-cache-types.ts +9 -5
  136. package/src/cache/document-cache-policy.ts +37 -1
  137. package/src/cache/document-cache.ts +12 -0
  138. package/src/cache/memory-segment-store.ts +3 -2
  139. package/src/cache/segment-codec.ts +2 -2
  140. package/src/cache/shell-snapshot.ts +1 -1
  141. package/src/cache/types.ts +12 -7
  142. package/src/cache/vercel/vercel-cache-store.ts +2 -1
  143. package/src/client-urls/types.ts +7 -5
  144. package/src/prefetch-markers.ts +10 -0
  145. package/src/prefetch-source-scope.ts +137 -0
  146. package/src/route-definition/helpers-types.ts +8 -5
  147. package/src/route-definition/redirect.ts +5 -0
  148. package/src/router/intercept-resolution.ts +94 -50
  149. package/src/router/loader-resolution.ts +8 -6
  150. package/src/router/match-api.ts +33 -29
  151. package/src/router/match-context.ts +5 -0
  152. package/src/router/match-handlers.ts +2 -1
  153. package/src/router/match-middleware/cache-lookup.ts +39 -14
  154. package/src/router/match-result.ts +45 -85
  155. package/src/router/middleware-types.ts +6 -0
  156. package/src/router/navigation-snapshot.ts +18 -3
  157. package/src/router/prefetch-cache-ttl.ts +15 -3
  158. package/src/router/revalidation.ts +63 -13
  159. package/src/router/router-context.ts +2 -1
  160. package/src/router/router-options.ts +6 -3
  161. package/src/router/segment-resolution/loader-cache.ts +21 -3
  162. package/src/router/segment-resolution/prefetch-deferral.ts +38 -31
  163. package/src/router/segment-resolution/revalidation.ts +40 -12
  164. package/src/router/source-reads.ts +122 -0
  165. package/src/router/types.ts +2 -1
  166. package/src/router.ts +11 -12
  167. package/src/rsc/helpers.ts +20 -6
  168. package/src/rsc/prefetch-render-error.ts +7 -2
  169. package/src/rsc/progressive-enhancement.ts +8 -2
  170. package/src/rsc/request-stages.ts +87 -1
  171. package/src/rsc/rsc-rendering.ts +78 -32
  172. package/src/rsc/server-action.ts +2 -1
  173. package/src/rsc/shell-capture.ts +4 -5
  174. package/src/rsc/shell-serve.ts +12 -1
  175. package/src/segment-system.tsx +1 -1
  176. package/src/server/context.ts +37 -6
  177. package/src/server/cookie-store.ts +22 -1
  178. package/src/server/document-location-state.ts +334 -0
  179. package/src/server/handle-store.ts +2 -0
  180. package/src/server/request-context.ts +137 -33
  181. package/src/testing/dispatch.ts +6 -0
  182. package/src/testing/e2e/fixture.ts +4 -12
  183. package/src/testing/e2e/server.ts +91 -4
  184. package/src/testing/render-route.tsx +309 -10
  185. package/src/testing/run-revalidate.ts +11 -1
  186. package/src/testing/serve-shell-request.ts +22 -3
  187. package/src/types/handler-context.ts +21 -4
  188. package/src/types/loader-types.ts +7 -4
  189. package/src/types/segments.ts +18 -1
  190. package/src/urls/path-helper-types.ts +7 -5
  191. package/src/vite/temp-rsc-server.ts +2 -2
  192. package/src/vite/utils/forward-user-plugins.ts +13 -5
@@ -7,20 +7,15 @@
7
7
  import type { EntryData, InterceptEntry, InterceptSelectorContext } from "../server/context";
8
8
  import type { ResolvedSegment } from "../types";
9
9
  import type { ResolutionPass } from "./segment-resolution/resolution-pass.js";
10
+ import { type InterceptResult } from "./router-context.js";
10
11
  /**
11
12
  * Check if an intercept's when conditions are satisfied.
12
13
  * All when() functions must return true for the intercept to activate.
13
14
  * If no when() conditions are defined, the intercept always activates.
14
15
  * A selector that throws yields the conservative default, no intercept (the
15
16
  * full page renders), and is logged; it never fails the request.
16
- *
17
- * During action revalidation, when() is NOT evaluated.
18
- */
19
- export declare function evaluateInterceptWhen(intercept: InterceptEntry, selectorContext: InterceptSelectorContext | null, isAction: boolean): boolean;
20
- /**
21
- * Find an intercept for the target route by walking up the entry chain.
22
- * Returns the first (innermost) matching intercept along with the entry that defines it.
23
17
  */
18
+ export declare function evaluateInterceptWhen(intercept: InterceptEntry, selectorContext: InterceptSelectorContext | null): boolean;
24
19
  /**
25
20
  * Collect every intercept TARGET route name reachable from an origin entry —
26
21
  * the same chain walk findInterceptForRoute() performs when this location is
@@ -33,10 +28,29 @@ export declare function evaluateInterceptWhen(intercept: InterceptEntry, selecto
33
28
  * loses its optimistic loading, never the reverse.
34
29
  */
35
30
  export declare function collectInterceptTargetNames(fromEntry: EntryData | null): string[];
36
- export declare function findInterceptForRoute(targetRouteKey: string, fromEntry: EntryData | null, selectorContext?: InterceptSelectorContext | null, isAction?: boolean): {
37
- intercept: InterceptEntry;
38
- entry: EntryData;
39
- } | null;
31
+ /**
32
+ * The intercept for the target route, with the entry that declares it.
33
+ * `targetRouteKeys` are the names it may be declared for, in precedence
34
+ * order: the route's own name, then its local name inside include()
35
+ * (match-api.ts). A navigation, or an action with no `held`, takes the first
36
+ * along the chain from `fromEntry` up under the first name that has one: on a
37
+ * navigation, the first whose when() passes; on an action, the first, when()
38
+ * not evaluated.
39
+ *
40
+ * An action given `held` (the request's segment ids) re-renders the intercept
41
+ * on screen: one whose slot id the client holds, null when it holds none. Its
42
+ * candidates are collected under every name in one walk, the route's own
43
+ * name's first: a held id under the own name (a parallel() default of the
44
+ * slot's name) must not win before the local name is looked at. One held:
45
+ * that one, when() not evaluated, so an open intercept stays open when its
46
+ * when() would now reject. Several held (intercepts on one entry that fill one
47
+ * slot share its id, as does a parallel() slot of that name): the ones whose
48
+ * slot has no parallel() default on their entry, if any (a default holds its
49
+ * slot id whether or not an intercept is open there). One left: that one,
50
+ * when() not evaluated. Else the first whose when() passes against the
51
+ * action's intercept source, else the first.
52
+ */
53
+ export declare function findInterceptForRoute(targetRouteKeys: string | readonly string[], fromEntry: EntryData | null, selectorContext?: InterceptSelectorContext | null, isAction?: boolean, held?: ReadonlySet<string>): InterceptResult | null;
40
54
  /** The segment id of `intercept`'s slot on `parentEntry`. */
41
55
  export declare function interceptSlotId(parentEntry: EntryData, intercept: InterceptEntry): string;
42
56
  /**
@@ -177,6 +177,7 @@ export interface MatchPipelineState {
177
177
  cachedSegments?: ResolvedSegment[];
178
178
  cachedMatchedIds?: string[];
179
179
  shouldRevalidate?: boolean;
180
+ storedStale?: boolean;
180
181
  cacheSource?: "runtime" | "prerender";
181
182
  segments: ResolvedSegment[];
182
183
  matchedIds: string[];
@@ -11,7 +11,7 @@ export interface MatchHandlerDeps<TEnv = any> {
11
11
  matchApiDeps: MatchApiDeps<TEnv>;
12
12
  defaultErrorBoundary: ReactNode | ErrorBoundaryHandler | undefined;
13
13
  findMatch: (pathname: string, ms?: any) => any;
14
- findInterceptForRoute: (routeKey: string, parentEntry: EntryData | null, selectorContext: InterceptSelectorContext | null, isAction: boolean) => {
14
+ findInterceptForRoute: (routeKeys: string | readonly string[], parentEntry: EntryData | null, selectorContext: InterceptSelectorContext | null, isAction: boolean, held?: ReadonlySet<string>) => {
15
15
  intercept: InterceptEntry;
16
16
  entry: EntryData;
17
17
  } | null;
@@ -56,6 +56,12 @@ export interface MiddlewareContext<TEnv = any, TParams = Record<string, string |
56
56
  */
57
57
  readonly theme?: Theme;
58
58
  setTheme?: (theme: Theme) => void;
59
+ /**
60
+ * Attach location state entries to this response (see the handler
61
+ * context's `setLocationState`). Entries accumulate across definitions;
62
+ * for one definition the last write wins. Set it here rather than in a
63
+ * handler on a `ppr` route: a shell HIT runs no handler.
64
+ */
59
65
  setLocationState(entries: LocationStateEntry | LocationStateEntry[]): void;
60
66
  /** Global route names only; see {@link GlobalReverseFunction}. */
61
67
  reverse: GlobalReverseFunction<DefaultReverseRouteMap>;
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Resolve the prefetch cache TTL once, at router init, into the values the
3
3
  * rest of the router consumes: seconds, and milliseconds for the client-side
4
- * in-memory prefetch cache (shipped in payload metadata). Prefetch responses
5
- * carry no Cache-Control: the browser HTTP cache would keep a prefetch whose
6
- * render failed after its 200 went out (#1170).
4
+ * in-memory prefetch cache (shipped in payload metadata). A prefetch with a
5
+ * live part carries no Cache-Control: the browser HTTP cache would keep a
6
+ * prefetch whose render failed after its 200 went out (#1170). One answered
7
+ * from storage carries storedPrefetchCacheControl.
7
8
  *
8
9
  * `false` disables prefetch caching (seconds 0). A non-finite input
9
10
  * (NaN/Infinity, e.g. from an env-derived number that failed to parse) is
@@ -22,3 +23,10 @@ export interface ResolvedPrefetchCacheTTL {
22
23
  ms: number;
23
24
  }
24
25
  export declare function resolvePrefetchCacheTTL(rawTTL: number | false | undefined): ResolvedPrefetchCacheTTL;
26
+ /**
27
+ * The Cache-Control of a prefetch answer with nothing live in it
28
+ * (`_servedFromStorage`): the browser HTTP cache keeps it as long
29
+ * as the in-memory prefetch cache would. Undefined when prefetch caching is
30
+ * off.
31
+ */
32
+ export declare function storedPrefetchCacheControl(ttlMs: number): string | undefined;
@@ -33,7 +33,7 @@ export interface RouterContext<TEnv = any> {
33
33
  };
34
34
  getMetricsStore: () => MetricsStore | undefined;
35
35
  createCacheScope: (cacheConfig: any, parent: CacheScope | null) => CacheScope | null;
36
- findInterceptForRoute: (routeKey: string, parentEntry: EntryData | null, selectorContext: InterceptSelectorContext, isAction: boolean) => InterceptResult | null;
36
+ findInterceptForRoute: (routeKeys: string | readonly string[], parentEntry: EntryData | null, selectorContext: InterceptSelectorContext, isAction: boolean, held?: ReadonlySet<string>) => InterceptResult | null;
37
37
  /** The segment walker; a full match passes freshPassInput (FRESH). */
38
38
  resolveAllSegments: (pass: ResolutionPassInput<TEnv>, entries: EntryData[], walk: RevalidatingWalkOptions) => Promise<{
39
39
  segments: ResolvedSegment[];
@@ -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";
@@ -95,10 +96,14 @@ export declare function interceptRedirectForPartial(response: Response, createRe
95
96
  /**
96
97
  * Attach location state set during a request to a payload's metadata.
97
98
  * No-op if no location state was set. Callers must ensure payload.metadata
98
- * is populated (the non-null assertion holds for the partial/action payloads
99
- * that reach this helper).
99
+ * is populated. A `document` payload (a page load's or a no-JS form post's
100
+ * bootstrap payload) that carries state marks the request
101
+ * `_documentLocationState`: it is the visitor's, so no shared cache keeps it
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).
100
105
  */
101
- export declare function attachLocationStateIfPresent(payload: RscPayload): void;
106
+ export declare function attachLocationStateIfPresent(payload: RscPayload, document?: boolean, locationState?: LocationStateEntry[] | undefined): void;
102
107
  /**
103
108
  * Only cache successful responses. Non-200 statuses (errors, redirects) are
104
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
@@ -36,6 +36,10 @@ export interface StageRouter<TEnv = unknown> {
36
36
  readonly telemetry?: TelemetrySink;
37
37
  readonly timeouts?: ResolvedTimeouts;
38
38
  readonly onTimeout?: OnTimeoutCallback<TEnv>;
39
+ /** Names the cookie location state crosses a document redirect in. */
40
+ readonly resolvedStateCookieName: string;
41
+ /** The in-memory prefetch TTL in ms; also a stored prefetch's max-age. */
42
+ readonly prefetchCacheTTL: number;
39
43
  }
40
44
  /** Report an error through the router's onError. */
41
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 {};
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Location state on document responses (#1330).
3
+ *
4
+ * A client navigation or an action carries server-set location state in its
5
+ * Flight payload, and a redirect on either becomes a Flight redirect that
6
+ * carries it too. A document request has no such channel for a 3xx: the
7
+ * browser follows it with a plain GET. This module carries the state across
8
+ * that one hop in a short-lived cookie:
9
+ *
10
+ * - `carryLocationStateAcrossRedirect` (rsc/request-stages.ts
11
+ * finalizeRequestResponse) writes it on a document request's same-origin
12
+ * 3xx when the request set location state;
13
+ * - `consumeDocumentLocationState` (rsc/request-stages.ts runRequestPasses)
14
+ * reads it on the next GET document request that navigates to the target
15
+ * pathname (`Sec-Fetch-Dest` a browsing context, where sent), seeds
16
+ * `_locationState` with its entries (so the render attaches them to the
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.
21
+ *
22
+ * A document that carries location state is per visitor: it is marked
23
+ * `_documentLocationState`, never stored by the document cache or as a PPR
24
+ * shell, and leaves with `Cache-Control: private, no-store`
25
+ * (`markDocumentResponsePrivate`).
26
+ *
27
+ * The value is JSON, not Flight: a cookie holds text, and the client merges
28
+ * it as `history.state` would hold it. `Path=/` because the `__Host-` prefix
29
+ * (https) requires it, which keeps a sibling subdomain from planting state;
30
+ * the target pathname inside the value decides who consumes it.
31
+ */
32
+ import { type RequestContext } from "./request-context.js";
33
+ /** The cookie's name for a router: `__Host-` on https. */
34
+ export declare function documentLocationStateCookieName(stateCookieName: string, secure: boolean): string;
35
+ /**
36
+ * Read the cookie a document redirect left for this request: seed
37
+ * `_locationState` with its slots and clear it. Only a GET document request
38
+ * of the target pathname that navigates consumes it (the request the browser
39
+ * makes for a 301/302/303, see isNavigationRequest); any other request, a
40
+ * form POST to that path or a prefetch of it included, leaves it to its
41
+ * expiry. A malformed value is cleared and ignored, and only `__rsc_ls_*`
42
+ * slots are taken from it (the value comes from the browser).
43
+ */
44
+ export declare function consumeDocumentLocationState(reqCtx: Pick<RequestContext, "_locationState" | "_carriedLocationStateCookie" | "setCookie">, request: Request, url: URL, stateCookieName: string): void;
45
+ /**
46
+ * Write the cookie on a document request's redirect that set location state,
47
+ * and mark the redirect private. Not for a partial or action request (their
48
+ * redirect is a Flight payload), an external or cross-origin target (the
49
+ * cookie would never reach it) or a response that is not a 3xx with a
50
+ * Location. Returns the response to send: a new one when its headers are
51
+ * immutable.
52
+ */
53
+ export declare function carryLocationStateAcrossRedirect(response: Response, reqCtx: Pick<RequestContext, "_locationState">, request: Request, url: URL, stateCookieName: string): Response;
54
+ /**
55
+ * `Cache-Control: private, no-store` on a document that carries location
56
+ * state, after every middleware: a shared cache must never store or replay
57
+ * one visitor's state. Returns the response to send.
58
+ */
59
+ export declare function markDocumentResponsePrivate(response: Response, reqCtx: Pick<RequestContext, "_documentLocationState">): Response;
@@ -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
  /**