@rangojs/router 0.20.0 → 0.21.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 (178) hide show
  1. package/dist/bin/rango.js +26 -14
  2. package/dist/types/browser/event-controller.d.ts +32 -0
  3. package/dist/types/browser/history-state.d.ts +16 -4
  4. package/dist/types/browser/navigation-transaction.d.ts +7 -0
  5. package/dist/types/browser/rango-state.d.ts +3 -1
  6. package/dist/types/browser/react/context.d.ts +11 -0
  7. package/dist/types/browser/react/location-state-shared.d.ts +84 -10
  8. package/dist/types/browser/react/location-state.d.ts +7 -2
  9. package/dist/types/browser/react/subscribe-to-nothing.d.ts +7 -0
  10. package/dist/types/cache/cache-scope.d.ts +4 -5
  11. package/dist/types/cache/cf/cf-cache-store.d.ts +27 -6
  12. package/dist/types/cache/cf/cf-cache-types.d.ts +12 -4
  13. package/dist/types/cache/handle-snapshot.d.ts +36 -50
  14. package/dist/types/cache/index.d.ts +2 -0
  15. package/dist/types/cache/shell-snapshot.d.ts +19 -3
  16. package/dist/types/cache/types.d.ts +21 -10
  17. package/dist/types/cache/vercel/vercel-cache-store.d.ts +28 -16
  18. package/dist/types/client-urls/navigation.d.ts +7 -1
  19. package/dist/types/client-urls/optimistic-location.d.ts +10 -4
  20. package/dist/types/handles/deferred-resolution.d.ts +11 -1
  21. package/dist/types/prerender/build-shell-capture.d.ts +8 -6
  22. package/dist/types/router/match-middleware/cache-lookup.d.ts +1 -1
  23. package/dist/types/router/navigation-snapshot.d.ts +8 -1
  24. package/dist/types/router/router-interfaces.d.ts +2 -1
  25. package/dist/types/router/router-options.d.ts +17 -7
  26. package/dist/types/router/segment-resolution/loader-cache.d.ts +21 -9
  27. package/dist/types/router-versions.d.ts +39 -0
  28. package/dist/types/rsc/handler-context.d.ts +5 -0
  29. package/dist/types/rsc/shell-build-manifest.d.ts +3 -2
  30. package/dist/types/rsc/types.d.ts +4 -6
  31. package/dist/types/server/build-version-table.d.ts +44 -0
  32. package/dist/types/server/handle-store.d.ts +87 -46
  33. package/dist/types/server/request-context.d.ts +22 -8
  34. package/dist/types/testing/build-versions.d.ts +32 -0
  35. package/dist/types/testing/dom.entry.d.ts +1 -1
  36. package/dist/types/testing/flight.entry.d.ts +1 -1
  37. package/dist/types/testing/index.d.ts +2 -0
  38. package/dist/types/testing/internal/seed-vars.d.ts +7 -2
  39. package/dist/types/testing/location-state-key.d.ts +5 -1
  40. package/dist/types/testing/render-route.d.ts +77 -6
  41. package/dist/types/testing/serve-shell-request.d.ts +21 -0
  42. package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
  43. package/dist/types/types/handler-context.d.ts +8 -5
  44. package/dist/types/vite/discovery/build-versions.d.ts +393 -0
  45. package/dist/types/vite/discovery/installed-packages.d.ts +26 -0
  46. package/dist/types/vite/discovery/router-versions-phase.d.ts +52 -0
  47. package/dist/types/vite/discovery/shell-prerender-phase.d.ts +7 -17
  48. package/dist/types/vite/discovery/state.d.ts +14 -0
  49. package/dist/types/vite/encryption-key.d.ts +18 -0
  50. package/dist/types/vite/plugin-types.d.ts +27 -0
  51. package/dist/types/vite/plugins/{version-injector.d.ts → entry-bootstrap-injector.d.ts} +4 -6
  52. package/dist/types/vite/plugins/expose-ids/export-analysis.d.ts +1 -0
  53. package/dist/types/vite/plugins/expose-ids/router-transform.d.ts +14 -0
  54. package/dist/types/vite/plugins/stable-rsc-output.d.ts +51 -0
  55. package/dist/types/vite/plugins/version-plugin.d.ts +9 -11
  56. package/dist/types/vite/plugins/virtual-entries.d.ts +18 -1
  57. package/dist/types/vite/router-discovery.d.ts +6 -0
  58. package/dist/types/vite/utils/client-chunks.d.ts +2 -0
  59. package/dist/types/vite/utils/compare-strings.d.ts +7 -0
  60. package/dist/types/vite/utils/package-resolution.d.ts +8 -0
  61. package/dist/types/vite/utils/prerender-utils.d.ts +2 -0
  62. package/dist/vite/index.js +1537 -444
  63. package/package.json +1 -2
  64. package/skills/cache-guide/SKILL.md +84 -20
  65. package/skills/caching/SKILL.md +16 -5
  66. package/skills/client-urls/SKILL.md +16 -0
  67. package/skills/cloudflare/SKILL.md +2 -1
  68. package/skills/comparison/references/framework-comparison.md +16 -12
  69. package/skills/hooks/SKILL.md +3 -0
  70. package/skills/hooks/handle-and-actions.md +6 -0
  71. package/skills/hooks/navigation.md +28 -0
  72. package/skills/hooks/state.md +209 -8
  73. package/skills/hooks/urls.md +40 -1
  74. package/skills/host-router/SKILL.md +13 -0
  75. package/skills/intercept/SKILL.md +6 -0
  76. package/skills/loader/SKILL.md +19 -1
  77. package/skills/middleware/SKILL.md +2 -0
  78. package/skills/ppr/SKILL.md +119 -40
  79. package/skills/prerender/SKILL.md +7 -3
  80. package/skills/rango/SKILL.md +8 -7
  81. package/skills/router-setup/SKILL.md +15 -1
  82. package/skills/testing/SKILL.md +30 -26
  83. package/skills/testing/client-components.md +193 -8
  84. package/skills/testing/handles.md +2 -0
  85. package/skills/testing/loader.md +1 -1
  86. package/skills/testing/middleware.md +1 -1
  87. package/skills/testing/render-handler.md +18 -18
  88. package/skills/testing/serve-shell-request.md +63 -14
  89. package/skills/testing/server-actions.md +17 -17
  90. package/skills/testing/server-tree.md +1 -1
  91. package/skills/typesafety/generated-files-and-cli.md +1 -1
  92. package/skills/use-cache/SKILL.md +16 -5
  93. package/skills/vercel/SKILL.md +3 -3
  94. package/src/bin/rango.ts +1 -1
  95. package/src/browser/event-controller.ts +75 -2
  96. package/src/browser/history-state.ts +39 -17
  97. package/src/browser/navigation-bridge.ts +41 -18
  98. package/src/browser/navigation-transaction.ts +15 -10
  99. package/src/browser/partial-update.ts +10 -2
  100. package/src/browser/rango-state.ts +3 -1
  101. package/src/browser/react/Link.tsx +2 -3
  102. package/src/browser/react/NavigationProvider.tsx +25 -1
  103. package/src/browser/react/context.ts +13 -0
  104. package/src/browser/react/location-state-shared.ts +200 -34
  105. package/src/browser/react/location-state.ts +40 -75
  106. package/src/browser/react/subscribe-to-nothing.ts +7 -0
  107. package/src/browser/react/use-handle.ts +20 -1
  108. package/src/browser/rsc-router.tsx +33 -11
  109. package/src/browser/server-action-bridge.ts +17 -1
  110. package/src/cache/cache-scope.ts +18 -8
  111. package/src/cache/cf/cf-cache-store.ts +75 -55
  112. package/src/cache/cf/cf-cache-types.ts +12 -4
  113. package/src/cache/handle-snapshot.ts +69 -87
  114. package/src/cache/index.ts +4 -0
  115. package/src/cache/shell-snapshot.ts +35 -5
  116. package/src/cache/types.ts +21 -10
  117. package/src/cache/vercel/vercel-cache-store.ts +48 -20
  118. package/src/client-urls/client-root.tsx +1 -0
  119. package/src/client-urls/navigation.ts +10 -0
  120. package/src/client-urls/optimistic-location.ts +10 -4
  121. package/src/handles/deferred-resolution.ts +31 -0
  122. package/src/prerender/build-shell-capture.ts +11 -9
  123. package/src/router/manifest.ts +2 -0
  124. package/src/router/match-middleware/cache-lookup.ts +11 -18
  125. package/src/router/navigation-snapshot.ts +25 -6
  126. package/src/router/request-classification.ts +15 -12
  127. package/src/router/router-interfaces.ts +2 -1
  128. package/src/router/router-options.ts +17 -7
  129. package/src/router/segment-resolution/loader-cache.ts +95 -60
  130. package/src/router-versions.ts +60 -0
  131. package/src/rsc/full-payload.ts +6 -2
  132. package/src/rsc/handler-context.ts +5 -0
  133. package/src/rsc/handler.ts +14 -4
  134. package/src/rsc/rsc-rendering.ts +12 -1
  135. package/src/rsc/shell-build-manifest.ts +10 -7
  136. package/src/rsc/shell-capture.ts +36 -26
  137. package/src/rsc/types.ts +4 -6
  138. package/src/server/build-version-table.ts +115 -0
  139. package/src/server/handle-store.ts +344 -127
  140. package/src/server/request-context.ts +62 -11
  141. package/src/ssr/ssr-root.tsx +16 -4
  142. package/src/testing/build-versions.ts +64 -0
  143. package/src/testing/dispatch.ts +6 -0
  144. package/src/testing/dom.entry.ts +1 -0
  145. package/src/testing/flight.entry.ts +1 -0
  146. package/src/testing/index.ts +3 -0
  147. package/src/testing/internal/context.ts +2 -1
  148. package/src/testing/internal/seed-vars.ts +14 -2
  149. package/src/testing/location-state-key.ts +18 -8
  150. package/src/testing/render-handler.ts +2 -1
  151. package/src/testing/render-route.tsx +277 -43
  152. package/src/testing/serve-shell-request.ts +80 -11
  153. package/src/testing/vitest-stubs/version.ts +4 -1
  154. package/src/types/handler-context.ts +8 -5
  155. package/src/vite/discovery/build-versions.ts +1281 -0
  156. package/src/vite/discovery/installed-packages.ts +159 -0
  157. package/src/vite/discovery/prerender-collection.ts +38 -1
  158. package/src/vite/discovery/router-versions-phase.ts +336 -0
  159. package/src/vite/discovery/shell-prerender-phase.ts +28 -38
  160. package/src/vite/discovery/state.ts +19 -0
  161. package/src/vite/discovery/virtual-module-codegen.ts +7 -2
  162. package/src/vite/encryption-key.ts +101 -9
  163. package/src/vite/plugin-types.ts +28 -0
  164. package/src/vite/plugins/{version-injector.ts → entry-bootstrap-injector.ts} +7 -22
  165. package/src/vite/plugins/expose-ids/export-analysis.ts +1 -1
  166. package/src/vite/plugins/expose-ids/router-transform.ts +36 -15
  167. package/src/vite/plugins/expose-internal-ids.ts +12 -3
  168. package/src/vite/plugins/stable-rsc-output.ts +147 -0
  169. package/src/vite/plugins/version-plugin.ts +19 -24
  170. package/src/vite/plugins/version.d.ts +17 -2
  171. package/src/vite/plugins/virtual-entries.ts +31 -5
  172. package/src/vite/rango.ts +28 -3
  173. package/src/vite/router-discovery.ts +82 -13
  174. package/src/vite/utils/client-chunks.ts +5 -1
  175. package/src/vite/utils/compare-strings.ts +9 -0
  176. package/src/vite/utils/package-resolution.ts +9 -0
  177. package/src/vite/utils/prerender-utils.ts +4 -1
  178. package/src/vite/utils/shared-utils.ts +1 -1
@@ -18,7 +18,9 @@
18
18
  * the capture's copy of a value: it is dynamic or has its own cache. A
19
19
  * bake-lane loader body that runs on a HIT reads the store too; its pinned
20
20
  * container paths stay the pin's (overlayLoaderContainer), and its settled
21
- * pushes stay the record's (HandleStore.pushRestored).
21
+ * pushes stay the record's (HandleStore.pushRestored). The two go together:
22
+ * a loader whose pin the entry lost runs fresh, and the record's copies of
23
+ * its pushes give way to that run's (restoreHandles).
22
24
  */
23
25
  import type { SegmentCacheStore, CacheGetResult, CacheItemResult, CacheItemOptions, CachedEntryData, ShellCacheEntry, ShellSnapshotRecord, CacheReadError } from "./types.js";
24
26
  /**
@@ -172,16 +174,30 @@ export interface ShellLoaderSeedEntry {
172
174
  * hole-free record: a hole-carrying one always runs.
173
175
  */
174
176
  runs: boolean;
177
+ /**
178
+ * The capture that wrote the pin recorded every settled push of the
179
+ * loader's run in the doc record (CachedEntryData.handleOwners): the pin
180
+ * carries the `runs` bit, which captures write since they record those
181
+ * pushes (v0.18). A replay then drops any other settled push of the loader
182
+ * (HandleStore RecordAuthority "pin"). False for an older pin, whose record
183
+ * may hold none and leaves them to the run.
184
+ */
185
+ complete: boolean;
175
186
  }
176
187
  /**
177
188
  * Materialize the loader-family seed from a shell snapshot for a HIT's tail
178
189
  * render: Flight-deserialize each recorded (promise-elided) bake-lane
179
- * container into a segment-key -> container Map, which serveShellHit assigns
180
- * to the tail context's `_shellLoaderSeed` for the resolveLoaderData overlay.
190
+ * container into a loader-id -> pin Map, which serveShellHit assigns to the
191
+ * tail context's `_shellLoaderSeed` (loader-cache.ts servedPins).
181
192
  * Lives here so every snapshot family is decoded in this module (the segment
182
193
  * family via {@link SeededShellStore}); the loader family is not a store
183
194
  * read, so it seeds the context instead of a store.
184
195
  *
196
+ * Keyed by loader, not by the segment a pin was stored under: a loader runs
197
+ * once per capture, so every segment that registers it pinned the same
198
+ * container, and the loader's value and its handle pushes are both looked up
199
+ * by loader. A key that does not parse pins nothing.
200
+ *
185
201
  * Deserializations run in parallel; a record that fails to decode is skipped
186
202
  * (that loader drifts — the pre-snapshot behavior — instead of failing the
187
203
  * HIT). Returns undefined when the snapshot carries no loader records, without
@@ -43,6 +43,13 @@ export interface CacheGetResult {
43
43
  * The store deals with serialized data - RSC serialization is handled
44
44
  * by the cache provider layer.
45
45
  *
46
+ * A store that outlives the process has to keep one build from reading what
47
+ * other code wrote. The built-in stores prefix their keys with the serving
48
+ * router's cache versions; a custom store reads the same pair with
49
+ * `getCacheVersions()` (`@rangojs/router/cache`): `data` for segment entries
50
+ * and items, `document` for responses and shells, and no version on its
51
+ * tag-invalidation records, so an invalidation reaches every version.
52
+ *
46
53
  * @typeParam TEnv - Platform bindings type (e.g., Cloudflare env)
47
54
  */
48
55
  export interface SegmentCacheStore<TEnv = unknown> {
@@ -361,13 +368,14 @@ export interface ShellCacheEntry {
361
368
  /** React.version captured at prerender time; the read-time invalidation gate. */
362
369
  reactVersion: string;
363
370
  /**
364
- * Build version captured at prerender time (the RSC handler's `version` —
365
- * the `@rangojs/router:version` build stamp by default, bumped per build and
366
- * on dev RSC-module edits). The second read-time gate: a persistent shared
371
+ * Build version captured at prerender time: the RSC handler's `version`,
372
+ * which is the router's DOCUMENT version (router-versions.ts) — a hash of
373
+ * its server code and the client asset names by default, a stamp bumped on
374
+ * dev RSC-module edits. The second read-time gate: a persistent shared
367
375
  * store (KV/runtime-cache) survives deploys, and an app-code change that
368
376
  * keeps the same React version would otherwise leave a stale-build
369
- * prelude+postponed live under the same key. A custom store returns it as
370
- * putShell received it.
377
+ * prelude+postponed live under the same key. A deploy that changes neither
378
+ * keeps the entry. A custom store returns it as putShell received it.
371
379
  */
372
380
  buildVersion: string;
373
381
  /**
@@ -696,10 +704,13 @@ export type SegmentHandleData = Record<string, unknown[]>;
696
704
  * index (null: not owned). Only a PPR shell capture writes owners: the
697
705
  * settled pushes of the loader bodies it ran (an `ssr: false` loader's own,
698
706
  * the loaders it awaits, and its own cache() replays), which the record keeps
699
- * because the prelude rendered them. A document HIT restores a bake-lane
700
- * owner's value through HandleStore.pushRestored, and it stands against a run
701
- * of its loader there; a live-lane owner's (a hole), and every owner's on
702
- * any other replay of the record (a client navigation), go through
703
- * pushReplayed, so a run of the loader replaces it with its live push.
707
+ * because the prelude rendered them. A restore of the record
708
+ * (handle-snapshot.ts restoreHandles) keeps an owner's value where the
709
+ * request serves that loader from the pin stored with the record
710
+ * (HandleStore.pushRestored: it stands against a run of the loader), on a
711
+ * document HIT and on a navigation replay alike. Every other owner's value
712
+ * is a placeholder (pushPlaceholder): a live-lane loader's, and every
713
+ * owner's where the record has no pin for it. The loader's run, or its own
714
+ * cache() entry, replaces it.
704
715
  */
705
716
  export type HandleOwners = Record<string, Record<string, (string | null)[]>>;
@@ -23,7 +23,8 @@
23
23
  * 3. The platform guardrails: the 2 MB per-item ceiling (oversized writes
24
24
  * silently no-op on Vercel, so we skip + report), the per-item tag cap, and
25
25
  * cross-deploy non-reconciliation (TTL/tag updates are not reconciled across
26
- * deployments - bake a build id into `version` or the getCache namespace).
26
+ * deployments - every key carries the serving router's cache version, see
27
+ * toStoreKey).
27
28
  *
28
29
  * Dependency stance: this module imports NOTHING from `@vercel/functions`. The
29
30
  * runtime cache handle (and `waitUntil`) are injected through the constructor and
@@ -92,14 +93,20 @@ export type VercelCacheDebug = boolean | ((event: VercelCacheReadDebugEvent) =>
92
93
  export interface VercelCacheStoreOptions<TEnv = unknown> {
93
94
  /**
94
95
  * The Vercel Runtime Cache handle - `getCache()` from `@vercel/functions`.
95
- * Required. Construct it with a build-hash namespace to bust stale-shaped
96
- * entries across deployments, since Vercel does not reconcile TTL/tags between
97
- * deploys:
96
+ * Required.
98
97
  *
99
98
  * ```ts
100
99
  * import { getCache } from "@vercel/functions";
101
- * new VercelCacheStore({ cache: getCache({ namespace: BUILD_ID }) });
100
+ * new VercelCacheStore({ cache: getCache() });
102
101
  * ```
102
+ *
103
+ * Vercel does not reconcile entries between deployments; the store does it
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
106
+ * router's old entries, and a deploy that does not keeps them. A namespace
107
+ * that changes per deployment (`getCache({ namespace: VERCEL_DEPLOYMENT_ID })`)
108
+ * still works and clears the cache on every deploy, which is what you want
109
+ * only if you need that.
103
110
  */
104
111
  cache: VercelRuntimeCache;
105
112
  /**
@@ -122,9 +129,15 @@ export interface VercelCacheStoreOptions<TEnv = unknown> {
122
129
  */
123
130
  keyGenerator?: (ctx: RequestContext<TEnv>, defaultKey: string) => string | Promise<string>;
124
131
  /**
125
- * Build/version id folded into every stored key as `v/{version}/...`. A second
126
- * cross-deploy busting layer in addition to (or instead of) the getCache
127
- * namespace. Changing it invalidates everything this store wrote previously.
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.
128
141
  */
129
142
  version?: string;
130
143
  /**
@@ -181,8 +194,8 @@ export interface VercelCacheStoreOptions<TEnv = unknown> {
181
194
  * that propagates expireTag rejections does get the strict guarantee, which is
182
195
  * what the unit suite exercises.)
183
196
  *
184
- * Key hashing / family isolation. The store namespaces its three value tiers as
185
- * `rg:{s|i|r}:{key}` so segment, `"use cache"`, and response entries occupy
197
+ * Key hashing / family isolation. The store namespaces its value tiers as
198
+ * `rg:{s|i|r|h}:{key}` so segment, `"use cache"`, response and shell entries occupy
186
199
  * disjoint keyspaces. That separation is only as strong as the cache's
187
200
  * `keyHashFunction`: `getCache`'s default is djb2, which folds every key to 32
188
201
  * bits (8 hex chars), so at a large live-key count a collision can let one
@@ -198,11 +211,9 @@ export interface VercelCacheStoreOptions<TEnv = unknown> {
198
211
  *
199
212
  * // One handle per process: getCache() resolves the platform cache on every
200
213
  * // call, and the PPR shell and tag-marker memos are kept per handle.
201
- * // `process.env` (not `import.meta.env`, which needs a VITE_ prefix and
202
- * // would be undefined here) so cross-deploy busting via `version` works.
203
- * const runtimeCache = getCache({
204
- * namespace: process.env.VERCEL_DEPLOYMENT_ID,
205
- * });
214
+ * // No deployment id in the namespace: the store's keys carry the serving
215
+ * // router's cache versions, so a deploy keeps what its code did not change.
216
+ * const runtimeCache = getCache();
206
217
  *
207
218
  * export const router = createRouter({
208
219
  * cache: () => ({
@@ -221,7 +232,8 @@ export declare class VercelCacheStore<TEnv = unknown> implements SegmentCacheSto
221
232
  readonly keyGenerator?: (ctx: RequestContext<TEnv>, defaultKey: string) => string | Promise<string>;
222
233
  private readonly cache;
223
234
  private readonly waitUntil?;
224
- private readonly version?;
235
+ /** The `version` option: used for every versioned family when set. */
236
+ private readonly explicitVersion?;
225
237
  private readonly maxItemBytes;
226
238
  private readonly memo;
227
239
  private readonly handleMemos;
@@ -7,6 +7,8 @@ export interface ClientUrlNavigationIntent {
7
7
  /** Absolute destination pathname (mount included) and search ("?..." or ""). */
8
8
  readonly pathname: string;
9
9
  readonly search: string;
10
+ /** `history.state` the navigation will push (OptimisticLocation.state). */
11
+ readonly state: unknown;
10
12
  /**
11
13
  * transition({ when }) gated this navigation off at the swap: the
12
14
  * destination presents urgently instead of in a transition lane.
@@ -26,6 +28,8 @@ export interface ClientUrlDestination {
26
28
  * params minus these are the mount's (include prefix) params.
27
29
  */
28
30
  readonly originLocalParamNames: readonly string[];
31
+ /** `history.state` the navigation will push (ClientUrlNavigationIntent.state). */
32
+ readonly state: unknown;
29
33
  }
30
34
  export declare function setActiveInterceptTargets(targets: readonly string[] | undefined): void;
31
35
  export interface ClientUrlNavigationPresentation {
@@ -39,7 +43,9 @@ export interface ClientUrlNavigationPresentation {
39
43
  clear(): void;
40
44
  }
41
45
  export declare function registerClientUrlGroup(definition: ClientUrlPatterns, mount: string, namePrefix: string, setIntent: (intent: ClientUrlNavigationIntent | null) => void, routeId?: string): () => void;
42
- export declare function beginClientUrlNavigation(targetUrl: URL, signal: AbortSignal, decideTransition?: (destination: ClientUrlDestination) => boolean): ClientUrlNavigationPresentation | null;
46
+ export declare function beginClientUrlNavigation(targetUrl: URL, signal: AbortSignal,
47
+ /** Builds the entry state; called only when the group presents the target. */
48
+ buildState?: () => unknown, decideTransition?: (destination: ClientUrlDestination) => boolean): ClientUrlNavigationPresentation | null;
43
49
  /**
44
50
  * Run the held clientUrls route's per-loader revalidate() predicates and
45
51
  * encode their decisions for the request header. Predicates are CLIENT code
@@ -3,15 +3,21 @@ import { type Context } from "react";
3
3
  * Route identity of an optimistically rendered clientUrls() destination:
4
4
  * the values the local trie match produced for the URL the user navigated
5
5
  * to. Provided by ClientUrlsRoot around the optimistic branch ONLY, so
6
- * useParams / usePathname / useSearchParams inside that branch describe the
7
- * route being rendered while the same hooks in chrome outside it keep the
8
- * committed location until the canonical response commits (or redirects, in
9
- * which case the branch — and these values — are discarded).
6
+ * useParams / usePathname / useSearchParams / useLocationState inside that
7
+ * branch describe the route being rendered while the same hooks in chrome
8
+ * outside it keep the committed location until the canonical response commits
9
+ * (or redirects, in which case the branch — and these values — are discarded).
10
10
  */
11
11
  export interface OptimisticLocation {
12
12
  readonly params: Readonly<Record<string, string>>;
13
13
  readonly pathname: string;
14
14
  /** Search string including the leading "?" (or ""). */
15
15
  readonly search: string;
16
+ /**
17
+ * `history.state` the navigation will push: its `state` option, without
18
+ * what the server adds (that arrives with the canonical commit). History
19
+ * still holds the entry being left, so useLocationState reads this instead.
20
+ */
21
+ readonly state: unknown;
16
22
  }
17
23
  export declare const OptimisticLocationContext: Context<OptimisticLocation | null>;
@@ -39,8 +39,18 @@ export declare function resolveDeferredHandleValues(data: HandleData): Promise<H
39
39
  * A hung handle promise blocks the full render like any unresolved await (no
40
40
  * handle-specific timeout). Partial / action payloads keep streaming via
41
41
  * `handleStore.stream()`.
42
+ *
43
+ * `recordedOnly` is a PPR shell capture's render (full-payload.ts): the
44
+ * snapshot is cut down to the pushes the capture's record keeps
45
+ * (getDataForSegment's `excludeLoaderPushes`, the filter captureHandles
46
+ * writes the record with). A HIT restores that record and hydrates with it,
47
+ * so a push the record leaves out must not be in the prelude either: it
48
+ * rendered an element no HIT's hydration data matched (issue #1035). Which
49
+ * pushes a record keeps is the capture funnel's decision (shell-capture.ts
50
+ * deriveShellCaptureContext: today it leaves out a deferred loader push and
51
+ * one holding a masked promise), not this function's.
42
52
  */
43
- export declare function resolvedHandleStream(handleStore: HandleStore): AsyncGenerator<HandleData, void, unknown>;
53
+ export declare function resolvedHandleStream(handleStore: HandleStore, recordedOnly?: boolean): AsyncGenerator<HandleData, void, unknown>;
44
54
  /**
45
55
  * Resolve the deferred (Promise) values in ONE segment's handle bucket
46
56
  * (`handleName -> entries[]`). Same shallow + drop-rejections rule as
@@ -20,6 +20,7 @@
20
20
  * identity, the same eligibility rules as at runtime.
21
21
  */
22
22
  import type { ShellCacheEntry } from "../cache/types.js";
23
+ import type { RouterVersions } from "../router-versions.js";
23
24
  import type { SSRModule } from "../rsc/types.js";
24
25
  import { type ResolvedPprConfig } from "../rsc/shell-serve.js";
25
26
  /**
@@ -67,13 +68,14 @@ export interface BuildShellCaptureOptions {
67
68
  /** Build-time env bindings (rango plugin buildEnv), if configured. */
68
69
  buildEnv?: unknown;
69
70
  /**
70
- * The MAIN build's version (the version plugin's value folded into the
71
- * shipped worker) — NOT the temp server's own version-plugin value. The
72
- * serve-side isValidShellHit gate compares entry.buildVersion against the
73
- * running worker's ctx.version; stamping the temp server's would make every
74
- * build entry an eternal MISS.
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
73
+ * version-plugin stamp. The serve-side isValidShellHit gate compares
74
+ * entry.buildVersion against the running worker's ctx.version, which is the
75
+ * document version; stamping the temp server's would make every build entry
76
+ * an eternal MISS.
75
77
  */
76
- buildVersion: string;
78
+ versions: RouterVersions;
77
79
  /**
78
80
  * The SSR half, composed by the plugin from the temp server's SSR
79
81
  * environment runner (react-dom/static prerender + Flight client), with the
@@ -92,7 +92,7 @@
92
92
  * - Action context (if POST)
93
93
  */
94
94
  import type { ResolvedSegment } from "../../types.js";
95
- import type { EntryData } from "../../server/context.js";
95
+ import { type EntryData } from "../../server/context.js";
96
96
  import type { MatchContext, MatchPipelineState } from "../match-context.js";
97
97
  /**
98
98
  * Whether the prerender store holds a baked entry for this route + params.
@@ -25,8 +25,15 @@ export interface ResolveNavigationDeps {
25
25
  * never produce a partial match, so the replay path must decide
26
26
  * `no-navigation-context` BEFORE spending shell-store reads — the two
27
27
  * predicates drifting apart would misattribute the bypass again.
28
+ *
29
+ * `requestOrigin` is the origin pathname matching uses (`url.origin`), which
30
+ * may not be `request.url`'s. `X-RSC-Router-Client-Path` is returned as sent.
31
+ * A Referer counts only when it resolves against that origin and stays there.
32
+ * A cross-origin Referer is dropped: resolveNavigation matches only the
33
+ * pathname, so a foreign URL would be treated as a route on this app. A value
34
+ * that does not parse is no context either.
28
35
  */
29
- export declare function getNavigationContextHeader(request: Request): string | null;
36
+ export declare function getNavigationContextHeader(request: Request, requestOrigin: string): string | null;
30
37
  /**
31
38
  * Whether withCacheLookup's prerender short-circuit will serve this partial
32
39
  * from the build-time prerender store: a Prerender() match (`matched.pr`)
@@ -357,7 +357,8 @@ export interface RangoInternal<TEnv = any, TRoutes extends Record<string, unknow
357
357
  */
358
358
  readonly nonce?: NonceProvider<TEnv>;
359
359
  /**
360
- * RSC version string
360
+ * The consumer-set version (createRouter({ version })), used for both of the
361
+ * router's versions. Undefined when the router uses its build versions.
361
362
  */
362
363
  readonly version?: string;
363
364
  /**
@@ -466,13 +466,23 @@ export interface RangoOptions<TEnv = any> {
466
466
  */
467
467
  nonce?: NonceProvider<TEnv>;
468
468
  /**
469
- * RSC version string included in metadata.
470
- * The browser sends this back on partial requests to detect version mismatches.
471
- *
472
- * Defaults to the auto-generated VERSION from `@rangojs/router:version` virtual module.
473
- * Only set this if you need a custom versioning strategy.
474
- *
475
- * @default VERSION from @rangojs/router:version
469
+ * Version override. When set, this exact value is used for both of the
470
+ * router's versions: it keys the router's cached data (segments, `"use
471
+ * cache"` values, loader data) and its stored HTML (PPR shells,
472
+ * document-cache responses), is sent in payload metadata, and is what the
473
+ * browser's `_rsc_v` is compared with (a mismatch reloads the tab).
474
+ *
475
+ * Leave it unset for the default: rango computes two versions per router at
476
+ * build time, a data version from the router's server code and a document
477
+ * version that also covers the client assets. The same code builds to the
478
+ * same versions, so a deploy that does not change this router keeps its
479
+ * cache. In dev both are a stamp bumped on every RSC module edit.
480
+ *
481
+ * Set it to clear this router's cache on demand (change the value), or to
482
+ * tie the cache to your own release id (a per-deploy value clears the cache
483
+ * on every deploy).
484
+ *
485
+ * @default the router's build versions
476
486
  */
477
487
  version?: string;
478
488
  /**
@@ -42,6 +42,8 @@
42
42
  */
43
43
  import { type EntryData, type LoaderEntry } from "../../server/context.js";
44
44
  import type { HandlerContext } from "../../types.js";
45
+ import { type OwnedPushDelivery } from "../../cache/handle-snapshot.js";
46
+ import { type RequestContext } from "../../server/request-context.js";
45
47
  /**
46
48
  * Whether a request over the matched chain `entries` resolves a loader bound
47
49
  * with its own cache(): on a chain entry, or on its parallel slots, orphan
@@ -51,15 +53,6 @@ import type { HandlerContext } from "../../types.js";
51
53
  * Memoized per entry.
52
54
  */
53
55
  export declare function bindsLoaderCache(entries: readonly EntryData[]): boolean;
54
- /**
55
- * The ids of the loaders a request over the matched chain `entries` runs on
56
- * the live lane (no `ssr: false`), on a chain entry, its orphan layouts or
57
- * its parallel slots. A document HIT tail restores the record's pushes of
58
- * every other owner as authoritative (withCacheLookup, HandleStore
59
- * pushRestored); a live-lane loader is a hole, so its live run replaces its
60
- * recorded pushes instead. Memoized per entry.
61
- */
62
- export declare function liveLaneLoaderIds(entries: readonly EntryData[]): ReadonlySet<string>;
63
56
  /**
64
57
  * The lane of each loader the matched chain `entries` registers: "live" (no
65
58
  * `ssr: false`) or "bake". A loader registered on both is "live". A PPR
@@ -67,6 +60,25 @@ export declare function liveLaneLoaderIds(entries: readonly EntryData[]): Readon
67
60
  * one is live (shell-capture.ts deriveShellCaptureContext).
68
61
  */
69
62
  export declare function routeLoaderLanes(entries: readonly EntryData[]): ReadonlyMap<string, "live" | "bake">;
63
+ /**
64
+ * What a record restored on a request over the matched chain `entries` is to
65
+ * each loader's handle pushes (HandleStore RecordAuthority), from the pins
66
+ * the request serves (servedPins). Call it when the record hits: the pins are
67
+ * read here, once, because a navigation replay arms its seed for the match
68
+ * only and the store asks while loader bodies run.
69
+ *
70
+ * Each side applies its own lane to the pin. The value is per registration
71
+ * (resolveLoaderData: an `ssr: false` registration is served its pin). The
72
+ * pushes are per loader: one the route also registers without `ssr: false`
73
+ * is a hole whatever its other registration's pin says, because the capture
74
+ * credited its pushes to the live registration's run (routeLoaderLanes).
75
+ *
76
+ * A dependency the route does not register has no pin of its own, and the
77
+ * record does not name the loader that ran it at capture: its copies stand
78
+ * only while every `ssr: false` loader is pinned (exact when the pins are
79
+ * all there or all gone).
80
+ */
81
+ export declare function loaderPins(entries: readonly EntryData[], reqCtx: RequestContext<any> | undefined): OwnedPushDelivery;
70
82
  /**
71
83
  * Resolve loader data with optional caching.
72
84
  *
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Per-router cache versions: the shapes shared by the build (which computes
3
+ * them, vite/discovery/build-versions.ts) and the runtime (which reads them,
4
+ * server/build-version-table.ts). No imports, so the Vite plugin bundle can
5
+ * use it without resolving the `@rangojs/router:version` virtual module.
6
+ *
7
+ * Design: docs/design/per-app-cache-version.md.
8
+ */
9
+ /**
10
+ * The two versions one `createRouter()` serves with.
11
+ *
12
+ * - `data` keys cached RSC data: segment entries, `"use cache"` values and
13
+ * loader data. It follows the router's server code only.
14
+ * - `document` keys stored HTML (PPR shells, document-cache responses), is the
15
+ * value the browser echoes as `_rsc_v`, and stamps `ShellCacheEntry.buildVersion`.
16
+ * It follows the server code and the client asset URLs.
17
+ */
18
+ export interface RouterVersions {
19
+ readonly data: string;
20
+ readonly document: string;
21
+ }
22
+ /**
23
+ * The build's table as the `@rangojs/router:version` module exports it:
24
+ * router id to `[data, document]`. {@link DEFAULT_ROUTER_VERSIONS_KEY} holds
25
+ * the whole-build pair, used for a router the build could not attribute.
26
+ */
27
+ export type RouterVersionsTable = Readonly<Record<string, readonly [data: string, document: string]>>;
28
+ /** Table key of the whole-build versions. Not a legal `$$id` (8 hex chars). */
29
+ export declare const DEFAULT_ROUTER_VERSIONS_KEY = "*";
30
+ /** One value for both versions: a consumer-set `version`, or the dev stamp. */
31
+ export declare function uniformVersions(version: string): RouterVersions;
32
+ /**
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
37
+ * the build's shell capture, which must stamp what the handler will check.
38
+ */
39
+ export declare function resolveVersionsFrom(table: RouterVersionsTable | undefined, routerId: string | undefined, override: string | undefined): RouterVersions | undefined;
@@ -12,6 +12,11 @@ import type { RSCDependencies, LoadSSRModule } from "./types.js";
12
12
  import type { SSRStreamMode } from "../router/router-options.js";
13
13
  export interface HandlerContext<TEnv = unknown> {
14
14
  router: RangoInternal<TEnv, any>;
15
+ /**
16
+ * The router's DOCUMENT version (router-versions.ts): payload metadata, the
17
+ * `_rsc_v` reload check, and PPR shell stamping and gating. The data version
18
+ * is not here; cache stores read it from the request context.
19
+ */
15
20
  version: string;
16
21
  devDiscoveryEpoch?: number;
17
22
  renderToReadableStream: RSCDependencies["renderToReadableStream"];
@@ -11,8 +11,9 @@
11
11
  * served through the same serveShellHit as a captured one.
12
12
  *
13
13
  * Lifecycle:
14
- * - No expiry until the next deploy — the buildVersion gate retires entries
15
- * the moment a new build ships (a new manifest replaces them anyway).
14
+ * - No expiry until the next deploy: every build ships its own manifest, and
15
+ * its entries carry that build's document version for the router, which is
16
+ * what the buildVersion gate compares.
16
17
  * - `ppr.ttl` drives STALENESS ONLY: past createdAt + ttl the entry still
17
18
  * serves, but a runtime recapture is scheduled — SWR is the UPGRADE path
18
19
  * from build entry to fresher runtime entry, not the bootstrap path. The
@@ -321,13 +321,11 @@ export interface CreateRSCHandlerOptions<TEnv = unknown, TRoutes extends Record<
321
321
  */
322
322
  cache?: HandlerCacheConfig | ((env: TEnv) => HandlerCacheConfig);
323
323
  /**
324
- * RSC version string included in metadata.
325
- * The browser sends this back on partial requests to detect version mismatches.
324
+ * Version override for this handler. Same meaning as
325
+ * `createRouter({ version })`, which it takes precedence over: this exact
326
+ * value is used for both of the router's versions.
326
327
  *
327
- * Defaults to the auto-generated VERSION from `rsc-router:version` virtual module.
328
- * Only set this if you need a custom versioning strategy.
329
- *
330
- * @default VERSION from rsc-router:version
328
+ * @default createRouter({ version }), else the router's build versions
331
329
  */
332
330
  version?: string;
333
331
  /**
@@ -0,0 +1,44 @@
1
+ import { type RouterVersions, type RouterVersionsTable } from "../router-versions.js";
2
+ /**
3
+ * The versions a router serves with, in order:
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.
9
+ */
10
+ export declare function resolveRouterVersions(routerId: string | undefined, override?: string): RouterVersions;
11
+ /**
12
+ * The versions of the router serving the current request, for a cache store
13
+ * building a key. A store operation with no request context (a detached task
14
+ * that lost the ALS) gets the whole-build versions: it misses the request's
15
+ * entries instead of reading another version's. When routers have versions of
16
+ * their own that is a write no router reads back, so it is logged, once per
17
+ * process: `ctx.waitUntil()` re-enters the request context for every task it
18
+ * runs (server/request-context.ts), and this line in a production log means
19
+ * some other path lost it.
20
+ *
21
+ * Public from `@rangojs/router/cache`, for a custom persistent store to key
22
+ * the way the built-in ones do. Prefix cached RSC data (segment entries, items) with
23
+ * `data` and stored HTML or responses (shells, document responses) with
24
+ * `document`, and leave tag-invalidation records unversioned; an empty string
25
+ * means "no version" (tests). Call it per operation, inside the request, not
26
+ * in the store's constructor: the cache factory runs before the request
27
+ * context exists.
28
+ */
29
+ export declare function getCacheVersions(): RouterVersions;
30
+ /** Which of the serving router's versions a store key carries; `null`: none. */
31
+ export type KeyVersion = keyof RouterVersions | null;
32
+ /**
33
+ * The `v/{version}/` prefix of a persistent store key, or "" for an
34
+ * unversioned one. A store-level `version` is used for every versioned kind;
35
+ * otherwise the version is the serving router's, read per operation because
36
+ * the cache factory builds the store before the request context exists.
37
+ */
38
+ export declare function versionKeyPrefix(storeVersion: string | undefined, kind: KeyVersion): string;
39
+ /**
40
+ * @internal Install a table, or restore the build's with `undefined`. A
41
+ * request handler resolves its router's versions once, when it is created, so
42
+ * a test that swaps the table (a "deploy") needs new handlers.
43
+ */
44
+ export declare function installRouterVersionsTable(next: RouterVersionsTable | undefined): void;