@rangojs/router 0.26.6 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/rango.js +1 -1
- package/dist/types/__internal.d.ts +7 -1
- package/dist/types/browser/action-coordinator.d.ts +0 -4
- package/dist/types/browser/action-fence.d.ts +11 -3
- package/dist/types/browser/event-controller.d.ts +20 -1
- package/dist/types/browser/history-state.d.ts +7 -9
- package/dist/types/browser/intercept-utils.d.ts +23 -0
- package/dist/types/browser/navigation-store.d.ts +8 -6
- package/dist/types/browser/react/filter-segment-order.d.ts +2 -2
- package/dist/types/browser/rsc-router.d.ts +1 -1
- package/dist/types/browser/segment-reconciler.d.ts +6 -4
- package/dist/types/browser/types.d.ts +2 -1
- package/dist/types/browser/wire-protocol.d.ts +25 -0
- package/dist/types/cache/cache-key-utils.d.ts +11 -5
- package/dist/types/cache/cache-scope.d.ts +34 -22
- package/dist/types/cache/cf/cf-cache-constants.d.ts +2 -2
- package/dist/types/cache/cf/cf-cache-store.d.ts +47 -65
- package/dist/types/cache/cf/cf-kv-utils.d.ts +1 -1
- package/dist/types/cache/handle-snapshot.d.ts +9 -4
- package/dist/types/cache/segment-codec.d.ts +14 -0
- package/dist/types/cache/shell-snapshot.d.ts +1 -1
- package/dist/types/cache/tag-invalidation.d.ts +10 -0
- package/dist/types/cache/taint.d.ts +5 -3
- package/dist/types/cache/types.d.ts +7 -3
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +8 -6
- package/dist/types/prefetch-markers.d.ts +4 -6
- package/dist/types/prerender/store.d.ts +31 -0
- package/dist/types/prerender/warm-request.d.ts +3 -3
- package/dist/types/prerender.d.ts +2 -2
- package/dist/types/route-name.d.ts +5 -0
- package/dist/types/router/error-handling.d.ts +4 -4
- package/dist/types/router/instrument.d.ts +4 -3
- package/dist/types/router/intercept-resolution.d.ts +25 -9
- package/dist/types/router/match-context.d.ts +12 -11
- package/dist/types/router/match-handlers.d.ts +2 -6
- package/dist/types/router/match-middleware/background-revalidation.d.ts +10 -20
- package/dist/types/router/match-middleware/cache-lookup.d.ts +35 -87
- package/dist/types/router/match-middleware/cache-store.d.ts +2 -2
- package/dist/types/router/match-middleware/segment-resolution.d.ts +8 -6
- package/dist/types/router/prerender-match.d.ts +21 -6
- package/dist/types/router/preview-match.d.ts +2 -7
- package/dist/types/router/request-classification.d.ts +5 -0
- package/dist/types/router/revalidation.d.ts +6 -1
- package/dist/types/router/router-context.d.ts +9 -63
- package/dist/types/router/router-interfaces.d.ts +8 -14
- package/dist/types/router/segment-resolution/loader-cache.d.ts +3 -3
- package/dist/types/router/segment-resolution/loader-mask.d.ts +4 -4
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +20 -18
- package/dist/types/router/segment-resolution/resolution-pass.d.ts +93 -0
- package/dist/types/router/segment-resolution/revalidation.d.ts +82 -31
- package/dist/types/router/segment-resolution.d.ts +1 -2
- package/dist/types/router/segment-wrappers.d.ts +11 -48
- package/dist/types/router/telemetry.d.ts +2 -0
- package/dist/types/router/types.d.ts +10 -0
- package/dist/types/rsc/full-payload.d.ts +2 -2
- package/dist/types/rsc/handler-context.d.ts +6 -6
- package/dist/types/rsc/json-route-result.d.ts +5 -9
- package/dist/types/rsc/request-stages.d.ts +203 -0
- package/dist/types/rsc/response-cache-serve.d.ts +7 -12
- package/dist/types/rsc/response-route-handler.d.ts +2 -2
- package/dist/types/rsc/routine-plan.d.ts +1 -1
- package/dist/types/rsc/server-action.d.ts +2 -2
- package/dist/types/rsc/shell-capture-constants.d.ts +2 -2
- package/dist/types/rsc/shell-capture.d.ts +132 -15
- package/dist/types/rsc/shell-serve.d.ts +37 -12
- package/dist/types/server/context.d.ts +9 -8
- package/dist/types/server/handle-store.d.ts +16 -0
- package/dist/types/server/render-pass.d.ts +72 -0
- package/dist/types/server/request-context.d.ts +24 -104
- package/dist/types/server/rewrite-trail.d.ts +2 -2
- package/dist/types/testing/dispatch.d.ts +35 -18
- package/dist/types/testing/serve-shell-request.d.ts +7 -0
- package/dist/types/types/cache-types.d.ts +6 -3
- package/dist/types/types/error-types.d.ts +9 -1
- package/dist/types/types/handler-context.d.ts +18 -7
- package/dist/types/types/segments.d.ts +1 -1
- package/dist/types/vite/discovery/build-discovery-phase.d.ts +40 -0
- package/dist/types/vite/discovery/build-env.d.ts +22 -0
- package/dist/types/vite/discovery/bundle-scan-phase.d.ts +12 -0
- package/dist/types/vite/discovery/dev-prerender-cache.d.ts +3 -2
- package/dist/types/vite/discovery/dev-server-phase.d.ts +26 -0
- package/dist/types/vite/discovery/discovery-errors.d.ts +1 -1
- package/dist/types/vite/discovery/state.d.ts +12 -0
- package/dist/types/vite/plugins/version-plugin.d.ts +1 -1
- package/dist/types/vite/plugins/virtual-entries.d.ts +1 -1
- package/dist/types/vite/router-discovery.d.ts +8 -47
- package/dist/types/vite/temp-rsc-server.d.ts +39 -0
- package/dist/types/vite/utils/prerender-utils.d.ts +9 -0
- package/dist/types/wire-params.d.ts +34 -0
- package/dist/vite/index.js +3809 -3710
- package/package.json +1 -1
- package/skills/cache-guide/SKILL.md +8 -5
- package/skills/caching/SKILL.md +37 -18
- package/skills/client-urls/SKILL.md +2 -1
- package/skills/document-cache/SKILL.md +18 -1
- package/skills/handler-use/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +53 -13
- package/skills/layout/SKILL.md +11 -7
- package/skills/loader/SKILL.md +30 -13
- package/skills/middleware/SKILL.md +19 -1
- package/skills/migrate-nextjs/SKILL.md +5 -4
- package/skills/parallel/SKILL.md +5 -4
- package/skills/ppr/SKILL.md +19 -3
- package/skills/prerender/SKILL.md +39 -15
- package/skills/rango/SKILL.md +23 -11
- package/skills/server-actions/SKILL.md +51 -6
- package/skills/testing/predicates.md +1 -1
- package/skills/testing/response-routes.md +6 -4
- package/skills/testing/serve-shell-request.md +7 -7
- package/skills/use-cache/SKILL.md +46 -8
- package/skills/ux-architecture/SKILL.md +3 -2
- package/src/__internal.ts +7 -1
- package/src/browser/action-coordinator.ts +2 -13
- package/src/browser/action-fence.ts +11 -3
- package/src/browser/connection-warmup.ts +3 -1
- package/src/browser/event-controller.ts +42 -3
- package/src/browser/history-state.ts +15 -13
- package/src/browser/intercept-utils.ts +51 -0
- package/src/browser/navigation-bridge.ts +34 -30
- package/src/browser/navigation-client.ts +21 -33
- package/src/browser/navigation-store.ts +25 -27
- package/src/browser/navigation-transaction.ts +7 -11
- package/src/browser/partial-update.ts +8 -0
- package/src/browser/prefetch/cache.ts +2 -1
- package/src/browser/prefetch/fetch.ts +8 -16
- package/src/browser/prefetch/queue.ts +4 -4
- package/src/browser/react/NavigationProvider.tsx +2 -1
- package/src/browser/react/filter-segment-order.ts +2 -2
- package/src/browser/rsc-router.tsx +17 -2
- package/src/browser/segment-reconciler.ts +66 -53
- package/src/browser/server-action-bridge.ts +265 -127
- package/src/browser/types.ts +2 -1
- package/src/browser/wire-protocol.ts +41 -0
- package/src/cache/cache-key-utils.ts +11 -5
- package/src/cache/cache-runtime.ts +110 -50
- package/src/cache/cache-scope.ts +110 -64
- package/src/cache/cf/cf-cache-constants.ts +2 -2
- package/src/cache/cf/cf-cache-store.ts +486 -874
- package/src/cache/cf/cf-kv-utils.ts +1 -1
- package/src/cache/document-cache.ts +42 -15
- package/src/cache/handle-snapshot.ts +19 -7
- package/src/cache/segment-codec.ts +55 -0
- package/src/cache/shell-snapshot.ts +2 -2
- package/src/cache/tag-invalidation.ts +21 -2
- package/src/cache/taint.ts +5 -3
- package/src/cache/types.ts +7 -3
- package/src/cache/vercel/vercel-cache-store.ts +243 -310
- package/src/handles/pushed-by.ts +1 -1
- package/src/prefetch-markers.ts +4 -7
- package/src/prerender/store.ts +76 -0
- package/src/prerender/warm-request.ts +8 -4
- package/src/prerender.ts +2 -2
- package/src/route-name.ts +8 -0
- package/src/router/error-handling.ts +4 -4
- package/src/router/instrument.ts +4 -3
- package/src/router/intercept-resolution.ts +77 -75
- package/src/router/loader-resolution.ts +5 -4
- package/src/router/match-api.ts +4 -5
- package/src/router/match-context.ts +33 -12
- package/src/router/match-handlers.ts +8 -20
- package/src/router/match-middleware/background-revalidation.ts +41 -53
- package/src/router/match-middleware/cache-lookup.ts +680 -720
- package/src/router/match-middleware/cache-store.ts +14 -12
- package/src/router/match-middleware/intercept-resolution.ts +3 -23
- package/src/router/match-middleware/segment-resolution.ts +34 -61
- package/src/router/match-result.ts +1 -20
- package/src/router/middleware.ts +69 -11
- package/src/router/navigation-snapshot.ts +4 -3
- package/src/router/prerender-match.ts +100 -44
- package/src/router/preview-match.ts +6 -41
- package/src/router/request-classification.ts +13 -7
- package/src/router/revalidation.ts +33 -7
- package/src/router/route-snapshot.ts +2 -5
- package/src/router/router-context.ts +14 -117
- package/src/router/router-interfaces.ts +8 -14
- package/src/router/segment-resolution/loader-cache.ts +14 -9
- package/src/router/segment-resolution/loader-mask.ts +5 -5
- package/src/router/segment-resolution/prefetch-deferral.ts +30 -27
- package/src/router/segment-resolution/resolution-pass.ts +137 -0
- package/src/router/segment-resolution/revalidation.ts +897 -776
- package/src/router/segment-resolution.ts +4 -13
- package/src/router/segment-wrappers.ts +51 -269
- package/src/router/telemetry.ts +5 -0
- package/src/router/types.ts +10 -0
- package/src/router.ts +12 -13
- package/src/rsc/full-payload.ts +6 -5
- package/src/rsc/handler-context.ts +8 -14
- package/src/rsc/handler.ts +126 -507
- package/src/rsc/json-route-result.ts +5 -9
- package/src/rsc/loader-fetch.ts +6 -5
- package/src/rsc/progressive-enhancement.ts +5 -0
- package/src/rsc/request-stages.ts +757 -0
- package/src/rsc/response-cache-serve.ts +10 -14
- package/src/rsc/response-route-handler.ts +13 -5
- package/src/rsc/rewrite-passes.ts +2 -1
- package/src/rsc/routine-plan.ts +1 -1
- package/src/rsc/rsc-rendering.ts +172 -349
- package/src/rsc/server-action.ts +87 -113
- package/src/rsc/shell-capture-constants.ts +3 -1
- package/src/rsc/shell-capture.ts +212 -169
- package/src/rsc/shell-serve.ts +121 -18
- package/src/rsc/ssr-setup.ts +5 -4
- package/src/segment-system.tsx +3 -3
- package/src/server/context.ts +22 -25
- package/src/server/handle-store.ts +55 -0
- package/src/server/render-pass.ts +291 -0
- package/src/server/request-context.ts +26 -115
- package/src/server/rewrite-trail.ts +6 -4
- package/src/ssr/index.tsx +1 -1
- package/src/ssr/ssr-root.tsx +1 -0
- package/src/testing/dispatch.ts +223 -710
- package/src/testing/e2e/page-helpers.ts +2 -2
- package/src/testing/serve-shell-request.ts +37 -32
- package/src/types/cache-types.ts +6 -3
- package/src/types/error-types.ts +9 -1
- package/src/types/handler-context.ts +21 -7
- package/src/types/segments.ts +1 -1
- package/src/vite/discovery/build-discovery-phase.ts +204 -0
- package/src/vite/discovery/build-env.ts +148 -0
- package/src/vite/discovery/bundle-scan-phase.ts +128 -0
- package/src/vite/discovery/dev-prerender-cache.ts +8 -3
- package/src/vite/discovery/dev-server-phase.ts +1776 -0
- package/src/vite/discovery/discover-routers.ts +1 -1
- package/src/vite/discovery/discovery-errors.ts +1 -1
- package/src/vite/discovery/prerender-collection.ts +24 -39
- package/src/vite/discovery/state.ts +15 -0
- package/src/vite/plugins/version-plugin.ts +1 -1
- package/src/vite/plugins/virtual-entries.ts +1 -1
- package/src/vite/router-discovery.ts +29 -2428
- package/src/vite/temp-rsc-server.ts +223 -0
- package/src/vite/utils/prerender-utils.ts +47 -0
- package/src/wire-params.ts +35 -0
- package/dist/types/router/segment-resolution/fresh.d.ts +0 -72
- package/src/router/segment-resolution/fresh.ts +0 -945
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 {
|
|
@@ -100,6 +102,7 @@ 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
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 =>
|
|
@@ -1268,11 +1124,14 @@ function renderPreparedRscResponse<TEnv>(
|
|
|
1268
1124
|
// carries deferred units answers one page's prefetch, and a fill is the
|
|
1269
1125
|
// second half of one adoption: neither is reused by a shared cache or the
|
|
1270
1126
|
// document cache (which also refuses a payload marked `_payloadDeferred`),
|
|
1271
|
-
// whatever Cache-Control the route set
|
|
1272
|
-
//
|
|
1127
|
+
// whatever Cache-Control the route set; so is a stale partial render, which
|
|
1128
|
+
// a shared cache keyed by URL must not store or serve. A route with no flag
|
|
1129
|
+
// keeps its headers.
|
|
1273
1130
|
if (
|
|
1274
1131
|
reqCtx._payloadDeferred ||
|
|
1275
|
-
(isPartial &&
|
|
1132
|
+
(isPartial &&
|
|
1133
|
+
(reqCtx._requestKind === "fill" ||
|
|
1134
|
+
url.searchParams.get(WIRE.stale) === "true"))
|
|
1276
1135
|
) {
|
|
1277
1136
|
rscHeaders["cache-control"] = NOT_REUSED;
|
|
1278
1137
|
}
|
|
@@ -1333,36 +1192,22 @@ function scheduleNavigationShellCapture<TEnv>(
|
|
|
1333
1192
|
input: RequestRenderInput<TEnv>,
|
|
1334
1193
|
captureKey: string,
|
|
1335
1194
|
): void {
|
|
1336
|
-
const { ctx,
|
|
1195
|
+
const { ctx, env, url, reqCtx } = input;
|
|
1337
1196
|
const pprConfig = resolvePprConfig(reqCtx._classifiedRoute?.manifestEntry)!;
|
|
1338
|
-
const store = reqCtx._cacheStore!;
|
|
1339
1197
|
const base = requestShellBase(ctx, reqCtx, url);
|
|
1340
1198
|
if (!base) return;
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
request,
|
|
1344
|
-
env,
|
|
1345
|
-
url,
|
|
1346
|
-
reqCtx,
|
|
1347
|
-
async (captureRequest, captureUrl) => {
|
|
1348
|
-
const [ssrModule, streamMode] = await getSSRSetup(
|
|
1349
|
-
ctx,
|
|
1350
|
-
captureRequest,
|
|
1351
|
-
env,
|
|
1352
|
-
captureUrl,
|
|
1353
|
-
undefined,
|
|
1354
|
-
);
|
|
1355
|
-
return streamMode === "allReady" ? null : ssrModule;
|
|
1356
|
-
},
|
|
1357
|
-
createShellCaptureDescriptor(
|
|
1199
|
+
const ssr = async (captureRequest: Request, captureUrl: URL) => {
|
|
1200
|
+
const [ssrModule, streamMode] = await getSSRSetup(
|
|
1358
1201
|
ctx,
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
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),
|
|
1366
1211
|
);
|
|
1367
1212
|
}
|
|
1368
1213
|
|
|
@@ -1460,6 +1305,11 @@ async function matchPartialWithPprReplay<TEnv>(
|
|
|
1460
1305
|
if (!getNavigationContextHeader(request, url.origin)) {
|
|
1461
1306
|
return runMatch({ outcome: "BYPASS", reason: "no-navigation-context" });
|
|
1462
1307
|
}
|
|
1308
|
+
// A stale partial renders live, as an action does (rendersLive,
|
|
1309
|
+
// router/match-context.ts): the record may predate the action's write.
|
|
1310
|
+
if (url.searchParams.get(WIRE.stale) === "true") {
|
|
1311
|
+
return runMatch({ outcome: "BYPASS", reason: "stale" });
|
|
1312
|
+
}
|
|
1463
1313
|
// Fragment passthrough (#700), extended to partial replay. Navigation
|
|
1464
1314
|
// context proves the request can be matched; the separate capability header
|
|
1465
1315
|
// proves its payload consumer can expand envelopes. Older clients and the
|
|
@@ -1486,7 +1336,7 @@ async function matchPartialWithPprReplay<TEnv>(
|
|
|
1486
1336
|
//
|
|
1487
1337
|
// The probe is a PRE-match fast path, not the truth: it reads only the
|
|
1488
1338
|
// non-intercept artifact, and whether the navigation IS an intercept (and
|
|
1489
|
-
// therefore whether
|
|
1339
|
+
// therefore whether readPrerenderSource reads `paramHash` or
|
|
1490
1340
|
// `paramHash + "/i"`) is resolved during the match by match-api's
|
|
1491
1341
|
// findInterceptForRoute — an intercept-source header proves nothing in
|
|
1492
1342
|
// either direction. runMatch reclassifies every BYPASS from the stamped
|
|
@@ -1816,10 +1666,10 @@ async function isSettled(value: unknown): Promise<boolean> {
|
|
|
1816
1666
|
* leaves the entry alone.
|
|
1817
1667
|
*/
|
|
1818
1668
|
async function degradeUnreplayableShell(
|
|
1819
|
-
|
|
1669
|
+
capture: ShellCaptureInput,
|
|
1820
1670
|
entry: ShellEntryHead,
|
|
1821
|
-
scheduleRecapture: () => void,
|
|
1822
1671
|
): Promise<void> {
|
|
1672
|
+
const { descriptor } = capture;
|
|
1823
1673
|
const store = descriptor.store;
|
|
1824
1674
|
try {
|
|
1825
1675
|
await store?.putShell?.(
|
|
@@ -1838,7 +1688,7 @@ async function degradeUnreplayableShell(
|
|
|
1838
1688
|
reportCacheError(error, "cache-write", "[ShellServe] tombstone");
|
|
1839
1689
|
}
|
|
1840
1690
|
store?.dropShellMemo?.(descriptor.key);
|
|
1841
|
-
|
|
1691
|
+
planShellCapture(capture);
|
|
1842
1692
|
}
|
|
1843
1693
|
|
|
1844
1694
|
/**
|
|
@@ -1851,16 +1701,13 @@ async function degradeUnreplayableShell(
|
|
|
1851
1701
|
* frame; the adapter may pull the stream outside it.
|
|
1852
1702
|
*/
|
|
1853
1703
|
function serveShellHit(
|
|
1854
|
-
|
|
1855
|
-
request: Request,
|
|
1856
|
-
env: any,
|
|
1857
|
-
url: URL,
|
|
1858
|
-
reqCtx: RequestContext<any>,
|
|
1859
|
-
handleStore: ReturnType<typeof getRequestContext>["_handleStore"],
|
|
1704
|
+
input: RequestRenderInput<any>,
|
|
1860
1705
|
ssrModule: SSRModule,
|
|
1861
1706
|
document: ShellDocument,
|
|
1862
|
-
|
|
1707
|
+
capture: ShellCaptureInput,
|
|
1863
1708
|
): Response {
|
|
1709
|
+
const { ctx, request, env, url, reqCtx, handleStore } = input;
|
|
1710
|
+
const { descriptor } = capture;
|
|
1864
1711
|
const { entry, prelude: preludeBytes } = document;
|
|
1865
1712
|
// A visitor-keyed shell shows the visitor's URL: the document cache keys
|
|
1866
1713
|
// this response by the target.
|
|
@@ -1879,8 +1726,6 @@ function serveShellHit(
|
|
|
1879
1726
|
}
|
|
1880
1727
|
: null;
|
|
1881
1728
|
const tailT0 = tailTiming ? performance.now() : 0;
|
|
1882
|
-
const scheduleRecapture = (): void =>
|
|
1883
|
-
scheduleShellCapture(ctx, request, env, url, reqCtx, ssrModule, descriptor);
|
|
1884
1729
|
/** Report a broken entry, then tombstone and recapture it. Never throws. */
|
|
1885
1730
|
const retireEntry = async (reason: string): Promise<void> => {
|
|
1886
1731
|
reportCacheError(
|
|
@@ -1889,7 +1734,7 @@ function serveShellHit(
|
|
|
1889
1734
|
"[ShellServe] retire",
|
|
1890
1735
|
);
|
|
1891
1736
|
try {
|
|
1892
|
-
await degradeUnreplayableShell(
|
|
1737
|
+
await degradeUnreplayableShell(capture, entry);
|
|
1893
1738
|
} catch (error) {
|
|
1894
1739
|
reportCacheError(error, "cache-write", "[ShellServe] retire");
|
|
1895
1740
|
}
|
|
@@ -1897,26 +1742,6 @@ function serveShellHit(
|
|
|
1897
1742
|
/** The first loader pin of the entry that failed to decode (dc-t89). */
|
|
1898
1743
|
let undecodablePin: string | undefined;
|
|
1899
1744
|
|
|
1900
|
-
const createTailContext = (): RequestContext<any> => {
|
|
1901
|
-
const tailCtx: RequestContext<any> = Object.create(reqCtx);
|
|
1902
|
-
// Matching writes render state onto the derived context. Its barrier must
|
|
1903
|
-
// close over that same context or a streaming tail inherits the base
|
|
1904
|
-
// context's premature non-streaming handle snapshot.
|
|
1905
|
-
wireRenderBarrier(tailCtx, handleStore);
|
|
1906
|
-
// The tail's render barrier resolves once its record is replayed and
|
|
1907
|
-
// before a loader runs (cache-lookup.ts withCacheLookup, yieldFromStore).
|
|
1908
|
-
// The store then holds what the prelude was rendered from, so that is
|
|
1909
|
-
// the snapshot the document hydrates with; what this request's loaders
|
|
1910
|
-
// do to it afterwards reaches the client after hydration
|
|
1911
|
-
// (HandleStore.freezeDocumentSnapshot, issue #1035).
|
|
1912
|
-
const resolveRenderBarrier = tailCtx._resolveRenderBarrier;
|
|
1913
|
-
tailCtx._resolveRenderBarrier = (segments) => {
|
|
1914
|
-
handleStore.freezeDocumentSnapshot();
|
|
1915
|
-
resolveRenderBarrier(segments);
|
|
1916
|
-
};
|
|
1917
|
-
return tailCtx;
|
|
1918
|
-
};
|
|
1919
|
-
|
|
1920
1745
|
const renderTail = async (
|
|
1921
1746
|
activeCtx: RequestContext<any>,
|
|
1922
1747
|
): Promise<ReadableStream<Uint8Array> | { redirect: string }> => {
|
|
@@ -2028,7 +1853,6 @@ function serveShellHit(
|
|
|
2028
1853
|
tailTiming.snapshotParseMs = Math.round(snapshotStats.parseMs);
|
|
2029
1854
|
}
|
|
2030
1855
|
}
|
|
2031
|
-
const seededCtx = createTailContext();
|
|
2032
1856
|
const records = snapshot ?? [];
|
|
2033
1857
|
// Loader-family records (bake-lane containers, loader-container-bake):
|
|
2034
1858
|
// decode into a seed Map for the resolveLoaderData overlay, so the
|
|
@@ -2052,32 +1876,31 @@ function serveShellHit(
|
|
|
2052
1876
|
`[Server][ppr] shell HIT: loader seed built +${Math.round(performance.now() - seedStart)}ms (abs ${Math.round(performance.now())})`,
|
|
2053
1877
|
);
|
|
2054
1878
|
}
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
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
|
+
});
|
|
2081
1904
|
return runWithRequestContext(seededCtx, () => renderTail(seededCtx));
|
|
2082
1905
|
})();
|
|
2083
1906
|
// The stream below is the only consumer; pre-attach a no-op catch so a tail
|
|
@@ -2222,8 +2045,8 @@ function serveShellHit(
|
|
|
2222
2045
|
// here AFTER the 200 + prelude flushed and would re-fail on every
|
|
2223
2046
|
// request until the entry ages out — recapture overwrites the entry.
|
|
2224
2047
|
// Client disconnects land here too; the recapture is idempotent and
|
|
2225
|
-
// bounded by
|
|
2226
|
-
|
|
2048
|
+
// bounded by planShellCapture's stampede guard + backoff.
|
|
2049
|
+
planShellCapture(capture);
|
|
2227
2050
|
if (tailTiming) {
|
|
2228
2051
|
tailTiming.outcome = "error";
|
|
2229
2052
|
tailTiming.completeMs = Math.round(performance.now() - tailT0);
|