@rangojs/router 0.27.0 → 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/event-controller.d.ts +2 -1
- package/dist/types/browser/history-state.d.ts +3 -9
- 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/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 +5 -14
- 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 +4 -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 +34 -86
- 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/router-context.d.ts +7 -46
- 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 +1 -1
- 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/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 +9 -1
- 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/document-cache/SKILL.md +13 -1
- package/skills/handler-use/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +17 -7
- package/skills/middleware/SKILL.md +18 -0
- package/skills/ppr/SKILL.md +18 -3
- package/skills/prerender/SKILL.md +39 -15
- package/skills/rango/SKILL.md +5 -1
- package/skills/server-actions/SKILL.md +8 -1
- package/skills/testing/response-routes.md +6 -4
- package/skills/use-cache/SKILL.md +46 -8
- package/src/__internal.ts +7 -1
- package/src/browser/connection-warmup.ts +3 -1
- package/src/browser/event-controller.ts +5 -3
- package/src/browser/history-state.ts +6 -12
- package/src/browser/navigation-bridge.ts +14 -13
- package/src/browser/navigation-client.ts +18 -25
- package/src/browser/navigation-store.ts +25 -27
- package/src/browser/navigation-transaction.ts +4 -1
- 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 +1 -0
- package/src/browser/segment-reconciler.ts +66 -53
- package/src/browser/server-action-bridge.ts +50 -57
- 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 +34 -13
- 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 +5 -16
- 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 +20 -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 +675 -726
- package/src/router/match-middleware/cache-store.ts +4 -8
- package/src/router/match-middleware/intercept-resolution.ts +3 -23
- package/src/router/match-middleware/segment-resolution.ts +34 -61
- package/src/router/middleware.ts +69 -11
- package/src/router/navigation-snapshot.ts +4 -4
- 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/route-snapshot.ts +2 -5
- package/src/router/router-context.ts +10 -95
- 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 +164 -349
- package/src/rsc/server-action.ts +87 -113
- package/src/rsc/shell-capture-constants.ts +1 -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/testing/dispatch.ts +223 -710
- package/src/testing/e2e/page-helpers.ts +2 -2
- package/src/testing/serve-shell-request.ts +23 -36
- package/src/types/cache-types.ts +6 -3
- package/src/types/error-types.ts +9 -1
- package/src/types/handler-context.ts +12 -1
- 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
package/src/rsc/shell-serve.ts
CHANGED
|
@@ -36,6 +36,10 @@ import {
|
|
|
36
36
|
warnOnce,
|
|
37
37
|
} from "./shell-capture-constants.js";
|
|
38
38
|
import { routerKeyPrefix } from "../cache/cache-key-utils.js";
|
|
39
|
+
import { reportCacheError } from "../cache/cache-error.js";
|
|
40
|
+
import { appendMetric } from "../router/metrics.js";
|
|
41
|
+
import type { MetricsStore } from "../server/context.js";
|
|
42
|
+
import { WIRE } from "../wire-params.js";
|
|
39
43
|
|
|
40
44
|
export {
|
|
41
45
|
PPR_REPLAY_STATUS_HEADER,
|
|
@@ -128,19 +132,13 @@ export function resolvePprConfig(
|
|
|
128
132
|
}
|
|
129
133
|
|
|
130
134
|
/**
|
|
131
|
-
* The forced-MISS
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
* the
|
|
136
|
-
*
|
|
137
|
-
|
|
138
|
-
export const SHELL_MISS_PARAM: string = "_rsc_shell";
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* The request without the forced-MISS marker, or undefined when it carries
|
|
142
|
-
* none. The handler calls it before anything reads the request: left in, the
|
|
143
|
-
* marker reached `ctx.request.url`, `originalUrl`, middleware, and the SSR
|
|
135
|
+
* The request without the forced-MISS marker (`WIRE.shellMiss`), or
|
|
136
|
+
* undefined when it carries none. A document HIT that cannot finish its tail
|
|
137
|
+
* reloads the page with the marker (shellReloadScript); the serve gate
|
|
138
|
+
* renders a request carrying it on axis 1, with no shell read and no
|
|
139
|
+
* capture, so the reload can never be a HIT that degrades again. The handler
|
|
140
|
+
* calls this on entry, before anything reads the request, and keeps only the
|
|
141
|
+
* flag (RequestContext._shellForcedMiss): left in, the marker reached `ctx.request.url`, `originalUrl`, middleware, and the SSR
|
|
144
142
|
* search seed (so `useSearchParams` rendered it), and a `"use cache"` key
|
|
145
143
|
* built from the request URL was never read again. GET and HEAD only: the
|
|
146
144
|
* reload is a navigation, and a request with a body cannot be re-created
|
|
@@ -148,11 +146,11 @@ export const SHELL_MISS_PARAM: string = "_rsc_shell";
|
|
|
148
146
|
* other request.
|
|
149
147
|
*/
|
|
150
148
|
export function withoutShellMissMarker(request: Request): Request | undefined {
|
|
151
|
-
if (!request.url.includes(
|
|
149
|
+
if (!request.url.includes(WIRE.shellMiss)) return undefined;
|
|
152
150
|
if (request.method !== "GET" && request.method !== "HEAD") return undefined;
|
|
153
151
|
const url = new URL(request.url);
|
|
154
|
-
if (!url.searchParams.has(
|
|
155
|
-
url.searchParams.delete(
|
|
152
|
+
if (!url.searchParams.has(WIRE.shellMiss)) return undefined;
|
|
153
|
+
url.searchParams.delete(WIRE.shellMiss);
|
|
156
154
|
return new Request(url, request);
|
|
157
155
|
}
|
|
158
156
|
|
|
@@ -168,8 +166,8 @@ export function withoutShellMissMarker(request: Request): Request | undefined {
|
|
|
168
166
|
export function shellReloadScript(): string {
|
|
169
167
|
return inlineShellScript(
|
|
170
168
|
"(function(){var u=new URL(location.href);" +
|
|
171
|
-
`if(u.searchParams.has(${JSON.stringify(
|
|
172
|
-
`u.searchParams.set(${JSON.stringify(
|
|
169
|
+
`if(u.searchParams.has(${JSON.stringify(WIRE.shellMiss)}))return;` +
|
|
170
|
+
`u.searchParams.set(${JSON.stringify(WIRE.shellMiss)},"miss");` +
|
|
173
171
|
"try{window.stop()}catch(e){}location.replace(u.href)})()",
|
|
174
172
|
);
|
|
175
173
|
}
|
|
@@ -305,6 +303,111 @@ export function openShellDocument(
|
|
|
305
303
|
};
|
|
306
304
|
}
|
|
307
305
|
|
|
306
|
+
/**
|
|
307
|
+
* A document serve's store read: a getShell entry, or a prelude-first read
|
|
308
|
+
* (readShellDocument) whose entry is the head and whose snapshot arrives on
|
|
309
|
+
* its own promise.
|
|
310
|
+
*/
|
|
311
|
+
export type ShellRead =
|
|
312
|
+
| { entry: ShellCacheEntry; shouldRevalidate?: boolean; read?: undefined }
|
|
313
|
+
| {
|
|
314
|
+
entry: ShellEntryHead;
|
|
315
|
+
shouldRevalidate?: boolean;
|
|
316
|
+
read: ShellDocumentRead;
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* The document serve's shell read from `source`, the app cache store, that
|
|
321
|
+
* degrades to null on failure (axis 1 MISS, never a 500) and records the raw
|
|
322
|
+
* `ppr:shell-read` outcome (pre-validity-gates, so a version-mismatch
|
|
323
|
+
* lifecycle miss stays distinguishable from a store miss). A store with a
|
|
324
|
+
* prelude-first read (readShellDocument) resolves before the snapshot is
|
|
325
|
+
* read, so the HIT commits without waiting for it. A build shell is read by
|
|
326
|
+
* shell-build-manifest.ts lookupBuildShell, which imports this module.
|
|
327
|
+
*/
|
|
328
|
+
export async function readShell(
|
|
329
|
+
source: SegmentCacheStore & {
|
|
330
|
+
getShell: NonNullable<SegmentCacheStore["getShell"]>;
|
|
331
|
+
},
|
|
332
|
+
key: string,
|
|
333
|
+
opts: { tagHints?: readonly string[]; metrics?: MetricsStore },
|
|
334
|
+
): Promise<ShellRead | null> {
|
|
335
|
+
const { tagHints, metrics } = opts;
|
|
336
|
+
let cached: ShellRead | null = null;
|
|
337
|
+
const shellReadStart = metrics ? performance.now() : 0;
|
|
338
|
+
try {
|
|
339
|
+
if (source.readShellDocument) {
|
|
340
|
+
const read = await source.readShellDocument(
|
|
341
|
+
key,
|
|
342
|
+
tagHints ? { tagHints } : undefined,
|
|
343
|
+
);
|
|
344
|
+
cached = read
|
|
345
|
+
? { entry: read.entry, shouldRevalidate: read.shouldRevalidate, read }
|
|
346
|
+
: null;
|
|
347
|
+
} else {
|
|
348
|
+
cached = await source.getShell(key);
|
|
349
|
+
}
|
|
350
|
+
} catch (error) {
|
|
351
|
+
reportCacheError(error, "cache-read", "[ShellServe] getShell");
|
|
352
|
+
}
|
|
353
|
+
if (metrics) {
|
|
354
|
+
const stats = cached?.read?.stats;
|
|
355
|
+
appendMetric(
|
|
356
|
+
metrics,
|
|
357
|
+
"ppr:shell-read",
|
|
358
|
+
shellReadStart,
|
|
359
|
+
performance.now() - shellReadStart,
|
|
360
|
+
undefined,
|
|
361
|
+
cached ? (stats ? `hit ${stats.tier}` : "hit") : "miss",
|
|
362
|
+
);
|
|
363
|
+
if (stats) recordShellReadStats(metrics, shellReadStart, stats);
|
|
364
|
+
}
|
|
365
|
+
return cached;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* A prelude-first read's sub-steps as `debugPerformance` rows under
|
|
370
|
+
* `ppr:shell-read`. The marker row starts with the prelude row because the
|
|
371
|
+
* two run in parallel; its desc says how long the read still waited on the
|
|
372
|
+
* marker once the prelude was in. On a deployed worker the clock only
|
|
373
|
+
* advances on I/O, so the byte counts are the cost signal for the CPU parts.
|
|
374
|
+
*/
|
|
375
|
+
function recordShellReadStats(
|
|
376
|
+
store: MetricsStore,
|
|
377
|
+
readStart: number,
|
|
378
|
+
stats: ShellReadStats,
|
|
379
|
+
): void {
|
|
380
|
+
let at = readStart;
|
|
381
|
+
const row = (label: string, ms: number | undefined, desc: string): void => {
|
|
382
|
+
if (ms !== undefined) appendMetric(store, label, at, ms, 1, desc);
|
|
383
|
+
};
|
|
384
|
+
if (stats.memo) {
|
|
385
|
+
row(
|
|
386
|
+
"ppr:shell-memo",
|
|
387
|
+
0,
|
|
388
|
+
`${stats.memo.hit ? "hit" : "miss"} size=${stats.memo.bytes}b`,
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
// A KV hit after an L1 miss: the L1 attempt first, then the KV read.
|
|
392
|
+
row("ppr:shell-l1-miss", stats.l1MissMs, stats.l1MissReason ?? "miss");
|
|
393
|
+
at += stats.l1MissMs ?? 0;
|
|
394
|
+
row("ppr:shell-match", stats.matchMs, stats.tier);
|
|
395
|
+
at += stats.matchMs ?? 0;
|
|
396
|
+
row("ppr:shell-head", stats.headMs, `bytes=${stats.headBytes ?? 0}`);
|
|
397
|
+
at += stats.headMs ?? 0;
|
|
398
|
+
row("ppr:shell-prelude", stats.preludeMs, `bytes=${stats.preludeBytes ?? 0}`);
|
|
399
|
+
let marker = `tags=${stats.tags ?? 0} ${stats.markerSerial ? "serial" : "parallel"} commit-wait=${(stats.markerWaitMs ?? 0).toFixed(2)}ms`;
|
|
400
|
+
if (stats.markerMemo) marker += ` memo=${stats.markerMemo}`;
|
|
401
|
+
if (stats.markerHinted) {
|
|
402
|
+
marker += ` hint=${stats.markerHintHits ?? 0}/${stats.markerHinted.length}`;
|
|
403
|
+
if (stats.markerLeadMs !== undefined) {
|
|
404
|
+
marker += ` lead=${stats.markerLeadMs.toFixed(2)}ms`;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (stats.freshReads) marker += " fresh-reads";
|
|
408
|
+
row("ppr:shell-marker", stats.markerMs, marker);
|
|
409
|
+
}
|
|
410
|
+
|
|
308
411
|
/**
|
|
309
412
|
* Prelude enqueue granularity. A streaming compressor in front of the worker
|
|
310
413
|
* emits its first byte only after compressing the whole write it was handed:
|
package/src/rsc/ssr-setup.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
import { _getRequestContext } from "../server/request-context.js";
|
|
25
25
|
import { renderedLocation } from "./full-payload.js";
|
|
26
26
|
import { markServerErrorPrinted } from "../server/print-server-error.js";
|
|
27
|
+
import { WIRE } from "../wire-params.js";
|
|
27
28
|
|
|
28
29
|
export type SSRSetup = readonly [SSRModule, SSRStreamMode];
|
|
29
30
|
|
|
@@ -233,10 +234,10 @@ function acceptsFlightExplicitly(request: Request, url: URL): boolean {
|
|
|
233
234
|
*/
|
|
234
235
|
export function mayNeedSSR(request: Request, url: URL): boolean {
|
|
235
236
|
if (
|
|
236
|
-
url.searchParams.has(
|
|
237
|
-
url.searchParams.has(
|
|
237
|
+
url.searchParams.has(WIRE.partial) ||
|
|
238
|
+
url.searchParams.has(WIRE.action) ||
|
|
238
239
|
requestHeaders(request).has("rsc-action") ||
|
|
239
|
-
url.searchParams.has(
|
|
240
|
+
url.searchParams.has(WIRE.loader) ||
|
|
240
241
|
url.searchParams.has("__rsc")
|
|
241
242
|
) {
|
|
242
243
|
return false;
|
|
@@ -264,7 +265,7 @@ export function isRscRequest(
|
|
|
264
265
|
): boolean {
|
|
265
266
|
return (
|
|
266
267
|
isPartial ||
|
|
267
|
-
url.searchParams.has(
|
|
268
|
+
url.searchParams.has(WIRE.partial) ||
|
|
268
269
|
url.searchParams.has("__rsc") ||
|
|
269
270
|
acceptsFlightExplicitly(request, url)
|
|
270
271
|
);
|
package/src/segment-system.tsx
CHANGED
|
@@ -295,7 +295,7 @@ function wrapDefaultOutletContent(
|
|
|
295
295
|
|
|
296
296
|
/**
|
|
297
297
|
* Per-loader stream map for read-site suspension. { ssr: false } loaders were
|
|
298
|
-
* awaited before flush (
|
|
298
|
+
* awaited before flush (resolveOwnerLoaders), so deliver the SETTLED result, not the
|
|
299
299
|
* settled promise: the read site decodes synchronously instead of use()ing a
|
|
300
300
|
* Flight chunk whose fulfilled-at-read-time status is a scheduling race the
|
|
301
301
|
* SSR-completeness contract must not depend on. Unflagged siblings keep the
|
|
@@ -1063,8 +1063,8 @@ function* segmentTreeWalk(
|
|
|
1063
1063
|
}
|
|
1064
1064
|
}
|
|
1065
1065
|
|
|
1066
|
-
// Segments arrive in root-to-leaf order from the server
|
|
1067
|
-
//
|
|
1066
|
+
// Segments arrive in root-to-leaf order from the server
|
|
1067
|
+
// (resolveSegment pushes segments in this order).
|
|
1068
1068
|
// All consumers (reconcileSegments, cache) preserve this order.
|
|
1069
1069
|
// No sorting needed — iterate bottom-to-top to process leaf segments first.
|
|
1070
1070
|
// This processes route/leaf layouts first, then parent layouts
|
package/src/server/context.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { invariant, DslContextError } from "../errors";
|
|
|
15
15
|
import type { DefaultRouteName } from "../types/global-namespace.js";
|
|
16
16
|
import type { ContextVar } from "../context-var.js";
|
|
17
17
|
import { PPR_LANE_HINT } from "../rsc/shell-capture-constants.js";
|
|
18
|
+
import type { ShellCaptureState } from "../rsc/shell-capture.js";
|
|
18
19
|
import {
|
|
19
20
|
noteWarmIdentityRead,
|
|
20
21
|
type WarmContext,
|
|
@@ -155,7 +156,7 @@ export type LoaderEntry = {
|
|
|
155
156
|
* thrown notFound()/redirect() deterministically precede first flush.
|
|
156
157
|
* Resolved at DSL-evaluation time from ctx.isSSR — entries are cached
|
|
157
158
|
* per-isSSR (router/manifest.ts cache key), so the flag is already
|
|
158
|
-
* request-mode-correct when
|
|
159
|
+
* request-mode-correct when resolveOwnerLoaders reads it and never
|
|
159
160
|
* appears on navigation-lane entries. Set by documentModeLoader, which
|
|
160
161
|
* getDocumentModeEntry also applies to a lazy include's captured ancestors.
|
|
161
162
|
*/
|
|
@@ -1133,7 +1134,7 @@ export interface IdentityReadWording {
|
|
|
1133
1134
|
* only observed; it is never rendered. A cached body, loader body or
|
|
1134
1135
|
* funnel they start is guarded again.
|
|
1135
1136
|
* 1. A PPR shell capture (`ctx` is the capture's derived context,
|
|
1136
|
-
* `
|
|
1137
|
+
* `_shellCaptureState`) trips first: the capture context is flagged (so a
|
|
1137
1138
|
* caught throw still refuses the capture) and the read throws. The shell is
|
|
1138
1139
|
* shared per host+URL, and every HIT replays what the capture read. There
|
|
1139
1140
|
* is no loader-body exemption here: a bake-lane loader and a loader a
|
|
@@ -1219,7 +1220,7 @@ export function requestReadCaptureFix(
|
|
|
1219
1220
|
|
|
1220
1221
|
/**
|
|
1221
1222
|
* True when `ctx` is the active capture render: the derived request context
|
|
1222
|
-
* shell-capture.ts builds (`
|
|
1223
|
+
* shell-capture.ts builds (`_shellCaptureState`), which only the capture has,
|
|
1223
1224
|
* so the foreground render reads identity normally to serve the real user.
|
|
1224
1225
|
* On true the capture context is flagged with the read (`surface`, e.g.
|
|
1225
1226
|
* "cookies()", "ctx.theme"), the fix the refusal warning gives, and the loader
|
|
@@ -1230,21 +1231,15 @@ function tripShellCaptureGuard(
|
|
|
1230
1231
|
surface: string,
|
|
1231
1232
|
fix: string,
|
|
1232
1233
|
): boolean {
|
|
1233
|
-
|
|
1234
|
-
ctx
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
)
|
|
1238
|
-
return false;
|
|
1239
|
-
}
|
|
1234
|
+
const captureState =
|
|
1235
|
+
ctx !== null && typeof ctx === "object"
|
|
1236
|
+
? (ctx as { _shellCaptureState?: ShellCaptureState })._shellCaptureState
|
|
1237
|
+
: undefined;
|
|
1238
|
+
if (captureState?.run !== true) return false;
|
|
1240
1239
|
// Record WHICH loader body (if any) made the read, so the refusal warning
|
|
1241
1240
|
// can name the real source instead of hardcoding a lane (issue #672).
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
_shellCaptureGuardTrippedLoaderId?: string;
|
|
1245
|
-
};
|
|
1246
|
-
flagged._shellCaptureGuardTripped = { surface, fix };
|
|
1247
|
-
flagged._shellCaptureGuardTrippedLoaderId = getCurrentLoaderBodyId();
|
|
1241
|
+
captureState.guardTripped = { surface, fix };
|
|
1242
|
+
captureState.guardTrippedLoaderId = getCurrentLoaderBodyId();
|
|
1248
1243
|
return true;
|
|
1249
1244
|
}
|
|
1250
1245
|
|
|
@@ -1530,12 +1525,13 @@ export function latchPprHeaderScopeForEntries(
|
|
|
1530
1525
|
* ONLY the ppr (shell) axis is dropped — dynamic() does NOT opt off the CACHE
|
|
1531
1526
|
* axis. Two cases:
|
|
1532
1527
|
* - Pure ppr funnel (no cache() boundary): clear the latch → writes re-permit.
|
|
1533
|
-
* - ppr route nested under a cache() boundary:
|
|
1534
|
-
* funnel top (first-wins), which MASKS the positional
|
|
1535
|
-
* handler still runs inside the cache scope
|
|
1536
|
-
* HIT skips that handler, so the write is
|
|
1537
|
-
* "cache" instead of clearing, so the
|
|
1538
|
-
* wording). This is why the check keys
|
|
1528
|
+
* - ppr route nested under a cache() boundary: resolveAllSegments
|
|
1529
|
+
* latches "ppr" at the funnel top (first-wins), which MASKS the positional
|
|
1530
|
+
* cache() latch, but the handler still runs inside the cache scope
|
|
1531
|
+
* (`insideCacheScope`). A cache() HIT skips that handler, so the write is
|
|
1532
|
+
* still non-deterministic — UNMASK to "cache" instead of clearing, so the
|
|
1533
|
+
* guard keeps throwing (accurate cache() wording). This is why the check keys
|
|
1534
|
+
* off `insideCacheScope`, not just kind.
|
|
1539
1535
|
*
|
|
1540
1536
|
* A subsequent cache() entered AFTER dynamic() on a pure-ppr funnel re-latches
|
|
1541
1537
|
* "cache" via latchCachedHeaderScope's `!store.cachedHeaderScope` guard (the
|
|
@@ -1568,9 +1564,10 @@ export function assertCachedHeaderWriteAllowed(
|
|
|
1568
1564
|
const scope = RangoContext.getStore()?.cachedHeaderScope;
|
|
1569
1565
|
if (!scope) return;
|
|
1570
1566
|
// Exempt DSL loaders (loaderScopeALS) ONLY. A registered loader re-runs on
|
|
1571
|
-
// every cache HIT (
|
|
1572
|
-
// resolveLoadersOnly), so its header/cookie
|
|
1573
|
-
// with no MISS/HIT divergence. A
|
|
1567
|
+
// every cache HIT (resolveOwnerLoaders runInsideLoaderScope, reached from
|
|
1568
|
+
// cache-lookup.ts resolveLoadersOnly), so its header/cookie
|
|
1569
|
+
// writes merge into every response with no MISS/HIT divergence. A
|
|
1570
|
+
// handler-invoked loader body has
|
|
1574
1571
|
// loaderBodyScopeALS active but loaderScopeALS unset (loader-resolution.ts
|
|
1575
1572
|
// derives isDslLoader from isInsideLoaderScope()); on a HIT the handler is
|
|
1576
1573
|
// skipped so that loader never re-runs and its write would land only on the
|
|
@@ -89,10 +89,14 @@ interface SlotTag {
|
|
|
89
89
|
* loader a copy stands for. Unset for a handler's push.
|
|
90
90
|
*/
|
|
91
91
|
by?: string;
|
|
92
|
+
/** A stored value a HIT restored (holdRecordedValues). */
|
|
93
|
+
record?: true;
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
// Shared by every plain DSL-loader push; tags are never mutated in place.
|
|
95
97
|
const LOADER_SLOT: SlotTag = Object.freeze({ loader: true });
|
|
98
|
+
// Shared by every held record value that had no tag (holdRecordedValues).
|
|
99
|
+
const RECORD_SLOT: SlotTag = Object.freeze({ record: true });
|
|
96
100
|
|
|
97
101
|
/**
|
|
98
102
|
* What a restored record is to one loader's handle pushes on this request
|
|
@@ -311,6 +315,24 @@ export interface HandleStore {
|
|
|
311
315
|
*/
|
|
312
316
|
replacePlaceholders(loaderIds: readonly string[], deliver: () => void): void;
|
|
313
317
|
|
|
318
|
+
/**
|
|
319
|
+
* A cache() or Prerender() HIT restored its stored values into
|
|
320
|
+
* `segmentIds` (cache-lookup.ts serveStoredSegments). The MISS ran each
|
|
321
|
+
* segment's handler after it started the segment's loaders, so a push into
|
|
322
|
+
* a restored array goes in front of the stored values until
|
|
323
|
+
* releaseRecordedValues: a loader's push lands where the same loader timing
|
|
324
|
+
* puts it on the MISS.
|
|
325
|
+
*/
|
|
326
|
+
holdRecordedValues(segmentIds: readonly string[]): void;
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* The HIT's loader walk reached the place where the MISS ran
|
|
330
|
+
* `segmentId`'s handler (revalidation.ts resolveLoadersOnly,
|
|
331
|
+
* intercept-resolution.ts resolveInterceptLoadersOnly): later pushes
|
|
332
|
+
* follow the record's values.
|
|
333
|
+
*/
|
|
334
|
+
releaseRecordedValues(segmentId: string): void;
|
|
335
|
+
|
|
314
336
|
/**
|
|
315
337
|
* Get all collected handle data after all handlers have settled.
|
|
316
338
|
* Waits for `settled` (handler lane), then returns the data at that point.
|
|
@@ -482,6 +504,8 @@ export function createHandleStore(): HandleStore {
|
|
|
482
504
|
// it restored placeholders for (pushPlaceholder).
|
|
483
505
|
const restoredLoaders = new Set<string>();
|
|
484
506
|
const placeholderLoaders = new Set<string>();
|
|
507
|
+
// Segments whose restored record values a push still goes in front of.
|
|
508
|
+
const held = new Set<string>();
|
|
485
509
|
let authority: ((loaderId: string) => RecordAuthority) | undefined;
|
|
486
510
|
const isRestored = (loaderId: string): boolean =>
|
|
487
511
|
restoredLoaders.has(loaderId) || authority?.(loaderId) === "pin";
|
|
@@ -620,6 +644,16 @@ export function createHandleStore(): HandleStore {
|
|
|
620
644
|
return at;
|
|
621
645
|
}
|
|
622
646
|
|
|
647
|
+
// The first restored record value in `values` (holdRecordedValues).
|
|
648
|
+
function firstRecordSlot(values: unknown[]): number | undefined {
|
|
649
|
+
const tags = slotTags.get(values);
|
|
650
|
+
if (!tags) return undefined;
|
|
651
|
+
for (let i = 0; i < values.length; i++) {
|
|
652
|
+
if (tags[i]?.record) return i;
|
|
653
|
+
}
|
|
654
|
+
return undefined;
|
|
655
|
+
}
|
|
656
|
+
|
|
623
657
|
// Where a live push by a loader with copies lands in `values`: its first
|
|
624
658
|
// push removes all of its copies and takes the first one's position in this
|
|
625
659
|
// array; later pushes follow the last push of its run. undefined: no place
|
|
@@ -954,6 +988,9 @@ export function createHandleStore(): HandleStore {
|
|
|
954
988
|
at = restoredInsertIndex(values, restoredBy);
|
|
955
989
|
if (replayOf === undefined) liveOf = restoredBy;
|
|
956
990
|
}
|
|
991
|
+
if (at === undefined && held.has(segmentId)) {
|
|
992
|
+
at = firstRecordSlot(values);
|
|
993
|
+
}
|
|
957
994
|
const by =
|
|
958
995
|
getHandlePushOwner() ??
|
|
959
996
|
replayOf ??
|
|
@@ -1058,6 +1095,24 @@ export function createHandleStore(): HandleStore {
|
|
|
1058
1095
|
signalEmission();
|
|
1059
1096
|
},
|
|
1060
1097
|
|
|
1098
|
+
holdRecordedValues(segmentIds: readonly string[]): void {
|
|
1099
|
+
for (const segmentId of segmentIds) {
|
|
1100
|
+
held.add(segmentId);
|
|
1101
|
+
for (const handleName in data) {
|
|
1102
|
+
const values = data[handleName][segmentId];
|
|
1103
|
+
if (!values?.length) continue;
|
|
1104
|
+
const tags = tagsFor(values);
|
|
1105
|
+
for (let i = 0; i < values.length; i++) {
|
|
1106
|
+
tags[i] = tags[i] ? { ...tags[i], record: true } : RECORD_SLOT;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
},
|
|
1111
|
+
|
|
1112
|
+
releaseRecordedValues(segmentId: string): void {
|
|
1113
|
+
held.delete(segmentId);
|
|
1114
|
+
},
|
|
1115
|
+
|
|
1061
1116
|
replacePlaceholders(
|
|
1062
1117
|
loaderIds: readonly string[],
|
|
1063
1118
|
deliver: () => void,
|