@rangojs/router 0.31.0 → 0.33.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/README.md +3 -2
- package/dist/bin/browser-7KIJ5HSV.js +15 -0
- package/dist/bin/chunk-3B7CKBRO.js +463 -0
- package/dist/bin/chunk-524VACCG.js +71 -0
- package/dist/bin/chunk-LD7UBHT4.js +196 -0
- package/dist/bin/chunk-TO6CRWLN.js +1429 -0
- package/dist/bin/chunk-TTXC2U3G.js +2477 -0
- package/dist/bin/client-urls-projection-DQLWPDAT.js +28 -0
- package/dist/bin/forward-user-plugins-URBZKJP4.js +62 -0
- package/dist/bin/rango.js +42 -10143
- package/dist/bin/runtime-EPUDEHO4.js +596 -0
- package/dist/bin/runtime-discovery-CZ2QHT4Q.js +352 -0
- package/dist/bin/state-ACD2MGKJ.js +16 -0
- package/dist/bin/temp-rsc-server-QRR6HD6T.js +3190 -0
- package/dist/types/bin/rango.d.ts +1 -0
- package/dist/types/browser/action-fence.d.ts +20 -0
- package/dist/types/browser/cookie-name.d.ts +51 -10
- package/dist/types/browser/history-state.d.ts +13 -2
- package/dist/types/browser/invalidate-client-cache.d.ts +8 -6
- package/dist/types/browser/navigation-bridge.d.ts +36 -1
- package/dist/types/browser/navigation-client.d.ts +19 -0
- package/dist/types/browser/navigation-store-handle.d.ts +37 -4
- package/dist/types/browser/navigation-store.d.ts +2 -2
- package/dist/types/browser/navigation-transaction.d.ts +0 -1
- package/dist/types/browser/partial-update.d.ts +13 -0
- package/dist/types/browser/prefetch/cache.d.ts +96 -8
- package/dist/types/browser/prefetch/fetch.d.ts +26 -2
- package/dist/types/browser/prefetch/queue.d.ts +3 -2
- package/dist/types/browser/rango-state.d.ts +34 -5
- package/dist/types/browser/react/Link.d.ts +19 -13
- package/dist/types/browser/react/location-state-shared.d.ts +2 -1
- package/dist/types/browser/types.d.ts +25 -0
- package/dist/types/build/generate-route-types.d.ts +1 -1
- package/dist/types/build/route-types/include-resolution.d.ts +9 -1
- package/dist/types/build/route-types/router-processing.d.ts +3 -1
- package/dist/types/cache/cache-key-utils.d.ts +12 -8
- package/dist/types/cache/cache-scope.d.ts +4 -2
- package/dist/types/cache/cf/cf-cache-types.d.ts +9 -5
- package/dist/types/cache/document-cache-policy.d.ts +9 -1
- package/dist/types/cache/memory-segment-store.d.ts +3 -2
- package/dist/types/cache/segment-codec.d.ts +2 -2
- package/dist/types/cache/shell-snapshot.d.ts +1 -1
- package/dist/types/cache/types.d.ts +12 -7
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +2 -1
- package/dist/types/client-urls/types.d.ts +7 -5
- package/dist/types/prefetch-markers.d.ts +9 -0
- package/dist/types/prefetch-source-scope.d.ts +66 -0
- package/dist/types/route-definition/helpers-types.d.ts +8 -5
- package/dist/types/route-definition/redirect.d.ts +5 -0
- package/dist/types/router/intercept-resolution.d.ts +25 -11
- package/dist/types/router/match-context.d.ts +1 -0
- package/dist/types/router/match-handlers.d.ts +1 -1
- package/dist/types/router/middleware-types.d.ts +6 -0
- package/dist/types/router/prefetch-cache-ttl.d.ts +11 -3
- package/dist/types/router/router-context.d.ts +1 -1
- package/dist/types/router/router-options.d.ts +6 -3
- package/dist/types/router/segment-resolution/loader-cache.d.ts +8 -0
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +23 -7
- package/dist/types/router/segment-resolution/revalidation.d.ts +19 -0
- package/dist/types/router/source-reads.d.ts +53 -0
- package/dist/types/router/types.d.ts +1 -1
- package/dist/types/rsc/helpers.d.ts +8 -3
- package/dist/types/rsc/prefetch-render-error.d.ts +7 -2
- package/dist/types/rsc/request-stages.d.ts +4 -0
- package/dist/types/rsc/shell-serve.d.ts +6 -1
- package/dist/types/server/context.d.ts +23 -6
- package/dist/types/server/cookie-store.d.ts +15 -1
- package/dist/types/server/document-location-state.d.ts +59 -0
- package/dist/types/server/handle-store.d.ts +2 -0
- package/dist/types/server/request-context.d.ts +65 -12
- package/dist/types/testing/e2e/server.d.ts +16 -1
- package/dist/types/testing/render-route.d.ts +83 -2
- package/dist/types/testing/serve-shell-request.d.ts +15 -3
- package/dist/types/types/handler-context.d.ts +21 -4
- package/dist/types/types/loader-types.d.ts +7 -4
- package/dist/types/types/segments.d.ts +18 -1
- package/dist/types/urls/path-helper-types.d.ts +7 -5
- package/dist/types/vite/temp-rsc-server.d.ts +2 -2
- package/dist/types/vite/utils/forward-user-plugins.d.ts +32 -2
- package/dist/vite/index.js +451 -191
- package/package.json +2 -2
- package/skills/app-structure/SKILL.md +74 -0
- package/skills/app-structure/e-commerce.md +10 -9
- package/skills/app-structure/large-app.md +15 -2
- package/skills/caching/SKILL.md +10 -5
- package/skills/client-urls/SKILL.md +18 -11
- package/skills/comparison/references/framework-comparison.md +16 -10
- package/skills/document-cache/SKILL.md +13 -0
- package/skills/hooks/navigation.md +8 -8
- package/skills/hooks/state.md +132 -12
- package/skills/intercept/SKILL.md +33 -11
- package/skills/layout/SKILL.md +11 -0
- package/skills/links/SKILL.md +101 -4
- package/skills/loader/SKILL.md +49 -22
- package/skills/migrate-nextjs/SKILL.md +8 -2
- package/skills/migrate-react-router/data-and-actions.md +5 -0
- package/skills/ppr/SKILL.md +38 -5
- package/skills/prerender/SKILL.md +4 -1
- package/skills/route/SKILL.md +6 -6
- package/skills/router-setup/SKILL.md +2 -1
- package/skills/server-actions/SKILL.md +49 -8
- package/skills/testing/client-components.md +73 -32
- package/skills/testing/serve-shell-request.md +45 -20
- package/skills/testing/server-actions.md +8 -0
- package/skills/ux-architecture/SKILL.md +67 -15
- package/src/bin/rango.ts +39 -13
- package/src/browser/action-fence.ts +72 -0
- package/src/browser/cookie-name.ts +82 -14
- package/src/browser/history-state.ts +24 -2
- package/src/browser/invalidate-client-cache.ts +9 -7
- package/src/browser/navigation-bridge.ts +164 -32
- package/src/browser/navigation-client.ts +95 -20
- package/src/browser/navigation-store-handle.ts +67 -5
- package/src/browser/navigation-store.ts +86 -17
- package/src/browser/navigation-transaction.ts +0 -6
- package/src/browser/partial-update.ts +31 -12
- package/src/browser/prefetch/cache.ts +188 -11
- package/src/browser/prefetch/fetch.ts +211 -51
- package/src/browser/prefetch/queue.ts +3 -2
- package/src/browser/rango-state.ts +50 -8
- package/src/browser/react/Link.tsx +24 -16
- package/src/browser/react/location-state-shared.ts +8 -1
- package/src/browser/react/use-navigation.ts +1 -1
- package/src/browser/rsc-router.tsx +13 -3
- package/src/browser/server-action-bridge.ts +3 -3
- package/src/browser/types.ts +25 -0
- package/src/build/generate-route-types.ts +1 -0
- package/src/build/route-types/include-resolution.ts +40 -3
- package/src/build/route-types/router-processing.ts +170 -25
- package/src/build/route-types/scan-filter.ts +1 -0
- package/src/build/runtime-discovery.ts +46 -8
- package/src/cache/cache-key-utils.ts +12 -8
- package/src/cache/cache-runtime.ts +5 -1
- package/src/cache/cache-scope.ts +28 -7
- package/src/cache/cf/cf-cache-types.ts +9 -5
- package/src/cache/document-cache-policy.ts +37 -1
- package/src/cache/document-cache.ts +12 -0
- package/src/cache/memory-segment-store.ts +3 -2
- package/src/cache/segment-codec.ts +2 -2
- package/src/cache/shell-snapshot.ts +1 -1
- package/src/cache/types.ts +12 -7
- package/src/cache/vercel/vercel-cache-store.ts +2 -1
- package/src/client-urls/types.ts +7 -5
- package/src/prefetch-markers.ts +10 -0
- package/src/prefetch-source-scope.ts +137 -0
- package/src/route-definition/helpers-types.ts +8 -5
- package/src/route-definition/redirect.ts +5 -0
- package/src/router/intercept-resolution.ts +94 -50
- package/src/router/loader-resolution.ts +8 -6
- package/src/router/match-api.ts +33 -29
- package/src/router/match-context.ts +5 -0
- package/src/router/match-handlers.ts +2 -1
- package/src/router/match-middleware/cache-lookup.ts +39 -14
- package/src/router/match-result.ts +45 -85
- package/src/router/middleware-types.ts +6 -0
- package/src/router/navigation-snapshot.ts +18 -3
- package/src/router/prefetch-cache-ttl.ts +15 -3
- package/src/router/revalidation.ts +63 -13
- package/src/router/router-context.ts +2 -1
- package/src/router/router-options.ts +6 -3
- package/src/router/segment-resolution/loader-cache.ts +21 -3
- package/src/router/segment-resolution/prefetch-deferral.ts +38 -31
- package/src/router/segment-resolution/revalidation.ts +40 -12
- package/src/router/source-reads.ts +122 -0
- package/src/router/types.ts +2 -1
- package/src/router.ts +11 -12
- package/src/rsc/helpers.ts +20 -6
- package/src/rsc/prefetch-render-error.ts +7 -2
- package/src/rsc/progressive-enhancement.ts +8 -2
- package/src/rsc/request-stages.ts +87 -1
- package/src/rsc/rsc-rendering.ts +78 -32
- package/src/rsc/server-action.ts +2 -1
- package/src/rsc/shell-capture.ts +4 -5
- package/src/rsc/shell-serve.ts +12 -1
- package/src/segment-system.tsx +1 -1
- package/src/server/context.ts +37 -6
- package/src/server/cookie-store.ts +22 -1
- package/src/server/document-location-state.ts +334 -0
- package/src/server/handle-store.ts +2 -0
- package/src/server/request-context.ts +137 -33
- package/src/testing/dispatch.ts +6 -0
- package/src/testing/e2e/fixture.ts +4 -12
- package/src/testing/e2e/server.ts +91 -4
- package/src/testing/render-route.tsx +309 -10
- package/src/testing/run-revalidate.ts +11 -1
- package/src/testing/serve-shell-request.ts +22 -3
- package/src/types/handler-context.ts +21 -4
- package/src/types/loader-types.ts +7 -4
- package/src/types/segments.ts +18 -1
- package/src/urls/path-helper-types.ts +7 -5
- package/src/vite/temp-rsc-server.ts +2 -2
- package/src/vite/utils/forward-user-plugins.ts +13 -5
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IS_BROWSER_DEBUG,
|
|
3
|
+
PREFETCH_FAILED_ROW,
|
|
4
|
+
PREFETCH_HEADER,
|
|
5
|
+
PREFETCH_INTENT,
|
|
6
|
+
WIRE,
|
|
7
|
+
WIRE_PREFIX,
|
|
8
|
+
buildParamsKey,
|
|
9
|
+
buildPrefetchKey,
|
|
10
|
+
buildSourceKey,
|
|
11
|
+
clearPrefetchInflight,
|
|
12
|
+
currentGeneration,
|
|
13
|
+
currentPrefetchEpoch,
|
|
14
|
+
debugLog,
|
|
15
|
+
getRangoState,
|
|
16
|
+
hasPrefetch,
|
|
17
|
+
isActionFenceActive,
|
|
18
|
+
isFragmentPassthroughEnabled,
|
|
19
|
+
isPrefetchCacheDisabled,
|
|
20
|
+
isPrefetchStale,
|
|
21
|
+
markPrefetchInflight,
|
|
22
|
+
paramsCandidateKeys,
|
|
23
|
+
paramsScopeLabel,
|
|
24
|
+
rememberParamsShape,
|
|
25
|
+
removePrefetch,
|
|
26
|
+
setInflightPromiseWithAliases,
|
|
27
|
+
stateEpoch,
|
|
28
|
+
storePrefetch
|
|
29
|
+
} from "./chunk-3B7CKBRO.js";
|
|
30
|
+
|
|
31
|
+
// src/browser/prefetch/resource-ready.ts
|
|
32
|
+
function waitForViewportImages() {
|
|
33
|
+
if (typeof document === "undefined") return Promise.resolve();
|
|
34
|
+
const pending = Array.from(document.querySelectorAll("img")).filter((img) => {
|
|
35
|
+
if (img.complete) return false;
|
|
36
|
+
const rect = img.getBoundingClientRect();
|
|
37
|
+
return rect.bottom > 0 && rect.right > 0 && rect.top < window.innerHeight && rect.left < window.innerWidth;
|
|
38
|
+
});
|
|
39
|
+
if (pending.length === 0) return Promise.resolve();
|
|
40
|
+
return new Promise((resolve) => {
|
|
41
|
+
const settled = /* @__PURE__ */ new Set();
|
|
42
|
+
const settle = (img) => {
|
|
43
|
+
if (settled.has(img)) return;
|
|
44
|
+
settled.add(img);
|
|
45
|
+
if (settled.size >= pending.length) resolve();
|
|
46
|
+
};
|
|
47
|
+
for (const img of pending) {
|
|
48
|
+
img.addEventListener("load", () => settle(img), { once: true });
|
|
49
|
+
img.addEventListener("error", () => settle(img), { once: true });
|
|
50
|
+
if (img.complete) settle(img);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function wait(ms) {
|
|
55
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
56
|
+
}
|
|
57
|
+
function waitForIdle(timeout = 200) {
|
|
58
|
+
if (typeof window !== "undefined" && "requestIdleCallback" in window) {
|
|
59
|
+
return new Promise((resolve) => {
|
|
60
|
+
window.requestIdleCallback(() => resolve(), { timeout });
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return new Promise((resolve) => {
|
|
64
|
+
setTimeout(resolve, 0);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/browser/prefetch/queue.ts
|
|
69
|
+
var maxConcurrent = 2;
|
|
70
|
+
var IMAGE_WAIT_TIMEOUT = 2e3;
|
|
71
|
+
function setPrefetchConcurrency(n) {
|
|
72
|
+
if (Number.isFinite(n) && n >= 1) {
|
|
73
|
+
maxConcurrent = Math.floor(n);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
var active = 0;
|
|
77
|
+
var queue = [];
|
|
78
|
+
var queued = /* @__PURE__ */ new Set();
|
|
79
|
+
var executing = /* @__PURE__ */ new Set();
|
|
80
|
+
var abortControllers = /* @__PURE__ */ new Map();
|
|
81
|
+
var drainScheduled = false;
|
|
82
|
+
var drainGeneration = 0;
|
|
83
|
+
function startExecution(key, execute) {
|
|
84
|
+
active++;
|
|
85
|
+
executing.add(key);
|
|
86
|
+
const ac = new AbortController();
|
|
87
|
+
abortControllers.set(key, ac);
|
|
88
|
+
execute(ac.signal).finally(() => {
|
|
89
|
+
abortControllers.delete(key);
|
|
90
|
+
if (executing.delete(key)) {
|
|
91
|
+
active--;
|
|
92
|
+
}
|
|
93
|
+
scheduleDrain();
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function scheduleDrain() {
|
|
97
|
+
if (drainScheduled) return;
|
|
98
|
+
if (active >= maxConcurrent || queue.length === 0) return;
|
|
99
|
+
drainScheduled = true;
|
|
100
|
+
const gen = drainGeneration;
|
|
101
|
+
waitForIdle().then(
|
|
102
|
+
() => Promise.race([waitForViewportImages(), wait(IMAGE_WAIT_TIMEOUT)])
|
|
103
|
+
).then(() => {
|
|
104
|
+
if (gen !== drainGeneration) return;
|
|
105
|
+
drainScheduled = false;
|
|
106
|
+
if (queue.length > 0) drain();
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
function drain() {
|
|
110
|
+
while (active < maxConcurrent && queue.length > 0) {
|
|
111
|
+
const item = queue.shift();
|
|
112
|
+
queued.delete(item.key);
|
|
113
|
+
startExecution(item.key, item.execute);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function enqueuePrefetch(key, execute) {
|
|
117
|
+
if (queued.has(key) || executing.has(key)) return;
|
|
118
|
+
queued.add(key);
|
|
119
|
+
queue.push({ key, execute });
|
|
120
|
+
scheduleDrain();
|
|
121
|
+
}
|
|
122
|
+
function normalizeForMatch(urlish) {
|
|
123
|
+
try {
|
|
124
|
+
const u = new URL(urlish, "http://placeholder");
|
|
125
|
+
for (const k of [...u.searchParams.keys()]) {
|
|
126
|
+
if (k.startsWith(WIRE_PREFIX)) u.searchParams.delete(k);
|
|
127
|
+
}
|
|
128
|
+
return u.pathname + u.search;
|
|
129
|
+
} catch {
|
|
130
|
+
return urlish;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function cancelAllPrefetches(keepUrl) {
|
|
134
|
+
queue.length = 0;
|
|
135
|
+
queued.clear();
|
|
136
|
+
drainScheduled = false;
|
|
137
|
+
drainGeneration++;
|
|
138
|
+
const normalizedKeep = keepUrl ? normalizeForMatch(keepUrl) : null;
|
|
139
|
+
for (const [key, ac] of abortControllers) {
|
|
140
|
+
const lastNul = key.lastIndexOf("\0");
|
|
141
|
+
const target = lastNul >= 0 ? key.substring(lastNul + 1) : "";
|
|
142
|
+
if (normalizedKeep && target && normalizeForMatch(target) === normalizedKeep)
|
|
143
|
+
continue;
|
|
144
|
+
ac.abort();
|
|
145
|
+
abortControllers.delete(key);
|
|
146
|
+
if (executing.delete(key)) {
|
|
147
|
+
active--;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function abortAllPrefetches() {
|
|
152
|
+
for (const ac of abortControllers.values()) {
|
|
153
|
+
ac.abort();
|
|
154
|
+
}
|
|
155
|
+
abortControllers.clear();
|
|
156
|
+
queue.length = 0;
|
|
157
|
+
queued.clear();
|
|
158
|
+
executing.clear();
|
|
159
|
+
active = 0;
|
|
160
|
+
drainScheduled = false;
|
|
161
|
+
drainGeneration++;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/browser/prefetch/policy.ts
|
|
165
|
+
function shouldPrefetch() {
|
|
166
|
+
if (typeof window === "undefined") return false;
|
|
167
|
+
if (isPrefetchCacheDisabled()) return false;
|
|
168
|
+
const nav = typeof navigator !== "undefined" ? navigator : void 0;
|
|
169
|
+
if (nav?.connection?.saveData) return false;
|
|
170
|
+
if (typeof window.matchMedia === "function") {
|
|
171
|
+
try {
|
|
172
|
+
if (window.matchMedia("(prefers-reduced-data: reduce)").matches) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
} catch {
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/browser/response-adapter.ts
|
|
182
|
+
var NULL_BODY_STATUS = /* @__PURE__ */ new Set([101, 204, 205, 304]);
|
|
183
|
+
function isForeignRouterId(response, expectedId) {
|
|
184
|
+
const got = response.headers.get("X-RSC-Router-Id");
|
|
185
|
+
if (!got || !expectedId) return false;
|
|
186
|
+
return got !== expectedId;
|
|
187
|
+
}
|
|
188
|
+
function teeWithCompletion(response, onComplete, signal, silent = false) {
|
|
189
|
+
let settled = false;
|
|
190
|
+
const settle = (endedCleanly) => {
|
|
191
|
+
if (!settled) {
|
|
192
|
+
settled = true;
|
|
193
|
+
onComplete(endedCleanly);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
if (!response.body || NULL_BODY_STATUS.has(response.status)) {
|
|
197
|
+
settle(true);
|
|
198
|
+
return response;
|
|
199
|
+
}
|
|
200
|
+
const [rscStream, trackingStream] = response.body.tee();
|
|
201
|
+
(async () => {
|
|
202
|
+
const reader = trackingStream.getReader();
|
|
203
|
+
const onAbort = signal ? reader.cancel.bind(reader) : void 0;
|
|
204
|
+
if (onAbort) signal.addEventListener("abort", onAbort, { once: true });
|
|
205
|
+
let cleanEof = false;
|
|
206
|
+
let tail = "";
|
|
207
|
+
try {
|
|
208
|
+
while (true) {
|
|
209
|
+
const { done, value } = await reader.read();
|
|
210
|
+
if (done) {
|
|
211
|
+
cleanEof = tail !== PREFETCH_FAILED_ROW;
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
tail = (tail + String.fromCharCode(...value.slice(-5))).slice(-5);
|
|
215
|
+
}
|
|
216
|
+
} finally {
|
|
217
|
+
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
218
|
+
reader.releaseLock();
|
|
219
|
+
settle(cleanEof && !signal?.aborted);
|
|
220
|
+
}
|
|
221
|
+
})().catch((error) => {
|
|
222
|
+
if (!silent && !signal?.aborted) {
|
|
223
|
+
console.error("[Browser] Error reading tracking stream:", error);
|
|
224
|
+
}
|
|
225
|
+
settle(false);
|
|
226
|
+
});
|
|
227
|
+
return new Response(rscStream, {
|
|
228
|
+
headers: response.headers,
|
|
229
|
+
status: response.status,
|
|
230
|
+
statusText: response.statusText
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// src/segment-fragments.ts
|
|
235
|
+
var SEGMENT_FRAGMENT_CAPABILITY_HEADER = "X-Rango-Fragment-Passthrough";
|
|
236
|
+
|
|
237
|
+
// src/browser/navigation-store-handle.ts
|
|
238
|
+
var registeredStore = null;
|
|
239
|
+
function getRegisteredStore() {
|
|
240
|
+
return registeredStore;
|
|
241
|
+
}
|
|
242
|
+
var readCommittedPage = null;
|
|
243
|
+
function getCommittedPage() {
|
|
244
|
+
return readCommittedPage ? readCommittedPage() : null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// src/browser/wire-protocol.ts
|
|
248
|
+
function writePartialParams(url, init) {
|
|
249
|
+
const params = url.searchParams;
|
|
250
|
+
if (init.action !== void 0) params.set(WIRE.action, init.action);
|
|
251
|
+
else params.set(WIRE.partial, "true");
|
|
252
|
+
if (init.segmentIds.length > 0 || init.emptySegments === "send") {
|
|
253
|
+
params.set(WIRE.segments, init.segmentIds.join(","));
|
|
254
|
+
}
|
|
255
|
+
if (init.stale) params.set(WIRE.stale, "true");
|
|
256
|
+
if (init.boundary) params.set(WIRE.boundary, init.boundary);
|
|
257
|
+
if (init.version) params.set(WIRE.version, init.version);
|
|
258
|
+
if (init.routerId) params.set(WIRE.routerId, init.routerId);
|
|
259
|
+
if (init.rewriteTarget) params.set(WIRE.rewriteTarget, init.rewriteTarget);
|
|
260
|
+
if (init.fill) params.set(WIRE.fill, "1");
|
|
261
|
+
return url;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// src/browser/prefetch/fetch.ts
|
|
265
|
+
var decoder = null;
|
|
266
|
+
var PREFETCH_FETCH_TIMEOUT_MS = 3e4;
|
|
267
|
+
function setPrefetchDecoder(fn) {
|
|
268
|
+
decoder = fn;
|
|
269
|
+
}
|
|
270
|
+
function makeRespawn(storageKey, reserve, slot, epoch) {
|
|
271
|
+
return () => {
|
|
272
|
+
const [body, nextReserve] = reserve.tee();
|
|
273
|
+
const payload = decoder(Promise.resolve(new Response(body)));
|
|
274
|
+
payload.catch(() => {
|
|
275
|
+
});
|
|
276
|
+
const entry = {
|
|
277
|
+
payload,
|
|
278
|
+
streamComplete: Promise.resolve(),
|
|
279
|
+
scope: slot.scope,
|
|
280
|
+
paramsLabel: slot.paramsLabel,
|
|
281
|
+
epoch,
|
|
282
|
+
complete: true,
|
|
283
|
+
dispose: () => {
|
|
284
|
+
nextReserve.cancel().catch(() => {
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
entry.respawn = makeRespawn(storageKey, nextReserve, slot, epoch);
|
|
289
|
+
payload.catch(() => removePrefetch(storageKey, entry));
|
|
290
|
+
return entry;
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
function committedHref() {
|
|
294
|
+
const committed = getRegisteredStore()?.getSegmentState().currentUrl;
|
|
295
|
+
return committed ? new URL(committed, window.location.origin).href : window.location.href;
|
|
296
|
+
}
|
|
297
|
+
function withoutHash(href) {
|
|
298
|
+
const hash = href.indexOf("#");
|
|
299
|
+
return hash === -1 ? href : href.slice(0, hash);
|
|
300
|
+
}
|
|
301
|
+
function isSamePage(url, sourceHref) {
|
|
302
|
+
try {
|
|
303
|
+
const target = new URL(url, window.location.origin);
|
|
304
|
+
const source = new URL(sourceHref, window.location.origin);
|
|
305
|
+
return target.pathname + target.search === source.pathname + source.search;
|
|
306
|
+
} catch {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function buildPrefetchUrl(url, segmentIds, version, routerId) {
|
|
311
|
+
let targetUrl;
|
|
312
|
+
try {
|
|
313
|
+
targetUrl = new URL(url, window.location.origin);
|
|
314
|
+
} catch {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
if (targetUrl.origin !== window.location.origin) {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
return writePartialParams(targetUrl, {
|
|
321
|
+
segmentIds,
|
|
322
|
+
emptySegments: "omit",
|
|
323
|
+
version,
|
|
324
|
+
routerId,
|
|
325
|
+
rewriteTarget: getRegisteredStore()?.getHistoryEntryMemory()?.rewriteTarget
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
function prefetchStorageSlot(forceSourceScope, scopeHeader, sourceKey, wildcardKey, stateKey, targetUrl) {
|
|
329
|
+
if (forceSourceScope || scopeHeader === "source") {
|
|
330
|
+
return { scope: "source", key: sourceKey };
|
|
331
|
+
}
|
|
332
|
+
const label = paramsScopeLabel(scopeHeader);
|
|
333
|
+
if (label === void 0) return { scope: "wildcard", key: wildcardKey };
|
|
334
|
+
return {
|
|
335
|
+
scope: "params",
|
|
336
|
+
key: buildParamsKey(stateKey, label, targetUrl),
|
|
337
|
+
paramsLabel: label
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
function executePrefetchFetch(wildcardKey, sourceKey, sourceHref, targetUrl, forceSourceScope, rangoState, intent, expectedRouterId, signal) {
|
|
341
|
+
const gen = currentGeneration();
|
|
342
|
+
const epoch = currentPrefetchEpoch();
|
|
343
|
+
const inflightKeys = forceSourceScope ? [sourceKey] : [wildcardKey, sourceKey];
|
|
344
|
+
for (const k of inflightKeys) markPrefetchInflight(k);
|
|
345
|
+
let publishedKey;
|
|
346
|
+
let publishedEntry;
|
|
347
|
+
const timeoutController = new AbortController();
|
|
348
|
+
const timeoutId = setTimeout(() => {
|
|
349
|
+
timeoutController.abort();
|
|
350
|
+
if (publishedKey !== void 0 && publishedEntry !== void 0) {
|
|
351
|
+
removePrefetch(publishedKey, publishedEntry);
|
|
352
|
+
}
|
|
353
|
+
}, PREFETCH_FETCH_TIMEOUT_MS);
|
|
354
|
+
let effectiveSignal;
|
|
355
|
+
if (!signal) {
|
|
356
|
+
effectiveSignal = timeoutController.signal;
|
|
357
|
+
} else if (typeof AbortSignal.any === "function") {
|
|
358
|
+
effectiveSignal = AbortSignal.any([signal, timeoutController.signal]);
|
|
359
|
+
} else {
|
|
360
|
+
effectiveSignal = timeoutController.signal;
|
|
361
|
+
if (signal.aborted) timeoutController.abort();
|
|
362
|
+
else
|
|
363
|
+
signal.addEventListener("abort", () => timeoutController.abort(), {
|
|
364
|
+
once: true
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
const promise = fetch(targetUrl.toString(), {
|
|
368
|
+
priority: "low",
|
|
369
|
+
// During an action's flight the state is not rotated, so the old
|
|
370
|
+
// X-Rango-State still matches the Vary-keyed HTTP-cache entry; bypass it so
|
|
371
|
+
// a prefetch fetches fresh rather than warming the map with stale bytes (the
|
|
372
|
+
// fence's HTTP-cache-bypass requirement applies to prefetch as well as
|
|
373
|
+
// navigation fetches).
|
|
374
|
+
...isActionFenceActive() && { cache: "no-store" },
|
|
375
|
+
signal: effectiveSignal,
|
|
376
|
+
headers: {
|
|
377
|
+
"X-Rango-State": rangoState,
|
|
378
|
+
"X-RSC-Router-Client-Path": sourceHref,
|
|
379
|
+
[PREFETCH_HEADER]: intent ? PREFETCH_INTENT : "1",
|
|
380
|
+
...isFragmentPassthroughEnabled() && {
|
|
381
|
+
[SEGMENT_FRAGMENT_CAPABILITY_HEADER]: "1"
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}).then((response) => {
|
|
385
|
+
if (!response.ok || !decoder) return null;
|
|
386
|
+
if (response.headers.has("X-RSC-Reload") || response.headers.has("X-RSC-Redirect")) {
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
if (isForeignRouterId(response, expectedRouterId)) {
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
const slot = prefetchStorageSlot(
|
|
393
|
+
forceSourceScope,
|
|
394
|
+
response.headers.get("x-rsc-prefetch-scope"),
|
|
395
|
+
sourceKey,
|
|
396
|
+
wildcardKey,
|
|
397
|
+
stateEpoch(rangoState),
|
|
398
|
+
targetUrl
|
|
399
|
+
);
|
|
400
|
+
const storageKey = slot.key;
|
|
401
|
+
let resolveStreamComplete;
|
|
402
|
+
let endedCleanly = false;
|
|
403
|
+
const streamComplete = new Promise((resolve) => {
|
|
404
|
+
resolveStreamComplete = resolve;
|
|
405
|
+
});
|
|
406
|
+
const tracked = teeWithCompletion(
|
|
407
|
+
response,
|
|
408
|
+
(clean) => {
|
|
409
|
+
endedCleanly = clean;
|
|
410
|
+
resolveStreamComplete();
|
|
411
|
+
},
|
|
412
|
+
effectiveSignal,
|
|
413
|
+
// Speculative prefetch: a never-consumed/aborted stream error is benign.
|
|
414
|
+
true
|
|
415
|
+
);
|
|
416
|
+
let decodeSource = tracked;
|
|
417
|
+
let reserve;
|
|
418
|
+
if (tracked.body) {
|
|
419
|
+
const [decodeBody, reserveBody] = tracked.body.tee();
|
|
420
|
+
decodeSource = new Response(decodeBody, { headers: tracked.headers });
|
|
421
|
+
reserve = reserveBody;
|
|
422
|
+
}
|
|
423
|
+
const payload = decoder(Promise.resolve(decodeSource));
|
|
424
|
+
payload.catch(() => {
|
|
425
|
+
});
|
|
426
|
+
const entry = {
|
|
427
|
+
payload,
|
|
428
|
+
streamComplete,
|
|
429
|
+
scope: slot.scope,
|
|
430
|
+
paramsLabel: slot.paramsLabel,
|
|
431
|
+
epoch,
|
|
432
|
+
complete: false,
|
|
433
|
+
stallTimer: timeoutId,
|
|
434
|
+
...reserve && {
|
|
435
|
+
dispose: () => {
|
|
436
|
+
reserve.cancel().catch(() => {
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
storePrefetch(storageKey, entry, gen, inflightKeys[0]);
|
|
442
|
+
if (slot.paramsLabel !== void 0) {
|
|
443
|
+
rememberParamsShape(targetUrl, slot.paramsLabel);
|
|
444
|
+
}
|
|
445
|
+
publishedKey = storageKey;
|
|
446
|
+
publishedEntry = entry;
|
|
447
|
+
payload.catch(() => removePrefetch(storageKey, entry));
|
|
448
|
+
streamComplete.then(() => {
|
|
449
|
+
if (!endedCleanly) removePrefetch(storageKey, entry);
|
|
450
|
+
});
|
|
451
|
+
Promise.allSettled([payload, streamComplete]).then(([decode]) => {
|
|
452
|
+
clearTimeout(timeoutId);
|
|
453
|
+
if (decode.status === "fulfilled" && endedCleanly) {
|
|
454
|
+
entry.complete = true;
|
|
455
|
+
if (reserve) {
|
|
456
|
+
entry.respawn = makeRespawn(storageKey, reserve, slot, epoch);
|
|
457
|
+
if (!isPrefetchStale(entry) && !hasPrefetch(storageKey)) {
|
|
458
|
+
storePrefetch(storageKey, entry.respawn(), gen);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
return entry;
|
|
464
|
+
}).catch(() => null).finally(() => {
|
|
465
|
+
clearPrefetchInflight(inflightKeys[0]);
|
|
466
|
+
if (publishedKey === void 0) clearTimeout(timeoutId);
|
|
467
|
+
});
|
|
468
|
+
setInflightPromiseWithAliases(inflightKeys, promise);
|
|
469
|
+
return promise;
|
|
470
|
+
}
|
|
471
|
+
function hasPrefetchHit(forceSourceScope, wildcardKey, sourceKey, stateKey, targetUrl) {
|
|
472
|
+
if (forceSourceScope) return hasPrefetch(sourceKey);
|
|
473
|
+
return hasPrefetch(wildcardKey) || hasPrefetch(sourceKey) || paramsCandidateKeys(stateKey, targetUrl, getCommittedPage()).some(
|
|
474
|
+
hasPrefetch
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
function prefetchDirect(url, segmentIds, version, routerId, prefetchKey) {
|
|
478
|
+
if (!shouldPrefetch()) return;
|
|
479
|
+
const targetUrl = buildPrefetchUrl(url, segmentIds, version, routerId);
|
|
480
|
+
if (!targetUrl) return;
|
|
481
|
+
const forceSourceScope = prefetchKey === ":source";
|
|
482
|
+
const sourceHref = committedHref();
|
|
483
|
+
if (!forceSourceScope && isSamePage(url, sourceHref)) {
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
const rangoState = getRangoState();
|
|
487
|
+
const stateKey = stateEpoch(rangoState);
|
|
488
|
+
const wildcardKey = buildPrefetchKey(stateKey, targetUrl);
|
|
489
|
+
const sourceKey = buildSourceKey(stateKey, sourceHref, targetUrl);
|
|
490
|
+
if (hasPrefetchHit(
|
|
491
|
+
forceSourceScope,
|
|
492
|
+
wildcardKey,
|
|
493
|
+
sourceKey,
|
|
494
|
+
stateKey,
|
|
495
|
+
targetUrl
|
|
496
|
+
)) {
|
|
497
|
+
if (IS_BROWSER_DEBUG) {
|
|
498
|
+
debugLog("[prefetch] direct dedup (key already exists)", {
|
|
499
|
+
url,
|
|
500
|
+
wildcardKey,
|
|
501
|
+
sourceKey,
|
|
502
|
+
forceSourceScope
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
if (IS_BROWSER_DEBUG) {
|
|
508
|
+
debugLog("[prefetch] direct fetch", {
|
|
509
|
+
url,
|
|
510
|
+
wildcardKey,
|
|
511
|
+
sourceKey,
|
|
512
|
+
source: sourceHref,
|
|
513
|
+
forceSourceScope
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
executePrefetchFetch(
|
|
517
|
+
wildcardKey,
|
|
518
|
+
sourceKey,
|
|
519
|
+
sourceHref,
|
|
520
|
+
targetUrl,
|
|
521
|
+
forceSourceScope,
|
|
522
|
+
rangoState,
|
|
523
|
+
true,
|
|
524
|
+
routerId
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
function prefetchQueued(url, segmentIds, version, routerId, prefetchKey) {
|
|
528
|
+
if (!shouldPrefetch()) return "";
|
|
529
|
+
const targetUrl = buildPrefetchUrl(url, segmentIds, version, routerId);
|
|
530
|
+
if (!targetUrl) return "";
|
|
531
|
+
const forceSourceScope = prefetchKey === ":source";
|
|
532
|
+
const sourceHref = committedHref();
|
|
533
|
+
if (!forceSourceScope && isSamePage(url, sourceHref)) {
|
|
534
|
+
return "";
|
|
535
|
+
}
|
|
536
|
+
const rangoState = getRangoState();
|
|
537
|
+
const stateKey = stateEpoch(rangoState);
|
|
538
|
+
const wildcardKey = buildPrefetchKey(stateKey, targetUrl);
|
|
539
|
+
const sourceKey = buildSourceKey(stateKey, sourceHref, targetUrl);
|
|
540
|
+
const queueKey = forceSourceScope ? sourceKey : wildcardKey;
|
|
541
|
+
if (hasPrefetchHit(
|
|
542
|
+
forceSourceScope,
|
|
543
|
+
wildcardKey,
|
|
544
|
+
sourceKey,
|
|
545
|
+
stateKey,
|
|
546
|
+
targetUrl
|
|
547
|
+
)) {
|
|
548
|
+
if (IS_BROWSER_DEBUG) {
|
|
549
|
+
debugLog("[prefetch] queued dedup (key already exists)", {
|
|
550
|
+
url,
|
|
551
|
+
wildcardKey,
|
|
552
|
+
sourceKey,
|
|
553
|
+
forceSourceScope
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
return queueKey;
|
|
557
|
+
}
|
|
558
|
+
enqueuePrefetch(queueKey, (signal) => {
|
|
559
|
+
const liveState = getRangoState();
|
|
560
|
+
if (stateEpoch(liveState) !== stateKey) return Promise.resolve();
|
|
561
|
+
if (hasPrefetchHit(
|
|
562
|
+
forceSourceScope,
|
|
563
|
+
wildcardKey,
|
|
564
|
+
sourceKey,
|
|
565
|
+
stateKey,
|
|
566
|
+
targetUrl
|
|
567
|
+
)) {
|
|
568
|
+
return Promise.resolve();
|
|
569
|
+
}
|
|
570
|
+
const committed = committedHref();
|
|
571
|
+
if (withoutHash(committed) !== withoutHash(sourceHref) || !forceSourceScope && isSamePage(url, committed)) {
|
|
572
|
+
return Promise.resolve();
|
|
573
|
+
}
|
|
574
|
+
return executePrefetchFetch(
|
|
575
|
+
wildcardKey,
|
|
576
|
+
sourceKey,
|
|
577
|
+
sourceHref,
|
|
578
|
+
targetUrl,
|
|
579
|
+
forceSourceScope,
|
|
580
|
+
liveState,
|
|
581
|
+
false,
|
|
582
|
+
routerId,
|
|
583
|
+
signal
|
|
584
|
+
).then(() => {
|
|
585
|
+
});
|
|
586
|
+
});
|
|
587
|
+
return queueKey;
|
|
588
|
+
}
|
|
589
|
+
export {
|
|
590
|
+
abortAllPrefetches,
|
|
591
|
+
cancelAllPrefetches,
|
|
592
|
+
prefetchDirect,
|
|
593
|
+
prefetchQueued,
|
|
594
|
+
setPrefetchConcurrency,
|
|
595
|
+
setPrefetchDecoder
|
|
596
|
+
};
|