@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-capture.ts
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* Capture does NOT flow through the HTTP middleware pipeline. The integrated PPR
|
|
5
5
|
* serve path (rsc-rendering.ts + shell-serve.ts) builds a ShellCaptureDescriptor
|
|
6
6
|
* from the route's `ppr` path option after the served response is built and calls
|
|
7
|
-
*
|
|
7
|
+
* planShellCapture with it (ShellCaptureInput). The capture then runs as a background task that re-derives
|
|
8
8
|
* the page via `ctx.router.match()` under its OWN derived request context — fresh
|
|
9
|
-
* handle store, `
|
|
9
|
+
* handle store, `_shellCaptureState.run` so live-lane loaders mask (loader-mask.ts)
|
|
10
10
|
* and postpone at their loading() or inline <Suspense> boundary. The match is
|
|
11
11
|
* MIXED-CHAIN: cache()'d segments replay from ring 3, uncached segments execute
|
|
12
12
|
* their handlers. The record-first step (settleCaptureRecord) waits for the
|
|
@@ -50,11 +50,10 @@ import type { TraceSpan } from "../router/tracing.js";
|
|
|
50
50
|
import {
|
|
51
51
|
runWithRequestContext,
|
|
52
52
|
setRequestContextParams,
|
|
53
|
-
wireRenderBarrier,
|
|
54
53
|
UNTRACKED_BACKGROUND_TASK,
|
|
55
54
|
type RequestContext,
|
|
56
|
-
type RouteRecordWindow,
|
|
57
55
|
} from "../server/request-context.js";
|
|
56
|
+
import { deriveRenderPass } from "../server/render-pass.js";
|
|
58
57
|
import {
|
|
59
58
|
DEFAULT_FUNCTION_TTL,
|
|
60
59
|
resolveSwrWindow,
|
|
@@ -149,7 +148,7 @@ const SHELL_SNAPSHOT_WRITE_SETTLE_MS = 1000;
|
|
|
149
148
|
* Upper bound on the pre-render WRITE BARRIER: before the capture's match/render,
|
|
150
149
|
* settle the background tasks the FOREGROUND request already scheduled — its
|
|
151
150
|
* deferred ring-3 cacheRoute and ring-1 setItem writes all go through
|
|
152
|
-
* reqCtx.waitUntil, and every one of them is scheduled BEFORE
|
|
151
|
+
* reqCtx.waitUntil, and every one of them is scheduled BEFORE planShellCapture
|
|
153
152
|
* runs (the response, and its onResponse callbacks, are committed first). Draining
|
|
154
153
|
* them turns the capture's cache reads from a RACE into an ORDERING EDGE: the
|
|
155
154
|
* capture deterministically observes the foreground's cache generation, replays it
|
|
@@ -226,7 +225,7 @@ async function raceTaskHardCap<T>(task: Promise<T>, key: string): Promise<T> {
|
|
|
226
225
|
* Module-level in-flight key map: the stampede guard for background captures, and
|
|
227
226
|
* its single owner. One capture runs per key per isolate; concurrent MISS/stale
|
|
228
227
|
* requests for the same key coalesce onto the first (the rest see the key present
|
|
229
|
-
* in
|
|
228
|
+
* in planShellCapture and skip). Added when a capture is scheduled and cleared
|
|
230
229
|
* in the task's settle paths via a token-guarded release, so a later request can
|
|
231
230
|
* recapture when TTL rolls. Living here (not split across the middleware) keeps
|
|
232
231
|
* the add/clear lifecycle in one layer.
|
|
@@ -234,7 +233,7 @@ async function raceTaskHardCap<T>(task: Promise<T>, key: string): Promise<T> {
|
|
|
234
233
|
* The value is the owning task's token ({@link CaptureGuardToken}) rather than
|
|
235
234
|
* a bare set entry: workerd can kill a capture's waitUntil context before its
|
|
236
235
|
* finally runs (or the task can wedge past every deadline), stranding the key.
|
|
237
|
-
*
|
|
236
|
+
* planShellCapture treats an entry older than
|
|
238
237
|
* SHELL_CAPTURE_TASK_HARD_CAP_MS as abandoned and reclaims it; the token guard
|
|
239
238
|
* keeps a late-settling stale task from releasing its replacement's entry.
|
|
240
239
|
*/
|
|
@@ -474,14 +473,14 @@ function refuseOnCaptureGuard(
|
|
|
474
473
|
);
|
|
475
474
|
return true;
|
|
476
475
|
}
|
|
477
|
-
const trip = derivedCtx.
|
|
476
|
+
const trip = derivedCtx._shellCaptureState?.guardTripped;
|
|
478
477
|
if (!trip) return false;
|
|
479
478
|
// Name the recorded source instead of hardcoding a lane: a trip can come
|
|
480
479
|
// from a bake-lane SEGMENT loader or from handler/render code, including a
|
|
481
480
|
// loader a handler awaits (issue #672). The guard recorded the read and its
|
|
482
481
|
// fix (server/context.ts guardIdentityRead): cookies()/headers(), a
|
|
483
482
|
// { cache: false } variable, and the theme reads (#971).
|
|
484
|
-
const loaderId = derivedCtx.
|
|
483
|
+
const loaderId = derivedCtx._shellCaptureState?.guardTrippedLoaderId;
|
|
485
484
|
const origin = loaderId
|
|
486
485
|
? `the loader "${loaderId}"`
|
|
487
486
|
: "handler/render code (no loader body was executing)";
|
|
@@ -625,7 +624,7 @@ export interface ShellCaptureDebugEvent {
|
|
|
625
624
|
* - skip-stored: a capture for the key stored after the scheduling request
|
|
626
625
|
* read the store and missed (ShellCaptureDescriptor.storedSeqAtRead), so
|
|
627
626
|
* the shell that request missed exists now
|
|
628
|
-
* - skip-in-flight:
|
|
627
|
+
* - skip-in-flight: planShellCapture found a capture already running for
|
|
629
628
|
* the key (stampede guard) and scheduled nothing
|
|
630
629
|
* - skip-backoff: the key is inside its refused-capture backoff window and
|
|
631
630
|
* the capture was not attempted
|
|
@@ -998,7 +997,7 @@ export function gateFlightForCapture(
|
|
|
998
997
|
* The background shell-capture descriptor: everything the capture task needs to
|
|
999
998
|
* store the shell. Built by the integrated PPR serve path (rsc-rendering.ts) from
|
|
1000
999
|
* the route's `ppr` path option (`PartialPrerenderProps`) and the app-level cache
|
|
1001
|
-
* store, and passed to
|
|
1000
|
+
* store, and passed to planShellCapture in its input — it is NOT threaded through
|
|
1002
1001
|
* the request context. `tags` carries the route's OPERATIONAL `ppr.tags`; the
|
|
1003
1002
|
* capture UNIONS them with the shell's own auto-collected (non-loader) request
|
|
1004
1003
|
* tags from its derived render (the collected set stays authoritative). That
|
|
@@ -1066,7 +1065,7 @@ export interface ShellCaptureDescriptor {
|
|
|
1066
1065
|
/**
|
|
1067
1066
|
* Set for a shell keyed by the visitor's URL (`rewrite(...).shell("visitor")`,
|
|
1068
1067
|
* visitorShellKey): the capture renders the visitor's location
|
|
1069
|
-
* (`
|
|
1068
|
+
* (`ShellCaptureState.forVisitor`) and stores it as
|
|
1070
1069
|
* ShellCacheEntry.pathname.
|
|
1071
1070
|
*/
|
|
1072
1071
|
pathname?: string;
|
|
@@ -1081,7 +1080,7 @@ export interface ShellCaptureDescriptor {
|
|
|
1081
1080
|
/**
|
|
1082
1081
|
* A `router.prerender()` warm's capture (rsc-rendering.ts shellServePlan):
|
|
1083
1082
|
* one explicit call that must store, so the guards that shed a herd of
|
|
1084
|
-
* visitors do not apply to it.
|
|
1083
|
+
* visitors do not apply to it. planShellCapture skips the in-flight
|
|
1085
1084
|
* dedup and the refused-capture backoff, and attemptCapture waits longer on
|
|
1086
1085
|
* the write barrier. Every correctness guard, the inert-store skip and the
|
|
1087
1086
|
* queue limits still decide.
|
|
@@ -1108,6 +1107,25 @@ function noteCaptureStored(key: string): void {
|
|
|
1108
1107
|
storedCaptureSeqs.set(key, ++storedCaptureSeq);
|
|
1109
1108
|
}
|
|
1110
1109
|
|
|
1110
|
+
/** One shell capture: the request it re-renders and what it stores. */
|
|
1111
|
+
export interface ShellCaptureInput {
|
|
1112
|
+
ctx: HandlerContext<any>;
|
|
1113
|
+
request: Request;
|
|
1114
|
+
env: any;
|
|
1115
|
+
url: URL;
|
|
1116
|
+
reqCtx: RequestContext<any>;
|
|
1117
|
+
/**
|
|
1118
|
+
* The SSR module for the capture's own request and URL, called once the
|
|
1119
|
+
* capture leaves the queue. A document passes the module it already
|
|
1120
|
+
* resolved, returned as is, so the capture awaits only a pending one.
|
|
1121
|
+
*/
|
|
1122
|
+
ssr: (
|
|
1123
|
+
request: Request,
|
|
1124
|
+
url: URL,
|
|
1125
|
+
) => SSRModule | null | Promise<SSRModule | null>;
|
|
1126
|
+
descriptor: ShellCaptureDescriptor;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1111
1129
|
/**
|
|
1112
1130
|
* Schedule the background shell capture for a served document. Stampede-guarded:
|
|
1113
1131
|
* one capture per key per isolate. Runs via runBackground (waitUntil on workerd,
|
|
@@ -1115,21 +1133,10 @@ function noteCaptureStored(key: string): void {
|
|
|
1115
1133
|
* error is routed through reportCacheError — capture is best-effort; a failure just
|
|
1116
1134
|
* means the next request recaptures.
|
|
1117
1135
|
*
|
|
1118
|
-
* Eligibility (nonce/partial/status/strategy) is decided by the caller.
|
|
1119
|
-
* module loader may be passed for cold partial requests; it runs only after the
|
|
1120
|
-
* capture enters the guarded background queue, never on response latency.
|
|
1136
|
+
* Eligibility (nonce/partial/status/strategy) is decided by the caller.
|
|
1121
1137
|
*/
|
|
1122
|
-
export function
|
|
1123
|
-
ctx
|
|
1124
|
-
request: Request,
|
|
1125
|
-
env: any,
|
|
1126
|
-
url: URL,
|
|
1127
|
-
reqCtx: RequestContext<any>,
|
|
1128
|
-
ssrModule:
|
|
1129
|
-
| SSRModule
|
|
1130
|
-
| ((request: Request, url: URL) => Promise<SSRModule | null>),
|
|
1131
|
-
descriptor: ShellCaptureDescriptor,
|
|
1132
|
-
): void {
|
|
1138
|
+
export function planShellCapture(input: ShellCaptureInput): void {
|
|
1139
|
+
const { ctx, request, env, url, reqCtx, descriptor } = input;
|
|
1133
1140
|
const key = descriptor.key;
|
|
1134
1141
|
// A forced capture installs its own token below: a visitor's capture that
|
|
1135
1142
|
// started before the warm may have read data from before the change the
|
|
@@ -1191,10 +1198,9 @@ export function scheduleShellCapture(
|
|
|
1191
1198
|
const captureRequest = descriptor.navigationOnly
|
|
1192
1199
|
? createNavigationCaptureRequest(request, captureUrl)
|
|
1193
1200
|
: request;
|
|
1201
|
+
const ssrModule = input.ssr(captureRequest, captureUrl);
|
|
1194
1202
|
const resolvedSsrModule =
|
|
1195
|
-
|
|
1196
|
-
? await ssrModule(captureRequest, captureUrl)
|
|
1197
|
-
: ssrModule;
|
|
1203
|
+
ssrModule instanceof Promise ? await ssrModule : ssrModule;
|
|
1198
1204
|
if (
|
|
1199
1205
|
!resolvedSsrModule ||
|
|
1200
1206
|
!resolvedSsrModule.resumeShellHTML ||
|
|
@@ -1350,7 +1356,7 @@ export function scheduleShellCapture(
|
|
|
1350
1356
|
/**
|
|
1351
1357
|
* The ttl/swr a shell is stored with: the route's ppr policy, capped by the
|
|
1352
1358
|
* route cache() records the capture replayed or wrote
|
|
1353
|
-
* (
|
|
1359
|
+
* (ShellCaptureState.routeRecordWindow). A HIT replays the capture's handler
|
|
1354
1360
|
* output, while a client navigation reads the route's cache() tier first, so
|
|
1355
1361
|
* a shell that outlived its record would show a document request other
|
|
1356
1362
|
* handler output than a navigation of the same URL. Freshness and total
|
|
@@ -1518,9 +1524,9 @@ type CaptureAttemptStats = Pick<
|
|
|
1518
1524
|
* several HTTP requests before a capture stuck. We retry ONLY on `no-shell`
|
|
1519
1525
|
* that did not run out of ppr.captureTimeout (CaptureAttemptStats.noShellCause),
|
|
1520
1526
|
* and on `expired`; a genuine render error is NOT retried — it propagates to
|
|
1521
|
-
*
|
|
1527
|
+
* planShellCapture's reportCacheError. See docs/design/ppr-shell-resume.md.
|
|
1522
1528
|
*
|
|
1523
|
-
* `request` and `url` are the capture's own identity (
|
|
1529
|
+
* `request` and `url` are the capture's own identity (planShellCapture
|
|
1524
1530
|
* strips a navigation-only capture's transport parameters). `retryDelayMs` is
|
|
1525
1531
|
* a parameter (defaulting to the module const) so unit tests can drive the
|
|
1526
1532
|
* retry without a real 400ms wall-clock wait.
|
|
@@ -1541,7 +1547,7 @@ async function runShellCapture(
|
|
|
1541
1547
|
// attemptCapture/captureAndStoreShell collecting the observability fields
|
|
1542
1548
|
// (barrier wait, write-settle wait, prelude/snapshot bytes), and the event
|
|
1543
1549
|
// folds them with the outcome. A genuine render error skips the attempt
|
|
1544
|
-
// event —
|
|
1550
|
+
// event — planShellCapture's catch publishes the terminal `error` event.
|
|
1545
1551
|
const timedAttempt = async (
|
|
1546
1552
|
attempt: number,
|
|
1547
1553
|
): Promise<{
|
|
@@ -1626,7 +1632,7 @@ async function runShellCapture(
|
|
|
1626
1632
|
// lane rule: see resolveLoaderData, loader-cache.ts) is the likely cause —
|
|
1627
1633
|
// warn once per key. Ordering matters: because the retry
|
|
1628
1634
|
// absorbs cold-start, cold-start routes almost never reach this warning. The
|
|
1629
|
-
// caller (
|
|
1635
|
+
// caller (planShellCapture) reads this `no-shell` return to back the key off.
|
|
1630
1636
|
warnNullCaptureOnce(key, true, second.stats);
|
|
1631
1637
|
return "no-shell";
|
|
1632
1638
|
}
|
|
@@ -1673,7 +1679,8 @@ export type CaptureRecordOutcome =
|
|
|
1673
1679
|
* a HIT restores, and its handle encode has its own 5s timeout that must
|
|
1674
1680
|
* never be the one that fires); then the capture's onResponse callbacks fire
|
|
1675
1681
|
* with a synthetic 200 (the implicit doc scope's cacheRoute, an explicit
|
|
1676
|
-
* scope's own write), and the deferred writes settle.
|
|
1682
|
+
* scope's own write), and the deferred writes settle. A match holding an
|
|
1683
|
+
* error or notFound boundary throws before the callbacks fire.
|
|
1677
1684
|
*/
|
|
1678
1685
|
export async function settleCaptureRecord(
|
|
1679
1686
|
match: MatchResult,
|
|
@@ -1690,7 +1697,7 @@ export async function settleCaptureRecord(
|
|
|
1690
1697
|
// (ctx.use(Handle).defer()), so a defer whose resolver depends on a masked
|
|
1691
1698
|
// loader never settles and the wait below runs into the deadline instead.
|
|
1692
1699
|
freshHandleStore.seal();
|
|
1693
|
-
const loaderRecords = derivedCtx.
|
|
1700
|
+
const loaderRecords = derivedCtx._shellCaptureState?.loaderRecords;
|
|
1694
1701
|
const settled = await raceDeadline(
|
|
1695
1702
|
Promise.all([
|
|
1696
1703
|
freshHandleStore
|
|
@@ -1741,6 +1748,32 @@ export async function settleCaptureRecord(
|
|
|
1741
1748
|
return { kind: "refused", refusal: "loader" };
|
|
1742
1749
|
}
|
|
1743
1750
|
|
|
1751
|
+
// A boundary the match rendered in place of a handler (route, layout or
|
|
1752
|
+
// slot) that threw, or threw notFound(), on this pass. A MISS whose own
|
|
1753
|
+
// render failed answered 500/404 and scheduled no capture, so on a first
|
|
1754
|
+
// capture the failure is this pass's own (a flaky upstream); a recapture
|
|
1755
|
+
// with no visitor render behind it (stale HIT, warm, stale build shell)
|
|
1756
|
+
// can only have failed. The pass's _setStatus is the foreground's and the
|
|
1757
|
+
// callbacks below get a synthetic 200, so nothing else refuses it. A stale
|
|
1758
|
+
// shell whose recapture fails keeps serving, as a failed cache() refresh's
|
|
1759
|
+
// record does: until ttl + swr ends (a build-time shell, until a recapture
|
|
1760
|
+
// stores or a tag evicts it). Checked after the settle so a guard trip in
|
|
1761
|
+
// a handler or a loader names the read, and before any write starts.
|
|
1762
|
+
// Thrown like captureAndStoreShell's render errors: no retry,
|
|
1763
|
+
// reportCacheError and backoff in scheduleShellCapture.
|
|
1764
|
+
const boundary = match.segments.find(
|
|
1765
|
+
(s) => s.type === "error" || s.type === "notFound",
|
|
1766
|
+
);
|
|
1767
|
+
if (boundary) {
|
|
1768
|
+
if (refuseOnCaptureGuard(capture.key, derivedCtx))
|
|
1769
|
+
return { kind: "refused", refusal: guardRefusal(derivedCtx) };
|
|
1770
|
+
throw new Error(
|
|
1771
|
+
`[rango] Shell capture for "${capture.key}" rendered the ${boundary.type} ` +
|
|
1772
|
+
`boundary for segment "${boundary.id}"; a failed render is not stored.`,
|
|
1773
|
+
{ cause: boundary.error ?? boundary.notFoundInfo },
|
|
1774
|
+
);
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1744
1777
|
const callbacks = derivedCtx._onResponseCallbacks?.splice(0) ?? [];
|
|
1745
1778
|
if (callbacks.length > 0) {
|
|
1746
1779
|
const synthetic = new Response(null, { status: 200 });
|
|
@@ -1770,7 +1803,7 @@ export async function settleCaptureRecord(
|
|
|
1770
1803
|
!Array.isArray(record.segments) ||
|
|
1771
1804
|
record.segments.length === 0
|
|
1772
1805
|
) {
|
|
1773
|
-
// Post-match serve-source truth (cache-lookup.ts
|
|
1806
|
+
// Post-match serve-source truth (cache-lookup.ts decodeStoredEntry).
|
|
1774
1807
|
if (derivedCtx._pprReplayPostMatchReason === "prerender-store") {
|
|
1775
1808
|
return { kind: "prerender", match };
|
|
1776
1809
|
}
|
|
@@ -1838,25 +1871,10 @@ function recordSegmentsHaveHandles(
|
|
|
1838
1871
|
/**
|
|
1839
1872
|
* One capture attempt in a DERIVED request context.
|
|
1840
1873
|
*
|
|
1841
|
-
* The derived context
|
|
1842
|
-
* post-middleware state (variables, cache store, env/request/url,
|
|
1843
|
-
*
|
|
1844
|
-
*
|
|
1845
|
-
* completion (its stream() flipped `completed` on settle) and would throw
|
|
1846
|
-
* LateHandlePushError on any re-push. Every downstream reader resolves the
|
|
1847
|
-
* store off the ambient context (setupLoaderAccess captures
|
|
1848
|
-
* _getRequestContext()._handleStore; trackHandler reads it), so the fresh
|
|
1849
|
-
* store on the derived context is what the capture match() writes handles to.
|
|
1850
|
-
* - _requestTags: a fresh Set. The capture collects its OWN shell tags here —
|
|
1851
|
-
* non-loader tags only, since loaders are masked — which is exactly the tag
|
|
1852
|
-
* set a shell entry should be invalidatable by (loader tags belong to holes).
|
|
1853
|
-
* - _shellCaptureRun: true — the switch loaders/cookies/headers guards read.
|
|
1854
|
-
* - _dynamic / dynamic(): an own latch the capture reads.
|
|
1855
|
-
* - _metricsStore: undefined so the capture never appends to the foreground's
|
|
1856
|
-
* (already-finalized) metrics.
|
|
1857
|
-
* - _renderBarrier family: an own barrier wired to the fresh handle store
|
|
1858
|
-
* (wireRenderBarrier), plus _treeHasStreaming/deadlock-guard resets — the
|
|
1859
|
-
* capture's rendered() lifecycle is its own, not the foreground's.
|
|
1874
|
+
* The derived context (deriveRenderPass, server/render-pass.ts) inherits the
|
|
1875
|
+
* foreground's post-middleware state (variables, cache store, env/request/url,
|
|
1876
|
+
* waitUntil) and owns the render-scoped accumulators, its render barrier
|
|
1877
|
+
* included: RENDER_PASS_FIELDS["shell-capture"] lists each and why.
|
|
1860
1878
|
*
|
|
1861
1879
|
* The capture is MIXED-CHAIN: its match() behaves like a normal render with
|
|
1862
1880
|
* respect to the segment cache — cache()'d segments replay from ring 3, UNCACHED
|
|
@@ -1913,7 +1931,7 @@ async function attemptCapture(
|
|
|
1913
1931
|
);
|
|
1914
1932
|
const { derivedCtx, freshHandleStore } = derivation;
|
|
1915
1933
|
if (descriptor.pathname !== undefined) {
|
|
1916
|
-
derivedCtx.
|
|
1934
|
+
derivedCtx._shellCaptureState!.forVisitor = true;
|
|
1917
1935
|
}
|
|
1918
1936
|
// The capture generation starts before matching or any snapshot read
|
|
1919
1937
|
// (ShellCacheEntry.createdAt): a tag invalidated after it wins.
|
|
@@ -2014,6 +2032,115 @@ async function attemptCapture(
|
|
|
2014
2032
|
});
|
|
2015
2033
|
}
|
|
2016
2034
|
|
|
2035
|
+
/**
|
|
2036
|
+
* @internal The freshness window of a route cache() record, as ms
|
|
2037
|
+
* timestamps: fresh until `freshUntil` (its `expiresAt`), stale until
|
|
2038
|
+
* `staleUntil` (plus its scope's swr). `ttl`, `swr` and `written` describe
|
|
2039
|
+
* the record that sets `staleUntil`: its cache() config, and whether this
|
|
2040
|
+
* capture wrote it (cacheRoute) rather than read it.
|
|
2041
|
+
*/
|
|
2042
|
+
export interface RouteRecordWindow {
|
|
2043
|
+
freshUntil: number;
|
|
2044
|
+
staleUntil: number;
|
|
2045
|
+
ttl: number;
|
|
2046
|
+
swr: number;
|
|
2047
|
+
written: boolean;
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
/**
|
|
2051
|
+
* @internal What a shell capture's render records about itself, on its
|
|
2052
|
+
* derived request context as `_shellCaptureState`.
|
|
2053
|
+
* RENDER_PASS_FIELDS["shell-capture"] (server/render-pass.ts) creates it with
|
|
2054
|
+
* every field present, so it keeps one shape while the capture writes it. A
|
|
2055
|
+
* background re-render the capture starts has none: it writes a route
|
|
2056
|
+
* record, under cache() rules (RENDER_PASS_FIELDS["background-revalidation"]).
|
|
2057
|
+
*/
|
|
2058
|
+
export interface ShellCaptureState {
|
|
2059
|
+
/**
|
|
2060
|
+
* @internal PPR shell-capture ACTIVE marker. True ONLY inside the background
|
|
2061
|
+
* capture task's derived request context (built by shell-capture.ts). This is
|
|
2062
|
+
* the switch every capture-specific behavior reads: loader masking
|
|
2063
|
+
* (loader-mask.ts isShellCaptureActive / resolveOwnerLoaders) and the
|
|
2064
|
+
* identity-read capture guard (server/context.ts guardIdentityRead). The
|
|
2065
|
+
* foreground render never sets it, so the
|
|
2066
|
+
* served response is byte-identical to axis 1. The capture descriptor itself
|
|
2067
|
+
* (key/ttl/swr/tags/store) is NOT threaded through the request context — the
|
|
2068
|
+
* integrated PPR serve path (rsc/shell-serve.ts + rsc-rendering.ts) builds it
|
|
2069
|
+
* locally and passes it to planShellCapture in its input.
|
|
2070
|
+
*/
|
|
2071
|
+
readonly run: true;
|
|
2072
|
+
|
|
2073
|
+
/**
|
|
2074
|
+
* @internal Set beside `run` when the shell is keyed by the
|
|
2075
|
+
* visitor's URL (`rewrite(...).shell("visitor")`): the capture renders the
|
|
2076
|
+
* visitor's location and `ctx.rewrittenFrom`; the theme stays the default.
|
|
2077
|
+
*/
|
|
2078
|
+
forVisitor: boolean | undefined;
|
|
2079
|
+
|
|
2080
|
+
/**
|
|
2081
|
+
* @internal Bake-lane loader containers collected DURING a shell capture:
|
|
2082
|
+
* segment-key -> the loader's (pre-wrap) result promise. Populated by
|
|
2083
|
+
* resolveLoaderData for bake-lane loaders (lane rule: see its JSDoc in
|
|
2084
|
+
* loader-cache.ts). Drained by captureAndStoreShell after the shell
|
|
2085
|
+
* quiesces: settled containers are promise-elided, Flight-serialized, and
|
|
2086
|
+
* pinned into the snapshot's loader family; a REJECTED container refuses
|
|
2087
|
+
* the capture (error UI must never bake into the shared shell).
|
|
2088
|
+
*/
|
|
2089
|
+
loaderRecords: Map<string, Promise<unknown>>;
|
|
2090
|
+
|
|
2091
|
+
/**
|
|
2092
|
+
* @internal Set when a loader pushed a
|
|
2093
|
+
* value the record cannot keep (masked nested promises, a pushed promise
|
|
2094
|
+
* that settled to one, a live-lane loader's push, or a push outside a
|
|
2095
|
+
* loader body): the loader records then carry `runs: 1`, so a HIT still
|
|
2096
|
+
* runs their bodies for those pushes.
|
|
2097
|
+
*/
|
|
2098
|
+
unrecordedLoaderPush: true | undefined;
|
|
2099
|
+
|
|
2100
|
+
/**
|
|
2101
|
+
* @internal The lane of each loader the capture's matched route registers
|
|
2102
|
+
* (loader-cache.ts routeLoaderLanes), set by createMatchContextForFull
|
|
2103
|
+
* before any loader runs. The capture's push funnel credits a push made
|
|
2104
|
+
* under a live-lane loader's body, at any depth, to that loader
|
|
2105
|
+
* (shell-capture.ts deriveShellCaptureContext).
|
|
2106
|
+
*/
|
|
2107
|
+
loaderLanes: ReadonlyMap<string, "live" | "bake"> | undefined;
|
|
2108
|
+
|
|
2109
|
+
/**
|
|
2110
|
+
* @internal Set by the capture guard (server/context.ts
|
|
2111
|
+
* guardIdentityRead) when it throws DURING a capture render: the read
|
|
2112
|
+
* ("cookies()", "headers()", `ctx.get() for a non-cacheable variable`,
|
|
2113
|
+
* "ctx.theme") and the fix the
|
|
2114
|
+
* refusal warning gives for it. Load-bearing for the
|
|
2115
|
+
* bake lane: a guard throw inside an executing loader is swallowed by
|
|
2116
|
+
* wrapLoaderPromise into per-loader error UI, which would otherwise bake
|
|
2117
|
+
* silently into the shared shell — the capture checks this flag after the
|
|
2118
|
+
* render and refuses instead. Deterministic, so the capture does not retry.
|
|
2119
|
+
*/
|
|
2120
|
+
guardTripped: { surface: string; fix: string } | undefined;
|
|
2121
|
+
|
|
2122
|
+
/**
|
|
2123
|
+
* @internal The loader $$id whose BODY was executing when the capture guard
|
|
2124
|
+
* tripped (read off the loader-body ALS scope at trip time), or undefined
|
|
2125
|
+
* when the read came from handler/render code. Only used to make the
|
|
2126
|
+
* once-per-key refusal warning name the real source — the old text
|
|
2127
|
+
* hardcoded "a bake-lane loader", which misattributed handler-land reads
|
|
2128
|
+
* and sent users debugging the wrong lane (issue #672, secondary).
|
|
2129
|
+
*/
|
|
2130
|
+
guardTrippedLoaderId: string | undefined;
|
|
2131
|
+
|
|
2132
|
+
/**
|
|
2133
|
+
* @internal The freshness window of the route cache() records this
|
|
2134
|
+
* capture read or wrote (cache-scope.ts noteRouteRecordWindow), as ms
|
|
2135
|
+
* timestamps: `freshUntil` is the record's `expiresAt`, `staleUntil` adds
|
|
2136
|
+
* its scope's swr. The narrowest record wins. A shell capture caps its
|
|
2137
|
+
* entry's ttl and ttl+swr to it (shell-capture.ts capShellWindow), so a
|
|
2138
|
+
* shell is never fresher, or served longer, than the route cache() entry
|
|
2139
|
+
* it replayed or wrote.
|
|
2140
|
+
*/
|
|
2141
|
+
routeRecordWindow: RouteRecordWindow | undefined;
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2017
2144
|
/**
|
|
2018
2145
|
* The derived capture context and its fresh (mask-funneled) handle store,
|
|
2019
2146
|
* shared by BOTH shell producers: the runtime background capture
|
|
@@ -2123,7 +2250,7 @@ export function deriveShellCaptureContext(
|
|
|
2123
2250
|
// A replay of a live-lane loader's push (a "use cache" or loader cache()
|
|
2124
2251
|
// hit a recapture makes inside a bake-lane body) is that hole's too.
|
|
2125
2252
|
const liveLaneOwner = (): string | undefined => {
|
|
2126
|
-
const lanes =
|
|
2253
|
+
const lanes = captureState.loaderLanes;
|
|
2127
2254
|
if (!lanes) return undefined;
|
|
2128
2255
|
return (
|
|
2129
2256
|
findEnclosingLoaderBody(
|
|
@@ -2190,7 +2317,7 @@ export function deriveShellCaptureContext(
|
|
|
2190
2317
|
const judged = mask(settled);
|
|
2191
2318
|
if (maskedNestedThenable) {
|
|
2192
2319
|
freshHandleStore.unrecordPush(handleName, segmentId, slot);
|
|
2193
|
-
|
|
2320
|
+
captureState.unrecordedLoaderPush = true;
|
|
2194
2321
|
}
|
|
2195
2322
|
return judged;
|
|
2196
2323
|
});
|
|
@@ -2220,7 +2347,7 @@ export function deriveShellCaptureContext(
|
|
|
2220
2347
|
// A push the record cannot keep reaches a HIT only from a live run of
|
|
2221
2348
|
// its loader, so the loader records ask for one (runs: 1).
|
|
2222
2349
|
const loaderPush = owner === undefined;
|
|
2223
|
-
if (loaderPush)
|
|
2350
|
+
if (loaderPush) captureState.unrecordedLoaderPush = true;
|
|
2224
2351
|
rawCapturePush(handleName, segmentId, masked, loaderPush, owner);
|
|
2225
2352
|
};
|
|
2226
2353
|
|
|
@@ -2229,111 +2356,25 @@ export function deriveShellCaptureContext(
|
|
|
2229
2356
|
// request before the derivation, or the capture would start its own and
|
|
2230
2357
|
// run key() under the capture guard.
|
|
2231
2358
|
reqCtx._resolvedCacheKeys ??= new Map();
|
|
2232
|
-
const
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2359
|
+
const recordingStore = reqCtx._cacheStore
|
|
2360
|
+
? new RecordingShellStore(reqCtx._cacheStore)
|
|
2361
|
+
: undefined;
|
|
2362
|
+
// What the capture owns, and why: RENDER_PASS_FIELDS["shell-capture"].
|
|
2363
|
+
const derivedCtx: RequestContext = deriveRenderPass(reqCtx, "shell-capture", {
|
|
2364
|
+
handleStore: freshHandleStore,
|
|
2365
|
+
identity,
|
|
2366
|
+
recording: recordingStore && {
|
|
2367
|
+
store: recordingStore,
|
|
2368
|
+
implicitCache: {
|
|
2369
|
+
ttl: descriptor.ttl,
|
|
2370
|
+
swr: descriptor.swr,
|
|
2371
|
+
store: new SnapshotOnlySegmentStore(recordingStore),
|
|
2372
|
+
keyPrefix: "doc",
|
|
2373
|
+
},
|
|
2374
|
+
},
|
|
2238
2375
|
});
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
derivedCtx.url = identity.url;
|
|
2242
|
-
derivedCtx.originalUrl = new URL(identity.url);
|
|
2243
|
-
derivedCtx.pathname = identity.url.pathname;
|
|
2244
|
-
derivedCtx.searchParams = identity.url.searchParams;
|
|
2245
|
-
}
|
|
2246
|
-
// Own render barrier, closure-bound to the derived ctx and the fresh store
|
|
2247
|
-
// (issue #684, plan 009). Without this every _renderBarrier* read fell
|
|
2248
|
-
// through the prototype to the foreground's ALREADY-RESOLVED barrier: a
|
|
2249
|
-
// bake-lane loader's `await ctx.rendered()` resolved instantly and
|
|
2250
|
-
// ctx.use(handle) read the FOREGROUND handle snapshot — foreground
|
|
2251
|
-
// per-request handle data could bake into the shared shell. wireRenderBarrier
|
|
2252
|
-
// also resets _treeHasStreaming (recomputed for the capture's tree) and the
|
|
2253
|
-
// deadlock-guard fields as own properties.
|
|
2254
|
-
wireRenderBarrier(derivedCtx, freshHandleStore);
|
|
2255
|
-
derivedCtx._requestTags = new Set<string>();
|
|
2256
|
-
// Own explicit-store registry: cache-store resolutions during the capture
|
|
2257
|
-
// (the implicit scope's SnapshotOnlySegmentStore, any per-capture explicit
|
|
2258
|
-
// store instance) must NOT register into the handler-lifetime
|
|
2259
|
-
// _explicitTaggedStores set — a capture-ephemeral store pinned there would
|
|
2260
|
-
// trip the partial-tag-store warning on every later updateTag() and retain
|
|
2261
|
-
// the whole capture snapshot in memory. Capture registrations die with this
|
|
2262
|
-
// context; module-singleton stores stay registered by normal renders.
|
|
2263
|
-
derivedCtx._explicitTaggedStores = new Set();
|
|
2264
|
-
// Own list: the capture's render errors refuse the capture only, and the
|
|
2265
|
-
// foreground's never reach it.
|
|
2266
|
-
derivedCtx._renderErrors = [];
|
|
2267
|
-
derivedCtx._recoveredHandlerErrors = undefined;
|
|
2268
|
-
derivedCtx._shellCaptureRun = true;
|
|
2269
|
-
// Own dynamic() latch. The inherited method writes the FOREGROUND context
|
|
2270
|
-
// (its closure), which already served, and nothing read it for a runtime
|
|
2271
|
-
// capture: handler code that opts out after an await (a handler promise the
|
|
2272
|
-
// capture now waits for) must refuse THIS capture instead
|
|
2273
|
-
// (settleCaptureRecord and captureAndStoreShell read _dynamic). The ppr
|
|
2274
|
-
// header latch is left alone: a capture's response is never sent.
|
|
2275
|
-
derivedCtx._dynamic = false;
|
|
2276
|
-
derivedCtx.dynamic = (): void => {
|
|
2277
|
-
derivedCtx._dynamic = true;
|
|
2278
|
-
};
|
|
2279
|
-
// Own-property reset: an inherited flag would make serializeSegments store
|
|
2280
|
-
// a double-encoded fragment. No current arming site reaches a capture
|
|
2281
|
-
// render (kept as defense-in-depth for a future one).
|
|
2282
|
-
derivedCtx._shellFragmentPayload = false;
|
|
2283
|
-
derivedCtx._metricsStore = undefined;
|
|
2284
|
-
// Spans, like perf metrics above, are a FOREGROUND surface: the capture
|
|
2285
|
-
// re-render must not emit a second rango.render/loader/ssr set after the
|
|
2286
|
-
// foreground rango.request span ended (orphan spans in the trace).
|
|
2287
|
-
// _tracing is otherwise inherited through Object.create(reqCtx).
|
|
2288
|
-
derivedCtx._tracing = undefined;
|
|
2289
|
-
// Bake-lane (`ssr: false`) loader containers, which execute during capture
|
|
2290
|
-
// (docs/design/loader-container-bake.md). resolveLoaderData registers each
|
|
2291
|
-
// container promise here; settleCaptureRecord waits for them, and the drain
|
|
2292
|
-
// in captureAndStoreShell elides + pins them into the snapshot's loader
|
|
2293
|
-
// family.
|
|
2294
|
-
derivedCtx._shellCaptureLoaderRecords = new Map();
|
|
2295
|
-
// Own onResponse list so the capture's match-middleware callbacks (the doc
|
|
2296
|
-
// record registers here) are ISOLATED from the foreground's shared array AND
|
|
2297
|
-
// can be fired by settleCaptureRecord. The segment write is gated behind
|
|
2298
|
-
// onResponse, which the capture never triggers (it builds no Response):
|
|
2299
|
-
// without the synthetic fire, no doc record is written.
|
|
2300
|
-
derivedCtx._onResponseCallbacks = [];
|
|
2301
|
-
|
|
2302
|
-
// Capture data snapshot: route the capture's cache-store calls through a
|
|
2303
|
-
// recording wrapper on the DERIVED context's store (own property, so the
|
|
2304
|
-
// shared reqCtx._cacheStore is untouched — the snapshot is per-capture). It
|
|
2305
|
-
// records the doc segment record, which rides inside the ShellCacheEntry
|
|
2306
|
-
// with the bake-lane loader pins; no cache read is recorded. See
|
|
2307
|
-
// cache/shell-snapshot.ts and the design doc.
|
|
2308
|
-
//
|
|
2309
|
-
// Cache writes are deferred (waitUntil): the doc record's write would
|
|
2310
|
-
// otherwise land after the match returns. Override the derived context's
|
|
2311
|
-
// waitUntil to COLLECT those write promises (still forwarding to the parent
|
|
2312
|
-
// so the write persists and the worker stays alive), then
|
|
2313
|
-
// settleCaptureRecord and captureAndStoreShell await them before reading
|
|
2314
|
-
// the record and draining.
|
|
2315
|
-
if (reqCtx._cacheStore) {
|
|
2316
|
-
const recordingStore = new RecordingShellStore(reqCtx._cacheStore);
|
|
2317
|
-
derivedCtx._cacheStore = recordingStore;
|
|
2318
|
-
derivedCtx.waitUntil = (fn: () => Promise<void>): void => {
|
|
2319
|
-
const p = Promise.resolve().then(fn);
|
|
2320
|
-
recordingStore.trackWrite(p);
|
|
2321
|
-
reqCtx.waitUntil(() => p);
|
|
2322
|
-
};
|
|
2323
|
-
// The doc record (capture side): the implicit doc-cache scope makes the
|
|
2324
|
-
// capture's match write ALL matched non-loader segments as one doc-keyed
|
|
2325
|
-
// segment record — into the snapshot only (SnapshotOnlySegmentStore), so
|
|
2326
|
-
// the record dies with the shell entry and the next capture's lookup
|
|
2327
|
-
// still misses (handlers re-run on recapture). A route deriving its own
|
|
2328
|
-
// cache() scope keeps it, and recordShellCaptureDocRecord (cache-store.ts)
|
|
2329
|
-
// writes the same record for it.
|
|
2330
|
-
derivedCtx._shellImplicitCache = {
|
|
2331
|
-
ttl: descriptor.ttl,
|
|
2332
|
-
swr: descriptor.swr,
|
|
2333
|
-
store: new SnapshotOnlySegmentStore(recordingStore),
|
|
2334
|
-
keyPrefix: "doc",
|
|
2335
|
-
};
|
|
2336
|
-
}
|
|
2376
|
+
// The row creates it; the push funnel above writes it.
|
|
2377
|
+
const captureState = derivedCtx._shellCaptureState!;
|
|
2337
2378
|
|
|
2338
2379
|
return {
|
|
2339
2380
|
derivedCtx,
|
|
@@ -2479,7 +2520,7 @@ async function captureAndStoreShell(
|
|
|
2479
2520
|
// container still pending here either pinned the tree (the trivial-prelude
|
|
2480
2521
|
// gate above already returned no-shell) or postponed under an ANCESTOR
|
|
2481
2522
|
// boundary (it is a hole; omitting the record keeps it live).
|
|
2482
|
-
const loaderRecords = reqCtx.
|
|
2523
|
+
const loaderRecords = reqCtx._shellCaptureState?.loaderRecords;
|
|
2483
2524
|
// Set once a bake-lane loader settles with real (non-hole) material: its data
|
|
2484
2525
|
// is frozen into the shell prelude regardless of whether snapshot
|
|
2485
2526
|
// serialization succeeds. Drives the opt-in debug metadata below.
|
|
@@ -2492,7 +2533,9 @@ async function captureAndStoreShell(
|
|
|
2492
2533
|
| typeof import("../cache/segment-codec.js").serializeResult
|
|
2493
2534
|
| undefined;
|
|
2494
2535
|
// Capture-wide, read once: every record of this snapshot agrees.
|
|
2495
|
-
const runs: 0 | 1 = reqCtx.
|
|
2536
|
+
const runs: 0 | 1 = reqCtx._shellCaptureState?.unrecordedLoaderPush
|
|
2537
|
+
? 1
|
|
2538
|
+
: 0;
|
|
2496
2539
|
for (const [segmentKey, containerPromise] of loaderRecords) {
|
|
2497
2540
|
const elided = await elideLoaderContainer(containerPromise);
|
|
2498
2541
|
if (elided.state === "rejected") {
|
|
@@ -2583,7 +2626,7 @@ async function captureAndStoreShell(
|
|
|
2583
2626
|
// The drain's container encode adds its Flight errors here too (#927).
|
|
2584
2627
|
// After the loader drain so a rejected bake-lane loader keeps its specific
|
|
2585
2628
|
// refusal. Thrown like a fatal shell error: no retry, reportCacheError and
|
|
2586
|
-
// backoff in
|
|
2629
|
+
// backoff in planShellCapture.
|
|
2587
2630
|
const renderErrors = reqCtx._renderErrors;
|
|
2588
2631
|
if (renderErrors && renderErrors.length > 0) throw renderErrors[0];
|
|
2589
2632
|
|
|
@@ -2744,7 +2787,7 @@ async function captureAndStoreShell(
|
|
|
2744
2787
|
// Capped to the route record the capture used: the policy the
|
|
2745
2788
|
// store would apply (resolveTtl/resolveSwrWindow, as putShell does),
|
|
2746
2789
|
// made explicit.
|
|
2747
|
-
const record = reqCtx.
|
|
2790
|
+
const record = reqCtx._shellCaptureState?.routeRecordWindow;
|
|
2748
2791
|
let window: { ttl?: number; swr?: number } = {
|
|
2749
2792
|
ttl: capture.ttl,
|
|
2750
2793
|
swr: capture.swr,
|