@rangojs/router 0.17.0 → 0.18.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 (152) hide show
  1. package/README.md +3 -2
  2. package/dist/types/browser/cookie-name.d.ts +6 -0
  3. package/dist/types/browser/history-state.d.ts +16 -0
  4. package/dist/types/cache/cache-key-utils.d.ts +17 -2
  5. package/dist/types/cache/cache-policy.d.ts +17 -0
  6. package/dist/types/cache/cache-scope.d.ts +98 -14
  7. package/dist/types/cache/cache-tag.d.ts +92 -7
  8. package/dist/types/cache/cf/cf-base64.d.ts +6 -0
  9. package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
  10. package/dist/types/cache/cf/cf-cache-store.d.ts +185 -44
  11. package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
  12. package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
  13. package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +34 -0
  14. package/dist/types/cache/handle-snapshot.d.ts +4 -3
  15. package/dist/types/cache/index.d.ts +1 -0
  16. package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
  17. package/dist/types/cache/shell-memo.d.ts +178 -0
  18. package/dist/types/cache/shell-snapshot.d.ts +110 -21
  19. package/dist/types/cache/tag-invalidation.d.ts +30 -3
  20. package/dist/types/cache/types.d.ts +207 -43
  21. package/dist/types/cache/vercel/vercel-cache-store.d.ts +111 -13
  22. package/dist/types/prerender/build-shell-capture.d.ts +11 -4
  23. package/dist/types/router/instrument.d.ts +8 -0
  24. package/dist/types/router/middleware-types.d.ts +6 -1
  25. package/dist/types/router/segment-resolution/loader-cache.d.ts +40 -11
  26. package/dist/types/router/segment-resolution/loader-snapshot.d.ts +21 -10
  27. package/dist/types/router/segment-resolution/mask-nested.d.ts +65 -24
  28. package/dist/types/rsc/full-payload.d.ts +14 -0
  29. package/dist/types/rsc/handler.d.ts +1 -1
  30. package/dist/types/rsc/shell-build-manifest.d.ts +8 -0
  31. package/dist/types/rsc/shell-capture-constants.d.ts +53 -6
  32. package/dist/types/rsc/shell-capture.d.ts +146 -15
  33. package/dist/types/rsc/shell-serve.d.ts +140 -17
  34. package/dist/types/rsc/types.d.ts +7 -0
  35. package/dist/types/server/context.d.ts +109 -15
  36. package/dist/types/server/cookie-store.d.ts +21 -0
  37. package/dist/types/server/request-context.d.ts +159 -33
  38. package/dist/types/ssr/index.d.ts +9 -0
  39. package/dist/types/ssr/ssr-root.d.ts +9 -8
  40. package/dist/types/testing/flight.entry.d.ts +7 -0
  41. package/dist/types/testing/run-loader.d.ts +18 -3
  42. package/dist/types/testing/serve-shell-request.d.ts +132 -0
  43. package/dist/types/testing/shell-status.d.ts +23 -10
  44. package/dist/types/types/handler-context.d.ts +14 -1
  45. package/dist/types/urls/pattern-types.d.ts +22 -18
  46. package/dist/types/vite/router-discovery.d.ts +29 -2
  47. package/dist/vite/index.js +46 -9
  48. package/package.json +1 -1
  49. package/skills/breadcrumbs/SKILL.md +17 -7
  50. package/skills/cache-guide/SKILL.md +123 -51
  51. package/skills/caching/SKILL.md +253 -23
  52. package/skills/catalog.json +1 -1
  53. package/skills/client-urls/SKILL.md +11 -6
  54. package/skills/cloudflare/SKILL.md +15 -0
  55. package/skills/comparison/references/framework-comparison.md +44 -32
  56. package/skills/defer-hydration/SKILL.md +50 -30
  57. package/skills/deployment-caching/SKILL.md +5 -2
  58. package/skills/document-cache/SKILL.md +8 -2
  59. package/skills/handler-use/SKILL.md +2 -0
  60. package/skills/hooks/handle-and-actions.md +3 -1
  61. package/skills/hooks/state.md +3 -1
  62. package/skills/loader/SKILL.md +161 -39
  63. package/skills/middleware/SKILL.md +16 -9
  64. package/skills/migrate-nextjs/SKILL.md +67 -36
  65. package/skills/migrate-react-router/SKILL.md +2 -1
  66. package/skills/migrate-react-router/component-migration.md +3 -1
  67. package/skills/migrate-react-router/data-and-actions.md +3 -1
  68. package/skills/migrate-react-router/route-mapping.md +7 -4
  69. package/skills/observability/SKILL.md +64 -0
  70. package/skills/parallel/SKILL.md +13 -8
  71. package/skills/ppr/SKILL.md +655 -363
  72. package/skills/prerender/SKILL.md +27 -14
  73. package/skills/rango/SKILL.md +52 -33
  74. package/skills/route/SKILL.md +12 -7
  75. package/skills/router-setup/SKILL.md +4 -2
  76. package/skills/scripts/SKILL.md +8 -1
  77. package/skills/server-actions/SKILL.md +11 -0
  78. package/skills/shell-manifest/SKILL.md +21 -12
  79. package/skills/testing/SKILL.md +25 -21
  80. package/skills/testing/cache-prerender.md +15 -14
  81. package/skills/testing/loader.md +18 -1
  82. package/skills/testing/serve-shell-request.md +188 -0
  83. package/skills/theme/SKILL.md +52 -4
  84. package/skills/typesafety/env-and-bindings.md +10 -2
  85. package/skills/use-cache/SKILL.md +39 -16
  86. package/skills/vercel/SKILL.md +14 -2
  87. package/skills/view-transitions/SKILL.md +1 -1
  88. package/src/browser/cookie-name.ts +23 -0
  89. package/src/browser/history-state.ts +23 -0
  90. package/src/browser/rsc-router.tsx +5 -0
  91. package/src/cache/cache-key-utils.ts +22 -2
  92. package/src/cache/cache-policy.ts +22 -0
  93. package/src/cache/cache-runtime.ts +34 -7
  94. package/src/cache/cache-scope.ts +345 -33
  95. package/src/cache/cache-tag.ts +375 -11
  96. package/src/cache/cf/cf-base64.ts +36 -4
  97. package/src/cache/cf/cf-cache-constants.ts +12 -0
  98. package/src/cache/cf/cf-cache-store.ts +990 -348
  99. package/src/cache/cf/cf-cache-types.ts +32 -4
  100. package/src/cache/cf/cf-shell-frame.ts +258 -0
  101. package/src/cache/cf/cf-tag-marker-memo.ts +143 -39
  102. package/src/cache/handle-snapshot.ts +4 -3
  103. package/src/cache/index.ts +2 -0
  104. package/src/cache/isolate-tag-memo.ts +308 -0
  105. package/src/cache/shell-memo.ts +341 -0
  106. package/src/cache/shell-snapshot.ts +217 -27
  107. package/src/cache/tag-invalidation.ts +104 -19
  108. package/src/cache/types.ts +211 -43
  109. package/src/cache/vercel/vercel-cache-store.ts +545 -61
  110. package/src/prerender/build-shell-capture.ts +65 -11
  111. package/src/route-definition/dsl-helpers.ts +4 -3
  112. package/src/router/handler-context.ts +38 -4
  113. package/src/router/instrument.ts +54 -2
  114. package/src/router/intercept-resolution.ts +19 -7
  115. package/src/router/loader-resolution.ts +63 -32
  116. package/src/router/match-api.ts +28 -9
  117. package/src/router/match-handlers.ts +5 -0
  118. package/src/router/match-middleware/cache-lookup.ts +17 -0
  119. package/src/router/match-middleware/cache-store.ts +31 -11
  120. package/src/router/middleware-types.ts +6 -1
  121. package/src/router/middleware.ts +23 -4
  122. package/src/router/prerender-match.ts +6 -0
  123. package/src/router/segment-resolution/fresh.ts +18 -9
  124. package/src/router/segment-resolution/helpers.ts +4 -2
  125. package/src/router/segment-resolution/loader-cache.ts +227 -56
  126. package/src/router/segment-resolution/loader-snapshot.ts +203 -51
  127. package/src/router/segment-resolution/mask-nested.ts +136 -42
  128. package/src/router/segment-resolution/revalidation.ts +27 -10
  129. package/src/rsc/full-payload.ts +25 -1
  130. package/src/rsc/handler.ts +14 -2
  131. package/src/rsc/progressive-enhancement.ts +3 -2
  132. package/src/rsc/response-cache-serve.ts +9 -6
  133. package/src/rsc/rsc-rendering.ts +601 -181
  134. package/src/rsc/shell-build-manifest.ts +27 -9
  135. package/src/rsc/shell-capture-constants.ts +60 -6
  136. package/src/rsc/shell-capture.ts +1001 -250
  137. package/src/rsc/shell-serve.ts +316 -34
  138. package/src/rsc/types.ts +7 -0
  139. package/src/server/context.ts +219 -28
  140. package/src/server/cookie-store.ts +87 -62
  141. package/src/server/request-context.ts +263 -38
  142. package/src/ssr/index.tsx +96 -37
  143. package/src/ssr/ssr-root.tsx +9 -8
  144. package/src/testing/flight.entry.ts +14 -0
  145. package/src/testing/run-loader.ts +63 -5
  146. package/src/testing/serve-shell-request.ts +378 -0
  147. package/src/testing/shell-status.ts +34 -12
  148. package/src/theme/ThemeProvider.tsx +7 -6
  149. package/src/types/handler-context.ts +14 -1
  150. package/src/urls/pattern-types.ts +22 -18
  151. package/src/vite/discovery/shell-prerender-phase.ts +9 -0
  152. package/src/vite/router-discovery.ts +14 -4
package/README.md CHANGED
@@ -524,8 +524,9 @@ parityDescribe("add to cart", (f) => {
524
524
  ```
525
525
 
526
526
  `runMiddleware`, `renderHandler` (Server Component handlers rendered to a
527
- real Flight tree), `renderRoute` (DOM tests) and the cache assertions follow
528
- the same pattern. See the [`/testing` skill](./skills/testing/SKILL.md).
527
+ real Flight tree), `serveShellRequest` (a `ppr` route's real shell capture and
528
+ HIT), `renderRoute` (DOM tests) and the cache assertions follow the same
529
+ pattern. See the [`/testing` skill](./skills/testing/SKILL.md).
529
530
 
530
531
  ## Everything else, when you need it
531
532
 
@@ -12,6 +12,12 @@
12
12
  */
13
13
  /** Default prefix when `stateCookiePrefix` is unset or empty after sanitization. */
14
14
  export declare const DEFAULT_STATE_COOKIE_PREFIX = "rango-state";
15
+ /**
16
+ * The fresh-reads cookie name for a router's resolved state cookie name
17
+ * (`{prefix}_{routerId}`; the prefix never contains `_`, see
18
+ * router/state-cookie-name.ts).
19
+ */
20
+ export declare function freshReadsCookieName(stateCookieName: string): string;
15
21
  /** Internal response header carrying the keepClientCache() directive. */
16
22
  export declare const KEEP_CACHE_HEADER = "x-rango-keep-cache";
17
23
  /** True for a per-client signal header. */
@@ -18,6 +18,22 @@ export declare function buildHistoryState(userState: unknown, routerState?: {
18
18
  * relying on the Navigation API.
19
19
  */
20
20
  export declare function pushHistoryWithIdx(state: Record<string, unknown> | null, url: string, replace: boolean): void;
21
+ /**
22
+ * The forced-MISS reload marker (`SHELL_MISS_PARAM` in rsc/shell-serve.ts,
23
+ * which the browser bundle must not import): a degraded PPR shell HIT
24
+ * reloads the page with it, and the server renders a marked request like a
25
+ * cache miss.
26
+ */
27
+ export declare const SHELL_MISS_MARKER: string;
28
+ /**
29
+ * Drop the forced-MISS marker from the address bar at boot, keeping the
30
+ * history entry's state (initBrowserApp calls it before any location read).
31
+ * Left in place, a refresh or a shared link would stay off the shell and the
32
+ * app's search params would carry it. The server strips the same marker from
33
+ * the request before rendering (shell-serve.ts withoutShellMissMarker), so
34
+ * the page was rendered for the clean URL and the hydrating client agrees.
35
+ */
36
+ export declare function stripShellMissMarker(): void;
21
37
  /**
22
38
  * Merge server-set location state into the current history entry.
23
39
  * Replaces the current history state and dispatches notification event
@@ -2,8 +2,8 @@
2
2
  * Shared Cache Key Utilities
3
3
  *
4
4
  * Deterministic normalization of search params and route params
5
- * for cache key generation. Used by cache-runtime, cache-scope,
6
- * document-cache, and loader-cache.
5
+ * for cache key generation, and the composition of nested cache() keys.
6
+ * Used by cache-runtime, cache-scope, document-cache, and loader-cache.
7
7
  */
8
8
  import type { SearchParamsFilter } from "./search-params-filter.js";
9
9
  /**
@@ -41,3 +41,18 @@ export declare function sortedRouteParams(params: Record<string, string> | undef
41
41
  * tier-specific suffixes (`:rsc`/`:html`, segment hash) after this base.
42
42
  */
43
43
  export declare function cacheKeyBase(host: string, pathname: string, searchParams?: URLSearchParams, params?: Record<string, string>, filter?: SearchParamsFilter): string;
44
+ /**
45
+ * The record key, or shell partition, of nested keyed cache() scopes (#970)
46
+ * from its parts, outermost first: the chain's `key()` results and, for an
47
+ * inner scope without its own `key()`, that scope's default key
48
+ * (CacheScope.resolveKeyFrom). One part is returned raw, so a single keyed
49
+ * scope keeps its key byte for byte. Two or more are joined by `|`, each
50
+ * URI-encoded (like partitionShellKey, rsc/shell-capture-constants.ts): an
51
+ * encoded part holds no `|`, so every tuple maps to its own key, of any
52
+ * depth. Raw, `("a|b", "c")` and `("a", "b|c")` named one record. A composed
53
+ * key holds no `:` either, so a single raw result with one (a prefixed
54
+ * `tier:...`, a `doc:` default key) never equals it; an unprefixed raw result
55
+ * can. Shared with the testing helper `shellCacheKey`, which composes a
56
+ * nested partition the same way.
57
+ */
58
+ export declare function composeCacheKeys(parts: readonly string[]): string;
@@ -42,6 +42,23 @@ export declare function resolveTtl(explicit: number | undefined, defaults: Cache
42
42
  * non-finite/negative (degrade rather than feed bad math into expiry).
43
43
  */
44
44
  export declare function resolveSwrWindow(explicit: number | undefined, defaults: CacheDefaults | undefined): number;
45
+ /**
46
+ * Minimum age (ms) of a stale PPR shell before a HIT schedules its recapture.
47
+ * A shell capped to a route cache() record inside its swr window is stale
48
+ * from the start (rsc/shell-capture.ts capShellWindow), so without a floor
49
+ * every HIT of it recaptured, and a capture's store write is one KV write per
50
+ * key: Cloudflare KV accepts one per second per key. Inside the floor the
51
+ * stale shell is still served. Per isolate: each isolate's stale HIT past the
52
+ * floor can schedule one (stampede-guarded per isolate).
53
+ */
54
+ export declare const SHELL_MIN_RECAPTURE_INTERVAL_MS = 1000;
55
+ /**
56
+ * Whether a stale shell HIT schedules its recapture (see the floor above).
57
+ * Fails open: an entry without a numeric `createdAt` recaptures.
58
+ */
59
+ export declare function staleShellRecaptureDue(entry: {
60
+ readonly createdAt?: number;
61
+ }, now: number): boolean;
45
62
  /**
46
63
  * Compute staleAt and expiresAt timestamps from TTL and SWR window.
47
64
  *
@@ -12,6 +12,7 @@ import type { ResolvedSegment } from "../types.js";
12
12
  import type { SegmentCacheStore } from "./types.js";
13
13
  import type { RequestContext } from "../server/request-context.js";
14
14
  export declare function resolveCacheTags(config: PartialCacheOptions | false, ctx: RequestContext | undefined): string[] | undefined;
15
+ type CacheKeyFn = NonNullable<PartialCacheOptions["key"]>;
15
16
  /**
16
17
  * Discriminated outcome of a route cache lookup — see
17
18
  * {@link CacheScope.lookupRouteDetailed} for what each status licenses.
@@ -56,6 +57,18 @@ export declare class CacheScope {
56
57
  readonly parent: CacheScope | null;
57
58
  /** Explicit store from cache() options, if specified */
58
59
  private readonly explicitStore;
60
+ /**
61
+ * @internal The `key()` functions that partition this scope's records,
62
+ * outermost first: every cache() on the parent chain that sets one, this
63
+ * scope's last (#970). `cache(false)` adds none and does not cut the chain,
64
+ * so a cache() re-enabled under it stays in the partition. Before, the
65
+ * innermost config alone decided the key: an inner cache() without `key()`
66
+ * wrote under the default key and an inner `key()` dropped the outer one,
67
+ * so a request in one partition HIT another partition's inner records.
68
+ */
69
+ readonly keyFns: readonly CacheKeyFn[];
70
+ /** Whether this scope's own config sets `key()` (the last of keyFns). */
71
+ private readonly hasOwnKey;
59
72
  constructor(config: PartialCacheOptions | false, parent?: CacheScope | null, defaultKeyPrefix?: "doc" | undefined,
60
73
  /**
61
74
  * @internal shortCode of the outermost cache() entry of this scope's
@@ -106,6 +119,57 @@ export declare class CacheScope {
106
119
  * @internal
107
120
  */
108
121
  private resolveKey;
122
+ /**
123
+ * A shell captured from a route record this request read or wrote must not
124
+ * outlive it (noteRouteRecordWindow). The implicit doc scope's own record
125
+ * is the shell's, so it notes nothing.
126
+ */
127
+ private noteRecordWindow;
128
+ /**
129
+ * @internal This scope's record key for `defaultKey`, resolved once per
130
+ * request: a ppr route's shell partition (resolvePartition) and the record
131
+ * lookup share the `key()` runs, and a rejection is shared too.
132
+ *
133
+ * With `key()` functions on the chain (keyFns) the parts are their results,
134
+ * outermost first, composed by composeCacheKeys (a single part raw), so a
135
+ * nested cache() keys its records within the enclosing partition (#970).
136
+ * A scope whose own config sets no `key()` appends its own default key
137
+ * (resolveDefaultKey). Reusing the outer `key()` result alone would give
138
+ * every route under the inner cache() one record: a `key()` replaces the
139
+ * whole default key, so an outer key that names no route (`tier:gold`)
140
+ * would make `/a` HIT `/b`'s record. A scope with its own `key()` adds no
141
+ * default key: a `key()` is a full override, and no keyGenerator runs.
142
+ * Without `key()` on the chain it is resolveDefaultKey alone. With one but
143
+ * no request context it rejects (keyWithoutContextError).
144
+ *
145
+ * Each `key()` result lives on the request context (`_resolvedCacheKeys`),
146
+ * by function: it runs once per request whatever the default key
147
+ * (document, partial or `doc` shell prefix) and however many scopes share
148
+ * it (sibling routes under one keyed cache()). A shell capture, whose
149
+ * context is `Object.create` of the request's (shell-capture.ts), reads the
150
+ * foreground's results through the prototype chain and never runs `key()`
151
+ * itself: a `key()` calling `cookies()` would trip the capture guard there,
152
+ * and a capture must store the partition its request resolved.
153
+ */
154
+ resolveKeyFrom(defaultKey: string): Promise<string>;
155
+ /**
156
+ * @internal The request partition of a ppr route's shell under this scope
157
+ * (resolveShellPartition): the chain's `key()` results, composed like the
158
+ * record key but without the default key, which the URL-based shell key
159
+ * already carries. A scope without its own `key()` whose store has a
160
+ * keyGenerator adds that result when it differs from the `doc` default
161
+ * key, as an unkeyed scope's partition always did: the record is split by
162
+ * it, so the shell must be too. Undefined (synchronously, with no work)
163
+ * when nothing can partition; null when nothing does.
164
+ */
165
+ resolvePartition(pathname: string, params: Record<string, string> | undefined): Promise<string | null> | undefined;
166
+ /** The chain's `key()` results, each once per request (memoizedKey). */
167
+ private resolveKeyResults;
168
+ /**
169
+ * This scope's store keyGenerator result for `defaultKey` (once per request
170
+ * and default key), else `defaultKey` (resolveCacheKey).
171
+ */
172
+ private resolveDefaultKey;
109
173
  /**
110
174
  * @internal Whether a cache read/write is allowed for the current request:
111
175
  * the scope is enabled AND its `condition` (if any) returns true. "read"
@@ -205,6 +269,21 @@ export declare class CacheScope {
205
269
  */
206
270
  cacheRoute(pathname: string, params: Record<string, string>, segments: ResolvedSegment[], isIntercept?: boolean): Promise<void>;
207
271
  }
272
+ /**
273
+ * @internal The request partition of a ppr route's shell: with a route
274
+ * cache() scope, its CacheScope.resolvePartition (the chain's `key()`
275
+ * results, composed, plus a keyGenerator's contribution for a scope without
276
+ * its own `key()` (#970)); with none, the app store's keyGenerator result
277
+ * given the document default key. Undefined (synchronously, with no work)
278
+ * when neither applies: the shell key stays host + path + filtered search.
279
+ * Resolves to null when the partition is the default key itself (a
280
+ * keyGenerator that returns it unchanged): that partitions nothing, so the
281
+ * shell stays unpartitioned and keeps its build shell. The shell's key, its
282
+ * read and capture, and partial replay all use it, so a visitor is only
283
+ * served its own partition. A rejected promise is a failed key resolution:
284
+ * the caller serves no shell.
285
+ */
286
+ export declare function resolveShellPartition(routeScope: CacheScope | null | undefined, appStore: SegmentCacheStore | null | undefined, pathname: string, params: Record<string, string> | undefined): Promise<string | null> | undefined;
208
287
  /**
209
288
  * Create a cache scope from entry's cache config. `shortCode` is the cache()
210
289
  * entry's: a scope nested in an enabled parent keeps the parent's boundary
@@ -221,24 +300,29 @@ type ShellImplicitCacheMarker = NonNullable<RequestContext["_shellImplicitCache"
221
300
  * {@link resolveShellImplicitCacheScope} (routes that derived no scope) and
222
301
  * the explicit-scope composition sites (capture doc record in the cache-store
223
302
  * middleware, seeded replay fallback in withCacheLookup) so both ends of the
224
- * shell contract resolve the SAME canonical document key.
303
+ * shell contract resolve the SAME canonical document key. A document HIT
304
+ * tail's marker carries the entry's own key (`fixedDocKey`), which wins over
305
+ * key resolution.
225
306
  */
226
307
  export declare function createShellImplicitDocScope(marker: ShellImplicitCacheMarker): CacheScope;
227
308
  /**
228
- * Shell fast path: when the route tree derived NO cache scope and the current
229
- * request context carries the `_shellImplicitCache` marker (a shell capture,
230
- * an eligible HIT tail, or a normal partial navigation replay), substitute an
231
- * implicit doc-level scope so withCacheLookup/withCacheStore treat the WHOLE
232
- * matched route as a cache() boundary — the shell entry IS a cache() of the
233
- * handler layer, with loaders as the live carve-outs
234
- * (resolveFreshLoadersAndYield).
309
+ * The doc record scope: when the current request context carries the
310
+ * `_shellImplicitCache` marker (a shell capture, a document HIT tail, or a
311
+ * normal partial navigation replay), substitute an implicit doc-level scope
312
+ * so withCacheLookup/withCacheStore treat the WHOLE matched route as a
313
+ * cache() boundary — the shell entry IS a cache() of the handler layer, with
314
+ * loaders as the live carve-outs (resolveFreshLoadersAndYield).
235
315
  *
236
- * An existing scope — including an explicit cache(false) opt-out — always
237
- * wins HERE: the consumer's cache() semantics (their ttl/swr/store/condition)
238
- * are never overridden, and cache(false) keeps the tail on the full handler
239
- * re-run path. On the navigation-replay serve path the marker still composes
240
- * with an explicit scope downstream (withCacheLookup's seeded fallback after
241
- * an explicit-tier miss) — see `onExplicitHit` on `_shellImplicitCache`.
316
+ * A document HIT tail (`docTail`) always gets the implicit scope: it replays
317
+ * the shell's own record and never consults a route-derived scope, whose
318
+ * opt-outs the serve gate evaluated before the commit (shellServePlan). Every
319
+ * other marker leaves an existing scope — including an explicit cache(false)
320
+ * — in place: the consumer's cache() semantics (their ttl/swr/store/condition)
321
+ * are never overridden. On the navigation-replay serve path the marker still
322
+ * composes with an explicit scope downstream (withCacheLookup's seeded
323
+ * fallback after an explicit-tier miss) — see `onExplicitHit` on
324
+ * `_shellImplicitCache`; during a capture, recordShellCaptureDocRecord
325
+ * (cache-store.ts) writes the record for such a scope.
242
326
  */
243
327
  export declare function resolveShellImplicitCacheScope(scope: CacheScope | null): CacheScope | null;
244
328
  export {};
@@ -10,6 +10,54 @@
10
10
  * profile/DSL tags before storing.
11
11
  */
12
12
  import { type RequestContext } from "../server/request-context.js";
13
+ import { type LoaderIdentityRead } from "../server/context.js";
14
+ /**
15
+ * Arm the record tag owners for `ctx`: its match resolved a cache scope, so
16
+ * it can write a route cache() record (match-api.ts). A capture's or
17
+ * background re-render's derived context inherits the flag.
18
+ */
19
+ export declare function armRecordTagOwners(ctx?: RequestContext | undefined): void;
20
+ /** Whether `ctx` records tag owners (see armRecordTagOwners). */
21
+ export declare function recordTagOwnersArmed(ctx?: RequestContext | undefined): boolean;
22
+ /**
23
+ * Call `fn(arg)` inside segment `segmentId`'s tag scope: tags recorded while
24
+ * it or its async continuations run (outside a loader body) are owned by that
25
+ * segment. Unarmed, it is a plain call.
26
+ */
27
+ export declare function runInSegmentTagScope<R, A = undefined>(segmentId: string, fn: (arg: A) => R, arg?: A): R;
28
+ /**
29
+ * Record that the current owner reads loader `loaderId`'s value (ctx.use),
30
+ * so the loader's tags follow the reader onto a record. The DSL funnel
31
+ * starting a loader (resolveLoaderData, inside the DSL loader scope but no
32
+ * loader body) is not a reader.
33
+ */
34
+ export declare function linkLoaderTags(loaderId: string, ctx?: RequestContext | undefined): void;
35
+ /**
36
+ * Record `tags` as loader `loaderId`'s own, read outside its body (its
37
+ * cache() config tags, or a loader-cache HIT's stored tags):
38
+ * recordRequestTags with an explicit owner.
39
+ */
40
+ export declare function recordLoaderTags(loaderId: string, tags: Iterable<string> | undefined, ctx?: RequestContext | undefined): void;
41
+ /**
42
+ * The tags segment `segmentId`'s content recorded in `ctx`: its own, plus
43
+ * those of every loader it consumed, transitively.
44
+ */
45
+ export declare function getSegmentTags(ctx: RequestContext, segmentId: string): ReadonlySet<string>;
46
+ /**
47
+ * Record `tags` onto each of `segmentIds` (a route cache() HIT replaying
48
+ * those segments): a record written from the replay, such as a shell
49
+ * capture's doc record (match-middleware/cache-store.ts), then carries them
50
+ * as a fresh render's record would. Unarmed, a no-op.
51
+ */
52
+ export declare function recordSegmentTags(segmentIds: readonly string[], tags: Iterable<string> | undefined, ctx?: RequestContext | undefined): void;
53
+ /**
54
+ * The owner of the loaders a shell capture bakes (loader-cache.ts bake
55
+ * lane): their data is shell material that no handler reads, so their tags
56
+ * reach the shell through this owner (shell-capture.ts), not a record.
57
+ */
58
+ export declare const SHELL_BAKE_TAG_OWNER: string;
59
+ /** Link loader `loaderId`'s tags to `ownerId` (see SHELL_BAKE_TAG_OWNER). */
60
+ export declare function linkLoaderTagsTo(ownerId: string, loaderId: string, ctx?: RequestContext | undefined): void;
13
61
  export declare function normalizeTag(tag: string): string | null;
14
62
  export declare function normalizeTags(tags: Iterable<string>): string[];
15
63
  /**
@@ -32,14 +80,15 @@ export declare function normalizeTags(tags: Iterable<string>): string[];
32
80
  * cache" in its tree. This is PPR's DERIVATIVE invalidation: PPR is
33
81
  * execution-PRESERVING (everything still runs underneath; only document bytes
34
82
  * are shortcut), so its tags ride this existing instrument rather than a
35
- * first-class ppr key/tag API. The shell-expiry invariant holds by
36
- * construction — baked ⇒ evicts (bake-lane loaders execute during capture and
37
- * record here), hole ⇒ fresh (masked loaders behind a renderable loading()
38
- * never execute during capture, so nothing under a hole can tag the shell).
83
+ * first-class ppr key/tag API. Whatever renders into the shell records here,
84
+ * at capture or on the route's cache() record the capture replays. A masked
85
+ * live-lane loader never executes at capture, so its own tags reach a shell
86
+ * only through a handler that reads it (ctx.use).
39
87
  *
40
- * Inside a cache() DSL segment the render-callable form records at the DOCUMENT
41
- * level, not the segment (only the "use cache" runtime enters the tag scope) — a
42
- * documented semantic, not a filtered one. An empty/whitespace-only tag is
88
+ * Inside a route cache() boundary the render-callable form also tags that
89
+ * route's cache() record, which re-records it on every HIT (#957). A loader's
90
+ * tags reach the record only when a handler consumes its value (ctx.use);
91
+ * see runInSegmentTagScope / linkLoaderTags. An empty/whitespace-only tag is
43
92
  * dropped in both forms (the render-callable form silently, via normalizeTags in
44
93
  * recordRequestTags; the scope form with a dev warning).
45
94
  *
@@ -63,6 +112,42 @@ export declare function normalizeTags(tags: Iterable<string>): string[];
63
112
  */
64
113
  export declare function cacheTag(...tags: string[]): void;
65
114
  export declare function recordRequestTags(tags: Iterable<string> | undefined, ctx?: RequestContext | undefined): void;
115
+ /**
116
+ * Arm the per-execution loader tag sets for `ctx`: its match resolved a
117
+ * loader bound with its own cache() (match-api.ts, bindsLoaderCache), or such
118
+ * a binding started (loader-cache.ts). A loader-cache MISS captures its own
119
+ * execution and encodes either way.
120
+ */
121
+ export declare function armLoaderTagSets(ctx?: RequestContext | undefined): void;
122
+ /** Whether `ctx` records per-execution loader tag sets (armLoaderTagSets). */
123
+ export declare function loaderTagSetsArmed(ctx: RequestContext | undefined): boolean;
124
+ /**
125
+ * Call `fn`, adding every tag recorded in its async chain
126
+ * (recordRequestTags, recordLoaderTags) to `into`, until a loader body
127
+ * started inside it takes over with its own set.
128
+ */
129
+ export declare function captureRecordedTags<T>(into: Set<string>, fn: () => T): T;
130
+ /** `into` also answers for every tag `from` records. */
131
+ export declare function linkRecordedTags(into: Set<string>, from: Set<string>): void;
132
+ /** Mark a loader's value promise as carrying `tags` (see readValueTags). */
133
+ export declare function tagLoaderValue(value: object, tags: Set<string>): void;
134
+ /** A loader value's tags also answer for `tags` (tagLoaderValue). */
135
+ export declare function linkValueTags(value: object, tags: Set<string>): void;
136
+ /** The current execution reads a loader's value: it takes on its tags. */
137
+ export declare function readValueTags(value: object): void;
138
+ /**
139
+ * A loader-cache HIT restores the read its entry's MISS recorded onto the
140
+ * value's set, so an unkeyed reader of the value refuses it as it would on
141
+ * the MISS (loader-cache.ts identity mark).
142
+ */
143
+ export declare function markIdentityRead(set: Set<string>, read: LoaderIdentityRead): void;
144
+ /**
145
+ * The identity read `root`'s execution recorded, or one a set it links to
146
+ * recorded, nearest first (breadth-first, so the execution's own read wins).
147
+ */
148
+ export declare function recordedIdentityRead(root: Set<string>): LoaderIdentityRead | undefined;
149
+ /** `root`'s tags plus those of every set it links to, transitively. */
150
+ export declare function flattenRecordedTags(root: Set<string>): Set<string>;
66
151
  /**
67
152
  * Run a function within a cache tag scope. Any cacheTag() calls inside `fn`
68
153
  * accumulate into the returned Set.
@@ -1,4 +1,10 @@
1
1
  /** Encode ArrayBuffer to base64 string. */
2
2
  export declare function bufferToBase64(buffer: ArrayBuffer): string;
3
+ /** Encode bytes (a view's own region only) to a base64 string. */
4
+ export declare function bytesToBase64(bytes: Uint8Array): string;
5
+ /** Decode a base64 string to bytes. Throws on a character outside the alphabet. */
6
+ export declare function base64ToBytes(base64: string): Uint8Array;
7
+ /** The byte length `base64` decodes to (padded or not), without decoding. */
8
+ export declare function base64ByteLength(base64: string): number;
3
9
  /** Decode base64 string to ArrayBuffer. */
4
10
  export declare function base64ToBuffer(base64: string): ArrayBuffer;
@@ -110,3 +110,14 @@ export declare const EDGE_READ_TIMEOUT_MS = 20;
110
110
  * Override per store via `CFCacheStoreOptions.kvReadTimeoutMs` (<= 0 disables).
111
111
  */
112
112
  export declare const KV_READ_TIMEOUT_MS = 170;
113
+ /**
114
+ * Floor (ms) on the budget for reading a PPR shell's snapshot, the rest of a
115
+ * frame whose head and prelude were already read. A document HIT has sent
116
+ * its prelude by then and cannot render the rest without the snapshot's doc
117
+ * record, so a timed-out read degrades the HIT (the entry is replaced, the
118
+ * page reloads once into a MISS; rsc-rendering.ts degradeUnreplayableShell).
119
+ * That costs far more than waiting, so a slow body read gets at least this
120
+ * long; `kvReadTimeoutMs` above it wins, and `kvReadTimeoutMs <= 0` still
121
+ * disables the bound.
122
+ */
123
+ export declare const SHELL_SNAPSHOT_READ_MIN_TIMEOUT_MS = 1000;