@rangojs/router 0.25.0 → 0.26.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 (79) hide show
  1. package/dist/bin/rango.js +1 -1
  2. package/dist/types/cache/cf/cf-cache-store.d.ts +0 -2
  3. package/dist/types/cache/cf/cf-cache-types.d.ts +0 -16
  4. package/dist/types/cache/handle-snapshot.d.ts +2 -2
  5. package/dist/types/cache/shell-snapshot.d.ts +6 -4
  6. package/dist/types/cache/types.d.ts +8 -6
  7. package/dist/types/cache/vercel/vercel-cache-store.d.ts +1 -15
  8. package/dist/types/handles/deferred-resolution.d.ts +3 -2
  9. package/dist/types/handles/meta.d.ts +5 -0
  10. package/dist/types/prerender/build-shell-capture.d.ts +1 -2
  11. package/dist/types/prerender/writable-store.d.ts +2 -3
  12. package/dist/types/router/dev-reload-handoff.d.ts +26 -0
  13. package/dist/types/router/router-interfaces.d.ts +0 -5
  14. package/dist/types/router/router-options.d.ts +0 -20
  15. package/dist/types/router/segment-resolution/mask-nested.d.ts +5 -3
  16. package/dist/types/router-versions.d.ts +8 -6
  17. package/dist/types/rsc/shell-capture-constants.d.ts +10 -6
  18. package/dist/types/rsc/shell-capture.d.ts +25 -8
  19. package/dist/types/rsc/types.d.ts +0 -8
  20. package/dist/types/server/build-version-table.d.ts +10 -13
  21. package/dist/types/server/context.d.ts +26 -2
  22. package/dist/types/server/handle-store.d.ts +17 -6
  23. package/dist/types/server/request-context.d.ts +4 -3
  24. package/dist/types/testing/build-versions.d.ts +0 -3
  25. package/dist/types/testing/internal/seed-vars.d.ts +4 -4
  26. package/dist/types/testing/serve-shell-request.d.ts +1 -1
  27. package/dist/types/types/loader-types.d.ts +6 -1
  28. package/dist/types/urls/pattern-types.d.ts +7 -6
  29. package/dist/vite/index.js +3 -8
  30. package/package.json +1 -1
  31. package/skills/cache-guide/SKILL.md +4 -8
  32. package/skills/caching/SKILL.md +3 -5
  33. package/skills/comparison/references/framework-comparison.md +1 -1
  34. package/skills/hooks/handle-and-actions.md +6 -6
  35. package/skills/host-router/SKILL.md +1 -1
  36. package/skills/loader/SKILL.md +10 -9
  37. package/skills/ppr/SKILL.md +83 -60
  38. package/skills/prerender/SKILL.md +3 -3
  39. package/skills/router-setup/SKILL.md +0 -2
  40. package/skills/testing/render-handler.md +17 -17
  41. package/skills/testing/serve-shell-request.md +1 -1
  42. package/skills/testing/server-actions.md +16 -16
  43. package/skills/vercel/SKILL.md +1 -1
  44. package/src/cache/cf/cf-cache-store.ts +4 -7
  45. package/src/cache/cf/cf-cache-types.ts +0 -17
  46. package/src/cache/handle-snapshot.ts +2 -2
  47. package/src/cache/shell-snapshot.ts +7 -3
  48. package/src/cache/types.ts +8 -6
  49. package/src/cache/vercel/vercel-cache-store.ts +2 -18
  50. package/src/handles/deferred-resolution.ts +3 -2
  51. package/src/handles/meta.ts +5 -0
  52. package/src/prerender/build-shell-capture.ts +1 -2
  53. package/src/prerender/writable-store.ts +2 -3
  54. package/src/route-definition/dsl-helpers.ts +14 -14
  55. package/src/router/dev-reload-handoff.ts +73 -0
  56. package/src/router/manifest.ts +9 -2
  57. package/src/router/match-middleware/intercept-resolution.ts +2 -2
  58. package/src/router/router-interfaces.ts +0 -6
  59. package/src/router/router-options.ts +0 -21
  60. package/src/router/segment-resolution/loader-cache.ts +4 -3
  61. package/src/router/segment-resolution/mask-nested.ts +5 -3
  62. package/src/router-versions.ts +7 -7
  63. package/src/router.ts +19 -8
  64. package/src/rsc/handler.ts +3 -7
  65. package/src/rsc/rsc-rendering.ts +44 -12
  66. package/src/rsc/shell-capture-constants.ts +10 -6
  67. package/src/rsc/shell-capture.ts +163 -88
  68. package/src/rsc/types.ts +0 -9
  69. package/src/server/build-version-table.ts +12 -24
  70. package/src/server/context.ts +64 -2
  71. package/src/server/handle-store.ts +77 -10
  72. package/src/server/request-context.ts +4 -3
  73. package/src/testing/build-versions.ts +0 -3
  74. package/src/testing/dispatch.ts +1 -3
  75. package/src/testing/internal/seed-vars.ts +4 -4
  76. package/src/testing/serve-shell-request.ts +2 -5
  77. package/src/types/loader-types.ts +6 -1
  78. package/src/urls/pattern-types.ts +7 -6
  79. package/src/vite/discovery/shell-prerender-phase.ts +1 -5
package/dist/bin/rango.js CHANGED
@@ -1644,7 +1644,7 @@ var init_package = __esm({
1644
1644
  "package.json"() {
1645
1645
  package_default = {
1646
1646
  name: "@rangojs/router",
1647
- version: "0.25.0",
1647
+ version: "0.26.1",
1648
1648
  description: "Django-inspired RSC router with composable URL patterns",
1649
1649
  keywords: [
1650
1650
  "react",
@@ -21,8 +21,6 @@ export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<T
21
21
  private readonly namespace?;
22
22
  private readonly explicitBaseUrl?;
23
23
  private readonly waitUntil?;
24
- /** The `version` option: used for every versioned family when set. */
25
- private readonly explicitVersion?;
26
24
  private readonly edgeLookupTimeoutMs;
27
25
  private readonly edgeReadTimeoutMs;
28
26
  private readonly kvReadTimeoutMs;
@@ -278,22 +278,6 @@ export interface CFCacheStoreOptions<TEnv = unknown> {
278
278
  * (safe to set large) rather than the invalidation-latency ceiling.
279
279
  */
280
280
  tagCacheTtl?: number;
281
- /**
282
- * Cache version string override. When set, this exact value prefixes every
283
- * versioned key the store writes (`v/{version}/...`), for cached data and
284
- * stored HTML alike; changing it makes every earlier entry unreachable.
285
- *
286
- * Leave it unset for the default: the store keys with the versions of the
287
- * router serving the request. Segment entries and `"use cache"` items use
288
- * the router's data version; document responses and PPR shells use its
289
- * document version (see `createRouter({ version })`). Those are computed
290
- * from the router's built code, so a deploy that does not change the router
291
- * keeps its entries.
292
- *
293
- * Tag invalidation markers are never versioned: an invalidation applies to
294
- * the entries of every version, including one that comes back in a rollback.
295
- */
296
- version?: string;
297
281
  /**
298
282
  * Latency budget (ms) for an L1 edge cache (CF Cache API) read. A `match`
299
283
  * slower than this is abandoned and treated as a miss, so a degraded colo
@@ -73,8 +73,8 @@ export type OwnedPushDelivery = (loaderId: string) => RecordAuthority;
73
73
  * says the record supplies that loader's value too ("pin", "copies"), and is
74
74
  * a placeholder (pushPlaceholder) everywhere else: without `owned`, every
75
75
  * owner. Neither claims its loader, so the loader's own cache() HIT still
76
- * delivers (the thenable pushes a restored copy lets through, or its entry's
77
- * pushes in a placeholder's place).
76
+ * delivers (the pushes holding a promise a restored copy lets through, or its
77
+ * entry's pushes in a placeholder's place).
78
78
  */
79
79
  export declare function restoreHandles(handles: Record<string, SegmentHandleData>, handleStore: HandleStore, owners?: HandleOwners, owned?: OwnedPushDelivery): void;
80
80
  /**
@@ -199,11 +199,13 @@ export interface ShellLoaderSeedEntry {
199
199
  * by loader. A key that does not parse pins nothing.
200
200
  *
201
201
  * Deserializations run in parallel; a record that fails to decode is skipped
202
- * (that loader drifts — the pre-snapshot behavior — instead of failing the
203
- * HIT). Returns undefined when the snapshot carries no loader records, without
204
- * touching the Flight codec (kept lazy for cold paths and non-RSC configs).
202
+ * and reported to `onUndecodable`: its loader runs instead, which a document
203
+ * HIT has already committed a prelude against, so serveShellHit retires the
204
+ * entry (dc-t89). Returns undefined when the snapshot carries no loader
205
+ * records, without touching the Flight codec (kept lazy for cold paths and
206
+ * non-RSC configs).
205
207
  */
206
- export declare function buildShellLoaderSeed(snapshot: ShellSnapshotRecord[]): Promise<Map<string, ShellLoaderSeedEntry> | undefined>;
208
+ export declare function buildShellLoaderSeed(snapshot: ShellSnapshotRecord[], onUndecodable?: (key: string) => void): Promise<Map<string, ShellLoaderSeedEntry> | undefined>;
207
209
  /** The key of a snapshot's one `handles` record (ShellSnapshotHandlesValue). */
208
210
  export declare const SHELL_HANDLES_RECORD_KEY = "handles";
209
211
  /**
@@ -389,7 +389,7 @@ export interface ShellCacheEntry {
389
389
  /** React.version captured at prerender time; the read-time invalidation gate. */
390
390
  reactVersion: string;
391
391
  /**
392
- * Build version captured at prerender time: the RSC handler's `version`,
392
+ * Build version captured at prerender time: the RSC handler's version,
393
393
  * which is the router's DOCUMENT version (router-versions.ts) — a hash of
394
394
  * its server code and the client asset names by default, a stamp bumped on
395
395
  * dev RSC-module edits. The second read-time gate: a persistent shared
@@ -625,11 +625,13 @@ export interface ShellSnapshotLoaderValue {
625
625
  */
626
626
  holes: 0 | 1;
627
627
  /**
628
- * 1 when the capture saw a loader push it could not record (a deferred
629
- * push, one with masked nested promises, or one made outside any loader
630
- * body): a HIT then still runs the loader body in the background
631
- * (pin-first) so those pushes reach the page. 0: a hole-free record is
632
- * served from the pin alone and its body does not run on a HIT.
628
+ * 1 when the capture saw a loader push it could not record (one whose
629
+ * settled value holds masked nested promises, one a live-lane loader made,
630
+ * or one made outside any loader body): a HIT then still runs the loader
631
+ * body in the background (pin-first) so those pushes reach the page. A
632
+ * pushed promise that settles thenable-free is recorded (#1182).
633
+ * 0: a hole-free record is served from the pin alone and its body does not
634
+ * run on a HIT.
633
635
  *
634
636
  * Both bits are always written. A record stored before they existed (v0.17)
635
637
  * reads each missing bit as 1 (buildShellLoaderSeed): its snapshot lacks
@@ -102,7 +102,7 @@ export interface VercelCacheStoreOptions<TEnv = unknown> {
102
102
  *
103
103
  * Vercel does not reconcile entries between deployments; the store does it
104
104
  * through its keys, which carry the serving router's cache versions (see
105
- * `version`). So a deploy that changes a router's code stops reading that
105
+ * `getCacheVersions`). So a deploy that changes a router's code stops reading that
106
106
  * router's old entries, and a deploy that does not keeps them. A namespace
107
107
  * that changes per deployment (`getCache({ namespace: VERCEL_DEPLOYMENT_ID })`)
108
108
  * still works and clears the cache on every deploy, which is what you want
@@ -128,18 +128,6 @@ export interface VercelCacheStoreOptions<TEnv = unknown> {
128
128
  * segmentation). Receives the request context and the default key.
129
129
  */
130
130
  keyGenerator?: (ctx: RequestContext<TEnv>, defaultKey: string) => string | Promise<string>;
131
- /**
132
- * Version override, folded into every versioned key as `v/{version}/...`.
133
- * When set, this exact value is used for cached data and stored HTML alike;
134
- * changing it makes everything this store wrote before unreachable.
135
- *
136
- * Leave it unset for the default: the store keys with the versions of the
137
- * router serving the request, as CFCacheStore does. Segments and `"use
138
- * cache"` items use the router's data version; responses and PPR shells use
139
- * its document version (see `createRouter({ version })`). Tag markers are
140
- * never versioned, so an invalidation applies to every version.
141
- */
142
- version?: string;
143
131
  /**
144
132
  * Max serialized entry size in bytes before a write is skipped. Defaults to
145
133
  * VERCEL_MAX_ITEM_BYTES (2 MB - the platform's hard cap).
@@ -238,8 +226,6 @@ export declare class VercelCacheStore<TEnv = unknown> implements SegmentCacheSto
238
226
  readonly keyGenerator?: (ctx: RequestContext<TEnv>, defaultKey: string) => string | Promise<string>;
239
227
  private readonly cache;
240
228
  private readonly waitUntil?;
241
- /** The `version` option: used for every versioned family when set. */
242
- private readonly explicitVersion?;
243
229
  private readonly maxItemBytes;
244
230
  private readonly memo;
245
231
  private readonly handleMemos;
@@ -48,8 +48,9 @@ export declare function resolveDeferredHandleValues(data: HandleData): Promise<H
48
48
  * so a push the record leaves out must not be in the prelude either: it
49
49
  * rendered an element no HIT's hydration data matched (issue #1035). Which
50
50
  * pushes a record keeps is the capture funnel's decision (shell-capture.ts
51
- * deriveShellCaptureContext: today it leaves out a deferred loader push and
52
- * one holding a masked promise), not this function's.
51
+ * deriveShellCaptureContext: it leaves out a loader push whose settled value
52
+ * holds a masked promise, and one a live-lane loader made), not this
53
+ * function's.
53
54
  */
54
55
  export declare function resolvedHandleStream(handleStore: HandleStore, recordedOnly?: boolean): AsyncGenerator<HandleData, void, unknown>;
55
56
  /**
@@ -39,5 +39,10 @@ import type { MetaDescriptor } from "../router/types.js";
39
39
  * (resolve-by-default), so they participate in deduplication and `%s`
40
40
  * title-templating identically to synchronous descriptors. A descriptor that
41
41
  * resolves to `null`/`undefined` (or rejects) is dropped.
42
+ *
43
+ * On a ppr route, a descriptor a handler or an `ssr: false` loader pushes,
44
+ * promise or not, is in the shell's `<head>` on every HIT (#1182). One a
45
+ * loader without `ssr: false` pushes is live: on a HIT it arrives after
46
+ * hydration.
42
47
  */
43
48
  export declare const Meta: Handle<MetaDescriptor, MetaDescriptor[]>;
@@ -68,8 +68,7 @@ export interface BuildShellCaptureOptions {
68
68
  /** Build-time env bindings (rango plugin buildEnv), if configured. */
69
69
  buildEnv?: unknown;
70
70
  /**
71
- * The versions the SHIPPED build serves this router with (the build table,
72
- * or the router's consumer-set version) — NOT the temp server's own
71
+ * The versions the SHIPPED build serves this router with (the build table) — NOT the temp server's own
73
72
  * version-plugin stamp. The serve-side isValidShellHit gate compares
74
73
  * entry.buildVersion against the running worker's ctx.version, which is the
75
74
  * document version; stamping the temp server's would make every build entry
@@ -19,9 +19,8 @@ import type { PrerenderEntry } from "./store.js";
19
19
  export interface PrerenderKey {
20
20
  routerId: string;
21
21
  /**
22
- * The owning router's cache version (its data version, or
23
- * `createRouter({ version })`). Opaque: never set or read by app code; a
24
- * store keys off it as given.
22
+ * The owning router's cache version (its data version). Opaque: never set
23
+ * or read by app code; a store keys off it as given.
25
24
  */
26
25
  version: string;
27
26
  routeName: string;
@@ -0,0 +1,26 @@
1
+ import type { RangoInternal } from "./router-interfaces.js";
2
+ /** The `import.meta.hot` subset this reads. */
3
+ interface RunnerReloadEvents {
4
+ on(event: "vite:beforeFullReload", listener: () => void): void;
5
+ }
6
+ /**
7
+ * Mark this module copy dropped on the runner's next full reload.
8
+ * @internal
9
+ */
10
+ export declare function watchRunnerReloads(events: RunnerReloadEvents | undefined): void;
11
+ /**
12
+ * Whether a runner full reload dropped this module copy.
13
+ * @internal
14
+ */
15
+ export declare function droppedByRunnerReload(): boolean;
16
+ /**
17
+ * The router the reload re-created for `routerId`, or undefined when none
18
+ * registered within DEV_RECREATED_ROUTER_WAIT_MS. Importing `sourceFile`
19
+ * evaluates it when nothing else is. While the reload's re-import is still
20
+ * evaluating it, the runner returns the unfinished module (it is in this
21
+ * copy's import call stack, `cachedRequest` in vite's module-runner), so this
22
+ * waits for the fresh registry to receive the router.
23
+ * @internal
24
+ */
25
+ export declare function routerAfterRunnerReload(routerId: string, sourceFile: string | undefined): Promise<RangoInternal<any, any> | undefined>;
26
+ export {};
@@ -378,11 +378,6 @@ export interface RangoInternal<TEnv = any, TRoutes extends Record<string, unknow
378
378
  * Nonce provider for CSP
379
379
  */
380
380
  readonly nonce?: NonceProvider<TEnv>;
381
- /**
382
- * The consumer-set version (createRouter({ version })), used for both of the
383
- * router's versions. Undefined when the router uses its build versions.
384
- */
385
- readonly version?: string;
386
381
  /**
387
382
  * URL patterns reference for build-time manifest generation
388
383
  */
@@ -489,26 +489,6 @@ export interface RangoOptions<TEnv = any> {
489
489
  * ```
490
490
  */
491
491
  nonce?: NonceProvider<TEnv>;
492
- /**
493
- * Version override. When set, this exact value is used for both of the
494
- * router's versions: it keys the router's cached data (segments, `"use
495
- * cache"` values, loader data) and its stored HTML (PPR shells,
496
- * document-cache responses), is sent in payload metadata, and is what the
497
- * browser's `_rsc_v` is compared with (a mismatch reloads the tab).
498
- *
499
- * Leave it unset for the default: rango computes two versions per router at
500
- * build time, a data version from the router's server code and a document
501
- * version that also covers the client assets. The same code builds to the
502
- * same versions, so a deploy that does not change this router keeps its
503
- * cache. In dev both are a stamp bumped on every RSC module edit.
504
- *
505
- * Set it to clear this router's cache on demand (change the value), or to
506
- * tie the cache to your own release id (a per-deploy value clears the cache
507
- * on every deploy).
508
- *
509
- * @default the router's build versions
510
- */
511
- version?: string;
512
492
  /**
513
493
  * TTL (in seconds) for the client-side in-memory prefetch cache.
514
494
  *
@@ -59,9 +59,11 @@ export declare function settleNestedThenables(value: unknown): Promise<void>;
59
59
  /**
60
60
  * True when `value` is a thenable or holds one where the capture's loader
61
61
  * push funnel masks it (maskNestedContainerThenables): through plain data
62
- * objects, arrays and React element props. Such a push stays out of a shell's
63
- * record and reaches a HIT only from a run of its loader there, so a HIT
64
- * keeps it; a push without one is the record's (HandleStore.pushRestored).
62
+ * objects, arrays and React element props. A settled push that holds one
63
+ * stays out of a shell's record and reaches a HIT only from a run of its
64
+ * loader there, so a HIT keeps it; a push without one is the record's
65
+ * (HandleStore.pushRestored). A top-level thenable is judged by the value it
66
+ * settles to, on both sides (#1182).
65
67
  */
66
68
  export declare function holdsThenable(value: unknown): boolean;
67
69
  /**
@@ -27,13 +27,15 @@ export interface RouterVersions {
27
27
  export type RouterVersionsTable = Readonly<Record<string, readonly [data: string, document: string]>>;
28
28
  /** Table key of the whole-build versions. Not a legal `$$id` (8 hex chars). */
29
29
  export declare const DEFAULT_ROUTER_VERSIONS_KEY = "*";
30
- /** One value for both versions: a consumer-set `version`, or the dev stamp. */
30
+ /**
31
+ * One value for both versions: the dev stamp, the version a build-time shell
32
+ * capture is asked for, or a test's seed.
33
+ */
31
34
  export declare function uniformVersions(version: string): RouterVersions;
32
35
  /**
33
- * The versions a router serves with under `table`: a consumer-set `version`
34
- * for both; else the router's own entry; else the whole-build pair, for a
35
- * router the table does not list. `undefined` when there is no table (dev,
36
- * tests) or it has neither entry. One function for the request handler and
36
+ * The versions a router serves with under `table`: the router's own entry,
37
+ * else the whole-build pair, for a router the table does not list.
38
+ * `undefined` when there is no table (dev, tests) or it has neither entry. One function for the request handler and
37
39
  * the build's shell capture, which must stamp what the handler will check.
38
40
  */
39
- export declare function resolveVersionsFrom(table: RouterVersionsTable | undefined, routerId: string | undefined, override: string | undefined): RouterVersions | undefined;
41
+ export declare function resolveVersionsFrom(table: RouterVersionsTable | undefined, routerId: string | undefined): RouterVersions | undefined;
@@ -148,13 +148,17 @@ export declare const SHELL_CAPTURE_TASK_HARD_CAP_MS = 25000;
148
148
  * (ShellCaptureDebugEvent.refusal, `caches.refusal` on a `router.prerender()`
149
149
  * result): `identity` (a request-scoped read: cookies(), headers(), the
150
150
  * theme, a `{ cache: false }` variable), `dynamic` (`ctx.dynamic()`),
151
- * `loader` (a `{ ssr: false }` loader rejected, or settled with
152
- * redirect()/notFound()), `no-record` (no doc record: the route's cache()
153
- * refused the write, or the handler output failed), `handles` (a pushed
151
+ * `loader` (a `{ ssr: false }` loader rejected, settled with
152
+ * redirect()/notFound(), or a promise it pushed rejected),
153
+ * `no-record` (no doc record: the route's cache() refused the write, or the
154
+ * handler output failed), `handles` (a pushed
154
155
  * handle value did not finish encoding), `size` (over the store's value
155
- * limit), `record-expired` (the route cache() record the capture wrote ran
156
- * out first), `invalidated` (a tag of the shell was invalidated after the
157
- * capture started), `uncacheable` (the store cannot hold the entry).
156
+ * limit, or `ssr: false` loader pins over `ppr.maxSnapshotBytes`: a
157
+ * navigation-only entry was stored in place of the document shell),
158
+ * `record-expired` (the route cache() record
159
+ * the capture wrote ran out first), `invalidated` (a tag of the shell was
160
+ * invalidated after the capture started), `uncacheable` (the store cannot
161
+ * hold the entry).
158
162
  */
159
163
  export type ShellCaptureRefusal = "identity" | "dynamic" | "loader" | "no-record" | "handles" | "size" | "record-expired" | "invalidated" | "uncacheable";
160
164
  /**
@@ -105,9 +105,8 @@ export declare const DEFAULT_SHELL_ENTRY_MAX_BYTES: number;
105
105
  * while still fitting any sane pinned-ring payload. Applied ONLY in
106
106
  * captureAndStoreShell (the single defaulting site — resolvePprConfig passes
107
107
  * the option through undefaulted), so every producer and direct caller gets
108
- * the same policy. The doc record is exempt; over the cap the other records
109
- * are dropped (shell still stored and replayed; the pins' loaders read the
110
- * live store — see PartialPrerenderProps.maxSnapshotBytes).
108
+ * the same policy. The doc record is exempt; over the cap only the
109
+ * navigation half is stored (see PartialPrerenderProps.maxSnapshotBytes).
111
110
  */
112
111
  export declare const DEFAULT_PPR_MAX_SNAPSHOT_BYTES: number;
113
112
  /**
@@ -165,7 +164,11 @@ export interface ShellCaptureDebugEvent {
165
164
  preludeBytes?: number;
166
165
  /** Serialized snapshot size in UTF-8 bytes. Absent when nothing was recorded. */
167
166
  snapshotBytes?: number;
168
- /** True when the snapshot exceeded maxSnapshotBytes and was dropped. */
167
+ /**
168
+ * True when the loader pins exceeded maxSnapshotBytes: no document shell
169
+ * was stored, only a navigation-only entry (the doc record, or a tombstone
170
+ * for a prerender-served capture). The outcome is `refused`, refusal `size`.
171
+ */
169
172
  snapshotSkipped?: boolean;
170
173
  /** A bake-lane loader settled into a shell that uses TTL/SWR-only invalidation. */
171
174
  untaggedBake?: true;
@@ -315,8 +318,8 @@ export interface ShellCaptureDescriptor {
315
318
  captureTimeout?: number;
316
319
  store?: SegmentCacheStore<any>;
317
320
  /**
318
- * Cap (serialized UTF-8 bytes) on the entry's capture data snapshot; over it
319
- * the snapshot is skipped and the shell stored without it (reported once per
321
+ * Cap (serialized UTF-8 bytes) on the entry's loader pins; over it only the
322
+ * navigation half is stored and the attempt refuses (reported once per
320
323
  * key). Absent = DEFAULT_PPR_MAX_SNAPSHOT_BYTES, applied in
321
324
  * captureAndStoreShell — the single defaulting site.
322
325
  */
@@ -502,8 +505,9 @@ export type CaptureRecordOutcome = {
502
505
  * component showed one value in the prelude and another on every HIT.
503
506
  *
504
507
  * Order, all bounded by the one capture deadline (`ppr.captureTimeout`):
505
- * top-level handle promises, the handler pushes' nested promises, and the
506
- * bake-lane loader containers settle first (the record encodes the handles
508
+ * top-level handle promises (a bake-lane loader's pushed promises among
509
+ * them, #1182), the handler pushes' nested promises, and the bake-lane
510
+ * loader containers settle first (the record encodes the handles
507
511
  * a HIT restores, and its handle encode has its own 5s timeout that must
508
512
  * never be the one that fires); then the capture's onResponse callbacks fire
509
513
  * with a synthetic 200 (the implicit doc scope's cacheRoute, an explicit
@@ -529,6 +533,19 @@ export interface CaptureContextDerivation {
529
533
  * record encodes its handles, so that encode never races a pending value.
530
534
  */
531
535
  handlerPushesSettled: () => Promise<void>;
536
+ /**
537
+ * Every promise a bake-lane loader body pushed (#1182), in push order.
538
+ * The record-first step waits for them (they are in the recorded cut) and
539
+ * names the loader and the handle of one that rejects or misses the
540
+ * deadline.
541
+ */
542
+ loaderPromisePushes: readonly LoaderPromisePush[];
543
+ }
544
+ /** A promise a bake-lane loader pushed at capture, owned by that loader. */
545
+ export interface LoaderPromisePush {
546
+ loaderId: string;
547
+ handleName: string;
548
+ state: "pending" | "fulfilled" | "rejected";
532
549
  }
533
550
  /**
534
551
  * Derive the capture request context from a base context. Producer A passes
@@ -333,14 +333,6 @@ export interface CreateRSCHandlerOptions<TEnv = unknown, TRoutes extends Record<
333
333
  * ```
334
334
  */
335
335
  cache?: HandlerCacheConfig | ((env: TEnv) => HandlerCacheConfig);
336
- /**
337
- * Version override for this handler. Same meaning as
338
- * `createRouter({ version })`, which it takes precedence over: this exact
339
- * value is used for both of the router's versions.
340
- *
341
- * @default createRouter({ version }), else the router's build versions
342
- */
343
- version?: string;
344
336
  /**
345
337
  * Nonce provider for Content Security Policy (CSP).
346
338
  *
@@ -2,21 +2,18 @@ import { type RouterVersions, type RouterVersionsTable } from "../router-version
2
2
  /**
3
3
  * The versions a router serves with, in order:
4
4
  *
5
- * 1. `override`, a consumer-set `version` (createRouter or createRSCHandler):
6
- * that exact value for both.
7
- * 2. The router's entry in the build table, else the whole-build entry.
8
- * 3. `VERSION` for both: the dev stamp, bumped on every RSC module edit.
5
+ * 1. The router's entry in the build table, else the whole-build entry.
6
+ * 2. `VERSION` for both: the dev stamp, bumped on every RSC module edit.
9
7
  */
10
- export declare function resolveRouterVersions(routerId: string | undefined, override?: string): RouterVersions;
8
+ export declare function resolveRouterVersions(routerId: string | undefined): RouterVersions;
11
9
  /**
12
10
  * The version an on-demand prerender overlay key carries: the router's data
13
11
  * version, because the overlay stores segment data, as the segment cache does
14
12
  * (a client-only deploy keeps both). The write side (`router.prerender`,
15
- * router.ts) and the read side (rsc/handler.ts) both call this with
16
- * `createRouter({ version })`, the only override the trigger can see, so they
17
- * cannot disagree; a `createRSCHandler({ version })` does not move it.
13
+ * router.ts) and the read side (rsc/handler.ts) both call this, so they
14
+ * cannot disagree.
18
15
  */
19
- export declare function resolvePrerenderVersion(routerId: string | undefined, routerVersion: string | undefined): string;
16
+ export declare function resolvePrerenderVersion(routerId: string | undefined): string;
20
17
  /**
21
18
  * The versions of the router serving the current request, for a cache store
22
19
  * building a key. A store operation with no request context (a detached task
@@ -40,11 +37,11 @@ export declare function getCacheVersions(): RouterVersions;
40
37
  export type KeyVersion = keyof RouterVersions | null;
41
38
  /**
42
39
  * The `v/{version}/` prefix of a persistent store key, or "" for an
43
- * unversioned one. A store-level `version` is used for every versioned kind;
44
- * otherwise the version is the serving router's, read per operation because
45
- * the cache factory builds the store before the request context exists.
40
+ * unversioned one. The version is the serving router's, read per operation
41
+ * because the cache factory builds the store before the request context
42
+ * exists.
46
43
  */
47
- export declare function versionKeyPrefix(storeVersion: string | undefined, kind: KeyVersion): string;
44
+ export declare function versionKeyPrefix(kind: KeyVersion): string;
48
45
  /**
49
46
  * @internal Install a table, or restore the build's with `undefined`. A
50
47
  * request handler resolves its router's versions once, when it is created, so
@@ -97,6 +97,11 @@ export type EntryPropRender = {
97
97
  * on both DSL evaluations, whatever `ssr` did to `loading`.
98
98
  */
99
99
  loadingPrefetch?: false;
100
+ /**
101
+ * loading(fallback, { ssr: false }), on both DSL evaluations; a document
102
+ * then sets `loading` to false (applyDocumentModeLoading).
103
+ */
104
+ loadingSsr?: false;
100
105
  transition?: TransitionConfig;
101
106
  };
102
107
  /**
@@ -117,7 +122,8 @@ export type LoaderEntry = {
117
122
  * Resolved at DSL-evaluation time from ctx.isSSR — entries are cached
118
123
  * per-isSSR (router/manifest.ts cache key), so the flag is already
119
124
  * request-mode-correct when resolveLoaders (fresh.ts) reads it and never
120
- * appears on navigation-lane entries.
125
+ * appears on navigation-lane entries. Set by documentModeLoader, which
126
+ * getDocumentModeEntry also applies to a lazy include's captured ancestors.
121
127
  */
122
128
  awaitBeforeFlush?: true;
123
129
  /**
@@ -325,7 +331,8 @@ interface HelperContext extends IncludeScopePosition {
325
331
  * different routers don't clobber each other (route-map-builder.ts). */
326
332
  routerId?: string;
327
333
  metrics?: MetricsStore;
328
- /** True when rendering for SSR (document requests) */
334
+ /** True when rendering for SSR (document requests). DSL decisions on it
335
+ * go through the document-mode helpers (documentModeLoader). */
329
336
  isSSR?: boolean;
330
337
  /** URL patterns map for path() routes (route name -> pattern) */
331
338
  patterns?: Map<string, string>;
@@ -479,6 +486,23 @@ export type { HelperContext };
479
486
  * parent pointer, handler) stays shared, which is correct and cheap.
480
487
  */
481
488
  export declare function getIsolatedLazyParent(captured: EntryData | null | undefined): EntryData | null;
489
+ /** loader(Def, { ssr: false }): a document awaits it before the first flush. */
490
+ export declare function documentModeLoader(entry: LoaderEntry): LoaderEntry;
491
+ /** loading(fallback, { ssr: false }): a document renders no fallback. */
492
+ export declare function applyDocumentModeLoading(entry: EntryPropRender): void;
493
+ /**
494
+ * `entry`, its ancestors, orphan layouts and parallels as a document (isSSR)
495
+ * DSL evaluation builds them, from an evaluation without isSSR.
496
+ *
497
+ * include() captures its parent chain on router.ts routes() or
498
+ * lazy-includes.ts runExpansion, neither of which has isSSR, and loadManifest
499
+ * reuses it for every request. Handed to a document as is, every ancestor
500
+ * outside the include kept its navigation-mode decisions (#1179). Intercepts
501
+ * stay as they are: a document never resolves one.
502
+ *
503
+ * Memoized per entry, so an ancestor several includes share maps to one copy.
504
+ */
505
+ export declare function getDocumentModeEntry(entry: EntryData): EntryData;
482
506
  export declare function getParallelEntries(parallels: ParallelEntries | EntryData[] | undefined): ParallelEntryData[];
483
507
  export declare function getParallelSlotEntries(parallels: ParallelEntries | EntryData[] | undefined): Array<{
484
508
  slot: `@${string}`;
@@ -123,12 +123,14 @@ export interface HandleStore {
123
123
  */
124
124
  pushPlaceholder(handleName: string, segmentId: string, data: unknown, loaderId: string): void;
125
125
  /**
126
- * push() a record's copy of `loaderId`'s settled push that stands
127
- * (restoreHandles): the record supplies the loader's value too. From then
128
- * on this request drops every settled, thenable-free push the loader makes
129
- * (live, or replayed from a cache entry, anywhere inside its body), and
130
- * keeps the ones holding a thenable (holdsThenable: what the record could
131
- * not keep), placed among the restored copies in the loader's push order.
126
+ * push() a record's copy of `loaderId`'s push that stands (restoreHandles):
127
+ * the record supplies the loader's value too. From then on this request
128
+ * drops every thenable-free push the loader makes (live, or replayed from
129
+ * a cache entry, anywhere inside its body), and keeps the ones holding a
130
+ * thenable (holdsThenable: what the record could not keep), placed among
131
+ * the restored copies in the loader's push order. A top-level promise is
132
+ * judged by its settled value, as the capture judged it (#1182): dropped
133
+ * when it settles thenable-free or rejects, else kept once it settles.
132
134
  */
133
135
  pushRestored(handleName: string, segmentId: string, data: unknown, loaderId: string): void;
134
136
  /**
@@ -140,6 +142,15 @@ export interface HandleStore {
140
142
  * takes a push made inside a hole it awaited.
141
143
  */
142
144
  setRecordAuthority(authority: (loaderId: string) => RecordAuthority): void;
145
+ /**
146
+ * Capture only (shell-capture.ts deriveShellCaptureContext): the slot
147
+ * holding `value` becomes a DSL-loader push without an owner, so the
148
+ * record and the prelude leave it out (getDataForSegment's
149
+ * `excludeLoaderPushes`). The capture owns a bake-lane loader's promise
150
+ * when it is pushed, and calls this once the promise settled to a value
151
+ * that holds one (#1182).
152
+ */
153
+ unrecordPush(handleName: string, segmentId: string, value: unknown): void;
143
154
  /**
144
155
  * `loaderId`'s run in this request settled (resolved or rejected), its
145
156
  * pushes made (loader-resolution.ts createLoaderExecutor). Its placeholders
@@ -264,9 +264,10 @@ export interface RequestContext<TEnv = DefaultEnv, TParams = Record<string, stri
264
264
  _shellCaptureLoaderRecords?: Map<string, Promise<unknown>>;
265
265
  /**
266
266
  * @internal Set on a capture's derived context when a loader pushed a
267
- * value the record cannot keep (a deferred push, masked nested promises,
268
- * or a push outside a loader body): the loader records then carry
269
- * `runs: 1`, so a HIT still runs their bodies for those pushes.
267
+ * value the record cannot keep (masked nested promises, a pushed promise
268
+ * that settled to one, a live-lane loader's push, or a push outside a
269
+ * loader body): the loader records then carry `runs: 1`, so a HIT still
270
+ * runs their bodies for those pushes.
270
271
  */
271
272
  _shellCaptureUnrecordedLoaderPush?: true;
272
273
  /**
@@ -15,9 +15,6 @@ export interface BuildVersions extends RouterVersions {
15
15
  * Install the cache versions of a simulated build, or remove them with no
16
16
  * argument (routers serve unversioned again, the default in tests).
17
17
  *
18
- * A router that sets `createRouter({ version })` keeps that value for both
19
- * versions, exactly as in production.
20
- *
21
18
  * @example A deploy that changes the client only keeps cached data
22
19
  * ```ts
23
20
  * setBuildVersions({ data: "d1", document: "h1" });
@@ -21,10 +21,10 @@ export interface StateCookieSeed {
21
21
  */
22
22
  routerId?: string;
23
23
  /**
24
- * The app's version, as `createRouter({ version })` would set it: the
25
- * rotated value's prefix (`{version}:{timestamp}`) and the version a cache
26
- * store keys with. Defaults to `"0"` for the prefix (resolved inside
27
- * createRequestContext) and to the build versions for the store.
24
+ * The app's version: the rotated value's prefix (`{version}:{timestamp}`)
25
+ * and the request's cache versions, which a store keys with. Defaults to
26
+ * `"0"` for the prefix (resolved inside createRequestContext) and to the
27
+ * build versions for the store.
28
28
  */
29
29
  version?: string;
30
30
  }
@@ -2,7 +2,7 @@
2
2
  * serveShellRequest — one request through the production PPR serve path.
3
3
  *
4
4
  * Builds the router's request handler as `router.fetch` does
5
- * (`createRSCHandler` with the router's `nonce`, `version` and `cache`
5
+ * (`createRSCHandler` with the router's `nonce` and `cache`
6
6
  * config) and serves one GET through it, so middleware, the shell serve path
7
7
  * and a MISS's background capture all run production code, on real Flight
8
8
  * (the vendored react-server-dom serializer). The call settles every
@@ -164,7 +164,12 @@ export type LoaderFn<T, TParams = Record<string, string | undefined>, TEnv = Def
164
164
  * deterministically precedes Response construction, where the streamed
165
165
  * default only wins that race opportunistically). Under ppr it is also the
166
166
  * BAKE lane: the loader executes at shell capture and its settled return is
167
- * shell material (nested promises stay live holes) — see the ppr docs.
167
+ * shell material (nested promises stay live holes), and so are its handle
168
+ * pushes, a pushed promise or `.defer()` included: the capture awaits it and
169
+ * every HIT serves the value it settled to (#1182). A pushed promise that
170
+ * rejects, or misses `ppr.captureTimeout`, keeps the route on MISS. A value
171
+ * that must stay per request belongs to a loader without `ssr: false` — see
172
+ * the ppr docs.
168
173
  *
169
174
  * Scoped per LOADER, not per segment: an `ssr: false` loader alongside a
170
175
  * deliberately streaming sibling awaits only itself, and the sibling keeps