@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/rsc-rendering.ts
CHANGED
|
@@ -10,8 +10,8 @@ import {
|
|
|
10
10
|
getRequestContext,
|
|
11
11
|
setRequestContextParams,
|
|
12
12
|
runWithRequestContext,
|
|
13
|
-
wireRenderBarrier,
|
|
14
13
|
} from "../server/request-context.js";
|
|
14
|
+
import { deriveRenderPass } from "../server/render-pass.js";
|
|
15
15
|
import {
|
|
16
16
|
SeededShellStore,
|
|
17
17
|
ShellRecordUnavailableError,
|
|
@@ -58,11 +58,11 @@ import {
|
|
|
58
58
|
capShellWindow,
|
|
59
59
|
lastStoredCaptureSeq,
|
|
60
60
|
markCaptureBackoff,
|
|
61
|
-
|
|
61
|
+
planShellCapture,
|
|
62
62
|
resolveShellCaptureDebugSink,
|
|
63
63
|
takeCaptureDebugEventForTiming,
|
|
64
64
|
describeShellCaptureEvent,
|
|
65
|
-
type
|
|
65
|
+
type ShellCaptureInput,
|
|
66
66
|
} from "./shell-capture.js";
|
|
67
67
|
import {
|
|
68
68
|
PPR_REPLAY_STATUS_HEADER,
|
|
@@ -87,8 +87,10 @@ import {
|
|
|
87
87
|
describeShellTailTiming,
|
|
88
88
|
publishShellTailTiming,
|
|
89
89
|
takeShellTailTimingForServerTiming,
|
|
90
|
+
readShell,
|
|
90
91
|
type ResolvedPprConfig,
|
|
91
92
|
type ShellDocument,
|
|
93
|
+
type ShellRead,
|
|
92
94
|
type ShellTailTiming,
|
|
93
95
|
} from "./shell-serve.js";
|
|
94
96
|
import {
|
|
@@ -99,7 +101,8 @@ import {
|
|
|
99
101
|
import { contextGet } from "../context-var.js";
|
|
100
102
|
import { rewriteShellAudience, rewriteTrail } from "../server/rewrite-trail.js";
|
|
101
103
|
import { visitorShellKey } from "./shell-capture-constants.js";
|
|
102
|
-
import { PREFETCH_HEADER
|
|
104
|
+
import { PREFETCH_HEADER } from "../prefetch-markers.js";
|
|
105
|
+
import { WIRE } from "../wire-params.js";
|
|
103
106
|
import {
|
|
104
107
|
isWarmReplace,
|
|
105
108
|
noteWarmShellEvent,
|
|
@@ -130,9 +133,7 @@ import { INTERNAL_RANGO_DEBUG } from "../internal-debug.js";
|
|
|
130
133
|
import type {
|
|
131
134
|
SegmentCacheStore,
|
|
132
135
|
ShellCacheEntry,
|
|
133
|
-
ShellDocumentRead,
|
|
134
136
|
ShellEntryHead,
|
|
135
|
-
ShellReadStats,
|
|
136
137
|
ShellSnapshotRecord,
|
|
137
138
|
} from "../cache/types.js";
|
|
138
139
|
import {
|
|
@@ -202,33 +203,41 @@ function requestShellBase(
|
|
|
202
203
|
};
|
|
203
204
|
}
|
|
204
205
|
|
|
205
|
-
|
|
206
|
-
|
|
206
|
+
/** The shell capture of `key` this request schedules. */
|
|
207
|
+
function createShellCapture(
|
|
208
|
+
input: RequestRenderInput<any>,
|
|
207
209
|
key: string,
|
|
208
210
|
base: RequestShellBase,
|
|
209
|
-
originSeed: string,
|
|
210
211
|
pprConfig: ResolvedPprConfig,
|
|
211
|
-
|
|
212
|
+
ssr: ShellCaptureInput["ssr"],
|
|
212
213
|
navigationOnly?: true,
|
|
213
|
-
):
|
|
214
|
+
): ShellCaptureInput {
|
|
215
|
+
const { ctx, request, env, url, reqCtx } = input;
|
|
214
216
|
return {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
217
|
+
ctx,
|
|
218
|
+
request,
|
|
219
|
+
env,
|
|
220
|
+
url,
|
|
221
|
+
reqCtx,
|
|
222
|
+
ssr,
|
|
223
|
+
descriptor: {
|
|
224
|
+
key,
|
|
225
|
+
searchSeed: base.searchSeed,
|
|
226
|
+
pathname: base.pathname,
|
|
227
|
+
// Derived from the same request URL as the key: seeding the origin
|
|
228
|
+
// keeps origin-dependent static markup (Link's data-external)
|
|
229
|
+
// identical across capture, resume, and browser hydration.
|
|
230
|
+
originSeed: url.origin,
|
|
231
|
+
buildVersion: ctx.version,
|
|
232
|
+
ttl: pprConfig.ttl,
|
|
233
|
+
swr: pprConfig.swr,
|
|
234
|
+
tags: pprConfig.tags,
|
|
235
|
+
captureTimeout: pprConfig.captureTimeout,
|
|
236
|
+
store: reqCtx._cacheStore,
|
|
237
|
+
maxSnapshotBytes: pprConfig.maxSnapshotBytes,
|
|
238
|
+
debugSink: resolveShellCaptureDebugSink(ctx.router.debugShellCapture),
|
|
239
|
+
navigationOnly,
|
|
240
|
+
},
|
|
232
241
|
};
|
|
233
242
|
}
|
|
234
243
|
|
|
@@ -343,7 +352,7 @@ interface RequestRenderInput<TEnv> {
|
|
|
343
352
|
|
|
344
353
|
type ShellServeOutcome =
|
|
345
354
|
| { kind: "serve"; response: Response }
|
|
346
|
-
| { kind: "miss";
|
|
355
|
+
| { kind: "miss"; capture: ShellCaptureInput }
|
|
347
356
|
| { kind: "pass" };
|
|
348
357
|
|
|
349
358
|
type PreparedRender =
|
|
@@ -454,18 +463,8 @@ function* requestRenderPlan<TEnv>(
|
|
|
454
463
|
response.status === 200 &&
|
|
455
464
|
(response.headers.get("content-type") ?? "").includes("text/html")
|
|
456
465
|
) {
|
|
457
|
-
const {
|
|
458
|
-
yield* handoff("shell-capture", () =>
|
|
459
|
-
scheduleShellCapture(
|
|
460
|
-
input.ctx,
|
|
461
|
-
input.request,
|
|
462
|
-
input.env,
|
|
463
|
-
input.url,
|
|
464
|
-
reqCtx,
|
|
465
|
-
ssrModule,
|
|
466
|
-
descriptor,
|
|
467
|
-
),
|
|
468
|
-
);
|
|
466
|
+
const { capture } = shell;
|
|
467
|
+
yield* handoff("shell-capture", () => planShellCapture(capture));
|
|
469
468
|
}
|
|
470
469
|
response.headers.set(SHELL_STATUS_HEADER, "MISS");
|
|
471
470
|
}
|
|
@@ -520,8 +519,7 @@ function warnIfPprWindowReduced(
|
|
|
520
519
|
function* shellServePlan<TEnv>(
|
|
521
520
|
input: RequestRenderInput<TEnv>,
|
|
522
521
|
): RoutinePlan<ShellServeOutcome> {
|
|
523
|
-
const { ctx, request, env, url, isPartial,
|
|
524
|
-
input;
|
|
522
|
+
const { ctx, request, env, url, isPartial, nonce, reqCtx } = input;
|
|
525
523
|
const manifestEntry = reqCtx._classifiedRoute?.manifestEntry;
|
|
526
524
|
|
|
527
525
|
// A router.prerender() warm (prerender/warm-request.ts) reports the shell of
|
|
@@ -627,7 +625,10 @@ function* shellServePlan<TEnv>(
|
|
|
627
625
|
const cached = warmReplace
|
|
628
626
|
? null
|
|
629
627
|
: yield* step("shell-read", () =>
|
|
630
|
-
|
|
628
|
+
readShell(store, key, {
|
|
629
|
+
tagHints: pprConfig.tags,
|
|
630
|
+
metrics: reqCtx._metricsStore,
|
|
631
|
+
}),
|
|
631
632
|
);
|
|
632
633
|
|
|
633
634
|
// allReady (ssr.resolveStreaming) bypasses PPR entirely: buffering defeats
|
|
@@ -643,14 +644,16 @@ function* shellServePlan<TEnv>(
|
|
|
643
644
|
) {
|
|
644
645
|
return { kind: "pass" };
|
|
645
646
|
}
|
|
646
|
-
|
|
647
|
-
|
|
647
|
+
// A MISS's capture after the response, or a HIT's recapture (stale, or a
|
|
648
|
+
// tail that could not finish).
|
|
649
|
+
const capture = createShellCapture(
|
|
650
|
+
input,
|
|
648
651
|
key,
|
|
649
652
|
base,
|
|
650
|
-
url.origin,
|
|
651
653
|
pprConfig,
|
|
652
|
-
|
|
654
|
+
() => ssrModule,
|
|
653
655
|
);
|
|
656
|
+
const { descriptor } = capture;
|
|
654
657
|
if (warm) {
|
|
655
658
|
// The warm learns its capture's outcome from the capture's own events,
|
|
656
659
|
// ahead of the router's debugShellCapture sink.
|
|
@@ -660,24 +663,27 @@ function* shellServePlan<TEnv>(
|
|
|
660
663
|
routerSink?.(event);
|
|
661
664
|
};
|
|
662
665
|
}
|
|
666
|
+
// Neither the stored shell nor the build shell is read: a warm renders.
|
|
667
|
+
if (warmReplace) {
|
|
668
|
+
return {
|
|
669
|
+
kind: "miss",
|
|
670
|
+
capture: { ...capture, descriptor: { ...descriptor, force: true } },
|
|
671
|
+
};
|
|
672
|
+
}
|
|
663
673
|
// MISS (no entry, invalid reactVersion, a tombstone, or store read
|
|
664
674
|
// failure): axis 1 + a background capture scheduled once the response is
|
|
665
675
|
// known servable.
|
|
666
676
|
const miss: ShellServeOutcome = {
|
|
667
677
|
kind: "miss",
|
|
668
|
-
descriptor: { ...descriptor, storedSeqAtRead },
|
|
669
|
-
ssrModule,
|
|
678
|
+
capture: { ...capture, descriptor: { ...descriptor, storedSeqAtRead } },
|
|
670
679
|
};
|
|
671
|
-
// Neither the stored shell nor the build shell is read: a warm renders.
|
|
672
|
-
if (warmReplace) {
|
|
673
|
-
return { ...miss, descriptor: { ...descriptor, force: true } };
|
|
674
|
-
}
|
|
675
680
|
|
|
676
681
|
// A HIT replays the handler layer from the entry's doc record (`docKey`),
|
|
677
682
|
// or, for a Prerender route, from the prerender store. An entry that has
|
|
678
683
|
// neither is a MISS: serving it would run handlers after the commit.
|
|
679
684
|
// The entry must have rendered this request's location: the browser
|
|
680
685
|
// hydrates against the address bar.
|
|
686
|
+
let hit: { document: ShellDocument; recapture: boolean } | undefined;
|
|
681
687
|
if (
|
|
682
688
|
cached &&
|
|
683
689
|
!cached.entry.navigationOnly &&
|
|
@@ -700,42 +706,15 @@ function* shellServePlan<TEnv>(
|
|
|
700
706
|
"[ShellServe] getShell",
|
|
701
707
|
);
|
|
702
708
|
} else {
|
|
703
|
-
// An onlyIfStale warm found a servable shell; a recapture scheduled
|
|
704
|
-
// below overwrites this through the capture's events.
|
|
705
|
-
if (warm) warm.shell = "fresh";
|
|
706
709
|
// Stale (SWR) hit: serve the stale shell now, recapture in the
|
|
707
|
-
// background (stampede-guarded + backoff inside
|
|
710
|
+
// background (stampede-guarded + backoff inside planShellCapture),
|
|
708
711
|
// at most once per SHELL_MIN_RECAPTURE_INTERVAL_MS of the shell's age.
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
ctx,
|
|
716
|
-
request,
|
|
717
|
-
env,
|
|
718
|
-
url,
|
|
719
|
-
reqCtx,
|
|
720
|
-
ssrModule,
|
|
721
|
-
descriptor,
|
|
722
|
-
),
|
|
723
|
-
);
|
|
724
|
-
}
|
|
725
|
-
const response = yield* step("shell-hit", () =>
|
|
726
|
-
serveShellHit(
|
|
727
|
-
ctx,
|
|
728
|
-
request,
|
|
729
|
-
env,
|
|
730
|
-
url,
|
|
731
|
-
reqCtx,
|
|
732
|
-
handleStore,
|
|
733
|
-
ssrModule,
|
|
734
|
-
document,
|
|
735
|
-
descriptor,
|
|
736
|
-
),
|
|
737
|
-
);
|
|
738
|
-
return { kind: "serve", response };
|
|
712
|
+
hit = {
|
|
713
|
+
document,
|
|
714
|
+
recapture:
|
|
715
|
+
!!cached.shouldRevalidate &&
|
|
716
|
+
staleShellRecaptureDue(cached.entry, Date.now()),
|
|
717
|
+
};
|
|
739
718
|
}
|
|
740
719
|
}
|
|
741
720
|
|
|
@@ -764,78 +743,59 @@ function* shellServePlan<TEnv>(
|
|
|
764
743
|
// server's /__rsc_shell endpoint for PRERENDERED routes only (production's
|
|
765
744
|
// exact candidate set). Folded away in production builds (NODE_ENV is a
|
|
766
745
|
// compile-time constant).
|
|
767
|
-
|
|
768
|
-
let buildHit: Awaited<ReturnType<typeof lookupBuildShell>> = null;
|
|
769
|
-
if (base.pathname !== undefined) {
|
|
746
|
+
if (!hit && base.pathname === undefined) {
|
|
770
747
|
// A build shell is the target's: never served under a visitor key.
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
ctx.version,
|
|
795
|
-
store,
|
|
796
|
-
devShellLookup,
|
|
797
|
-
reqCtx._searchParamsFilter,
|
|
798
|
-
),
|
|
799
|
-
);
|
|
800
|
-
}
|
|
801
|
-
// The document serve decodes the baked prelude once here; an undecodable
|
|
802
|
-
// one is a MISS like a corrupt runtime entry.
|
|
803
|
-
const buildDocument = buildHit
|
|
804
|
-
? openShellDocumentMetered(reqCtx, { entry: buildHit.entry })
|
|
805
|
-
: null;
|
|
806
|
-
if (buildHit && buildDocument) {
|
|
807
|
-
if (warm) warm.shell = "fresh";
|
|
808
|
-
// Past ppr.ttl: still serve the baked entry, recapture upgrades it.
|
|
809
|
-
if (buildHit.stale) {
|
|
810
|
-
yield* handoff("shell-recapture", () =>
|
|
811
|
-
scheduleShellCapture(
|
|
812
|
-
ctx,
|
|
813
|
-
request,
|
|
814
|
-
env,
|
|
748
|
+
const devShellLookup = resolveDevShellLookup(reqCtx, pprConfig);
|
|
749
|
+
if (key !== baseKey) {
|
|
750
|
+
// Probed once per path without a build shell, and not again once the
|
|
751
|
+
// route warned. A search-bearing URL never reads one: nothing to probe.
|
|
752
|
+
const routeName = reqCtx._classifiedRoute?.routeKey;
|
|
753
|
+
if (
|
|
754
|
+
!partitionBuildShellCheckDone(ctx.router.id, url.pathname, routeName) &&
|
|
755
|
+
shellSearchSeed(url, reqCtx._searchParamsFilter) === ""
|
|
756
|
+
) {
|
|
757
|
+
const found = yield* step("build-shell-check", () =>
|
|
758
|
+
hasBuildShell(ctx.router.id, url.pathname, devShellLookup),
|
|
759
|
+
);
|
|
760
|
+
notePartitionBuildShellCheck(
|
|
761
|
+
ctx.router.id,
|
|
762
|
+
url.pathname,
|
|
763
|
+
routeName,
|
|
764
|
+
found,
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
} else {
|
|
768
|
+
const built = yield* step("build-shell-lookup", () =>
|
|
769
|
+
lookupBuildShell(
|
|
770
|
+
ctx.router.id,
|
|
815
771
|
url,
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
772
|
+
ctx.version,
|
|
773
|
+
store,
|
|
774
|
+
devShellLookup,
|
|
775
|
+
reqCtx._searchParamsFilter,
|
|
819
776
|
),
|
|
820
777
|
);
|
|
778
|
+
// The document serve decodes the baked prelude once here; an
|
|
779
|
+
// undecodable one is a MISS like a corrupt runtime entry.
|
|
780
|
+
const document =
|
|
781
|
+
built && openShellDocumentMetered(reqCtx, { entry: built.entry });
|
|
782
|
+
// Past ppr.ttl: still serve the baked entry, recapture upgrades it.
|
|
783
|
+
if (document) hit = { document, recapture: built.stale };
|
|
821
784
|
}
|
|
822
|
-
const response = yield* step("shell-hit", () =>
|
|
823
|
-
serveShellHit(
|
|
824
|
-
ctx,
|
|
825
|
-
request,
|
|
826
|
-
env,
|
|
827
|
-
url,
|
|
828
|
-
reqCtx,
|
|
829
|
-
handleStore,
|
|
830
|
-
ssrModule,
|
|
831
|
-
buildDocument,
|
|
832
|
-
descriptor,
|
|
833
|
-
),
|
|
834
|
-
);
|
|
835
|
-
return { kind: "serve", response };
|
|
836
785
|
}
|
|
786
|
+
if (!hit) return miss;
|
|
837
787
|
|
|
838
|
-
|
|
788
|
+
// An onlyIfStale warm found a servable shell; a recapture scheduled below
|
|
789
|
+
// overwrites this through the capture's events.
|
|
790
|
+
if (warm) warm.shell = "fresh";
|
|
791
|
+
if (hit.recapture) {
|
|
792
|
+
yield* handoff("shell-recapture", () => planShellCapture(capture));
|
|
793
|
+
}
|
|
794
|
+
const { document } = hit;
|
|
795
|
+
const response = yield* step("shell-hit", () =>
|
|
796
|
+
serveShellHit(input, ssrModule, document, capture),
|
|
797
|
+
);
|
|
798
|
+
return { kind: "serve", response };
|
|
839
799
|
}
|
|
840
800
|
|
|
841
801
|
/**
|
|
@@ -979,117 +939,13 @@ async function matchPartialAndRecordParams<TEnv>(
|
|
|
979
939
|
return replay;
|
|
980
940
|
}
|
|
981
941
|
|
|
982
|
-
/**
|
|
983
|
-
* A document serve's store read: a getShell entry, or a prelude-first read
|
|
984
|
-
* (readShellDocument) whose entry is the head and whose snapshot arrives on
|
|
985
|
-
* its own promise.
|
|
986
|
-
*/
|
|
987
|
-
type ShellStoreRead =
|
|
988
|
-
| { entry: ShellCacheEntry; shouldRevalidate?: boolean; read?: undefined }
|
|
989
|
-
| {
|
|
990
|
-
entry: ShellEntryHead;
|
|
991
|
-
shouldRevalidate?: boolean;
|
|
992
|
-
read: ShellDocumentRead;
|
|
993
|
-
};
|
|
994
|
-
|
|
995
|
-
/**
|
|
996
|
-
* Shell store read that degrades to null on failure (axis 1 MISS, never a 500)
|
|
997
|
-
* and records the raw `ppr:shell-read` outcome (pre-validity-gates, so a
|
|
998
|
-
* version-mismatch lifecycle miss stays distinguishable from a store miss).
|
|
999
|
-
* A store with a prelude-first read (readShellDocument) resolves before the
|
|
1000
|
-
* snapshot is read, so the HIT commits without waiting for it.
|
|
1001
|
-
*/
|
|
1002
|
-
async function readShellEntry(
|
|
1003
|
-
store: SegmentCacheStore & {
|
|
1004
|
-
getShell: NonNullable<SegmentCacheStore["getShell"]>;
|
|
1005
|
-
},
|
|
1006
|
-
key: string,
|
|
1007
|
-
reqCtx: ReturnType<typeof getRequestContext>,
|
|
1008
|
-
tagHints: readonly string[] | undefined,
|
|
1009
|
-
): Promise<ShellStoreRead | null> {
|
|
1010
|
-
let cached: ShellStoreRead | null = null;
|
|
1011
|
-
const shellReadStart = reqCtx._metricsStore ? performance.now() : 0;
|
|
1012
|
-
try {
|
|
1013
|
-
if (store.readShellDocument) {
|
|
1014
|
-
const read = await store.readShellDocument(
|
|
1015
|
-
key,
|
|
1016
|
-
tagHints ? { tagHints } : undefined,
|
|
1017
|
-
);
|
|
1018
|
-
cached = read
|
|
1019
|
-
? { entry: read.entry, shouldRevalidate: read.shouldRevalidate, read }
|
|
1020
|
-
: null;
|
|
1021
|
-
} else {
|
|
1022
|
-
cached = await store.getShell(key);
|
|
1023
|
-
}
|
|
1024
|
-
} catch (error) {
|
|
1025
|
-
reportCacheError(error, "cache-read", "[ShellServe] getShell");
|
|
1026
|
-
}
|
|
1027
|
-
if (reqCtx._metricsStore) {
|
|
1028
|
-
const stats = cached?.read?.stats;
|
|
1029
|
-
appendMetric(
|
|
1030
|
-
reqCtx._metricsStore,
|
|
1031
|
-
"ppr:shell-read",
|
|
1032
|
-
shellReadStart,
|
|
1033
|
-
performance.now() - shellReadStart,
|
|
1034
|
-
undefined,
|
|
1035
|
-
cached ? (stats ? `hit ${stats.tier}` : "hit") : "miss",
|
|
1036
|
-
);
|
|
1037
|
-
if (stats)
|
|
1038
|
-
recordShellReadStats(reqCtx._metricsStore, shellReadStart, stats);
|
|
1039
|
-
}
|
|
1040
|
-
return cached;
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
/**
|
|
1044
|
-
* A prelude-first read's sub-steps as `debugPerformance` rows under
|
|
1045
|
-
* `ppr:shell-read`. The marker row starts with the prelude row because the
|
|
1046
|
-
* two run in parallel; its desc says how long the read still waited on the
|
|
1047
|
-
* marker once the prelude was in. On a deployed worker the clock only
|
|
1048
|
-
* advances on I/O, so the byte counts are the cost signal for the CPU parts.
|
|
1049
|
-
*/
|
|
1050
|
-
function recordShellReadStats(
|
|
1051
|
-
store: MetricsStore,
|
|
1052
|
-
readStart: number,
|
|
1053
|
-
stats: ShellReadStats,
|
|
1054
|
-
): void {
|
|
1055
|
-
let at = readStart;
|
|
1056
|
-
const row = (label: string, ms: number | undefined, desc: string): void => {
|
|
1057
|
-
if (ms !== undefined) appendMetric(store, label, at, ms, 1, desc);
|
|
1058
|
-
};
|
|
1059
|
-
if (stats.memo) {
|
|
1060
|
-
row(
|
|
1061
|
-
"ppr:shell-memo",
|
|
1062
|
-
0,
|
|
1063
|
-
`${stats.memo.hit ? "hit" : "miss"} size=${stats.memo.bytes}b`,
|
|
1064
|
-
);
|
|
1065
|
-
}
|
|
1066
|
-
// A KV hit after an L1 miss: the L1 attempt first, then the KV read.
|
|
1067
|
-
row("ppr:shell-l1-miss", stats.l1MissMs, stats.l1MissReason ?? "miss");
|
|
1068
|
-
at += stats.l1MissMs ?? 0;
|
|
1069
|
-
row("ppr:shell-match", stats.matchMs, stats.tier);
|
|
1070
|
-
at += stats.matchMs ?? 0;
|
|
1071
|
-
row("ppr:shell-head", stats.headMs, `bytes=${stats.headBytes ?? 0}`);
|
|
1072
|
-
at += stats.headMs ?? 0;
|
|
1073
|
-
row("ppr:shell-prelude", stats.preludeMs, `bytes=${stats.preludeBytes ?? 0}`);
|
|
1074
|
-
let marker = `tags=${stats.tags ?? 0} ${stats.markerSerial ? "serial" : "parallel"} commit-wait=${(stats.markerWaitMs ?? 0).toFixed(2)}ms`;
|
|
1075
|
-
if (stats.markerMemo) marker += ` memo=${stats.markerMemo}`;
|
|
1076
|
-
if (stats.markerHinted) {
|
|
1077
|
-
marker += ` hint=${stats.markerHintHits ?? 0}/${stats.markerHinted.length}`;
|
|
1078
|
-
if (stats.markerLeadMs !== undefined) {
|
|
1079
|
-
marker += ` lead=${stats.markerLeadMs.toFixed(2)}ms`;
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
if (stats.freshReads) marker += " fresh-reads";
|
|
1083
|
-
row("ppr:shell-marker", stats.markerMs, marker);
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
942
|
/**
|
|
1087
943
|
* openShellDocument (the HIT's integrity gate and only prelude decode) as a
|
|
1088
944
|
* `ppr:shell-open` row: CPU only, so it reads 0 on a deployed worker.
|
|
1089
945
|
*/
|
|
1090
946
|
function openShellDocumentMetered(
|
|
1091
947
|
reqCtx: RequestContext<any>,
|
|
1092
|
-
stored:
|
|
948
|
+
stored: ShellRead,
|
|
1093
949
|
): ShellDocument | null {
|
|
1094
950
|
const read = stored.read;
|
|
1095
951
|
const open = (): ShellDocument | null =>
|
|
@@ -1275,7 +1131,7 @@ function renderPreparedRscResponse<TEnv>(
|
|
|
1275
1131
|
reqCtx._payloadDeferred ||
|
|
1276
1132
|
(isPartial &&
|
|
1277
1133
|
(reqCtx._requestKind === "fill" ||
|
|
1278
|
-
url.searchParams.get(
|
|
1134
|
+
url.searchParams.get(WIRE.stale) === "true"))
|
|
1279
1135
|
) {
|
|
1280
1136
|
rscHeaders["cache-control"] = NOT_REUSED;
|
|
1281
1137
|
}
|
|
@@ -1336,36 +1192,22 @@ function scheduleNavigationShellCapture<TEnv>(
|
|
|
1336
1192
|
input: RequestRenderInput<TEnv>,
|
|
1337
1193
|
captureKey: string,
|
|
1338
1194
|
): void {
|
|
1339
|
-
const { ctx,
|
|
1195
|
+
const { ctx, env, url, reqCtx } = input;
|
|
1340
1196
|
const pprConfig = resolvePprConfig(reqCtx._classifiedRoute?.manifestEntry)!;
|
|
1341
|
-
const store = reqCtx._cacheStore!;
|
|
1342
1197
|
const base = requestShellBase(ctx, reqCtx, url);
|
|
1343
1198
|
if (!base) return;
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
request,
|
|
1347
|
-
env,
|
|
1348
|
-
url,
|
|
1349
|
-
reqCtx,
|
|
1350
|
-
async (captureRequest, captureUrl) => {
|
|
1351
|
-
const [ssrModule, streamMode] = await getSSRSetup(
|
|
1352
|
-
ctx,
|
|
1353
|
-
captureRequest,
|
|
1354
|
-
env,
|
|
1355
|
-
captureUrl,
|
|
1356
|
-
undefined,
|
|
1357
|
-
);
|
|
1358
|
-
return streamMode === "allReady" ? null : ssrModule;
|
|
1359
|
-
},
|
|
1360
|
-
createShellCaptureDescriptor(
|
|
1199
|
+
const ssr = async (captureRequest: Request, captureUrl: URL) => {
|
|
1200
|
+
const [ssrModule, streamMode] = await getSSRSetup(
|
|
1361
1201
|
ctx,
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1202
|
+
captureRequest,
|
|
1203
|
+
env,
|
|
1204
|
+
captureUrl,
|
|
1205
|
+
undefined,
|
|
1206
|
+
);
|
|
1207
|
+
return streamMode === "allReady" ? null : ssrModule;
|
|
1208
|
+
};
|
|
1209
|
+
planShellCapture(
|
|
1210
|
+
createShellCapture(input, captureKey, base, pprConfig, ssr, true),
|
|
1369
1211
|
);
|
|
1370
1212
|
}
|
|
1371
1213
|
|
|
@@ -1465,7 +1307,7 @@ async function matchPartialWithPprReplay<TEnv>(
|
|
|
1465
1307
|
}
|
|
1466
1308
|
// A stale partial renders live, as an action does (rendersLive,
|
|
1467
1309
|
// router/match-context.ts): the record may predate the action's write.
|
|
1468
|
-
if (url.searchParams.get(
|
|
1310
|
+
if (url.searchParams.get(WIRE.stale) === "true") {
|
|
1469
1311
|
return runMatch({ outcome: "BYPASS", reason: "stale" });
|
|
1470
1312
|
}
|
|
1471
1313
|
// Fragment passthrough (#700), extended to partial replay. Navigation
|
|
@@ -1494,7 +1336,7 @@ async function matchPartialWithPprReplay<TEnv>(
|
|
|
1494
1336
|
//
|
|
1495
1337
|
// The probe is a PRE-match fast path, not the truth: it reads only the
|
|
1496
1338
|
// non-intercept artifact, and whether the navigation IS an intercept (and
|
|
1497
|
-
// therefore whether
|
|
1339
|
+
// therefore whether readPrerenderSource reads `paramHash` or
|
|
1498
1340
|
// `paramHash + "/i"`) is resolved during the match by match-api's
|
|
1499
1341
|
// findInterceptForRoute — an intercept-source header proves nothing in
|
|
1500
1342
|
// either direction. runMatch reclassifies every BYPASS from the stamped
|
|
@@ -1824,10 +1666,10 @@ async function isSettled(value: unknown): Promise<boolean> {
|
|
|
1824
1666
|
* leaves the entry alone.
|
|
1825
1667
|
*/
|
|
1826
1668
|
async function degradeUnreplayableShell(
|
|
1827
|
-
|
|
1669
|
+
capture: ShellCaptureInput,
|
|
1828
1670
|
entry: ShellEntryHead,
|
|
1829
|
-
scheduleRecapture: () => void,
|
|
1830
1671
|
): Promise<void> {
|
|
1672
|
+
const { descriptor } = capture;
|
|
1831
1673
|
const store = descriptor.store;
|
|
1832
1674
|
try {
|
|
1833
1675
|
await store?.putShell?.(
|
|
@@ -1846,7 +1688,7 @@ async function degradeUnreplayableShell(
|
|
|
1846
1688
|
reportCacheError(error, "cache-write", "[ShellServe] tombstone");
|
|
1847
1689
|
}
|
|
1848
1690
|
store?.dropShellMemo?.(descriptor.key);
|
|
1849
|
-
|
|
1691
|
+
planShellCapture(capture);
|
|
1850
1692
|
}
|
|
1851
1693
|
|
|
1852
1694
|
/**
|
|
@@ -1859,16 +1701,13 @@ async function degradeUnreplayableShell(
|
|
|
1859
1701
|
* frame; the adapter may pull the stream outside it.
|
|
1860
1702
|
*/
|
|
1861
1703
|
function serveShellHit(
|
|
1862
|
-
|
|
1863
|
-
request: Request,
|
|
1864
|
-
env: any,
|
|
1865
|
-
url: URL,
|
|
1866
|
-
reqCtx: RequestContext<any>,
|
|
1867
|
-
handleStore: ReturnType<typeof getRequestContext>["_handleStore"],
|
|
1704
|
+
input: RequestRenderInput<any>,
|
|
1868
1705
|
ssrModule: SSRModule,
|
|
1869
1706
|
document: ShellDocument,
|
|
1870
|
-
|
|
1707
|
+
capture: ShellCaptureInput,
|
|
1871
1708
|
): Response {
|
|
1709
|
+
const { ctx, request, env, url, reqCtx, handleStore } = input;
|
|
1710
|
+
const { descriptor } = capture;
|
|
1872
1711
|
const { entry, prelude: preludeBytes } = document;
|
|
1873
1712
|
// A visitor-keyed shell shows the visitor's URL: the document cache keys
|
|
1874
1713
|
// this response by the target.
|
|
@@ -1887,8 +1726,6 @@ function serveShellHit(
|
|
|
1887
1726
|
}
|
|
1888
1727
|
: null;
|
|
1889
1728
|
const tailT0 = tailTiming ? performance.now() : 0;
|
|
1890
|
-
const scheduleRecapture = (): void =>
|
|
1891
|
-
scheduleShellCapture(ctx, request, env, url, reqCtx, ssrModule, descriptor);
|
|
1892
1729
|
/** Report a broken entry, then tombstone and recapture it. Never throws. */
|
|
1893
1730
|
const retireEntry = async (reason: string): Promise<void> => {
|
|
1894
1731
|
reportCacheError(
|
|
@@ -1897,7 +1734,7 @@ function serveShellHit(
|
|
|
1897
1734
|
"[ShellServe] retire",
|
|
1898
1735
|
);
|
|
1899
1736
|
try {
|
|
1900
|
-
await degradeUnreplayableShell(
|
|
1737
|
+
await degradeUnreplayableShell(capture, entry);
|
|
1901
1738
|
} catch (error) {
|
|
1902
1739
|
reportCacheError(error, "cache-write", "[ShellServe] retire");
|
|
1903
1740
|
}
|
|
@@ -1905,26 +1742,6 @@ function serveShellHit(
|
|
|
1905
1742
|
/** The first loader pin of the entry that failed to decode (dc-t89). */
|
|
1906
1743
|
let undecodablePin: string | undefined;
|
|
1907
1744
|
|
|
1908
|
-
const createTailContext = (): RequestContext<any> => {
|
|
1909
|
-
const tailCtx: RequestContext<any> = Object.create(reqCtx);
|
|
1910
|
-
// Matching writes render state onto the derived context. Its barrier must
|
|
1911
|
-
// close over that same context or a streaming tail inherits the base
|
|
1912
|
-
// context's premature non-streaming handle snapshot.
|
|
1913
|
-
wireRenderBarrier(tailCtx, handleStore);
|
|
1914
|
-
// The tail's render barrier resolves once its record is replayed and
|
|
1915
|
-
// before a loader runs (cache-lookup.ts withCacheLookup, yieldFromStore).
|
|
1916
|
-
// The store then holds what the prelude was rendered from, so that is
|
|
1917
|
-
// the snapshot the document hydrates with; what this request's loaders
|
|
1918
|
-
// do to it afterwards reaches the client after hydration
|
|
1919
|
-
// (HandleStore.freezeDocumentSnapshot, issue #1035).
|
|
1920
|
-
const resolveRenderBarrier = tailCtx._resolveRenderBarrier;
|
|
1921
|
-
tailCtx._resolveRenderBarrier = (segments) => {
|
|
1922
|
-
handleStore.freezeDocumentSnapshot();
|
|
1923
|
-
resolveRenderBarrier(segments);
|
|
1924
|
-
};
|
|
1925
|
-
return tailCtx;
|
|
1926
|
-
};
|
|
1927
|
-
|
|
1928
1745
|
const renderTail = async (
|
|
1929
1746
|
activeCtx: RequestContext<any>,
|
|
1930
1747
|
): Promise<ReadableStream<Uint8Array> | { redirect: string }> => {
|
|
@@ -2036,7 +1853,6 @@ function serveShellHit(
|
|
|
2036
1853
|
tailTiming.snapshotParseMs = Math.round(snapshotStats.parseMs);
|
|
2037
1854
|
}
|
|
2038
1855
|
}
|
|
2039
|
-
const seededCtx = createTailContext();
|
|
2040
1856
|
const records = snapshot ?? [];
|
|
2041
1857
|
// Loader-family records (bake-lane containers, loader-container-bake):
|
|
2042
1858
|
// decode into a seed Map for the resolveLoaderData overlay, so the
|
|
@@ -2060,32 +1876,31 @@ function serveShellHit(
|
|
|
2060
1876
|
`[Server][ppr] shell HIT: loader seed built +${Math.round(performance.now() - seedStart)}ms (abs ${Math.round(performance.now())})`,
|
|
2061
1877
|
);
|
|
2062
1878
|
}
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
seededCtx._shellFragmentPayload = true;
|
|
1879
|
+
// What the tail owns, and why: RENDER_PASS_FIELDS["ppr-tail"].
|
|
1880
|
+
const seededCtx = deriveRenderPass(reqCtx, "ppr-tail", {
|
|
1881
|
+
handleStore,
|
|
1882
|
+
loaderSeed,
|
|
1883
|
+
// The handler layer is REPLAYED from the entry's own doc record, never
|
|
1884
|
+
// executed: the implicit doc scope replaces any route-derived scope
|
|
1885
|
+
// (docTail), looks the record up by the key the capture wrote
|
|
1886
|
+
// (fixedDocKey), and reads it from a segment-only overlay of the
|
|
1887
|
+
// snapshot (the real store never holds a doc: record). A lookup that
|
|
1888
|
+
// does not hit throws ShellRecordUnavailableError (withCacheLookup),
|
|
1889
|
+
// handled by the degrade below. Live and promise-carrying bake-lane
|
|
1890
|
+
// loaders still run fresh; a promise-free bake-lane loader is served
|
|
1891
|
+
// from its pin (_shellLoaderSeed, loader-cache.ts). A Prerender route's
|
|
1892
|
+
// tail takes the handler layer from the prerender store before this
|
|
1893
|
+
// scope is consulted, and the loader pushes from `prerenderHandles`.
|
|
1894
|
+
implicitCache: {
|
|
1895
|
+
ttl: descriptor.ttl,
|
|
1896
|
+
swr: descriptor.swr,
|
|
1897
|
+
store: new SeededShellStore(reqCtx._cacheStore!, records),
|
|
1898
|
+
keyPrefix: "doc",
|
|
1899
|
+
docTail: true,
|
|
1900
|
+
fixedDocKey: entry.docKey,
|
|
1901
|
+
prerenderHandles: shellPrerenderHandles(records),
|
|
1902
|
+
},
|
|
1903
|
+
});
|
|
2089
1904
|
return runWithRequestContext(seededCtx, () => renderTail(seededCtx));
|
|
2090
1905
|
})();
|
|
2091
1906
|
// The stream below is the only consumer; pre-attach a no-op catch so a tail
|
|
@@ -2230,8 +2045,8 @@ function serveShellHit(
|
|
|
2230
2045
|
// here AFTER the 200 + prelude flushed and would re-fail on every
|
|
2231
2046
|
// request until the entry ages out — recapture overwrites the entry.
|
|
2232
2047
|
// Client disconnects land here too; the recapture is idempotent and
|
|
2233
|
-
// bounded by
|
|
2234
|
-
|
|
2048
|
+
// bounded by planShellCapture's stampede guard + backoff.
|
|
2049
|
+
planShellCapture(capture);
|
|
2235
2050
|
if (tailTiming) {
|
|
2236
2051
|
tailTiming.outcome = "error";
|
|
2237
2052
|
tailTiming.completeMs = Math.round(performance.now() - tailT0);
|