@rangojs/router 0.26.6 → 0.28.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 +1 -1
- package/dist/types/__internal.d.ts +7 -1
- package/dist/types/browser/action-coordinator.d.ts +0 -4
- package/dist/types/browser/action-fence.d.ts +11 -3
- package/dist/types/browser/event-controller.d.ts +20 -1
- package/dist/types/browser/history-state.d.ts +7 -9
- package/dist/types/browser/intercept-utils.d.ts +23 -0
- package/dist/types/browser/navigation-store.d.ts +8 -6
- package/dist/types/browser/react/filter-segment-order.d.ts +2 -2
- package/dist/types/browser/rsc-router.d.ts +1 -1
- package/dist/types/browser/segment-reconciler.d.ts +6 -4
- package/dist/types/browser/types.d.ts +2 -1
- package/dist/types/browser/wire-protocol.d.ts +25 -0
- package/dist/types/cache/cache-key-utils.d.ts +11 -5
- package/dist/types/cache/cache-scope.d.ts +34 -22
- package/dist/types/cache/cf/cf-cache-constants.d.ts +2 -2
- package/dist/types/cache/cf/cf-cache-store.d.ts +47 -65
- package/dist/types/cache/cf/cf-kv-utils.d.ts +1 -1
- package/dist/types/cache/handle-snapshot.d.ts +9 -4
- package/dist/types/cache/segment-codec.d.ts +14 -0
- package/dist/types/cache/shell-snapshot.d.ts +1 -1
- package/dist/types/cache/tag-invalidation.d.ts +10 -0
- package/dist/types/cache/taint.d.ts +5 -3
- package/dist/types/cache/types.d.ts +7 -3
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +8 -6
- package/dist/types/prefetch-markers.d.ts +4 -6
- package/dist/types/prerender/store.d.ts +31 -0
- package/dist/types/prerender/warm-request.d.ts +3 -3
- package/dist/types/prerender.d.ts +2 -2
- package/dist/types/route-name.d.ts +5 -0
- package/dist/types/router/error-handling.d.ts +4 -4
- package/dist/types/router/instrument.d.ts +4 -3
- package/dist/types/router/intercept-resolution.d.ts +25 -9
- package/dist/types/router/match-context.d.ts +12 -11
- package/dist/types/router/match-handlers.d.ts +2 -6
- package/dist/types/router/match-middleware/background-revalidation.d.ts +10 -20
- package/dist/types/router/match-middleware/cache-lookup.d.ts +35 -87
- package/dist/types/router/match-middleware/cache-store.d.ts +2 -2
- package/dist/types/router/match-middleware/segment-resolution.d.ts +8 -6
- package/dist/types/router/prerender-match.d.ts +21 -6
- package/dist/types/router/preview-match.d.ts +2 -7
- package/dist/types/router/request-classification.d.ts +5 -0
- package/dist/types/router/revalidation.d.ts +6 -1
- package/dist/types/router/router-context.d.ts +9 -63
- package/dist/types/router/router-interfaces.d.ts +8 -14
- package/dist/types/router/segment-resolution/loader-cache.d.ts +3 -3
- package/dist/types/router/segment-resolution/loader-mask.d.ts +4 -4
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +20 -18
- package/dist/types/router/segment-resolution/resolution-pass.d.ts +93 -0
- package/dist/types/router/segment-resolution/revalidation.d.ts +82 -31
- package/dist/types/router/segment-resolution.d.ts +1 -2
- package/dist/types/router/segment-wrappers.d.ts +11 -48
- package/dist/types/router/telemetry.d.ts +2 -0
- package/dist/types/router/types.d.ts +10 -0
- package/dist/types/rsc/full-payload.d.ts +2 -2
- package/dist/types/rsc/handler-context.d.ts +6 -6
- package/dist/types/rsc/json-route-result.d.ts +5 -9
- package/dist/types/rsc/request-stages.d.ts +203 -0
- package/dist/types/rsc/response-cache-serve.d.ts +7 -12
- package/dist/types/rsc/response-route-handler.d.ts +2 -2
- package/dist/types/rsc/routine-plan.d.ts +1 -1
- package/dist/types/rsc/server-action.d.ts +2 -2
- package/dist/types/rsc/shell-capture-constants.d.ts +2 -2
- package/dist/types/rsc/shell-capture.d.ts +132 -15
- package/dist/types/rsc/shell-serve.d.ts +37 -12
- package/dist/types/server/context.d.ts +9 -8
- package/dist/types/server/handle-store.d.ts +16 -0
- package/dist/types/server/render-pass.d.ts +72 -0
- package/dist/types/server/request-context.d.ts +24 -104
- package/dist/types/server/rewrite-trail.d.ts +2 -2
- package/dist/types/testing/dispatch.d.ts +35 -18
- package/dist/types/testing/serve-shell-request.d.ts +7 -0
- package/dist/types/types/cache-types.d.ts +6 -3
- package/dist/types/types/error-types.d.ts +9 -1
- package/dist/types/types/handler-context.d.ts +18 -7
- package/dist/types/types/segments.d.ts +1 -1
- package/dist/types/vite/discovery/build-discovery-phase.d.ts +40 -0
- package/dist/types/vite/discovery/build-env.d.ts +22 -0
- package/dist/types/vite/discovery/bundle-scan-phase.d.ts +12 -0
- package/dist/types/vite/discovery/dev-prerender-cache.d.ts +3 -2
- package/dist/types/vite/discovery/dev-server-phase.d.ts +26 -0
- package/dist/types/vite/discovery/discovery-errors.d.ts +1 -1
- package/dist/types/vite/discovery/state.d.ts +12 -0
- package/dist/types/vite/plugins/version-plugin.d.ts +1 -1
- package/dist/types/vite/plugins/virtual-entries.d.ts +1 -1
- package/dist/types/vite/router-discovery.d.ts +8 -47
- package/dist/types/vite/temp-rsc-server.d.ts +39 -0
- package/dist/types/vite/utils/prerender-utils.d.ts +9 -0
- package/dist/types/wire-params.d.ts +34 -0
- package/dist/vite/index.js +3809 -3710
- package/package.json +1 -1
- package/skills/cache-guide/SKILL.md +8 -5
- package/skills/caching/SKILL.md +37 -18
- package/skills/client-urls/SKILL.md +2 -1
- package/skills/document-cache/SKILL.md +18 -1
- package/skills/handler-use/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +53 -13
- package/skills/layout/SKILL.md +11 -7
- package/skills/loader/SKILL.md +30 -13
- package/skills/middleware/SKILL.md +19 -1
- package/skills/migrate-nextjs/SKILL.md +5 -4
- package/skills/parallel/SKILL.md +5 -4
- package/skills/ppr/SKILL.md +19 -3
- package/skills/prerender/SKILL.md +39 -15
- package/skills/rango/SKILL.md +23 -11
- package/skills/server-actions/SKILL.md +51 -6
- package/skills/testing/predicates.md +1 -1
- package/skills/testing/response-routes.md +6 -4
- package/skills/testing/serve-shell-request.md +7 -7
- package/skills/use-cache/SKILL.md +46 -8
- package/skills/ux-architecture/SKILL.md +3 -2
- package/src/__internal.ts +7 -1
- package/src/browser/action-coordinator.ts +2 -13
- package/src/browser/action-fence.ts +11 -3
- package/src/browser/connection-warmup.ts +3 -1
- package/src/browser/event-controller.ts +42 -3
- package/src/browser/history-state.ts +15 -13
- package/src/browser/intercept-utils.ts +51 -0
- package/src/browser/navigation-bridge.ts +34 -30
- package/src/browser/navigation-client.ts +21 -33
- package/src/browser/navigation-store.ts +25 -27
- package/src/browser/navigation-transaction.ts +7 -11
- package/src/browser/partial-update.ts +8 -0
- package/src/browser/prefetch/cache.ts +2 -1
- package/src/browser/prefetch/fetch.ts +8 -16
- package/src/browser/prefetch/queue.ts +4 -4
- package/src/browser/react/NavigationProvider.tsx +2 -1
- package/src/browser/react/filter-segment-order.ts +2 -2
- package/src/browser/rsc-router.tsx +17 -2
- package/src/browser/segment-reconciler.ts +66 -53
- package/src/browser/server-action-bridge.ts +265 -127
- package/src/browser/types.ts +2 -1
- package/src/browser/wire-protocol.ts +41 -0
- package/src/cache/cache-key-utils.ts +11 -5
- package/src/cache/cache-runtime.ts +110 -50
- package/src/cache/cache-scope.ts +110 -64
- package/src/cache/cf/cf-cache-constants.ts +2 -2
- package/src/cache/cf/cf-cache-store.ts +486 -874
- package/src/cache/cf/cf-kv-utils.ts +1 -1
- package/src/cache/document-cache.ts +42 -15
- package/src/cache/handle-snapshot.ts +19 -7
- package/src/cache/segment-codec.ts +55 -0
- package/src/cache/shell-snapshot.ts +2 -2
- package/src/cache/tag-invalidation.ts +21 -2
- package/src/cache/taint.ts +5 -3
- package/src/cache/types.ts +7 -3
- package/src/cache/vercel/vercel-cache-store.ts +243 -310
- package/src/handles/pushed-by.ts +1 -1
- package/src/prefetch-markers.ts +4 -7
- package/src/prerender/store.ts +76 -0
- package/src/prerender/warm-request.ts +8 -4
- package/src/prerender.ts +2 -2
- package/src/route-name.ts +8 -0
- package/src/router/error-handling.ts +4 -4
- package/src/router/instrument.ts +4 -3
- package/src/router/intercept-resolution.ts +77 -75
- package/src/router/loader-resolution.ts +5 -4
- package/src/router/match-api.ts +4 -5
- package/src/router/match-context.ts +33 -12
- package/src/router/match-handlers.ts +8 -20
- package/src/router/match-middleware/background-revalidation.ts +41 -53
- package/src/router/match-middleware/cache-lookup.ts +680 -720
- package/src/router/match-middleware/cache-store.ts +14 -12
- package/src/router/match-middleware/intercept-resolution.ts +3 -23
- package/src/router/match-middleware/segment-resolution.ts +34 -61
- package/src/router/match-result.ts +1 -20
- package/src/router/middleware.ts +69 -11
- package/src/router/navigation-snapshot.ts +4 -3
- package/src/router/prerender-match.ts +100 -44
- package/src/router/preview-match.ts +6 -41
- package/src/router/request-classification.ts +13 -7
- package/src/router/revalidation.ts +33 -7
- package/src/router/route-snapshot.ts +2 -5
- package/src/router/router-context.ts +14 -117
- package/src/router/router-interfaces.ts +8 -14
- package/src/router/segment-resolution/loader-cache.ts +14 -9
- package/src/router/segment-resolution/loader-mask.ts +5 -5
- package/src/router/segment-resolution/prefetch-deferral.ts +30 -27
- package/src/router/segment-resolution/resolution-pass.ts +137 -0
- package/src/router/segment-resolution/revalidation.ts +897 -776
- package/src/router/segment-resolution.ts +4 -13
- package/src/router/segment-wrappers.ts +51 -269
- package/src/router/telemetry.ts +5 -0
- package/src/router/types.ts +10 -0
- package/src/router.ts +12 -13
- package/src/rsc/full-payload.ts +6 -5
- package/src/rsc/handler-context.ts +8 -14
- package/src/rsc/handler.ts +126 -507
- package/src/rsc/json-route-result.ts +5 -9
- package/src/rsc/loader-fetch.ts +6 -5
- package/src/rsc/progressive-enhancement.ts +5 -0
- package/src/rsc/request-stages.ts +757 -0
- package/src/rsc/response-cache-serve.ts +10 -14
- package/src/rsc/response-route-handler.ts +13 -5
- package/src/rsc/rewrite-passes.ts +2 -1
- package/src/rsc/routine-plan.ts +1 -1
- package/src/rsc/rsc-rendering.ts +172 -349
- package/src/rsc/server-action.ts +87 -113
- package/src/rsc/shell-capture-constants.ts +3 -1
- package/src/rsc/shell-capture.ts +212 -169
- package/src/rsc/shell-serve.ts +121 -18
- package/src/rsc/ssr-setup.ts +5 -4
- package/src/segment-system.tsx +3 -3
- package/src/server/context.ts +22 -25
- package/src/server/handle-store.ts +55 -0
- package/src/server/render-pass.ts +291 -0
- package/src/server/request-context.ts +26 -115
- package/src/server/rewrite-trail.ts +6 -4
- package/src/ssr/index.tsx +1 -1
- package/src/ssr/ssr-root.tsx +1 -0
- package/src/testing/dispatch.ts +223 -710
- package/src/testing/e2e/page-helpers.ts +2 -2
- package/src/testing/serve-shell-request.ts +37 -32
- package/src/types/cache-types.ts +6 -3
- package/src/types/error-types.ts +9 -1
- package/src/types/handler-context.ts +21 -7
- package/src/types/segments.ts +1 -1
- package/src/vite/discovery/build-discovery-phase.ts +204 -0
- package/src/vite/discovery/build-env.ts +148 -0
- package/src/vite/discovery/bundle-scan-phase.ts +128 -0
- package/src/vite/discovery/dev-prerender-cache.ts +8 -3
- package/src/vite/discovery/dev-server-phase.ts +1776 -0
- package/src/vite/discovery/discover-routers.ts +1 -1
- package/src/vite/discovery/discovery-errors.ts +1 -1
- package/src/vite/discovery/prerender-collection.ts +24 -39
- package/src/vite/discovery/state.ts +15 -0
- package/src/vite/plugins/version-plugin.ts +1 -1
- package/src/vite/plugins/virtual-entries.ts +1 -1
- package/src/vite/router-discovery.ts +29 -2428
- package/src/vite/temp-rsc-server.ts +223 -0
- package/src/vite/utils/prerender-utils.ts +47 -0
- package/src/wire-params.ts +35 -0
- package/dist/types/router/segment-resolution/fresh.d.ts +0 -72
- package/src/router/segment-resolution/fresh.ts +0 -945
|
@@ -52,6 +52,18 @@ export interface NavigationEntry {
|
|
|
52
52
|
phase: NavigationPhase;
|
|
53
53
|
startedAt: number;
|
|
54
54
|
options?: NavigateOptionsInternal & { skipLoadingState?: boolean };
|
|
55
|
+
/**
|
|
56
|
+
* Resolves once the navigation has ended: its transaction ended and every
|
|
57
|
+
* stream it opened closed. True when it committed, false when it ended
|
|
58
|
+
* without a commit (a failure) or was left (aborted, which settles it at
|
|
59
|
+
* once). A `prefetch: false` fill holds the transaction open
|
|
60
|
+
* (partial-update.ts awaits fillHoles before it resolves) and its stream
|
|
61
|
+
* until the fill's answer streamed, so a waiter never cuts a fill or a
|
|
62
|
+
* stream short. Only the action bridge's follow-up awaits it
|
|
63
|
+
* (server-action-bridge.ts followUp); no hook reads it, and settling it
|
|
64
|
+
* notifies nothing.
|
|
65
|
+
*/
|
|
66
|
+
settled: Promise<boolean>;
|
|
55
67
|
}
|
|
56
68
|
|
|
57
69
|
/**
|
|
@@ -246,7 +258,8 @@ export interface EventController {
|
|
|
246
258
|
/** Originating history entry key; scopes location-state arbitration. */
|
|
247
259
|
cohort?: string,
|
|
248
260
|
): ActionHandle;
|
|
249
|
-
|
|
261
|
+
/** Abort every action in flight except `except` (an ActionHandle id). */
|
|
262
|
+
abortAllActions(except?: string): void;
|
|
250
263
|
|
|
251
264
|
// State access
|
|
252
265
|
getState(): DerivedNavigationState;
|
|
@@ -351,6 +364,12 @@ export interface EventController {
|
|
|
351
364
|
|
|
352
365
|
// Direct state access for advanced use
|
|
353
366
|
getCurrentNavigation(): NavigationEntry | null;
|
|
367
|
+
/**
|
|
368
|
+
* The newest navigation until it settles (NavigationEntry.settled): before
|
|
369
|
+
* its commit, and after it while its transaction or a stream is still
|
|
370
|
+
* open. Null once it settled or was left.
|
|
371
|
+
*/
|
|
372
|
+
getUnsettledNavigation(): NavigationEntry | null;
|
|
354
373
|
getInflightActions(): Map<string, ActionEntry>;
|
|
355
374
|
/** Whether any concurrent actions have occurred (shared across all handles) */
|
|
356
375
|
hadAnyConcurrentActions(): boolean;
|
|
@@ -513,6 +532,9 @@ export function createEventController(
|
|
|
513
532
|
// links a request to its signal only until then): the navigation stops
|
|
514
533
|
// reading it, and it streams to its end.
|
|
515
534
|
let streamingNavigation: AbortController | null = null;
|
|
535
|
+
// The newest navigation until it settles (getUnsettledNavigation). Not
|
|
536
|
+
// derived state: no hook reads it, and moving it notifies nothing.
|
|
537
|
+
let unsettledNavigation: NavigationEntry | null = null;
|
|
516
538
|
|
|
517
539
|
const inflightActions = new Map<string, ActionEntry>();
|
|
518
540
|
|
|
@@ -743,19 +765,31 @@ export function createEventController(
|
|
|
743
765
|
}
|
|
744
766
|
|
|
745
767
|
const abort = new AbortController();
|
|
768
|
+
let resolveSettled!: (committed: boolean) => void;
|
|
746
769
|
const entry: NavigationEntry = {
|
|
747
770
|
url,
|
|
748
771
|
abort,
|
|
749
772
|
phase: "fetching",
|
|
750
773
|
startedAt: Date.now(),
|
|
751
774
|
options,
|
|
775
|
+
settled: new Promise((resolve) => (resolveSettled = resolve)),
|
|
776
|
+
};
|
|
777
|
+
const settle = (committed: boolean): void => {
|
|
778
|
+
if (unsettledNavigation === entry) unsettledNavigation = null;
|
|
779
|
+
resolveSettled(committed);
|
|
752
780
|
};
|
|
781
|
+
abort.signal.addEventListener("abort", () => settle(false));
|
|
753
782
|
|
|
754
783
|
currentNavigation = entry;
|
|
784
|
+
unsettledNavigation = entry;
|
|
755
785
|
notify();
|
|
756
786
|
|
|
757
787
|
let completed = false;
|
|
788
|
+
let disposed = false;
|
|
758
789
|
let openStreams = 0;
|
|
790
|
+
const settleIfEnded = (): void => {
|
|
791
|
+
if (disposed && openStreams === 0) settle(completed);
|
|
792
|
+
};
|
|
759
793
|
|
|
760
794
|
return {
|
|
761
795
|
abort,
|
|
@@ -780,6 +814,7 @@ export function createEventController(
|
|
|
780
814
|
if (--openStreams === 0 && streamingNavigation === abort) {
|
|
781
815
|
streamingNavigation = null;
|
|
782
816
|
}
|
|
817
|
+
settleIfEnded();
|
|
783
818
|
notify();
|
|
784
819
|
},
|
|
785
820
|
};
|
|
@@ -797,6 +832,8 @@ export function createEventController(
|
|
|
797
832
|
|
|
798
833
|
// Disposable: cleanup if not completed (e.g., error thrown)
|
|
799
834
|
[Symbol.dispose]() {
|
|
835
|
+
disposed = true;
|
|
836
|
+
settleIfEnded();
|
|
800
837
|
// If aborted by another navigation, don't touch state
|
|
801
838
|
if (abort.signal.aborted) return;
|
|
802
839
|
|
|
@@ -1072,13 +1109,14 @@ export function createEventController(
|
|
|
1072
1109
|
};
|
|
1073
1110
|
}
|
|
1074
1111
|
|
|
1075
|
-
|
|
1112
|
+
/** Abort every action in flight except `except` (an ActionHandle id). */
|
|
1113
|
+
function abortAllActions(except?: string) {
|
|
1076
1114
|
for (const [id, entry] of inflightActions) {
|
|
1077
1115
|
// Preserve settling entries — they have already been handled by
|
|
1078
1116
|
// fail()/complete() and will self-cleanup via the settlement timeout.
|
|
1079
1117
|
// Clearing them here would prevent debounced notifications from
|
|
1080
1118
|
// delivering the error/result state to subscribers.
|
|
1081
|
-
if (entry.phase === "settling") continue;
|
|
1119
|
+
if (entry.phase === "settling" || id === except) continue;
|
|
1082
1120
|
entry.abort.abort();
|
|
1083
1121
|
inflightActions.delete(id);
|
|
1084
1122
|
}
|
|
@@ -1287,6 +1325,7 @@ export function createEventController(
|
|
|
1287
1325
|
|
|
1288
1326
|
// Direct access
|
|
1289
1327
|
getCurrentNavigation: () => currentNavigation,
|
|
1328
|
+
getUnsettledNavigation: () => unsettledNavigation,
|
|
1290
1329
|
getInflightActions: () => inflightActions,
|
|
1291
1330
|
hadAnyConcurrentActions: () => hadAnyConcurrentActions,
|
|
1292
1331
|
};
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
resolveLocationStateEntries,
|
|
10
10
|
stampLocationState,
|
|
11
11
|
} from "./react/location-state-shared.js";
|
|
12
|
+
import { WIRE } from "../wire-params.js";
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Check if state is from typed LocationStateEntry[] (has __rsc_ls_ keys)
|
|
@@ -137,16 +138,9 @@ export function pushHistoryWithIdx(
|
|
|
137
138
|
}
|
|
138
139
|
|
|
139
140
|
/**
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
* cache miss.
|
|
144
|
-
*/
|
|
145
|
-
export const SHELL_MISS_MARKER: string = "_rsc_shell";
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Drop the forced-MISS marker from the address bar at boot, keeping the
|
|
149
|
-
* history entry's state (initBrowserApp calls it before any location read).
|
|
141
|
+
* Drop the forced-MISS marker (`WIRE.shellMiss`, rsc/shell-serve.ts) from the
|
|
142
|
+
* address bar at boot, keeping the history entry's state (initBrowserApp
|
|
143
|
+
* calls it before any location read).
|
|
150
144
|
* Left in place, a refresh or a shared link would stay off the shell and the
|
|
151
145
|
* app's search params would carry it. The server strips the same marker from
|
|
152
146
|
* the request before rendering (shell-serve.ts withoutShellMissMarker), so
|
|
@@ -154,8 +148,8 @@ export const SHELL_MISS_MARKER: string = "_rsc_shell";
|
|
|
154
148
|
*/
|
|
155
149
|
export function stripShellMissMarker(): void {
|
|
156
150
|
const url = new URL(window.location.href);
|
|
157
|
-
if (!url.searchParams.has(
|
|
158
|
-
url.searchParams.delete(
|
|
151
|
+
if (!url.searchParams.has(WIRE.shellMiss)) return;
|
|
152
|
+
url.searchParams.delete(WIRE.shellMiss);
|
|
159
153
|
window.history.replaceState(window.history.state, "", url.href);
|
|
160
154
|
}
|
|
161
155
|
|
|
@@ -169,13 +163,21 @@ export function stripShellMissMarker(): void {
|
|
|
169
163
|
*
|
|
170
164
|
* A slot is recognised by its key alone (the `~r` suffix): at start-up the
|
|
171
165
|
* module that defines it may not be loaded.
|
|
166
|
+
*
|
|
167
|
+
* The same pass drops the entry's intercept marks (`intercept`,
|
|
168
|
+
* `sourceUrl`): a document request never renders an intercept, so the entry
|
|
169
|
+
* is a full page for later Back/Forward.
|
|
172
170
|
*/
|
|
173
171
|
export function clearLocationStateOnDocumentLoad(): void {
|
|
174
172
|
const state: unknown = window.history.state;
|
|
175
173
|
if (state === null || typeof state !== "object") return;
|
|
176
174
|
let next: Record<string, unknown> | undefined;
|
|
177
175
|
for (const key in state) {
|
|
178
|
-
if (
|
|
176
|
+
if (
|
|
177
|
+
(isLocationStateKey(key) && key.endsWith(CLEAR_ON_RELOAD_KEY_SUFFIX)) ||
|
|
178
|
+
key === "intercept" ||
|
|
179
|
+
key === "sourceUrl"
|
|
180
|
+
) {
|
|
179
181
|
next ??= { ...state };
|
|
180
182
|
delete next[key];
|
|
181
183
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { ResolvedSegment } from "./types.js";
|
|
2
2
|
import type { SlotState } from "../types.js";
|
|
3
|
+
import { isPlaceholder } from "../types/segments.js";
|
|
4
|
+
import { loaderParentId } from "./react/filter-segment-order.js";
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Check if a segment is an intercept segment.
|
|
@@ -51,3 +53,52 @@ export function hasActiveIntercept(slots?: Record<string, SlotState>): boolean {
|
|
|
51
53
|
export function isInterceptOnlyCache(segments: ResolvedSegment[]): boolean {
|
|
52
54
|
return segments.some(isInterceptSegment);
|
|
53
55
|
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* An intercept's `matched`: the client's ids without any of the slot's (its
|
|
59
|
+
* id and every loader segment on it, another intercept's into the same slot
|
|
60
|
+
* included), then the slot's (interceptSegmentIds). One order for the
|
|
61
|
+
* prefetch, its fill, an action and a revisit: a fill whose `matched` differs
|
|
62
|
+
* from its tree's is rendered as a new tree (browser/partial-update.ts
|
|
63
|
+
* fillHoles). Shared by router/match-result.ts and wholeInterceptMatched.
|
|
64
|
+
*/
|
|
65
|
+
export function interceptMatched(
|
|
66
|
+
clientIds: readonly string[],
|
|
67
|
+
slotIds: readonly string[],
|
|
68
|
+
): string[] {
|
|
69
|
+
// loaderParentId leaves a non-loader id as it is: the slot's own id goes too.
|
|
70
|
+
return [
|
|
71
|
+
...clientIds.filter((id) => loaderParentId(id) !== slotIds[0]),
|
|
72
|
+
...slotIds,
|
|
73
|
+
];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The `matched` the browser keeps for an intercept answer to a request that
|
|
78
|
+
* held no segments (a followed redirect, a Back/Forward revalidation with
|
|
79
|
+
* handles pending, the dev HMR refetch), or undefined to keep the answer's.
|
|
80
|
+
* The server never renders the page under an intercept and lists it only
|
|
81
|
+
* from the ids a request held, so such an answer has an empty outlet under
|
|
82
|
+
* the slot. The page it lands over (`held`) stays, as the server lists it for
|
|
83
|
+
* a request that holds it.
|
|
84
|
+
*/
|
|
85
|
+
export function wholeInterceptMatched(
|
|
86
|
+
held: readonly ResolvedSegment[],
|
|
87
|
+
answer: {
|
|
88
|
+
matched?: string[];
|
|
89
|
+
segments?: ResolvedSegment[];
|
|
90
|
+
slots?: Record<string, SlotState>;
|
|
91
|
+
},
|
|
92
|
+
): string[] | undefined {
|
|
93
|
+
if (!hasActiveIntercept(answer.slots)) return undefined;
|
|
94
|
+
const heldIds = held.filter((s) => !isPlaceholder(s)).map((s) => s.id);
|
|
95
|
+
if (heldIds.length === 0) return undefined;
|
|
96
|
+
const slot = answer.segments?.find(
|
|
97
|
+
(s) => isInterceptSegment(s) && s.type !== "loader",
|
|
98
|
+
);
|
|
99
|
+
if (!slot) return undefined;
|
|
100
|
+
const slotLoaderIds = (answer.matched ?? []).filter(
|
|
101
|
+
(id) => id !== slot.id && loaderParentId(id) === slot.id,
|
|
102
|
+
);
|
|
103
|
+
return interceptMatched(heldIds, [slot.id, ...slotLoaderIds]);
|
|
104
|
+
}
|
|
@@ -273,15 +273,18 @@ export function createNavigationBridge(
|
|
|
273
273
|
targetUrl.pathname === new URL(window.location.href).pathname
|
|
274
274
|
) {
|
|
275
275
|
// Preserve intercept context from the current history entry so that
|
|
276
|
-
// popstate uses the
|
|
277
|
-
//
|
|
276
|
+
// popstate uses the same cache key and restores the right full-page
|
|
277
|
+
// vs intercept semantics.
|
|
278
278
|
const currentHistoryState = window.history.state;
|
|
279
279
|
const isIntercept = currentHistoryState?.intercept === true;
|
|
280
280
|
const interceptSourceUrl = isIntercept
|
|
281
281
|
? currentHistoryState?.sourceUrl
|
|
282
282
|
: undefined;
|
|
283
283
|
|
|
284
|
-
const historyKey = generateHistoryKey(url, {
|
|
284
|
+
const historyKey = generateHistoryKey(url, {
|
|
285
|
+
intercept: isIntercept,
|
|
286
|
+
interceptSource: interceptSourceUrl,
|
|
287
|
+
});
|
|
285
288
|
|
|
286
289
|
// Copy current segments to the new history key so back/forward restores instantly.
|
|
287
290
|
// The array itself, not a clone: getPageHistoryKeys reads a shared
|
|
@@ -405,12 +408,6 @@ export function createNavigationBridge(
|
|
|
405
408
|
});
|
|
406
409
|
}
|
|
407
410
|
|
|
408
|
-
// Also check if there's an intercept cache entry for this URL
|
|
409
|
-
// If so, this URL CAN be intercepted, and we shouldn't use the non-intercept cache
|
|
410
|
-
// because the navigation might result in an intercept (depending on source URL)
|
|
411
|
-
const interceptHistoryKey = generateHistoryKey(url, { intercept: true });
|
|
412
|
-
const hasInterceptCache = store.hasHistoryCache(interceptHistoryKey);
|
|
413
|
-
|
|
414
411
|
// Skip cached SWR for:
|
|
415
412
|
// 1. intercept caches - interception depends on source page context
|
|
416
413
|
// 2. routes that CAN be intercepted - we don't know if this navigation will intercept
|
|
@@ -420,10 +417,12 @@ export function createNavigationBridge(
|
|
|
420
417
|
const hasUsableCache =
|
|
421
418
|
isRestorable(cachedSegments) &&
|
|
422
419
|
!isInterceptOnlyCache(cachedSegments) &&
|
|
423
|
-
!hasInterceptCache &&
|
|
424
420
|
!isLeavingIntercept &&
|
|
425
421
|
!cached?.stale &&
|
|
426
|
-
!options?._skipCache
|
|
422
|
+
!options?._skipCache &&
|
|
423
|
+
// A cached intercept entry for this URL (from any source page) means
|
|
424
|
+
// the navigation might intercept, depending on the source URL.
|
|
425
|
+
!store.hasInterceptCache(historyKey);
|
|
427
426
|
|
|
428
427
|
// Forward navigations always await fetchPartialUpdate before rendering,
|
|
429
428
|
// so useNavigation should always report "loading". skipLoadingState is
|
|
@@ -643,16 +642,17 @@ export function createNavigationBridge(
|
|
|
643
642
|
const isIntercept = historyState?.intercept === true;
|
|
644
643
|
const interceptSourceUrl = historyState?.sourceUrl;
|
|
645
644
|
|
|
646
|
-
//
|
|
647
|
-
//
|
|
645
|
+
// Actions in flight keep running across an intercept change, as across
|
|
646
|
+
// any navigation (server-action-bridge.ts followUp).
|
|
648
647
|
const currentInterceptSource = store.getInterceptSourceUrl();
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
648
|
+
// An intercept is fetched only from its source page (the page under
|
|
649
|
+
// an open intercept counts as on screen). From any other page the
|
|
650
|
+
// entry loads as a document, which renders its URL's full page.
|
|
651
|
+
const loadsDocument =
|
|
652
|
+
isIntercept &&
|
|
653
|
+
generateHistoryKey(
|
|
654
|
+
currentInterceptSource ?? store.getSegmentState().currentUrl,
|
|
655
|
+
) !== generateHistoryKey(interceptSourceUrl);
|
|
656
656
|
|
|
657
657
|
// Popstate that exits an intercept to a non-intercept destination. The
|
|
658
658
|
// fallback fetch path below needs `leave-intercept` mode so it filters
|
|
@@ -663,8 +663,10 @@ export function createNavigationBridge(
|
|
|
663
663
|
const isLeavingIntercept =
|
|
664
664
|
!isIntercept && currentInterceptSource !== null;
|
|
665
665
|
|
|
666
|
-
|
|
667
|
-
|
|
666
|
+
const historyKey = generateHistoryKey(url, {
|
|
667
|
+
intercept: isIntercept,
|
|
668
|
+
interceptSource: interceptSourceUrl,
|
|
669
|
+
});
|
|
668
670
|
|
|
669
671
|
debugLog(
|
|
670
672
|
"[Browser] Popstate -",
|
|
@@ -673,13 +675,6 @@ export function createNavigationBridge(
|
|
|
673
675
|
historyKey,
|
|
674
676
|
);
|
|
675
677
|
|
|
676
|
-
// If this is an intercept, restore the intercept context
|
|
677
|
-
if (isIntercept && interceptSourceUrl) {
|
|
678
|
-
store.setInterceptSourceUrl(interceptSourceUrl);
|
|
679
|
-
} else {
|
|
680
|
-
store.setInterceptSourceUrl(null);
|
|
681
|
-
}
|
|
682
|
-
|
|
683
678
|
// Helper to check if streaming is in progress
|
|
684
679
|
const isStreaming = () => eventController.getState().isStreaming;
|
|
685
680
|
|
|
@@ -701,6 +696,7 @@ export function createNavigationBridge(
|
|
|
701
696
|
const isStale = (cached?.stale ?? false) || isActionFenceActive();
|
|
702
697
|
|
|
703
698
|
if (isRestorable(cachedSegments)) {
|
|
699
|
+
store.setInterceptSourceUrl(interceptSourceUrl ?? null);
|
|
704
700
|
// transition({ when }) decides the restore (kind "pop") against the
|
|
705
701
|
// entry being left, so before the store moves to the restored entry.
|
|
706
702
|
// The decision the entry was committed with is not reused: it was
|
|
@@ -872,7 +868,15 @@ export function createNavigationBridge(
|
|
|
872
868
|
debugLog("[Browser] History cache miss for key:", historyKey);
|
|
873
869
|
}
|
|
874
870
|
|
|
875
|
-
// Fetch if not cached
|
|
871
|
+
// Fetch if not cached. The document load starts at the top: pagehide
|
|
872
|
+
// saves the scroll on screen under this entry, and the browser
|
|
873
|
+
// restores it after the reload. Instant, so `scroll-behavior: smooth`
|
|
874
|
+
// cannot leave it part-way when pagehide saves it.
|
|
875
|
+
if (loadsDocument) {
|
|
876
|
+
window.scrollTo({ top: 0, behavior: "instant" });
|
|
877
|
+
return window.location.reload();
|
|
878
|
+
}
|
|
879
|
+
store.setInterceptSourceUrl(interceptSourceUrl ?? null);
|
|
876
880
|
const tx = createNavigationTransaction(store, eventController, url, {
|
|
877
881
|
replace: true,
|
|
878
882
|
});
|
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
type DecodedPrefetch,
|
|
39
39
|
} from "./prefetch/cache.js";
|
|
40
40
|
import { cancelAllPrefetches } from "./prefetch/loader.js";
|
|
41
|
-
import {
|
|
41
|
+
import { writePartialParams } from "./wire-protocol.js";
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
44
|
* Create a navigation client for fetching RSC payloads
|
|
@@ -103,30 +103,23 @@ export function createNavigationClient(
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
// Build fetch URL with partial rendering params (used for both
|
|
106
|
-
// cache key lookup and actual fetch if cache misses)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
// A param, not a header: the document cache decides from the raw URL
|
|
125
|
-
// (cache/document-cache.ts), and it keeps the fill out of every
|
|
126
|
-
// prefetch key below.
|
|
127
|
-
if (fill) {
|
|
128
|
-
fetchUrl.searchParams.set(FILL_PARAM, "1");
|
|
129
|
-
}
|
|
106
|
+
// cache key lookup and actual fetch if cache misses). The page left's
|
|
107
|
+
// rewrite target (#1144) is in both prefetch keys, like the params
|
|
108
|
+
// around it. The fill is a param, not a header: the document cache
|
|
109
|
+
// decides from the raw URL (cache/document-cache.ts), and it keeps the
|
|
110
|
+
// fill out of every prefetch key below.
|
|
111
|
+
const fetchUrl = writePartialParams(
|
|
112
|
+
new URL(targetUrl, window.location.origin),
|
|
113
|
+
{
|
|
114
|
+
segmentIds,
|
|
115
|
+
emptySegments: "send",
|
|
116
|
+
stale: staleRevalidation,
|
|
117
|
+
version,
|
|
118
|
+
routerId,
|
|
119
|
+
rewriteTarget,
|
|
120
|
+
fill,
|
|
121
|
+
},
|
|
122
|
+
);
|
|
130
123
|
|
|
131
124
|
// If the lazy prefetch runtime has not loaded yet, prevent a deferred
|
|
132
125
|
// speculative request from starting after this navigation misses the
|
|
@@ -144,15 +137,10 @@ export function createNavigationClient(
|
|
|
144
137
|
// server-action invalidation) auto-invalidates both scopes.
|
|
145
138
|
// Skip cache for stale revalidation (needs fresh data), HMR (needs
|
|
146
139
|
// fresh modules), and intercept contexts (source-dependent responses).
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
// action's response decides whether anything changed.
|
|
140
|
+
// A prefetch served during an action's flight holds pre-write data:
|
|
141
|
+
// the action bridge's follow-up refetches the page it commits.
|
|
150
142
|
const canUsePrefetch =
|
|
151
|
-
!staleRevalidation &&
|
|
152
|
-
!hmr &&
|
|
153
|
-
!fill &&
|
|
154
|
-
!interceptSourceUrl &&
|
|
155
|
-
!isActionFenceActive();
|
|
143
|
+
!staleRevalidation && !hmr && !fill && !interceptSourceUrl;
|
|
156
144
|
const rangoState = getRangoState();
|
|
157
145
|
const wildcardKey = buildPrefetchKey(rangoState, fetchUrl);
|
|
158
146
|
const cacheKey = buildSourceKey(rangoState, previousUrl, fetchUrl);
|
|
@@ -34,7 +34,7 @@ const HISTORY_ENTRY_MEMORY_SIZE = 200;
|
|
|
34
34
|
// stale=true means the data may be outdated and should be revalidated on access.
|
|
35
35
|
// navInstance is the monotonic nav-instance token (see navInstance below): it
|
|
36
36
|
// identifies the per-commit visit that owns this entry. generateHistoryKey is
|
|
37
|
-
// URL
|
|
37
|
+
// the URL (plus the source page for an intercept), so A->B->A reuses the key; the token lets a late async
|
|
38
38
|
// resolution tell its own visit's entry apart from a newer same-URL visit's, so
|
|
39
39
|
// a stale nav can never clobber a fresher one.
|
|
40
40
|
// handlesPending=true means the entry's handle data is INCOMPLETE (a deferred
|
|
@@ -124,36 +124,35 @@ function getCacheInvalidationChannel(): BroadcastChannel | null {
|
|
|
124
124
|
* Options for generating a history key
|
|
125
125
|
*/
|
|
126
126
|
export interface HistoryKeyOptions {
|
|
127
|
-
/**
|
|
127
|
+
/** Key an intercept entry (apart from the plain entry for the URL). */
|
|
128
128
|
intercept?: boolean;
|
|
129
|
+
/** The page the intercept was opened over (history.state.sourceUrl). */
|
|
130
|
+
interceptSource?: string;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function urlKey(url: string): string {
|
|
134
|
+
const parsed = new URL(url, "http://localhost");
|
|
135
|
+
return parsed.pathname + parsed.search;
|
|
129
136
|
}
|
|
130
137
|
|
|
131
138
|
/**
|
|
132
|
-
* Generate a cache key from a URL.
|
|
133
|
-
*
|
|
134
|
-
* Hash fragments (#) are excluded since they don't affect server data.
|
|
139
|
+
* Generate a cache key from a URL: pathname + search. Hash fragments (#) are
|
|
140
|
+
* excluded since they don't affect server data.
|
|
135
141
|
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
142
|
+
* An intercept entry is keyed `<url>:intercept:<source>`: its cached tree
|
|
143
|
+
* includes the page under the intercept. hasInterceptCache relies on the
|
|
144
|
+
* `:intercept:` separator.
|
|
138
145
|
*/
|
|
139
146
|
export function generateHistoryKey(
|
|
140
147
|
url?: string,
|
|
141
148
|
options?: HistoryKeyOptions,
|
|
142
149
|
): string {
|
|
143
|
-
|
|
144
|
-
url
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
let key = parsed.pathname + parsed.search;
|
|
150
|
-
|
|
151
|
-
// Append intercept suffix for separate caching
|
|
152
|
-
if (options?.intercept) {
|
|
153
|
-
key += ":intercept";
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return key;
|
|
150
|
+
const key = urlKey(
|
|
151
|
+
url || (typeof window !== "undefined" ? window.location.href : "/"),
|
|
152
|
+
);
|
|
153
|
+
if (!options?.intercept) return key;
|
|
154
|
+
const source = options.interceptSource;
|
|
155
|
+
return `${key}:intercept:${source ? urlKey(source) : ""}`;
|
|
157
156
|
}
|
|
158
157
|
|
|
159
158
|
/**
|
|
@@ -258,7 +257,8 @@ export function createNavigationStore(
|
|
|
258
257
|
|
|
259
258
|
// Monotonic nav-instance token. Bumped each time a cache entry is created or
|
|
260
259
|
// replaced in cacheSegmentsForHistory (i.e. once per commit). Because
|
|
261
|
-
// generateHistoryKey is URL
|
|
260
|
+
// generateHistoryKey is the URL (plus the source page for an intercept), two
|
|
261
|
+
// visits to the same URL share a key; this
|
|
262
262
|
// token gives each visit a distinct identity so a late async handle resolution
|
|
263
263
|
// can tell whether it still owns the live page / the target cache entry, and
|
|
264
264
|
// never overwrite a newer same-URL visit's state.
|
|
@@ -580,11 +580,9 @@ export function createNavigationStore(
|
|
|
580
580
|
};
|
|
581
581
|
},
|
|
582
582
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
hasHistoryCache(historyKey: string): boolean {
|
|
587
|
-
return historyCache.some(([key]) => key === historyKey);
|
|
583
|
+
hasInterceptCache(historyKey: string): boolean {
|
|
584
|
+
const prefix = `${historyKey}:intercept:`;
|
|
585
|
+
return historyCache.some(([key]) => key.startsWith(prefix));
|
|
588
586
|
},
|
|
589
587
|
|
|
590
588
|
/**
|
|
@@ -156,7 +156,6 @@ export function createNavigationTransaction(
|
|
|
156
156
|
url: string,
|
|
157
157
|
options?: NavigateOptionsInternal & { skipLoadingState?: boolean },
|
|
158
158
|
): NavigationTransaction {
|
|
159
|
-
let committed = false;
|
|
160
159
|
const currentUrl = window.location.href;
|
|
161
160
|
|
|
162
161
|
const handle = eventController.startNavigation(url, options);
|
|
@@ -165,8 +164,6 @@ export function createNavigationTransaction(
|
|
|
165
164
|
* Commit the navigation - updates store and URL atomically
|
|
166
165
|
*/
|
|
167
166
|
function commit(opts: CommitOptions): CommitResult {
|
|
168
|
-
committed = true;
|
|
169
|
-
|
|
170
167
|
const {
|
|
171
168
|
url,
|
|
172
169
|
segmentIds,
|
|
@@ -183,7 +180,10 @@ export function createNavigationTransaction(
|
|
|
183
180
|
|
|
184
181
|
const parsedUrl = new URL(url, window.location.origin);
|
|
185
182
|
|
|
186
|
-
const historyKey = generateHistoryKey(url, {
|
|
183
|
+
const historyKey = generateHistoryKey(url, {
|
|
184
|
+
intercept,
|
|
185
|
+
interceptSource: interceptSourceUrl,
|
|
186
|
+
});
|
|
187
187
|
|
|
188
188
|
if (cacheOnly) {
|
|
189
189
|
const currentHandleData = eventController.getHandleState().data;
|
|
@@ -304,14 +304,10 @@ export function createNavigationTransaction(
|
|
|
304
304
|
};
|
|
305
305
|
},
|
|
306
306
|
|
|
307
|
+
// Reaches the handle committed or not: its dispose settles the entry
|
|
308
|
+
// (NavigationEntry.settled), and resets no state once it completed.
|
|
307
309
|
[Symbol.dispose]() {
|
|
308
|
-
if (handle.signal.aborted)
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
if (!committed) {
|
|
313
|
-
handle[Symbol.dispose]();
|
|
314
|
-
}
|
|
310
|
+
if (!handle.signal.aborted) handle[Symbol.dispose]();
|
|
315
311
|
},
|
|
316
312
|
};
|
|
317
313
|
}
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
import {
|
|
25
25
|
hasActiveIntercept as hasActiveInterceptSlots,
|
|
26
26
|
isInterceptSegment,
|
|
27
|
+
wholeInterceptMatched,
|
|
27
28
|
} from "./intercept-utils.js";
|
|
28
29
|
import type { BoundTransaction } from "./navigation-transaction.js";
|
|
29
30
|
import { ServerRedirect } from "../errors.js";
|
|
@@ -741,6 +742,13 @@ export function createPartialUpdater(
|
|
|
741
742
|
if (redirected(payload.metadata)) return;
|
|
742
743
|
|
|
743
744
|
if (payload.metadata?.isPartial) {
|
|
745
|
+
// Held nothing, answered as an intercept: the page it lands over stays
|
|
746
|
+
// under the slot, in `matched` for every reader below (the commit's
|
|
747
|
+
// handle cleanup included).
|
|
748
|
+
const kept =
|
|
749
|
+
segments.length === 0 &&
|
|
750
|
+
wholeInterceptMatched(cachedSegs, payload.metadata);
|
|
751
|
+
if (kept) payload.metadata.matched = kept;
|
|
744
752
|
const { segments: newSegments, matched, diff } = payload.metadata;
|
|
745
753
|
let serverSegments = newSegments || [];
|
|
746
754
|
let sent = diff || [];
|
|
@@ -43,6 +43,7 @@ import { abortAllPrefetches } from "./loader.js";
|
|
|
43
43
|
import { notifyPrefetchCacheInvalidated } from "./invalidation.js";
|
|
44
44
|
import { invalidateRangoState } from "../rango-state.js";
|
|
45
45
|
import type { RscPayload } from "../types.js";
|
|
46
|
+
import { WIRE } from "../../wire-params.js";
|
|
46
47
|
|
|
47
48
|
/**
|
|
48
49
|
* A prefetch that has been fetched AND eagerly decoded. Storing the decoded
|
|
@@ -190,7 +191,7 @@ let generation = 0;
|
|
|
190
191
|
*/
|
|
191
192
|
export function buildPrefetchKey(prefix: string, targetUrl: URL): string {
|
|
192
193
|
const normalizedTarget = new URL(targetUrl);
|
|
193
|
-
normalizedTarget.searchParams.delete(
|
|
194
|
+
normalizedTarget.searchParams.delete(WIRE.segments);
|
|
194
195
|
return prefix + "\0" + normalizedTarget.pathname + normalizedTarget.search;
|
|
195
196
|
}
|
|
196
197
|
|