@rangojs/router 0.19.1 → 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.
- package/dist/bin/rango.js +26 -14
- package/dist/types/browser/event-controller.d.ts +32 -0
- package/dist/types/browser/history-state.d.ts +16 -4
- package/dist/types/browser/navigation-transaction.d.ts +7 -0
- package/dist/types/browser/rango-state.d.ts +3 -1
- package/dist/types/browser/react/context.d.ts +11 -0
- package/dist/types/browser/react/location-state-shared.d.ts +84 -10
- package/dist/types/browser/react/location-state.d.ts +7 -2
- package/dist/types/browser/react/subscribe-to-nothing.d.ts +7 -0
- package/dist/types/cache/cache-scope.d.ts +4 -5
- package/dist/types/cache/cf/cf-cache-store.d.ts +27 -6
- package/dist/types/cache/cf/cf-cache-types.d.ts +12 -4
- package/dist/types/cache/handle-snapshot.d.ts +36 -50
- package/dist/types/cache/index.d.ts +2 -0
- package/dist/types/cache/shell-snapshot.d.ts +19 -3
- package/dist/types/cache/types.d.ts +21 -10
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +28 -16
- package/dist/types/client-urls/navigation.d.ts +7 -1
- package/dist/types/client-urls/optimistic-location.d.ts +10 -4
- package/dist/types/handles/deferred-resolution.d.ts +11 -1
- package/dist/types/prerender/build-shell-capture.d.ts +8 -6
- package/dist/types/router/match-middleware/cache-lookup.d.ts +1 -1
- package/dist/types/router/navigation-snapshot.d.ts +8 -1
- package/dist/types/router/router-interfaces.d.ts +2 -1
- package/dist/types/router/router-options.d.ts +17 -7
- package/dist/types/router/segment-resolution/loader-cache.d.ts +21 -9
- package/dist/types/router-versions.d.ts +39 -0
- package/dist/types/rsc/handler-context.d.ts +5 -0
- package/dist/types/rsc/shell-build-manifest.d.ts +3 -2
- package/dist/types/rsc/types.d.ts +4 -6
- package/dist/types/server/build-version-table.d.ts +44 -0
- package/dist/types/server/handle-store.d.ts +87 -46
- package/dist/types/server/request-context.d.ts +22 -8
- package/dist/types/ssr/entry-preload-priority.d.ts +69 -0
- package/dist/types/ssr/index.d.ts +1 -0
- package/dist/types/ssr/preinit-client-references.d.ts +17 -5
- package/dist/types/testing/build-versions.d.ts +32 -0
- package/dist/types/testing/dom.entry.d.ts +1 -1
- package/dist/types/testing/flight.entry.d.ts +1 -1
- package/dist/types/testing/index.d.ts +2 -0
- package/dist/types/testing/internal/seed-vars.d.ts +7 -2
- package/dist/types/testing/location-state-key.d.ts +5 -1
- package/dist/types/testing/render-route.d.ts +77 -6
- package/dist/types/testing/serve-shell-request.d.ts +21 -0
- package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
- package/dist/types/types/handler-context.d.ts +8 -5
- package/dist/types/vite/discovery/build-versions.d.ts +393 -0
- package/dist/types/vite/discovery/installed-packages.d.ts +26 -0
- package/dist/types/vite/discovery/router-versions-phase.d.ts +52 -0
- package/dist/types/vite/discovery/shell-prerender-phase.d.ts +7 -17
- package/dist/types/vite/discovery/state.d.ts +14 -0
- package/dist/types/vite/encryption-key.d.ts +18 -0
- package/dist/types/vite/plugin-types.d.ts +47 -6
- package/dist/types/vite/plugins/{version-injector.d.ts → entry-bootstrap-injector.d.ts} +4 -6
- package/dist/types/vite/plugins/expose-ids/export-analysis.d.ts +1 -0
- package/dist/types/vite/plugins/expose-ids/router-transform.d.ts +14 -0
- package/dist/types/vite/plugins/stable-rsc-output.d.ts +51 -0
- package/dist/types/vite/plugins/version-plugin.d.ts +9 -11
- package/dist/types/vite/plugins/virtual-entries.d.ts +21 -2
- package/dist/types/vite/router-discovery.d.ts +6 -0
- package/dist/types/vite/utils/client-chunks.d.ts +24 -2
- package/dist/types/vite/utils/compare-strings.d.ts +7 -0
- package/dist/types/vite/utils/package-resolution.d.ts +8 -0
- package/dist/types/vite/utils/prerender-utils.d.ts +2 -0
- package/dist/vite/index.js +1544 -420
- package/package.json +1 -2
- package/skills/bundle-analysis/SKILL.md +1 -1
- package/skills/cache-guide/SKILL.md +84 -20
- package/skills/caching/SKILL.md +16 -5
- package/skills/client-urls/SKILL.md +16 -0
- package/skills/cloudflare/SKILL.md +2 -1
- package/skills/comparison/references/framework-comparison.md +16 -12
- package/skills/hooks/SKILL.md +3 -0
- package/skills/hooks/handle-and-actions.md +6 -0
- package/skills/hooks/navigation.md +28 -0
- package/skills/hooks/state.md +209 -8
- package/skills/hooks/urls.md +40 -1
- package/skills/host-router/SKILL.md +13 -0
- package/skills/intercept/SKILL.md +6 -0
- package/skills/loader/SKILL.md +19 -1
- package/skills/middleware/SKILL.md +2 -0
- package/skills/ppr/SKILL.md +119 -40
- package/skills/prerender/SKILL.md +7 -3
- package/skills/rango/SKILL.md +8 -7
- package/skills/router-setup/SKILL.md +23 -3
- package/skills/testing/SKILL.md +30 -26
- package/skills/testing/client-components.md +193 -8
- package/skills/testing/handles.md +2 -0
- package/skills/testing/loader.md +1 -1
- package/skills/testing/middleware.md +1 -1
- package/skills/testing/render-handler.md +18 -18
- package/skills/testing/serve-shell-request.md +63 -14
- package/skills/testing/server-actions.md +17 -17
- package/skills/testing/server-tree.md +1 -1
- package/skills/typesafety/generated-files-and-cli.md +1 -1
- package/skills/use-cache/SKILL.md +16 -5
- package/skills/vercel/SKILL.md +3 -3
- package/src/bin/rango.ts +1 -1
- package/src/browser/event-controller.ts +75 -2
- package/src/browser/history-state.ts +39 -17
- package/src/browser/navigation-bridge.ts +41 -18
- package/src/browser/navigation-transaction.ts +15 -10
- package/src/browser/partial-update.ts +10 -2
- package/src/browser/rango-state.ts +3 -1
- package/src/browser/react/Link.tsx +2 -3
- package/src/browser/react/NavigationProvider.tsx +25 -1
- package/src/browser/react/context.ts +13 -0
- package/src/browser/react/location-state-shared.ts +200 -34
- package/src/browser/react/location-state.ts +40 -75
- package/src/browser/react/subscribe-to-nothing.ts +7 -0
- package/src/browser/react/use-handle.ts +20 -1
- package/src/browser/rsc-router.tsx +33 -11
- package/src/browser/server-action-bridge.ts +17 -1
- package/src/cache/cache-scope.ts +18 -8
- package/src/cache/cf/cf-cache-store.ts +75 -55
- package/src/cache/cf/cf-cache-types.ts +12 -4
- package/src/cache/handle-snapshot.ts +69 -87
- package/src/cache/index.ts +4 -0
- package/src/cache/shell-snapshot.ts +35 -5
- package/src/cache/types.ts +21 -10
- package/src/cache/vercel/vercel-cache-store.ts +48 -20
- package/src/client-urls/client-root.tsx +1 -0
- package/src/client-urls/navigation.ts +10 -0
- package/src/client-urls/optimistic-location.ts +10 -4
- package/src/handles/deferred-resolution.ts +31 -0
- package/src/prerender/build-shell-capture.ts +11 -9
- package/src/router/manifest.ts +2 -0
- package/src/router/match-middleware/cache-lookup.ts +11 -18
- package/src/router/navigation-snapshot.ts +25 -6
- package/src/router/request-classification.ts +15 -12
- package/src/router/router-interfaces.ts +2 -1
- package/src/router/router-options.ts +17 -7
- package/src/router/segment-resolution/loader-cache.ts +95 -60
- package/src/router-versions.ts +60 -0
- package/src/rsc/full-payload.ts +6 -2
- package/src/rsc/handler-context.ts +5 -0
- package/src/rsc/handler.ts +14 -4
- package/src/rsc/rsc-rendering.ts +12 -1
- package/src/rsc/shell-build-manifest.ts +10 -7
- package/src/rsc/shell-capture.ts +36 -26
- package/src/rsc/types.ts +4 -6
- package/src/server/build-version-table.ts +115 -0
- package/src/server/handle-store.ts +344 -127
- package/src/server/request-context.ts +62 -11
- package/src/ssr/entry-preload-priority.ts +308 -0
- package/src/ssr/index.tsx +46 -9
- package/src/ssr/preinit-client-references.ts +17 -5
- package/src/ssr/ssr-root.tsx +16 -4
- package/src/testing/build-versions.ts +64 -0
- package/src/testing/dispatch.ts +6 -0
- package/src/testing/dom.entry.ts +1 -0
- package/src/testing/flight.entry.ts +1 -0
- package/src/testing/index.ts +3 -0
- package/src/testing/internal/context.ts +2 -1
- package/src/testing/internal/seed-vars.ts +14 -2
- package/src/testing/location-state-key.ts +18 -8
- package/src/testing/render-handler.ts +2 -1
- package/src/testing/render-route.tsx +277 -43
- package/src/testing/serve-shell-request.ts +80 -11
- package/src/testing/vitest-stubs/version.ts +4 -1
- package/src/types/handler-context.ts +8 -5
- package/src/vite/discovery/build-versions.ts +1281 -0
- package/src/vite/discovery/installed-packages.ts +159 -0
- package/src/vite/discovery/prerender-collection.ts +38 -1
- package/src/vite/discovery/router-versions-phase.ts +336 -0
- package/src/vite/discovery/shell-prerender-phase.ts +28 -38
- package/src/vite/discovery/state.ts +19 -0
- package/src/vite/discovery/virtual-module-codegen.ts +7 -2
- package/src/vite/encryption-key.ts +101 -9
- package/src/vite/plugin-types.ts +48 -6
- package/src/vite/plugins/{version-injector.ts → entry-bootstrap-injector.ts} +7 -22
- package/src/vite/plugins/expose-ids/export-analysis.ts +1 -1
- package/src/vite/plugins/expose-ids/router-transform.ts +36 -15
- package/src/vite/plugins/expose-internal-ids.ts +12 -3
- package/src/vite/plugins/stable-rsc-output.ts +147 -0
- package/src/vite/plugins/version-plugin.ts +19 -24
- package/src/vite/plugins/version.d.ts +17 -2
- package/src/vite/plugins/virtual-entries.ts +40 -7
- package/src/vite/rango.ts +30 -3
- package/src/vite/router-discovery.ts +82 -13
- package/src/vite/utils/client-chunks.ts +84 -14
- package/src/vite/utils/compare-strings.ts +9 -0
- package/src/vite/utils/package-resolution.ts +9 -0
- package/src/vite/utils/prerender-utils.ts +4 -1
- package/src/vite/utils/shared-utils.ts +1 -1
package/src/cache/index.ts
CHANGED
|
@@ -56,3 +56,7 @@ export {
|
|
|
56
56
|
} from "./document-cache.js";
|
|
57
57
|
|
|
58
58
|
export type { CacheErrorCategory } from "./cache-error.js";
|
|
59
|
+
|
|
60
|
+
// For a custom persistent store: the versions the built-in stores key with.
|
|
61
|
+
export { getCacheVersions } from "../server/build-version-table.js";
|
|
62
|
+
export type { RouterVersions } from "../router-versions.js";
|
|
@@ -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
|
|
|
24
26
|
import type {
|
|
@@ -367,17 +369,40 @@ export interface ShellLoaderSeedEntry {
|
|
|
367
369
|
* hole-free record: a hole-carrying one always runs.
|
|
368
370
|
*/
|
|
369
371
|
runs: boolean;
|
|
372
|
+
/**
|
|
373
|
+
* The capture that wrote the pin recorded every settled push of the
|
|
374
|
+
* loader's run in the doc record (CachedEntryData.handleOwners): the pin
|
|
375
|
+
* carries the `runs` bit, which captures write since they record those
|
|
376
|
+
* pushes (v0.18). A replay then drops any other settled push of the loader
|
|
377
|
+
* (HandleStore RecordAuthority "pin"). False for an older pin, whose record
|
|
378
|
+
* may hold none and leaves them to the run.
|
|
379
|
+
*/
|
|
380
|
+
complete: boolean;
|
|
370
381
|
}
|
|
371
382
|
|
|
383
|
+
/**
|
|
384
|
+
* A pin's loader. A pin is stored under its loader segment id,
|
|
385
|
+
* `${shortCode}D${index}.${loaderId}` (fresh.ts resolveLoaders), and a
|
|
386
|
+
* shortCode holds no "D" and no "." (server/context.ts getShortCode: M, L, P,
|
|
387
|
+
* R, C, I and digits), so the first `D<index>.` ends it. A loader id can hold
|
|
388
|
+
* both.
|
|
389
|
+
*/
|
|
390
|
+
const PIN_KEY_PREFIX = /^[^D.]*D\d+\./;
|
|
391
|
+
|
|
372
392
|
/**
|
|
373
393
|
* Materialize the loader-family seed from a shell snapshot for a HIT's tail
|
|
374
394
|
* render: Flight-deserialize each recorded (promise-elided) bake-lane
|
|
375
|
-
* container into a
|
|
376
|
-
*
|
|
395
|
+
* container into a loader-id -> pin Map, which serveShellHit assigns to the
|
|
396
|
+
* tail context's `_shellLoaderSeed` (loader-cache.ts servedPins).
|
|
377
397
|
* Lives here so every snapshot family is decoded in this module (the segment
|
|
378
398
|
* family via {@link SeededShellStore}); the loader family is not a store
|
|
379
399
|
* read, so it seeds the context instead of a store.
|
|
380
400
|
*
|
|
401
|
+
* Keyed by loader, not by the segment a pin was stored under: a loader runs
|
|
402
|
+
* once per capture, so every segment that registers it pinned the same
|
|
403
|
+
* container, and the loader's value and its handle pushes are both looked up
|
|
404
|
+
* by loader. A key that does not parse pins nothing.
|
|
405
|
+
*
|
|
381
406
|
* Deserializations run in parallel; a record that fails to decode is skipped
|
|
382
407
|
* (that loader drifts — the pre-snapshot behavior — instead of failing the
|
|
383
408
|
* HIT). Returns undefined when the snapshot carries no loader records, without
|
|
@@ -396,10 +421,14 @@ export async function buildShellLoaderSeed(
|
|
|
396
421
|
const entries = await Promise.all(
|
|
397
422
|
loaderRecords.map(
|
|
398
423
|
async (rec): Promise<[string, ShellLoaderSeedEntry] | null> => {
|
|
424
|
+
const prefix = PIN_KEY_PREFIX.exec(rec.key);
|
|
425
|
+
if (!prefix) return null;
|
|
399
426
|
try {
|
|
400
|
-
|
|
427
|
+
// Typed as always carrying the bits; an older record lacks them.
|
|
428
|
+
const stored = rec.value as Partial<ShellSnapshotLoaderValue> &
|
|
429
|
+
Pick<ShellSnapshotLoaderValue, "value">;
|
|
401
430
|
return [
|
|
402
|
-
rec.key,
|
|
431
|
+
rec.key.slice(prefix[0].length),
|
|
403
432
|
{
|
|
404
433
|
container: await deserializeResult(stored.value),
|
|
405
434
|
// A record stored before the bits existed (v0.17) lacks the
|
|
@@ -407,6 +436,7 @@ export async function buildShellLoaderSeed(
|
|
|
407
436
|
// so the loader body supplies them.
|
|
408
437
|
holes: stored.holes !== 0,
|
|
409
438
|
runs: stored.runs !== 0,
|
|
439
|
+
complete: stored.runs !== undefined,
|
|
410
440
|
},
|
|
411
441
|
];
|
|
412
442
|
} catch {
|
package/src/cache/types.ts
CHANGED
|
@@ -49,6 +49,13 @@ export interface CacheGetResult {
|
|
|
49
49
|
* The store deals with serialized data - RSC serialization is handled
|
|
50
50
|
* by the cache provider layer.
|
|
51
51
|
*
|
|
52
|
+
* A store that outlives the process has to keep one build from reading what
|
|
53
|
+
* other code wrote. The built-in stores prefix their keys with the serving
|
|
54
|
+
* router's cache versions; a custom store reads the same pair with
|
|
55
|
+
* `getCacheVersions()` (`@rangojs/router/cache`): `data` for segment entries
|
|
56
|
+
* and items, `document` for responses and shells, and no version on its
|
|
57
|
+
* tag-invalidation records, so an invalidation reaches every version.
|
|
58
|
+
*
|
|
52
59
|
* @typeParam TEnv - Platform bindings type (e.g., Cloudflare env)
|
|
53
60
|
*/
|
|
54
61
|
export interface SegmentCacheStore<TEnv = unknown> {
|
|
@@ -415,13 +422,14 @@ export interface ShellCacheEntry {
|
|
|
415
422
|
/** React.version captured at prerender time; the read-time invalidation gate. */
|
|
416
423
|
reactVersion: string;
|
|
417
424
|
/**
|
|
418
|
-
* Build version captured at prerender time
|
|
419
|
-
* the
|
|
420
|
-
*
|
|
425
|
+
* Build version captured at prerender time: the RSC handler's `version`,
|
|
426
|
+
* which is the router's DOCUMENT version (router-versions.ts) — a hash of
|
|
427
|
+
* its server code and the client asset names by default, a stamp bumped on
|
|
428
|
+
* dev RSC-module edits. The second read-time gate: a persistent shared
|
|
421
429
|
* store (KV/runtime-cache) survives deploys, and an app-code change that
|
|
422
430
|
* keeps the same React version would otherwise leave a stale-build
|
|
423
|
-
* prelude+postponed live under the same key. A
|
|
424
|
-
* putShell received it.
|
|
431
|
+
* prelude+postponed live under the same key. A deploy that changes neither
|
|
432
|
+
* keeps the entry. A custom store returns it as putShell received it.
|
|
425
433
|
*/
|
|
426
434
|
buildVersion: string;
|
|
427
435
|
/**
|
|
@@ -761,10 +769,13 @@ export type SegmentHandleData = Record<string, unknown[]>;
|
|
|
761
769
|
* index (null: not owned). Only a PPR shell capture writes owners: the
|
|
762
770
|
* settled pushes of the loader bodies it ran (an `ssr: false` loader's own,
|
|
763
771
|
* the loaders it awaits, and its own cache() replays), which the record keeps
|
|
764
|
-
* because the prelude rendered them. A
|
|
765
|
-
* owner's value
|
|
766
|
-
*
|
|
767
|
-
*
|
|
768
|
-
*
|
|
772
|
+
* because the prelude rendered them. A restore of the record
|
|
773
|
+
* (handle-snapshot.ts restoreHandles) keeps an owner's value where the
|
|
774
|
+
* request serves that loader from the pin stored with the record
|
|
775
|
+
* (HandleStore.pushRestored: it stands against a run of the loader), on a
|
|
776
|
+
* document HIT and on a navigation replay alike. Every other owner's value
|
|
777
|
+
* is a placeholder (pushPlaceholder): a live-lane loader's, and every
|
|
778
|
+
* owner's where the record has no pin for it. The loader's run, or its own
|
|
779
|
+
* cache() entry, replaces it.
|
|
769
780
|
*/
|
|
770
781
|
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 -
|
|
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
|
|
@@ -52,6 +53,10 @@ import {
|
|
|
52
53
|
_getRequestContext,
|
|
53
54
|
type RequestContext,
|
|
54
55
|
} from "../../server/request-context.js";
|
|
56
|
+
import {
|
|
57
|
+
versionKeyPrefix,
|
|
58
|
+
type KeyVersion,
|
|
59
|
+
} from "../../server/build-version-table.js";
|
|
55
60
|
import { isPerClientSignalHeader } from "../../browser/cookie-name.js";
|
|
56
61
|
import {
|
|
57
62
|
resolveTtl,
|
|
@@ -170,6 +175,19 @@ const REVALIDATION_LOCK_MS = 30_000;
|
|
|
170
175
|
* the suffix; `rg:` namespaces every Rango entry. `h` is the PPR shell tier. */
|
|
171
176
|
type CacheFamily = "s" | "i" | "r" | "h" | "tm";
|
|
172
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Which of the serving router's versions each family's keys carry
|
|
180
|
+
* (versionKeyPrefix). A tag marker carries none: an invalidation applies to
|
|
181
|
+
* every version, including one a rollback brings back.
|
|
182
|
+
*/
|
|
183
|
+
const FAMILY_VERSION: Record<CacheFamily, KeyVersion> = {
|
|
184
|
+
s: "data",
|
|
185
|
+
i: "data",
|
|
186
|
+
r: "document",
|
|
187
|
+
h: "document",
|
|
188
|
+
tm: null,
|
|
189
|
+
};
|
|
190
|
+
|
|
173
191
|
/**
|
|
174
192
|
* TTL for tag-invalidation marker entries ("tm" family), written by
|
|
175
193
|
* invalidateTags for isTagsInvalidatedSince (the build-shell read-through,
|
|
@@ -177,7 +195,7 @@ type CacheFamily = "s" | "i" | "r" | "h" | "tm";
|
|
|
177
195
|
* The platform's expireTag() DELETES tagged entries (no queryable
|
|
178
196
|
* history), so the markers are rango's own record of "tag X was invalidated
|
|
179
197
|
* at T". One year: runtime tagged-shell retention is capped to this lifetime,
|
|
180
|
-
* while
|
|
198
|
+
* while a build shell is replaced by the next build's. An expired marker
|
|
181
199
|
* therefore cannot resurrect a runtime shell that outlived its invalidation.
|
|
182
200
|
*/
|
|
183
201
|
const TAG_MARKER_TTL_SECONDS = 365 * 24 * 60 * 60;
|
|
@@ -299,14 +317,20 @@ export type VercelCacheDebug =
|
|
|
299
317
|
export interface VercelCacheStoreOptions<TEnv = unknown> {
|
|
300
318
|
/**
|
|
301
319
|
* The Vercel Runtime Cache handle - `getCache()` from `@vercel/functions`.
|
|
302
|
-
* Required.
|
|
303
|
-
* entries across deployments, since Vercel does not reconcile TTL/tags between
|
|
304
|
-
* deploys:
|
|
320
|
+
* Required.
|
|
305
321
|
*
|
|
306
322
|
* ```ts
|
|
307
323
|
* import { getCache } from "@vercel/functions";
|
|
308
|
-
* new VercelCacheStore({ cache: getCache(
|
|
324
|
+
* new VercelCacheStore({ cache: getCache() });
|
|
309
325
|
* ```
|
|
326
|
+
*
|
|
327
|
+
* Vercel does not reconcile entries between deployments; the store does it
|
|
328
|
+
* through its keys, which carry the serving router's cache versions (see
|
|
329
|
+
* `version`). So a deploy that changes a router's code stops reading that
|
|
330
|
+
* router's old entries, and a deploy that does not keeps them. A namespace
|
|
331
|
+
* that changes per deployment (`getCache({ namespace: VERCEL_DEPLOYMENT_ID })`)
|
|
332
|
+
* still works and clears the cache on every deploy, which is what you want
|
|
333
|
+
* only if you need that.
|
|
310
334
|
*/
|
|
311
335
|
cache: VercelRuntimeCache;
|
|
312
336
|
|
|
@@ -336,9 +360,15 @@ export interface VercelCacheStoreOptions<TEnv = unknown> {
|
|
|
336
360
|
) => string | Promise<string>;
|
|
337
361
|
|
|
338
362
|
/**
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
363
|
+
* Version override, folded into every versioned key as `v/{version}/...`.
|
|
364
|
+
* When set, this exact value is used for cached data and stored HTML alike;
|
|
365
|
+
* changing it makes everything this store wrote before unreachable.
|
|
366
|
+
*
|
|
367
|
+
* Leave it unset for the default: the store keys with the versions of the
|
|
368
|
+
* router serving the request, as CFCacheStore does. Segments and `"use
|
|
369
|
+
* cache"` items use the router's data version; responses and PPR shells use
|
|
370
|
+
* its document version (see `createRouter({ version })`). Tag markers are
|
|
371
|
+
* never versioned, so an invalidation applies to every version.
|
|
342
372
|
*/
|
|
343
373
|
version?: string;
|
|
344
374
|
|
|
@@ -447,8 +477,8 @@ function tagStamp(tags: string[]): { ta?: number } {
|
|
|
447
477
|
* that propagates expireTag rejections does get the strict guarantee, which is
|
|
448
478
|
* what the unit suite exercises.)
|
|
449
479
|
*
|
|
450
|
-
* Key hashing / family isolation. The store namespaces its
|
|
451
|
-
* `rg:{s|i|r}:{key}` so segment, `"use cache"`, and
|
|
480
|
+
* Key hashing / family isolation. The store namespaces its value tiers as
|
|
481
|
+
* `rg:{s|i|r|h}:{key}` so segment, `"use cache"`, response and shell entries occupy
|
|
452
482
|
* disjoint keyspaces. That separation is only as strong as the cache's
|
|
453
483
|
* `keyHashFunction`: `getCache`'s default is djb2, which folds every key to 32
|
|
454
484
|
* bits (8 hex chars), so at a large live-key count a collision can let one
|
|
@@ -464,11 +494,9 @@ function tagStamp(tags: string[]): { ta?: number } {
|
|
|
464
494
|
*
|
|
465
495
|
* // One handle per process: getCache() resolves the platform cache on every
|
|
466
496
|
* // call, and the PPR shell and tag-marker memos are kept per handle.
|
|
467
|
-
* //
|
|
468
|
-
* //
|
|
469
|
-
* const runtimeCache = getCache(
|
|
470
|
-
* namespace: process.env.VERCEL_DEPLOYMENT_ID,
|
|
471
|
-
* });
|
|
497
|
+
* // No deployment id in the namespace: the store's keys carry the serving
|
|
498
|
+
* // router's cache versions, so a deploy keeps what its code did not change.
|
|
499
|
+
* const runtimeCache = getCache();
|
|
472
500
|
*
|
|
473
501
|
* export const router = createRouter({
|
|
474
502
|
* cache: () => ({
|
|
@@ -493,7 +521,8 @@ export class VercelCacheStore<
|
|
|
493
521
|
|
|
494
522
|
private readonly cache: VercelRuntimeCache;
|
|
495
523
|
private readonly waitUntil?: (promise: Promise<unknown>) => void;
|
|
496
|
-
|
|
524
|
+
/** The `version` option: used for every versioned family when set. */
|
|
525
|
+
private readonly explicitVersion?: string;
|
|
497
526
|
private readonly maxItemBytes: number;
|
|
498
527
|
private readonly memo: ResolvedShellMemoOptions;
|
|
499
528
|
private readonly handleMemos: VercelHandleMemos;
|
|
@@ -518,7 +547,7 @@ export class VercelCacheStore<
|
|
|
518
547
|
this.waitUntil = options.waitUntil;
|
|
519
548
|
this.defaults = options.defaults;
|
|
520
549
|
this.keyGenerator = options.keyGenerator;
|
|
521
|
-
this.
|
|
550
|
+
this.explicitVersion = options.version;
|
|
522
551
|
this.maxItemBytes = options.maxItemBytes ?? VERCEL_MAX_ITEM_BYTES;
|
|
523
552
|
this.maxShellEntryBytes = Math.floor((this.maxItemBytes * 3) / 4);
|
|
524
553
|
this.memo = resolveShellMemoOptions(options.memo, {
|
|
@@ -1469,8 +1498,7 @@ export class VercelCacheStore<
|
|
|
1469
1498
|
// cross-family one reads as corrupt. See the class doc for passing a wider
|
|
1470
1499
|
// hash (sha256) when many keys are live.
|
|
1471
1500
|
private toStoreKey(key: string, family: CacheFamily): string {
|
|
1472
|
-
|
|
1473
|
-
return `${versionPrefix}rg:${family}:${key}`;
|
|
1501
|
+
return `${versionKeyPrefix(this.explicitVersion, FAMILY_VERSION[family])}rg:${family}:${key}`;
|
|
1474
1502
|
}
|
|
1475
1503
|
|
|
1476
1504
|
private async write(
|
|
@@ -17,6 +17,8 @@ export interface ClientUrlNavigationIntent {
|
|
|
17
17
|
/** Absolute destination pathname (mount included) and search ("?..." or ""). */
|
|
18
18
|
readonly pathname: string;
|
|
19
19
|
readonly search: string;
|
|
20
|
+
/** `history.state` the navigation will push (OptimisticLocation.state). */
|
|
21
|
+
readonly state: unknown;
|
|
20
22
|
/**
|
|
21
23
|
* transition({ when }) gated this navigation off at the swap: the
|
|
22
24
|
* destination presents urgently instead of in a transition lane.
|
|
@@ -37,6 +39,8 @@ export interface ClientUrlDestination {
|
|
|
37
39
|
* params minus these are the mount's (include prefix) params.
|
|
38
40
|
*/
|
|
39
41
|
readonly originLocalParamNames: readonly string[];
|
|
42
|
+
/** `history.state` the navigation will push (ClientUrlNavigationIntent.state). */
|
|
43
|
+
readonly state: unknown;
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
interface ActiveClientUrlGroup {
|
|
@@ -124,6 +128,8 @@ export function registerClientUrlGroup(
|
|
|
124
128
|
export function beginClientUrlNavigation(
|
|
125
129
|
targetUrl: URL,
|
|
126
130
|
signal: AbortSignal,
|
|
131
|
+
/** Builds the entry state; called only when the group presents the target. */
|
|
132
|
+
buildState?: () => unknown,
|
|
127
133
|
decideTransition?: (destination: ClientUrlDestination) => boolean,
|
|
128
134
|
): ClientUrlNavigationPresentation | null {
|
|
129
135
|
const group = activeGroup;
|
|
@@ -149,6 +155,8 @@ export function beginClientUrlNavigation(
|
|
|
149
155
|
: undefined;
|
|
150
156
|
if (canonicalName && activeInterceptTargets.has(canonicalName)) return null;
|
|
151
157
|
|
|
158
|
+
const state = buildState?.();
|
|
159
|
+
|
|
152
160
|
// A same-route intent never swaps (ClientUrlsRoot), so transition({ when })
|
|
153
161
|
// decides at the canonical commit; a cross-route one decides here, at the
|
|
154
162
|
// navigation's first presentation.
|
|
@@ -163,6 +171,7 @@ export function beginClientUrlNavigation(
|
|
|
163
171
|
routeName: canonicalName,
|
|
164
172
|
when: typeof when === "function" ? when : undefined,
|
|
165
173
|
originLocalParamNames: Object.keys(originMatch?.params ?? {}),
|
|
174
|
+
state,
|
|
166
175
|
});
|
|
167
176
|
}
|
|
168
177
|
|
|
@@ -171,6 +180,7 @@ export function beginClientUrlNavigation(
|
|
|
171
180
|
params: match.params,
|
|
172
181
|
pathname: targetUrl.pathname,
|
|
173
182
|
search: targetUrl.search,
|
|
183
|
+
state,
|
|
174
184
|
transitionGatedOff,
|
|
175
185
|
};
|
|
176
186
|
group.intent = intent;
|
|
@@ -6,16 +6,22 @@ import { createContext, type Context } from "react";
|
|
|
6
6
|
* Route identity of an optimistically rendered clientUrls() destination:
|
|
7
7
|
* the values the local trie match produced for the URL the user navigated
|
|
8
8
|
* to. Provided by ClientUrlsRoot around the optimistic branch ONLY, so
|
|
9
|
-
* useParams / usePathname / useSearchParams inside that
|
|
10
|
-
* route being rendered while the same hooks in chrome
|
|
11
|
-
* committed location until the canonical response commits
|
|
12
|
-
* which case the branch — and these values — are discarded).
|
|
9
|
+
* useParams / usePathname / useSearchParams / useLocationState inside that
|
|
10
|
+
* branch describe the route being rendered while the same hooks in chrome
|
|
11
|
+
* outside it keep the committed location until the canonical response commits
|
|
12
|
+
* (or redirects, in which case the branch — and these values — are discarded).
|
|
13
13
|
*/
|
|
14
14
|
export interface OptimisticLocation {
|
|
15
15
|
readonly params: Readonly<Record<string, string>>;
|
|
16
16
|
readonly pathname: string;
|
|
17
17
|
/** Search string including the leading "?" (or ""). */
|
|
18
18
|
readonly search: string;
|
|
19
|
+
/**
|
|
20
|
+
* `history.state` the navigation will push: its `state` option, without
|
|
21
|
+
* what the server adds (that arrives with the canonical commit). History
|
|
22
|
+
* still holds the entry being left, so useLocationState reads this instead.
|
|
23
|
+
*/
|
|
24
|
+
readonly state: unknown;
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
export const OptimisticLocationContext: Context<OptimisticLocation | null> =
|
|
@@ -88,9 +88,20 @@ async function resolveValues(values: unknown[]): Promise<unknown[]> {
|
|
|
88
88
|
* A hung handle promise blocks the full render like any unresolved await (no
|
|
89
89
|
* handle-specific timeout). Partial / action payloads keep streaming via
|
|
90
90
|
* `handleStore.stream()`.
|
|
91
|
+
*
|
|
92
|
+
* `recordedOnly` is a PPR shell capture's render (full-payload.ts): the
|
|
93
|
+
* snapshot is cut down to the pushes the capture's record keeps
|
|
94
|
+
* (getDataForSegment's `excludeLoaderPushes`, the filter captureHandles
|
|
95
|
+
* writes the record with). A HIT restores that record and hydrates with it,
|
|
96
|
+
* so a push the record leaves out must not be in the prelude either: it
|
|
97
|
+
* rendered an element no HIT's hydration data matched (issue #1035). Which
|
|
98
|
+
* pushes a record keeps is the capture funnel's decision (shell-capture.ts
|
|
99
|
+
* deriveShellCaptureContext: today it leaves out a deferred loader push and
|
|
100
|
+
* one holding a masked promise), not this function's.
|
|
91
101
|
*/
|
|
92
102
|
export async function* resolvedHandleStream(
|
|
93
103
|
handleStore: HandleStore,
|
|
104
|
+
recordedOnly?: boolean,
|
|
94
105
|
): AsyncGenerator<HandleData, void, unknown> {
|
|
95
106
|
// Drain stream() (NOT getData()) for the converged snapshot: consuming a
|
|
96
107
|
// stream arms the store's late-push guard (LateHandlePushError for pushes
|
|
@@ -109,9 +120,29 @@ export async function* resolvedHandleStream(
|
|
|
109
120
|
for await (const data of handleStore.stream("settled")) {
|
|
110
121
|
snapshot = data;
|
|
111
122
|
}
|
|
123
|
+
if (recordedOnly) snapshot = recordedHandleData(handleStore, snapshot);
|
|
112
124
|
yield await resolveDeferredHandleValues(snapshot);
|
|
113
125
|
}
|
|
114
126
|
|
|
127
|
+
/** `snapshot` without the pushes a record leaves out (resolvedHandleStream). */
|
|
128
|
+
function recordedHandleData(
|
|
129
|
+
handleStore: HandleStore,
|
|
130
|
+
snapshot: HandleData,
|
|
131
|
+
): HandleData {
|
|
132
|
+
const segmentIds = new Set<string>();
|
|
133
|
+
for (const handleName in snapshot) {
|
|
134
|
+
for (const segmentId in snapshot[handleName]) segmentIds.add(segmentId);
|
|
135
|
+
}
|
|
136
|
+
const recorded: HandleData = {};
|
|
137
|
+
for (const segmentId of segmentIds) {
|
|
138
|
+
const kept = handleStore.getDataForSegment(segmentId, true);
|
|
139
|
+
for (const handleName in kept) {
|
|
140
|
+
(recorded[handleName] ??= {})[segmentId] = kept[handleName];
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return recorded;
|
|
144
|
+
}
|
|
145
|
+
|
|
115
146
|
/**
|
|
116
147
|
* Resolve the deferred (Promise) values in ONE segment's handle bucket
|
|
117
148
|
* (`handleName -> entries[]`). Same shallow + drop-rejections rule as
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
import type { ShellCacheEntry } from "../cache/types.js";
|
|
24
|
+
import type { RouterVersions } from "../router-versions.js";
|
|
24
25
|
import type { MatchResult } from "../types.js";
|
|
25
26
|
import { MemorySegmentCacheStore } from "../cache/memory-segment-store.js";
|
|
26
27
|
import {
|
|
@@ -105,13 +106,14 @@ export interface BuildShellCaptureOptions {
|
|
|
105
106
|
/** Build-time env bindings (rango plugin buildEnv), if configured. */
|
|
106
107
|
buildEnv?: unknown;
|
|
107
108
|
/**
|
|
108
|
-
* The
|
|
109
|
-
*
|
|
110
|
-
* serve-side isValidShellHit gate compares
|
|
111
|
-
* running worker's ctx.version
|
|
112
|
-
*
|
|
109
|
+
* The versions the SHIPPED build serves this router with (the build table,
|
|
110
|
+
* or the router's consumer-set version) — NOT the temp server's own
|
|
111
|
+
* version-plugin stamp. The serve-side isValidShellHit gate compares
|
|
112
|
+
* entry.buildVersion against the running worker's ctx.version, which is the
|
|
113
|
+
* document version; stamping the temp server's would make every build entry
|
|
114
|
+
* an eternal MISS.
|
|
113
115
|
*/
|
|
114
|
-
|
|
116
|
+
versions: RouterVersions;
|
|
115
117
|
/**
|
|
116
118
|
* The SSR half, composed by the plugin from the temp server's SSR
|
|
117
119
|
* environment runner (react-dom/static prerender + Flight client), with the
|
|
@@ -192,7 +194,7 @@ async function attemptBuildCapture(
|
|
|
192
194
|
cacheStore: new MemorySegmentCacheStore(),
|
|
193
195
|
themeConfig: router.themeConfig ?? null,
|
|
194
196
|
stateCookieName: router.resolvedStateCookieName,
|
|
195
|
-
|
|
197
|
+
versions: opts.versions,
|
|
196
198
|
});
|
|
197
199
|
// Scope registry lookups (root-scope/search-schema) per router during the
|
|
198
200
|
// bake, mirroring rsc/handler.ts on the request path (#762).
|
|
@@ -215,7 +217,7 @@ async function attemptBuildCapture(
|
|
|
215
217
|
|
|
216
218
|
const descriptor: ShellCaptureDescriptor = {
|
|
217
219
|
key: opts.key,
|
|
218
|
-
buildVersion: opts.
|
|
220
|
+
buildVersion: opts.versions.document,
|
|
219
221
|
ttl: opts.ttl,
|
|
220
222
|
swr: opts.swr,
|
|
221
223
|
tags: opts.tags,
|
|
@@ -448,7 +450,7 @@ async function runBuildCaptureFinal(
|
|
|
448
450
|
// buildFullPayload reads only ctx.router.* and ctx.version.
|
|
449
451
|
{
|
|
450
452
|
router,
|
|
451
|
-
version: opts.
|
|
453
|
+
version: opts.versions.document,
|
|
452
454
|
} as unknown as HandlerContext<any>,
|
|
453
455
|
url,
|
|
454
456
|
derivedCtx,
|
package/src/router/manifest.ts
CHANGED
|
@@ -38,6 +38,8 @@ const MODULE_LOAD_FAILURE = Symbol.for("rango.moduleLoadFailure");
|
|
|
38
38
|
// LP1 in docs/internal/matching-and-lazy-discovery.md. VERSION comes from the
|
|
39
39
|
// @rangojs/router:version virtual module which Vite invalidates on RSC module HMR.
|
|
40
40
|
// When VERSION changes, this module re-evaluates and the cache is recreated empty.
|
|
41
|
+
// Dev module generation only: in production VERSION is constant per process, so
|
|
42
|
+
// the per-router cache versions play no part in this cache.
|
|
41
43
|
const manifestModuleCache = new Map<string, Map<string, EntryData>>();
|
|
42
44
|
|
|
43
45
|
/**
|
|
@@ -91,15 +91,14 @@
|
|
|
91
91
|
* - Request object
|
|
92
92
|
* - Action context (if POST)
|
|
93
93
|
*/
|
|
94
|
-
import type {
|
|
95
|
-
import type
|
|
96
|
-
import type { EntryData } from "../../server/context.js";
|
|
94
|
+
import type { ResolvedSegment } from "../../types.js";
|
|
95
|
+
import { isPprEntry, type EntryData } from "../../server/context.js";
|
|
97
96
|
import type { MatchContext, MatchPipelineState } from "../match-context.js";
|
|
98
97
|
import { getRouterContext, type RouterContext } from "../router-context.js";
|
|
99
98
|
import { observeEvent } from "../instrument.js";
|
|
100
99
|
import { pushRevalidationTraceEntry, isTraceActive } from "../logging.js";
|
|
101
100
|
import { treeHasStreaming } from "./segment-resolution.js";
|
|
102
|
-
import {
|
|
101
|
+
import { loaderPins } from "../segment-resolution/loader-cache.js";
|
|
103
102
|
import type { PrerenderStore, PrerenderEntry } from "../../prerender/store.js";
|
|
104
103
|
import {
|
|
105
104
|
_getRequestContext,
|
|
@@ -546,20 +545,14 @@ export function withCacheLookup<TEnv>(
|
|
|
546
545
|
return;
|
|
547
546
|
}
|
|
548
547
|
|
|
549
|
-
//
|
|
550
|
-
//
|
|
551
|
-
//
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
claim: (ctx.handlerContext as InternalHandlerContext)._claimLoaderPushes,
|
|
558
|
-
};
|
|
559
|
-
if (tailMarker) {
|
|
560
|
-
ownedPushes.liveLane = liveLaneLoaderIds(ctx.entries);
|
|
561
|
-
ownedPushes.restore = tailMarker.docTail === true;
|
|
562
|
-
}
|
|
548
|
+
// Only a ppr route's records hold loader pushes, and only its shell pins
|
|
549
|
+
// loaders (CachedEntryData.handleOwners, the loader seed): any other
|
|
550
|
+
// route's record restores as a plain replay.
|
|
551
|
+
const leaf = ctx.entries[ctx.entries.length - 1];
|
|
552
|
+
const ownedPushes =
|
|
553
|
+
leaf !== undefined && isPprEntry(leaf)
|
|
554
|
+
? () => loaderPins(ctx.entries, pipelineReqCtx)
|
|
555
|
+
: undefined;
|
|
563
556
|
const explicitLookup = await ctx.cacheScope.lookupRouteDetailed(
|
|
564
557
|
ctx.pathname,
|
|
565
558
|
ctx.matched.params,
|
|
@@ -38,12 +38,31 @@ export interface ResolveNavigationDeps {
|
|
|
38
38
|
* never produce a partial match, so the replay path must decide
|
|
39
39
|
* `no-navigation-context` BEFORE spending shell-store reads — the two
|
|
40
40
|
* predicates drifting apart would misattribute the bypass again.
|
|
41
|
+
*
|
|
42
|
+
* `requestOrigin` is the origin pathname matching uses (`url.origin`), which
|
|
43
|
+
* may not be `request.url`'s. `X-RSC-Router-Client-Path` is returned as sent.
|
|
44
|
+
* A Referer counts only when it resolves against that origin and stays there.
|
|
45
|
+
* A cross-origin Referer is dropped: resolveNavigation matches only the
|
|
46
|
+
* pathname, so a foreign URL would be treated as a route on this app. A value
|
|
47
|
+
* that does not parse is no context either.
|
|
41
48
|
*/
|
|
42
|
-
export function getNavigationContextHeader(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
);
|
|
49
|
+
export function getNavigationContextHeader(
|
|
50
|
+
request: Request,
|
|
51
|
+
requestOrigin: string,
|
|
52
|
+
): string | null {
|
|
53
|
+
const headers = requestHeaders(request);
|
|
54
|
+
const clientPath = headers.get("X-RSC-Router-Client-Path");
|
|
55
|
+
if (clientPath) return clientPath;
|
|
56
|
+
|
|
57
|
+
const referer = headers.get("Referer");
|
|
58
|
+
if (!referer) return null;
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
if (new URL(referer, requestOrigin).origin !== requestOrigin) return null;
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return referer;
|
|
47
66
|
}
|
|
48
67
|
|
|
49
68
|
/**
|
|
@@ -84,7 +103,7 @@ export async function resolveNavigation(
|
|
|
84
103
|
const clientSegmentIds =
|
|
85
104
|
url.searchParams.get("_rsc_segments")?.split(",").filter(Boolean) || [];
|
|
86
105
|
const stale = url.searchParams.get("_rsc_stale") === "true";
|
|
87
|
-
const previousUrl = getNavigationContextHeader(request);
|
|
106
|
+
const previousUrl = getNavigationContextHeader(request, url.origin);
|
|
88
107
|
const interceptSourceUrl = getInterceptSourceHeader(request);
|
|
89
108
|
const isHmr = !!requestHeaders(request).get("X-RSC-HMR");
|
|
90
109
|
|
|
@@ -170,6 +170,21 @@ export async function classifyRequest<TEnv = any>(
|
|
|
170
170
|
const willFullRender =
|
|
171
171
|
!isAction && !isLoaderFetch && !isPartialReq && !isPeRender;
|
|
172
172
|
|
|
173
|
+
// Before the version check: each router has its own version, so a tab of
|
|
174
|
+
// another app always carries one this router does not have. That request is
|
|
175
|
+
// an app switch, not a stale client.
|
|
176
|
+
const clientRouterId = url.searchParams.get("_rsc_rid");
|
|
177
|
+
if (
|
|
178
|
+
clientRouterId &&
|
|
179
|
+
clientRouterId !== deps.routerId &&
|
|
180
|
+
url.searchParams.has("_rsc_partial")
|
|
181
|
+
) {
|
|
182
|
+
return {
|
|
183
|
+
mode: "app-switch",
|
|
184
|
+
reloadUrl: stripInternalParams(url).toString(),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
173
188
|
const clientVersion = url.searchParams.get("_rsc_v");
|
|
174
189
|
if (
|
|
175
190
|
deps.routerVersion &&
|
|
@@ -195,18 +210,6 @@ export async function classifyRequest<TEnv = any>(
|
|
|
195
210
|
};
|
|
196
211
|
}
|
|
197
212
|
|
|
198
|
-
const clientRouterId = url.searchParams.get("_rsc_rid");
|
|
199
|
-
if (
|
|
200
|
-
clientRouterId &&
|
|
201
|
-
clientRouterId !== deps.routerId &&
|
|
202
|
-
url.searchParams.has("_rsc_partial")
|
|
203
|
-
) {
|
|
204
|
-
return {
|
|
205
|
-
mode: "app-switch",
|
|
206
|
-
reloadUrl: stripInternalParams(url).toString(),
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
|
|
210
213
|
const result = await resolveRoute<TEnv>(pathname, {
|
|
211
214
|
findMatch: deps.findMatch,
|
|
212
215
|
lite: true,
|
|
@@ -457,7 +457,8 @@ export interface RangoInternal<
|
|
|
457
457
|
readonly nonce?: NonceProvider<TEnv>;
|
|
458
458
|
|
|
459
459
|
/**
|
|
460
|
-
*
|
|
460
|
+
* The consumer-set version (createRouter({ version })), used for both of the
|
|
461
|
+
* router's versions. Undefined when the router uses its build versions.
|
|
461
462
|
*/
|
|
462
463
|
readonly version?: string;
|
|
463
464
|
|