@rangojs/router 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +255 -89
- package/dist/testing/vitest.js +252 -0
- package/dist/types/browser/cookie-name.d.ts +6 -0
- package/dist/types/browser/history-state.d.ts +16 -0
- package/dist/types/browser/navigation-transaction.d.ts +6 -0
- package/dist/types/browser/partial-update.d.ts +11 -0
- package/dist/types/browser/react/ScrollRestoration.d.ts +6 -6
- package/dist/types/browser/rsc-router.d.ts +1 -1
- package/dist/types/browser/scroll-restoration.d.ts +7 -0
- package/dist/types/cache/cache-exec-scope.d.ts +18 -1
- package/dist/types/cache/cache-key-utils.d.ts +17 -2
- package/dist/types/cache/cache-policy.d.ts +17 -0
- package/dist/types/cache/cache-scope.d.ts +132 -19
- package/dist/types/cache/cache-tag.d.ts +92 -7
- package/dist/types/cache/cf/cf-base64.d.ts +6 -0
- package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
- package/dist/types/cache/cf/cf-cache-store.d.ts +185 -44
- package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
- package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
- package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +34 -0
- package/dist/types/cache/handle-capture.d.ts +41 -3
- package/dist/types/cache/handle-snapshot.d.ts +54 -13
- package/dist/types/cache/index.d.ts +1 -0
- package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
- package/dist/types/cache/segment-codec.d.ts +10 -3
- package/dist/types/cache/shell-memo.d.ts +178 -0
- package/dist/types/cache/shell-snapshot.d.ts +110 -21
- package/dist/types/cache/tag-invalidation.d.ts +30 -3
- package/dist/types/cache/taint.d.ts +1 -1
- package/dist/types/cache/types.d.ts +220 -44
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +111 -13
- package/dist/types/client.d.ts +4 -4
- package/dist/types/client.rsc.d.ts +4 -5
- package/dist/types/components/DefaultDocument.d.ts +5 -2
- package/dist/types/context-var.d.ts +6 -5
- package/dist/types/escape-script.d.ts +1 -1
- package/dist/types/handles/MetaTags.d.ts +5 -4
- package/dist/types/handles/Scripts.d.ts +8 -7
- package/dist/types/handles/meta.d.ts +2 -2
- package/dist/types/handles/script.d.ts +17 -16
- package/dist/types/href-client.d.ts +1 -1
- package/dist/types/html.d.ts +12 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.rsc.d.ts +3 -2
- package/dist/types/loader-store.d.ts +7 -4
- package/dist/types/prerender/build-shell-capture.d.ts +11 -4
- package/dist/types/reverse.d.ts +36 -4
- package/dist/types/route-definition/dsl-helpers.d.ts +3 -3
- package/dist/types/route-definition/helpers-types.d.ts +33 -9
- package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
- package/dist/types/route-types.d.ts +1 -1
- package/dist/types/router/debug-manifest.d.ts +3 -2
- package/dist/types/router/error-handling.d.ts +20 -0
- package/dist/types/router/handler-context.d.ts +6 -0
- package/dist/types/router/instrument.d.ts +8 -0
- package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
- package/dist/types/router/middleware-types.d.ts +9 -3
- package/dist/types/router/router-context.d.ts +3 -1
- package/dist/types/router/router-interfaces.d.ts +24 -2
- package/dist/types/router/router-options.d.ts +8 -5
- package/dist/types/router/segment-resolution/fresh.d.ts +10 -0
- package/dist/types/router/segment-resolution/loader-cache.d.ts +66 -1
- package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +23 -12
- package/dist/types/router/segment-resolution/mask-nested.d.ts +67 -26
- package/dist/types/router/segment-resolution/revalidation.d.ts +6 -2
- package/dist/types/router/segment-wrappers.d.ts +1 -1
- package/dist/types/rsc/full-payload.d.ts +14 -0
- package/dist/types/rsc/handler.d.ts +1 -1
- package/dist/types/rsc/response-route-handler.d.ts +1 -1
- package/dist/types/rsc/rsc-rendering.d.ts +1 -2
- package/dist/types/rsc/shell-build-manifest.d.ts +8 -0
- package/dist/types/rsc/shell-capture-constants.d.ts +61 -6
- package/dist/types/rsc/shell-capture.d.ts +151 -19
- package/dist/types/rsc/shell-serve.d.ts +140 -17
- package/dist/types/rsc/ssr-setup.d.ts +6 -6
- package/dist/types/rsc/types.d.ts +17 -0
- package/dist/types/server/context.d.ts +139 -15
- package/dist/types/server/cookie-store.d.ts +21 -0
- package/dist/types/server/handle-store.d.ts +26 -2
- package/dist/types/server/request-context.d.ts +182 -53
- package/dist/types/ssr/index.d.ts +24 -0
- package/dist/types/ssr/ssr-root.d.ts +9 -8
- package/dist/types/static-handler.d.ts +4 -1
- package/dist/types/testing/flight.entry.d.ts +7 -0
- package/dist/types/testing/generated-routes.d.ts +2 -2
- package/dist/types/testing/internal/context.d.ts +1 -1
- package/dist/types/testing/render-route.d.ts +32 -6
- package/dist/types/testing/run-loader.d.ts +18 -3
- package/dist/types/testing/serve-shell-request.d.ts +132 -0
- package/dist/types/testing/shell-status.d.ts +23 -10
- package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +28 -5
- package/dist/types/testing/vitest.d.ts +35 -1
- package/dist/types/theme/ThemeScript.d.ts +3 -3
- package/dist/types/types/cache-types.d.ts +14 -10
- package/dist/types/types/handler-context.d.ts +28 -1
- package/dist/types/types/loader-types.d.ts +8 -6
- package/dist/types/urls/pattern-types.d.ts +22 -18
- package/dist/types/urls/response-types.d.ts +3 -12
- package/dist/types/use-loader.d.ts +12 -3
- package/dist/types/vite/plugins/use-cache-transform.d.ts +33 -2
- package/dist/types/vite/rango.d.ts +1 -1
- package/dist/types/vite/router-discovery.d.ts +29 -2
- package/dist/vite/index.js +1420 -1348
- package/package.json +2 -2
- package/skills/api-client/SKILL.md +22 -10
- package/skills/breadcrumbs/SKILL.md +75 -22
- package/skills/bundle-analysis/SKILL.md +22 -22
- package/skills/cache-guide/SKILL.md +306 -124
- package/skills/caching/SKILL.md +530 -98
- package/skills/catalog.json +16 -16
- package/skills/client-urls/SKILL.md +65 -38
- package/skills/cloudflare/SKILL.md +87 -20
- package/skills/cloudflare/references/d1-and-local-dev.md +15 -2
- package/skills/cloudflare/references/streaming-and-deploy.md +10 -3
- package/skills/cloudflare/references/webhooks-and-crypto.md +9 -2
- package/skills/comparison/SKILL.md +5 -0
- package/skills/comparison/references/framework-comparison.md +75 -57
- package/skills/composability/SKILL.md +38 -29
- package/skills/css/SKILL.md +10 -13
- package/skills/debug-manifest/SKILL.md +74 -31
- package/skills/defer-hydration/SKILL.md +52 -31
- package/skills/deployment-caching/SKILL.md +11 -3
- package/skills/document-cache/SKILL.md +98 -28
- package/skills/fonts/SKILL.md +16 -8
- package/skills/handler-use/SKILL.md +55 -17
- package/skills/hooks/SKILL.md +28 -20
- package/skills/hooks/data.md +23 -3
- package/skills/hooks/handle-and-actions.md +34 -17
- package/skills/hooks/navigation.md +39 -10
- package/skills/hooks/outlets.md +13 -6
- package/skills/hooks/state.md +39 -15
- package/skills/hooks/urls.md +7 -5
- package/skills/host-router/SKILL.md +64 -12
- package/skills/i18n/SKILL.md +60 -34
- package/skills/intercept/SKILL.md +161 -26
- package/skills/layout/SKILL.md +106 -50
- package/skills/links/SKILL.md +51 -19
- package/skills/loader/SKILL.md +386 -128
- package/skills/middleware/SKILL.md +129 -26
- package/skills/migrate-nextjs/SKILL.md +156 -70
- package/skills/migrate-nextjs/backend-host-swap.md +5 -2
- package/skills/migrate-react-router/SKILL.md +51 -39
- package/skills/migrate-react-router/component-migration.md +28 -7
- package/skills/migrate-react-router/data-and-actions.md +21 -6
- package/skills/migrate-react-router/route-mapping.md +19 -10
- package/skills/mime-routes/SKILL.md +12 -7
- package/skills/observability/SKILL.md +92 -14
- package/skills/parallel/SKILL.md +88 -47
- package/skills/ppr/SKILL.md +866 -379
- package/skills/prerender/SKILL.md +210 -108
- package/skills/rango/SKILL.md +115 -65
- package/skills/react-compiler/SKILL.md +6 -5
- package/skills/response-routes/SKILL.md +133 -25
- package/skills/route/SKILL.md +170 -60
- package/skills/router-setup/SKILL.md +420 -240
- package/skills/scripts/SKILL.md +39 -21
- package/skills/server-actions/SKILL.md +231 -59
- package/skills/shell-manifest/SKILL.md +28 -14
- package/skills/streams-and-websockets/SKILL.md +32 -14
- package/skills/tailwind/SKILL.md +37 -33
- package/skills/testing/SKILL.md +73 -33
- package/skills/testing/cache-prerender.md +60 -33
- package/skills/testing/client-components.md +59 -6
- package/skills/testing/e2e-parity.md +12 -2
- package/skills/testing/flight.md +19 -11
- package/skills/testing/handles.md +8 -5
- package/skills/testing/loader.md +26 -6
- package/skills/testing/middleware.md +8 -3
- package/skills/testing/predicates.md +120 -0
- package/skills/testing/render-handler.md +70 -22
- package/skills/testing/response-routes.md +11 -4
- package/skills/testing/reverse-and-types.md +20 -14
- package/skills/testing/serve-shell-request.md +188 -0
- package/skills/testing/server-actions.md +12 -3
- package/skills/testing/server-tree.md +9 -0
- package/skills/testing/setup.md +9 -5
- package/skills/theme/SKILL.md +155 -30
- package/skills/typesafety/SKILL.md +20 -9
- package/skills/typesafety/env-and-bindings.md +137 -50
- package/skills/typesafety/generated-files-and-cli.md +26 -14
- package/skills/typesafety/params-and-search.md +44 -19
- package/skills/typesafety/route-types.md +50 -14
- package/skills/use-cache/SKILL.md +224 -83
- package/skills/vercel/SKILL.md +35 -13
- package/skills/view-transitions/SKILL.md +42 -21
- package/src/__augment-tests__/augmented.check.ts +26 -1
- package/src/browser/cookie-name.ts +23 -0
- package/src/browser/history-state.ts +23 -0
- package/src/browser/index.ts +2 -2
- package/src/browser/navigation-bridge.ts +3 -4
- package/src/browser/navigation-transaction.ts +22 -2
- package/src/browser/partial-update.ts +35 -25
- package/src/browser/react/ScrollRestoration.tsx +6 -6
- package/src/browser/rsc-router.tsx +6 -1
- package/src/browser/scroll-restoration.ts +53 -6
- package/src/cache/cache-exec-scope.ts +37 -4
- package/src/cache/cache-key-utils.ts +23 -8
- package/src/cache/cache-policy.ts +22 -0
- package/src/cache/cache-runtime.ts +264 -68
- package/src/cache/cache-scope.ts +419 -43
- package/src/cache/cache-tag.ts +375 -11
- package/src/cache/cf/cf-base64.ts +36 -4
- package/src/cache/cf/cf-cache-constants.ts +12 -0
- package/src/cache/cf/cf-cache-store.ts +990 -348
- package/src/cache/cf/cf-cache-types.ts +32 -4
- package/src/cache/cf/cf-shell-frame.ts +258 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +143 -39
- package/src/cache/document-cache.ts +15 -0
- package/src/cache/handle-capture.ts +90 -10
- package/src/cache/handle-snapshot.ts +133 -28
- package/src/cache/index.ts +2 -0
- package/src/cache/isolate-tag-memo.ts +308 -0
- package/src/cache/segment-codec.ts +26 -7
- package/src/cache/shell-memo.ts +341 -0
- package/src/cache/shell-snapshot.ts +217 -27
- package/src/cache/tag-invalidation.ts +108 -22
- package/src/cache/taint.ts +1 -1
- package/src/cache/types.ts +225 -44
- package/src/cache/vercel/vercel-cache-store.ts +545 -61
- package/src/client.rsc.tsx +10 -9
- package/src/client.tsx +5 -4
- package/src/components/DefaultDocument.tsx +7 -2
- package/src/context-var.ts +6 -5
- package/src/escape-script.ts +1 -1
- package/src/handles/MetaTags.tsx +13 -11
- package/src/handles/Scripts.tsx +11 -10
- package/src/handles/meta.ts +2 -2
- package/src/handles/script.ts +17 -16
- package/src/href-client.ts +1 -1
- package/src/html.ts +12 -0
- package/src/index.rsc.ts +5 -0
- package/src/index.ts +1 -0
- package/src/loader-store.ts +7 -4
- package/src/prerender/build-shell-capture.ts +73 -22
- package/src/reverse.ts +44 -4
- package/src/route-definition/dsl-helpers.ts +121 -33
- package/src/route-definition/helpers-types.ts +33 -9
- package/src/route-definition/resolve-handler-use.ts +121 -6
- package/src/route-types.ts +0 -3
- package/src/router/debug-manifest.ts +10 -2
- package/src/router/error-handling.ts +51 -15
- package/src/router/handler-context.ts +71 -28
- package/src/router/instrument.ts +54 -2
- package/src/router/intercept-resolution.ts +141 -43
- package/src/router/loader-resolution.ts +115 -39
- package/src/router/match-api.ts +40 -26
- package/src/router/match-handlers.ts +5 -0
- package/src/router/match-middleware/background-revalidation.ts +161 -96
- package/src/router/match-middleware/cache-lookup.ts +110 -10
- package/src/router/match-middleware/cache-store.ts +48 -101
- package/src/router/middleware-types.ts +9 -6
- package/src/router/middleware.ts +75 -13
- package/src/router/prerender-match.ts +59 -19
- package/src/router/route-snapshot.ts +11 -1
- package/src/router/router-context.ts +2 -0
- package/src/router/router-interfaces.ts +25 -2
- package/src/router/router-options.ts +8 -5
- package/src/router/segment-resolution/fresh.ts +62 -21
- package/src/router/segment-resolution/helpers.ts +4 -2
- package/src/router/segment-resolution/loader-cache.ts +407 -76
- package/src/router/segment-resolution/loader-mask.ts +12 -22
- package/src/router/segment-resolution/loader-snapshot.ts +208 -56
- package/src/router/segment-resolution/mask-nested.ts +138 -44
- package/src/router/segment-resolution/revalidation.ts +61 -16
- package/src/router/segment-wrappers.ts +4 -0
- package/src/rsc/full-payload.ts +25 -1
- package/src/rsc/handler.ts +14 -2
- package/src/rsc/progressive-enhancement.ts +3 -2
- package/src/rsc/render-pipeline.ts +3 -0
- package/src/rsc/response-cache-serve.ts +9 -6
- package/src/rsc/response-route-handler.ts +11 -3
- package/src/rsc/rsc-rendering.ts +608 -225
- package/src/rsc/shell-build-manifest.ts +27 -9
- package/src/rsc/shell-capture-constants.ts +73 -6
- package/src/rsc/shell-capture.ts +1068 -296
- package/src/rsc/shell-serve.ts +316 -34
- package/src/rsc/ssr-setup.ts +14 -8
- package/src/rsc/types.ts +18 -0
- package/src/server/context.ts +300 -32
- package/src/server/cookie-store.ts +87 -61
- package/src/server/handle-store.ts +232 -7
- package/src/server/request-context.ts +297 -67
- package/src/ssr/index.tsx +136 -44
- package/src/ssr/ssr-root.tsx +9 -8
- package/src/static-handler.ts +4 -1
- package/src/testing/flight-runtime.d.ts +21 -0
- package/src/testing/flight.entry.ts +14 -0
- package/src/testing/generated-routes.ts +2 -2
- package/src/testing/internal/context.ts +1 -1
- package/src/testing/internal/flight-client-globals.ts +7 -5
- package/src/testing/render-route.tsx +85 -24
- package/src/testing/run-loader.ts +68 -6
- package/src/testing/serve-shell-request.ts +378 -0
- package/src/testing/shell-status.ts +34 -12
- package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
- package/src/testing/vitest.ts +62 -1
- package/src/theme/ThemeProvider.tsx +7 -6
- package/src/theme/ThemeScript.tsx +3 -3
- package/src/types/cache-types.ts +14 -10
- package/src/types/handler-context.ts +28 -1
- package/src/types/loader-types.ts +8 -6
- package/src/urls/pattern-types.ts +22 -18
- package/src/urls/response-types.ts +3 -23
- package/src/use-loader.tsx +12 -3
- package/src/vite/discovery/shell-prerender-phase.ts +9 -0
- package/src/vite/plugins/use-cache-transform.ts +118 -74
- package/src/vite/rango.ts +7 -3
- package/src/vite/router-discovery.ts +14 -4
|
@@ -12,6 +12,7 @@ import type { RscPayload } from "./types.js";
|
|
|
12
12
|
import type { HandlerContext } from "./handler-context.js";
|
|
13
13
|
import type { RequestContext } from "../server/request-context.js";
|
|
14
14
|
import type { HandleStore } from "../server/handle-store.js";
|
|
15
|
+
import type { Theme } from "../theme/types.js";
|
|
15
16
|
/**
|
|
16
17
|
* Build the metadata payload for a full (non-partial) document render.
|
|
17
18
|
*
|
|
@@ -20,3 +21,16 @@ import type { HandleStore } from "../server/handle-store.js";
|
|
|
20
21
|
* derived-context store.
|
|
21
22
|
*/
|
|
22
23
|
export declare function buildFullPayload(m: MatchResult, ctx: HandlerContext<any>, url: URL, reqCtx: RequestContext<any>, handleStore: HandleStore): RscPayload;
|
|
24
|
+
/**
|
|
25
|
+
* The theme a document payload carries as `metadata.initialTheme` (the
|
|
26
|
+
* ThemeProvider's first state), read with no identity guard: it is the
|
|
27
|
+
* router's own read, not handler code (#971).
|
|
28
|
+
*
|
|
29
|
+
* A shell capture carries the no-cookie default, not the capturing visitor's
|
|
30
|
+
* theme. The shell and its ShellCacheEntry.initialTheme serve every visitor,
|
|
31
|
+
* and ThemeProvider re-syncs only from an explicitly stored theme, so a
|
|
32
|
+
* visitor with none kept the capturer's `useTheme().theme` for the page's
|
|
33
|
+
* lifetime. A visitor with a stored theme gets it pre-paint (theme script)
|
|
34
|
+
* and in the provider after mount.
|
|
35
|
+
*/
|
|
36
|
+
export declare function payloadInitialTheme(reqCtx: Pick<RequestContext<any>, "_themeConfig" | "_shellCaptureRun" | "_readTheme">): Theme | undefined;
|
|
@@ -6,4 +6,4 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { CreateRSCHandlerOptions } from "./types.js";
|
|
8
8
|
import type { RouterRequestInput } from "../router/router-interfaces.js";
|
|
9
|
-
export declare function createRSCHandler<TEnv = unknown, TRoutes extends Record<string, string> = Record<string, string>>(options: CreateRSCHandlerOptions<TEnv, TRoutes>): (
|
|
9
|
+
export declare function createRSCHandler<TEnv = unknown, TRoutes extends Record<string, string> = Record<string, string>>(options: CreateRSCHandlerOptions<TEnv, TRoutes>): (incomingRequest: Request, input?: RouterRequestInput<TEnv>) => Promise<Response>;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* dispatch, route middleware execution, and response caching with SWR.
|
|
7
7
|
*/
|
|
8
8
|
import type { MiddlewareFn } from "../router/middleware.js";
|
|
9
|
-
import type
|
|
9
|
+
import { type EntryData } from "../server/context.js";
|
|
10
10
|
import type { HandlerContext } from "./handler-context.js";
|
|
11
11
|
export interface ResponseRouteMatch {
|
|
12
12
|
responseType: string;
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
* RSC Rendering Handler (Navigation)
|
|
3
3
|
*
|
|
4
4
|
* Handles RSC rendering for both partial (client-side navigation) and full
|
|
5
|
-
* (initial page load) requests.
|
|
6
|
-
* static generation.
|
|
5
|
+
* (initial page load) requests.
|
|
7
6
|
*/
|
|
8
7
|
import { getRequestContext } from "../server/request-context.js";
|
|
9
8
|
import type { HandlerContext } from "./handler-context.js";
|
|
@@ -73,6 +73,14 @@ export interface DevShellLookup {
|
|
|
73
73
|
/** Resolved `ppr.captureTimeout` (ms) — the endpoint's capture honors it. */
|
|
74
74
|
captureTimeout?: number;
|
|
75
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Whether a build-time shell exists for `pathname` (the caller checks the
|
|
78
|
+
* request is search-less, like the entries lookupBuildShell serves): a
|
|
79
|
+
* manifest entry for the path in production, a Prerender route in dev. No
|
|
80
|
+
* asset load and no dev capture: it only decides whether a request that
|
|
81
|
+
* cannot read the build shell deserves a warning.
|
|
82
|
+
*/
|
|
83
|
+
export declare function hasBuildShell(pathname: string, dev?: DevShellLookup): Promise<boolean>;
|
|
76
84
|
/**
|
|
77
85
|
* Look up the baked shell entry for a request, applying every serve gate:
|
|
78
86
|
* search-less requests only (the build captured the bare pathname; a
|
|
@@ -14,17 +14,64 @@
|
|
|
14
14
|
* costs latency-to-HIT only — never a served response — and 5s spuriously
|
|
15
15
|
* refused legitimately-slow deferred shell material (a real storefront's meta
|
|
16
16
|
* chains settle at ~7s). Ceiling math: workerd's waitUntil lifetime is ~30s
|
|
17
|
-
* past response completion
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
17
|
+
* past response completion. An attempt that consumed the whole budget is not
|
|
18
|
+
* retried in place (shell-capture.ts noShellCauses), so the envelope is one
|
|
19
|
+
* budget plus store I/O; the retry follows only an attempt that ended early
|
|
20
|
+
* (a cold-module abort). Past the ceiling the platform kill degrades to the
|
|
21
|
+
* existing best-effort contract (the key stays MISS; a later request
|
|
22
|
+
* re-captures).
|
|
23
23
|
* Node/dev and build-time captures have no waitUntil ceiling. The per-route
|
|
24
24
|
* `ppr.captureTimeout` knob remains for tightening below the default. See
|
|
25
25
|
* docs/design/ppr-shell-resume.md (Cost model).
|
|
26
26
|
*/
|
|
27
27
|
export declare const SHELL_CAPTURE_MAX_WAIT_MS = 15000;
|
|
28
|
+
/**
|
|
29
|
+
* The shell key for one request partition (cache-scope.ts
|
|
30
|
+
* resolveShellPartition): the route cache() `key()` or store keyGenerator
|
|
31
|
+
* result that partitions the route's record partitions its shell too, so
|
|
32
|
+
* each partition captures and serves its own. Here, in the leaf module, so
|
|
33
|
+
* the testing helper `shellCacheKey` (testing/shell-status.ts) calls it
|
|
34
|
+
* without pulling shell-serve.ts.
|
|
35
|
+
*
|
|
36
|
+
* The partition is URI-encoded: it is request-derived, and raw it could end
|
|
37
|
+
* in a suffix another key is built with (navigationShellKey), so a partition
|
|
38
|
+
* `gold:navigation` named gold's navigation entry and its document shell
|
|
39
|
+
* replayed to gold's navigations. Encoded, it holds no `:` or `|`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function partitionShellKey(key: string, partition: string): string;
|
|
42
|
+
/**
|
|
43
|
+
* The navigation-only entry beside a document shell key (a cold partial
|
|
44
|
+
* request's capture, rsc-rendering.ts). A suffix after the (encoded)
|
|
45
|
+
* partition: partitionShellKey keeps the two unambiguous.
|
|
46
|
+
*/
|
|
47
|
+
export declare function navigationShellKey(key: string): string;
|
|
48
|
+
/**
|
|
49
|
+
* Fixed number of macrotask hops between `quiesce` resolving and the abort. These
|
|
50
|
+
* give React's fizz worker turns to flush the settled shell into the prelude and
|
|
51
|
+
* mark still-pending boundaries as POSTPONED (rather than errored) before
|
|
52
|
+
* controller.abort() lands. Not a wall-clock wait. Taken by captureShellHTML
|
|
53
|
+
* (ssr/index.tsx) and by the testing SSR stub (testing/serve-shell-request.ts),
|
|
54
|
+
* so the stub freezes the same Flight the real capture would.
|
|
55
|
+
*
|
|
56
|
+
* Why 16 and not the original 2: under the REPLAY-ONLY capture model
|
|
57
|
+
* (docs/design/ppr-shell-resume.md), the capture Flight render serializes ring-3
|
|
58
|
+
* cached segments that are ALREADY serialized, so it emits the whole shell payload
|
|
59
|
+
* in the first tick and the gate declares quiesce almost immediately (~a few ms).
|
|
60
|
+
* On the old fresh-execution path the Flight dribbled out as handlers ran, so
|
|
61
|
+
* Flight-quiet effectively meant "the shell has rendered" and 2 hops sufficed.
|
|
62
|
+
*
|
|
63
|
+
* Hops alone are NOT render-readiness: fizz cannot emit even <html> until the
|
|
64
|
+
* payload root settles, nor complete a shell whose client components are
|
|
65
|
+
* still loading — real module-runner I/O in dev (100ms+ cold), which no fixed
|
|
66
|
+
* count of near-zero-cost task hops can buy. captureShellHTML therefore
|
|
67
|
+
* awaits the payload-settled signal (SsrRootOptions.onPayloadSettled) and
|
|
68
|
+
* then the client-reference loads in flight in the isolate (captureClientLoads),
|
|
69
|
+
* deadline-bounded, between quiesce and these hops; the hops then only flush
|
|
70
|
+
* the settled tree and mark pending boundaries POSTPONED. Still task-based
|
|
71
|
+
* (masked loaders never emit, so more hops never lets a hole settle). Bounded
|
|
72
|
+
* by maxWaitMs end to end.
|
|
73
|
+
*/
|
|
74
|
+
export declare const POST_QUIESCE_TASK_HOPS = 16;
|
|
28
75
|
/**
|
|
29
76
|
* Hard cap on one capture TASK — runShellCapture end to end (both attempts +
|
|
30
77
|
* the in-place retry delay). SHELL_CAPTURE_MAX_WAIT_MS arms only inside
|
|
@@ -42,3 +89,11 @@ export declare const SHELL_CAPTURE_MAX_WAIT_MS = 15000;
|
|
|
42
89
|
* check on the stampede guard, keyed off the same constant.
|
|
43
90
|
*/
|
|
44
91
|
export declare const SHELL_CAPTURE_TASK_HARD_CAP_MS = 25000;
|
|
92
|
+
/**
|
|
93
|
+
* Shared PPR loader-lane hint, appended to every capture warning or error
|
|
94
|
+
* whose fix depends on the lane (shell-capture.ts warnings, the cookie-store.ts
|
|
95
|
+
* capture guard). Lives in this import-free leaf so cookie-store.ts can import
|
|
96
|
+
* it without pulling the capture orchestration module. Source anchor for the
|
|
97
|
+
* rule: resolveLoaderData (router/segment-resolution/loader-cache.ts).
|
|
98
|
+
*/
|
|
99
|
+
export declare const PPR_LANE_HINT: string;
|
|
@@ -15,8 +15,9 @@
|
|
|
15
15
|
* the triggering request, and the derived context inherits its post-middleware
|
|
16
16
|
* state (variables, cache store). Guarding is serve-time.
|
|
17
17
|
*/
|
|
18
|
+
import { type MatchResult } from "../types.js";
|
|
18
19
|
import { SHELL_CAPTURE_MAX_WAIT_MS } from "./shell-capture-constants.js";
|
|
19
|
-
import { type RequestContext } from "../server/request-context.js";
|
|
20
|
+
import { type RequestContext, type RouteRecordWindow } from "../server/request-context.js";
|
|
20
21
|
import { type HandleStore } from "../server/handle-store.js";
|
|
21
22
|
import type { SegmentCacheStore } from "../cache/types.js";
|
|
22
23
|
import type { HandlerContext } from "./handler-context.js";
|
|
@@ -50,10 +51,11 @@ declare function delay(ms: number): Promise<void>;
|
|
|
50
51
|
* mode's ceiling (60s in production, {@link REFUSED_CAPTURE_DEV_MAX_MS} in dev).
|
|
51
52
|
*
|
|
52
53
|
* Why exponential and not a flat 60s: a flat long window conflates two very
|
|
53
|
-
* different failures. A STRUCTURALLY ineligible route (
|
|
54
|
-
* reader) fails forever and wants the long 60s cap. But a
|
|
55
|
-
* can also fail the in-place retry under a truly cold
|
|
56
|
-
* or a cold worker under parallel load) — and it
|
|
54
|
+
* different failures. A STRUCTURALLY ineligible route (a boundary-less live
|
|
55
|
+
* loader read, a cookie reader) fails forever and wants the long 60s cap. But a
|
|
56
|
+
* cold-but-ELIGIBLE route can also fail the in-place retry under a truly cold
|
|
57
|
+
* graph (dev module transform, or a cold worker under parallel load) — and it
|
|
58
|
+
* must recover FAST, on the next
|
|
57
59
|
* request or two, not be frozen for 60s (that would re-break the very cold-start DX
|
|
58
60
|
* the retry fixes; it bit the cloudflare dev e2e). Escalating from 1s means the
|
|
59
61
|
* eligible route re-probes almost immediately (warm now → HIT and clear), while the
|
|
@@ -90,6 +92,13 @@ declare function isCaptureBackedOff(key: string): boolean;
|
|
|
90
92
|
declare function markCaptureBackoff(key: string): void;
|
|
91
93
|
/** Clear any backoff for a key that just captured successfully. */
|
|
92
94
|
declare function clearCaptureBackoff(key: string): void;
|
|
95
|
+
/**
|
|
96
|
+
* Default limit (bytes) on a whole stored shell entry — prelude, postponed
|
|
97
|
+
* state, and snapshot — when the store declares none
|
|
98
|
+
* (SegmentCacheStore.maxShellEntryBytes). Cloudflare KV's 25 MiB value limit:
|
|
99
|
+
* the tightest common store tier a shell entry lands in.
|
|
100
|
+
*/
|
|
101
|
+
export declare const DEFAULT_SHELL_ENTRY_MAX_BYTES: number;
|
|
93
102
|
/**
|
|
94
103
|
* Default cap (serialized UTF-8 bytes) on the capture data snapshot riding
|
|
95
104
|
* inside a shell entry, when the route's `ppr` option does not set
|
|
@@ -99,8 +108,9 @@ declare function clearCaptureBackoff(key: string): void;
|
|
|
99
108
|
* while still fitting any sane pinned-ring payload. Applied ONLY in
|
|
100
109
|
* captureAndStoreShell (the single defaulting site — resolvePprConfig passes
|
|
101
110
|
* the option through undefaulted), so every producer and direct caller gets
|
|
102
|
-
* the same policy.
|
|
103
|
-
*
|
|
111
|
+
* the same policy. The doc record is exempt; over the cap the other records
|
|
112
|
+
* are dropped (shell still stored and replayed; the pins' loaders read the
|
|
113
|
+
* live store — see PartialPrerenderProps.maxSnapshotBytes).
|
|
104
114
|
*/
|
|
105
115
|
export declare const DEFAULT_PPR_MAX_SNAPSHOT_BYTES: number;
|
|
106
116
|
/**
|
|
@@ -115,10 +125,13 @@ export interface ShellCaptureDebugEvent {
|
|
|
115
125
|
key: string;
|
|
116
126
|
/**
|
|
117
127
|
* What happened:
|
|
118
|
-
* - stored / redirect / no-shell / refused: one capture
|
|
119
|
-
* (see CaptureAttemptOutcome for the semantics of each)
|
|
128
|
+
* - stored / redirect / no-shell / refused / expired: one capture
|
|
129
|
+
* ATTEMPT's outcome (see CaptureAttemptOutcome for the semantics of each)
|
|
120
130
|
* - error: the capture task failed with a genuine error (also routed through
|
|
121
131
|
* reportCacheError; the key is backed off)
|
|
132
|
+
* - skip-stored: a capture for the key stored after the scheduling request
|
|
133
|
+
* read the store and missed (ShellCaptureDescriptor.storedSeqAtRead), so
|
|
134
|
+
* the shell that request missed exists now
|
|
122
135
|
* - skip-in-flight: scheduleShellCapture found a capture already running for
|
|
123
136
|
* the key (stampede guard) and scheduled nothing
|
|
124
137
|
* - skip-backoff: the key is inside its refused-capture backoff window and
|
|
@@ -136,7 +149,7 @@ export interface ShellCaptureDebugEvent {
|
|
|
136
149
|
* - backoff: the key entered (or escalated) backoff after a terminal
|
|
137
150
|
* no-shell — carries the new backoff state
|
|
138
151
|
*/
|
|
139
|
-
outcome: "stored" | "redirect" | "no-shell" | "refused" | "error" | "skip-in-flight" | "skip-backoff" | "skip-capacity" | "skip-inert-store" | "skip-queue-timeout" | "backoff";
|
|
152
|
+
outcome: "stored" | "redirect" | "no-shell" | "refused" | "expired" | "error" | "skip-in-flight" | "skip-stored" | "skip-backoff" | "skip-capacity" | "skip-inert-store" | "skip-queue-timeout" | "backoff";
|
|
140
153
|
/** Attempt number (1 = first, 2 = in-place retry). Absent on skips. */
|
|
141
154
|
attempt?: number;
|
|
142
155
|
/** Wall-clock ms of the whole attempt (barrier + render + drain + put). */
|
|
@@ -183,6 +196,14 @@ export interface ShellCaptureDebugEvent {
|
|
|
183
196
|
* it attributable. Absent when nothing held the gate.
|
|
184
197
|
*/
|
|
185
198
|
bakeWaitMs?: number;
|
|
199
|
+
/**
|
|
200
|
+
* How long the capture waited for its doc record: the handler layer's
|
|
201
|
+
* promises, async server components and handle pushes settling, then the
|
|
202
|
+
* record's encode (settleCaptureRecord). Part of the one capture deadline.
|
|
203
|
+
*/
|
|
204
|
+
recordSettleMs?: number;
|
|
205
|
+
/** Bytes of the stored entry: prelude, postponed state, and snapshot. */
|
|
206
|
+
entryBytes?: number;
|
|
186
207
|
}
|
|
187
208
|
/**
|
|
188
209
|
* Debug sink for the capture pipeline, mirroring {@link CFCacheDebug}: `true`
|
|
@@ -329,7 +350,17 @@ export interface ShellCaptureDescriptor {
|
|
|
329
350
|
debugSink?: (event: ShellCaptureDebugEvent) => void;
|
|
330
351
|
/** Store the snapshot for navigation replay, never the captured HTML prelude. */
|
|
331
352
|
navigationOnly?: true;
|
|
353
|
+
/**
|
|
354
|
+
* lastStoredCaptureSeq(key) when a document request read the store and
|
|
355
|
+
* missed. The request schedules its capture only after rendering the MISS,
|
|
356
|
+
* so a capture another request started can store in between; a later
|
|
357
|
+
* sequence number then skips the redundant one (`skip-stored`), which would
|
|
358
|
+
* re-render the page and drop the isolate's memo of the shell just stored.
|
|
359
|
+
*/
|
|
360
|
+
storedSeqAtRead?: number;
|
|
332
361
|
}
|
|
362
|
+
/** The latest stored capture's sequence number for `key` in this isolate, or 0. */
|
|
363
|
+
export declare function lastStoredCaptureSeq(key: string): number;
|
|
333
364
|
/**
|
|
334
365
|
* Schedule the background shell capture for a served document. Stampede-guarded:
|
|
335
366
|
* one capture per key per isolate. Runs via runBackground (waitUntil on workerd,
|
|
@@ -342,6 +373,26 @@ export interface ShellCaptureDescriptor {
|
|
|
342
373
|
* capture enters the guarded background queue, never on response latency.
|
|
343
374
|
*/
|
|
344
375
|
export declare function scheduleShellCapture(ctx: HandlerContext<any>, request: Request, env: any, url: URL, reqCtx: RequestContext<any>, ssrModule: SSRModule | ((request: Request, url: URL) => Promise<SSRModule | null>), descriptor: ShellCaptureDescriptor): void;
|
|
376
|
+
/**
|
|
377
|
+
* The ttl/swr a shell is stored with: the route's ppr policy, capped by the
|
|
378
|
+
* route cache() records the capture replayed or wrote
|
|
379
|
+
* (RequestContext._routeRecordWindow). A HIT replays the capture's handler
|
|
380
|
+
* output, while a client navigation reads the route's cache() tier first, so
|
|
381
|
+
* a shell that outlived its record would show a document request other
|
|
382
|
+
* handler output than a navigation of the same URL. Freshness and total
|
|
383
|
+
* lifetime are capped separately, like the record's own SWR: the shell is
|
|
384
|
+
* fresh while the record is (at most `ttl`), and servable while the record
|
|
385
|
+
* is (at most `ttl + swr`), so a record already inside its swr window yields
|
|
386
|
+
* a shell that is stale from the start and serves while it recaptures.
|
|
387
|
+
*
|
|
388
|
+
* Whole seconds, rounded up (the SegmentCacheStore.putShell contract), so the
|
|
389
|
+
* shell may outlast the record by under a second. Null when the record's
|
|
390
|
+
* total lifetime has run out: the shell would be dead on arrival.
|
|
391
|
+
*/
|
|
392
|
+
export declare function capShellWindow(ttl: number, swr: number, record: Pick<RouteRecordWindow, "freshUntil" | "staleUntil">, now: number): {
|
|
393
|
+
ttl: number;
|
|
394
|
+
swr: number;
|
|
395
|
+
} | null;
|
|
345
396
|
/**
|
|
346
397
|
* The outcome of one capture attempt.
|
|
347
398
|
* - `stored`: a usable shell was captured (and a putShell was attempted; a store
|
|
@@ -349,10 +400,16 @@ export declare function scheduleShellCapture(ctx: HandlerContext<any>, request:
|
|
|
349
400
|
* the capture itself worked).
|
|
350
401
|
* - `redirect`: the matched route redirects, so there is no shell to capture.
|
|
351
402
|
* - `no-shell`: captureShellHTML returned null because the prelude was unusable
|
|
352
|
-
* or its private capture abort landed before the shell completed
|
|
353
|
-
*
|
|
403
|
+
* or its private capture abort landed before the shell completed, or the
|
|
404
|
+
* capture ran out of ppr.captureTimeout. Retried once in place, when no
|
|
405
|
+
* deadline ran out (noShellCauses).
|
|
406
|
+
* - `expired`: the route cache() record the capture read (not wrote) ran out
|
|
407
|
+
* before the store (capShellWindow null): it was near its end (its age,
|
|
408
|
+
* queue wait). Retried once in place, where a fresh match reads or renders
|
|
409
|
+
* a newer record; a second `expired`, or no hard-cap budget left for the
|
|
410
|
+
* retry, is terminal `no-shell` (backoff, warned once per route).
|
|
354
411
|
*/
|
|
355
|
-
type CaptureAttemptOutcome = "stored" | "redirect" | "no-shell" | "refused";
|
|
412
|
+
type CaptureAttemptOutcome = "stored" | "redirect" | "no-shell" | "refused" | "expired";
|
|
356
413
|
/**
|
|
357
414
|
* Per-attempt observability fields, filled along the capture path (barrier in
|
|
358
415
|
* attemptCapture, the rest in captureAndStoreShell) and folded into the
|
|
@@ -360,7 +417,7 @@ type CaptureAttemptOutcome = "stored" | "redirect" | "no-shell" | "refused";
|
|
|
360
417
|
* bag, not a return value: captureAndStoreShell's outcome type stays a string
|
|
361
418
|
* union its existing callers (producer B, tests) consume unchanged.
|
|
362
419
|
*/
|
|
363
|
-
type CaptureAttemptStats = Pick<ShellCaptureDebugEvent, "barrierWaitMs" | "writeSettleMs" | "preludeBytes" | "snapshotBytes" | "snapshotSkipped" | "untaggedBake" | "storeWrite" | "bakeWaitMs">;
|
|
420
|
+
type CaptureAttemptStats = Pick<ShellCaptureDebugEvent, "barrierWaitMs" | "writeSettleMs" | "preludeBytes" | "snapshotBytes" | "snapshotSkipped" | "untaggedBake" | "storeWrite" | "bakeWaitMs" | "recordSettleMs" | "entryBytes">;
|
|
364
421
|
/**
|
|
365
422
|
* Run the shell capture with a single in-place retry, then store the result.
|
|
366
423
|
*
|
|
@@ -372,26 +429,93 @@ type CaptureAttemptStats = Pick<ShellCaptureDebugEvent, "barrierWaitMs" | "write
|
|
|
372
429
|
* when we froze the shell; the attempt itself warmed the module graph, so a second
|
|
373
430
|
* attempt a short beat later usually completes the shell in the SAME background
|
|
374
431
|
* task. That kills the old multi-request warmup where the caller had to re-issue
|
|
375
|
-
* several HTTP requests before a capture stuck. We retry ONLY on `no-shell
|
|
376
|
-
*
|
|
432
|
+
* several HTTP requests before a capture stuck. We retry ONLY on `no-shell`
|
|
433
|
+
* that did not run out of ppr.captureTimeout (noShellCauses); a genuine render
|
|
434
|
+
* error is NOT retried — it propagates to scheduleShellCapture's
|
|
377
435
|
* reportCacheError. See docs/design/ppr-shell-resume.md.
|
|
378
436
|
*
|
|
379
437
|
* `retryDelayMs` is a parameter (defaulting to the module const) so unit tests can
|
|
380
438
|
* drive the retry without a real 400ms wall-clock wait.
|
|
381
439
|
*/
|
|
382
440
|
declare function runShellCapture(ctx: HandlerContext<any>, request: Request, env: any, url: URL, reqCtx: RequestContext<any>, ssrModule: SSRModule, descriptor: ShellCaptureDescriptor, retryDelayMs?: number): Promise<CaptureAttemptOutcome>;
|
|
441
|
+
/**
|
|
442
|
+
* What the record-first step produced ({@link settleCaptureRecord}):
|
|
443
|
+
* - `record`: the doc record settled; `match` carries its fragments in place
|
|
444
|
+
* of the handler layer's elements, so the capture's Flight payload is the
|
|
445
|
+
* bytes every HIT replays.
|
|
446
|
+
* - `prerender`: the prerender store supplied the handler layer (a
|
|
447
|
+
* Prerender route); its HIT tail replays the same build-time segments, so
|
|
448
|
+
* the capture renders the match as is.
|
|
449
|
+
* - `timeout`: handler output did not settle within the capture budget.
|
|
450
|
+
* - `refused`: a deterministic refusal (a capture guard tripped, the route
|
|
451
|
+
* opted out, or the handler output failed); the warning already fired.
|
|
452
|
+
*/
|
|
453
|
+
export type CaptureRecordOutcome = {
|
|
454
|
+
kind: "record" | "prerender";
|
|
455
|
+
match: MatchResult;
|
|
456
|
+
} | {
|
|
457
|
+
kind: "timeout";
|
|
458
|
+
reason: string;
|
|
459
|
+
} | {
|
|
460
|
+
kind: "refused";
|
|
461
|
+
};
|
|
462
|
+
/**
|
|
463
|
+
* Record-first capture: the doc record is both the settle signal and the
|
|
464
|
+
* parity source.
|
|
465
|
+
*
|
|
466
|
+
* Writing the record Flight-serializes every non-loader segment
|
|
467
|
+
* (serializeSegments), which runs each handler's async server components and
|
|
468
|
+
* waits for every promise the handler output carries — the capture's "is the
|
|
469
|
+
* handler layer done" signal. Live-lane loader data is not in the record
|
|
470
|
+
* (loader segments are excluded and their masked promises never settle), so
|
|
471
|
+
* the wait never depends on a hole.
|
|
472
|
+
*
|
|
473
|
+
* The capture then renders its OWN Flight payload from the record's
|
|
474
|
+
* fragments (fragmentSegments, the function a HIT tail uses) instead of from
|
|
475
|
+
* the elements the match produced. Server components therefore render once
|
|
476
|
+
* per capture, inside the record's encode: before this, the capture's Flight
|
|
477
|
+
* render and the record's encode each ran them, so an uncached async
|
|
478
|
+
* component showed one value in the prelude and another on every HIT.
|
|
479
|
+
*
|
|
480
|
+
* Order, all bounded by the one capture deadline (`ppr.captureTimeout`):
|
|
481
|
+
* top-level handle promises, the handler pushes' nested promises, and the
|
|
482
|
+
* bake-lane loader containers settle first (the record encodes the handles
|
|
483
|
+
* a HIT restores, and its handle encode has its own 5s timeout that must
|
|
484
|
+
* never be the one that fires); then the capture's onResponse callbacks fire
|
|
485
|
+
* with a synthetic 200 (the implicit doc scope's cacheRoute, an explicit
|
|
486
|
+
* scope's own write), and the deferred writes settle.
|
|
487
|
+
*/
|
|
488
|
+
export declare function settleCaptureRecord(match: MatchResult, derivation: CaptureContextDerivation, capture: Pick<ShellCaptureDescriptor, "key">, deadline: number): Promise<CaptureRecordOutcome>;
|
|
489
|
+
/**
|
|
490
|
+
* Race `work` against an absolute deadline, keeping its value. The losing
|
|
491
|
+
* work is not cancelled (a router match has no abort signal); callers that
|
|
492
|
+
* lose do not start it again (runShellCapture skips its in-place retry).
|
|
493
|
+
*/
|
|
494
|
+
export declare function withinDeadline<T>(work: Promise<T>, deadline: number): Promise<{
|
|
495
|
+
done: true;
|
|
496
|
+
value: T;
|
|
497
|
+
} | {
|
|
498
|
+
done: false;
|
|
499
|
+
}>;
|
|
383
500
|
/**
|
|
384
501
|
* The derived capture context and its fresh (mask-funneled) handle store,
|
|
385
502
|
* shared by BOTH shell producers: the runtime background capture
|
|
386
503
|
* (attemptCapture, producer A) and the build-time prerender shell capture
|
|
387
504
|
* (prerender/build-shell-capture.ts, producer B — issue #699). One
|
|
388
|
-
* implementation so the capture semantics — the
|
|
389
|
-
*
|
|
390
|
-
* scope — cannot drift between producers.
|
|
505
|
+
* implementation so the capture semantics — the handle push funnel (handler
|
|
506
|
+
* pushes settled, loader pushes masked), the capture guards, snapshot
|
|
507
|
+
* recording, the implicit doc-cache scope — cannot drift between producers.
|
|
391
508
|
*/
|
|
392
509
|
export interface CaptureContextDerivation {
|
|
393
510
|
derivedCtx: RequestContext;
|
|
394
511
|
freshHandleStore: HandleStore;
|
|
512
|
+
/**
|
|
513
|
+
* Resolves once every handler handle push has settled at any depth of
|
|
514
|
+
* plain objects and arrays (top-level promise, then the promises nested
|
|
515
|
+
* in what it resolved to). The record-first step awaits it before the doc
|
|
516
|
+
* record encodes its handles, so that encode never races a pending value.
|
|
517
|
+
*/
|
|
518
|
+
handlerPushesSettled: () => Promise<void>;
|
|
395
519
|
}
|
|
396
520
|
/**
|
|
397
521
|
* Derive the capture request context from a base context. Producer A passes
|
|
@@ -422,3 +546,11 @@ export declare function deriveShellCaptureContext(reqCtx: RequestContext<any>, d
|
|
|
422
546
|
declare function captureAndStoreShell(ssrModule: SSRModule, rscStream: ReadableStream<Uint8Array>, handleStore: HandleStore, reqCtx: RequestContext<any>, capture: ShellCaptureDescriptor, stats?: CaptureAttemptStats, captureStartedAt?: number): Promise<Exclude<CaptureAttemptOutcome, "redirect">>;
|
|
423
547
|
export { runShellCapture, captureAndStoreShell, delay, SHELL_CAPTURE_RETRY_DELAY_MS, };
|
|
424
548
|
export { isCaptureBackedOff, markCaptureBackoff, clearCaptureBackoff, REFUSED_CAPTURE_BASE_MS, REFUSED_CAPTURE_MAX_MS, REFUSED_CAPTURE_DEV_MAX_MS, };
|
|
549
|
+
/**
|
|
550
|
+
* @internal Reset this module's per-isolate state: the capture stampede
|
|
551
|
+
* guard, the refused-capture backoff, the recorded no-shell causes (which
|
|
552
|
+
* also skip a timed-out capture's in-place retry), the once-per-key warnings
|
|
553
|
+
* and the buffered debug events. Tests only (testing/serve-shell-request.ts
|
|
554
|
+
* resetShellTestState), between requests, never while a capture runs.
|
|
555
|
+
*/
|
|
556
|
+
export declare function resetShellCaptureStateForTests(): void;
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { type EntryData } from "../server/context.js";
|
|
19
19
|
import type { SearchParamsFilter } from "../cache/search-params-filter.js";
|
|
20
|
-
import type { DocumentShellCacheEntry, ShellCacheEntry, SegmentCacheStore } from "../cache/types.js";
|
|
20
|
+
import type { DocumentShellCacheEntry, ShellCacheEntry, ShellDocumentRead, ShellReadStats, ShellSnapshotFailure, ShellSnapshotRecord, SegmentCacheStore } from "../cache/types.js";
|
|
21
21
|
/** Debug/status header the browser (and e2e assertions) can read: HIT | MISS. */
|
|
22
22
|
export declare const SHELL_STATUS_HEADER = "x-rango-shell";
|
|
23
23
|
/** Partial-navigation status header set only after captured PPR segments are consumed. */
|
|
@@ -94,6 +94,41 @@ export declare function resolvePprConfig(entry: EntryData | undefined | null): R
|
|
|
94
94
|
* and lookup makes every shell request a permanent miss.
|
|
95
95
|
*/
|
|
96
96
|
export declare function buildShellKey(url: URL, filter?: SearchParamsFilter): string;
|
|
97
|
+
export { navigationShellKey, partitionShellKey, } from "./shell-capture-constants.js";
|
|
98
|
+
/**
|
|
99
|
+
* The forced-MISS query marker. A document HIT that cannot finish its tail
|
|
100
|
+
* reloads the page with it (shellReloadScript); the serve gate renders a
|
|
101
|
+
* request carrying it on axis 1, with no shell read and no capture, so the
|
|
102
|
+
* reload can never be a HIT that degrades again. The handler drops it from
|
|
103
|
+
* the request on entry (withoutShellMissMarker) and keeps only the flag
|
|
104
|
+
* (RequestContext._shellForcedMiss), so nothing else sees it.
|
|
105
|
+
*/
|
|
106
|
+
export declare const SHELL_MISS_PARAM: string;
|
|
107
|
+
/**
|
|
108
|
+
* The request without the forced-MISS marker, or undefined when it carries
|
|
109
|
+
* none. The handler calls it before anything reads the request: left in, the
|
|
110
|
+
* marker reached `ctx.request.url`, `originalUrl`, middleware, and the SSR
|
|
111
|
+
* search seed (so `useSearchParams` rendered it), and a `"use cache"` key
|
|
112
|
+
* built from the request URL was never read again. GET and HEAD only: the
|
|
113
|
+
* reload is a navigation, and a request with a body cannot be re-created
|
|
114
|
+
* without re-streaming it. The substring test keeps the URL parse off every
|
|
115
|
+
* other request.
|
|
116
|
+
*/
|
|
117
|
+
export declare function withoutShellMissMarker(request: Request): Request | undefined;
|
|
118
|
+
/**
|
|
119
|
+
* The inline script a degraded document HIT ends with: reload once into a
|
|
120
|
+
* forced MISS. The marker in the URL is the loop bound — a request carrying
|
|
121
|
+
* it is never a HIT, so this script never runs on the reload, and if it ever
|
|
122
|
+
* did (a server that ignores the marker) it would return without touching
|
|
123
|
+
* the page. window.stop() runs only when it reloads: it keeps the half-sent
|
|
124
|
+
* Flight stream from being closed by DOMContentLoaded, which would throw
|
|
125
|
+
* React's "Connection closed" (#412) before the reload lands. `nonce` is the
|
|
126
|
+
* request's CSP nonce; a ppr route with an active nonce is axis 1
|
|
127
|
+
* (rsc-rendering.ts shellServePlan), so on a HIT it is normally absent.
|
|
128
|
+
*/
|
|
129
|
+
export declare function shellReloadScript(nonce?: string): string;
|
|
130
|
+
/** An inline script a shell HIT's tail appends, carrying the CSP nonce if any. */
|
|
131
|
+
export declare function inlineShellScript(body: string, nonce?: string): string;
|
|
97
132
|
/**
|
|
98
133
|
* The shell key's search portion (`?`-prefixed sorted search with the
|
|
99
134
|
* cache.searchParams filter applied, or "") — ALSO the string the capture and
|
|
@@ -117,16 +152,11 @@ export declare function shellSearchSeed(url: URL, filter?: SearchParamsFilter):
|
|
|
117
152
|
*/
|
|
118
153
|
export declare function isValidShellHit(entry: ShellCacheEntry, buildVersion: string): boolean;
|
|
119
154
|
/**
|
|
120
|
-
* DOCUMENT-half
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* page that never hydrates, re-served on every request until the entry ages
|
|
126
|
-
* out (no eviction path exists; failure schedules no recapture by itself).
|
|
127
|
-
* Checking here turns a corrupt entry (store-layer fault) into a plain MISS
|
|
128
|
-
* the recapture overwrites. Cost: one duplicate decode/parse per HIT, sub-ms
|
|
129
|
-
* against a prelude flush that dominates the path.
|
|
155
|
+
* DOCUMENT-half structural gate and type narrowing, without decoding the
|
|
156
|
+
* prelude: the prelude is a string and the postponed blob parses. Partial
|
|
157
|
+
* replay (which never serves the prelude) and the build-manifest read-through
|
|
158
|
+
* use it as-is; the document HIT path runs it inside
|
|
159
|
+
* {@link openShellDocument}, whose single decode is the prelude's check.
|
|
130
160
|
*
|
|
131
161
|
* navigationOnly entries store no document half (prelude/postponed absent) and
|
|
132
162
|
* therefore never pass. The partial-replay path skips this gate for them
|
|
@@ -135,8 +165,46 @@ export declare function isValidShellHit(entry: ShellCacheEntry, buildVersion: st
|
|
|
135
165
|
* capture).
|
|
136
166
|
*/
|
|
137
167
|
export declare function hasIntactShellPayload(entry: ShellCacheEntry): entry is DocumentShellCacheEntry;
|
|
138
|
-
/**
|
|
139
|
-
export
|
|
168
|
+
/** A document shell ready to serve: its prelude decoded exactly once. */
|
|
169
|
+
export interface ShellDocument {
|
|
170
|
+
entry: ShellCacheEntry;
|
|
171
|
+
prelude: Uint8Array;
|
|
172
|
+
/** React's postponed state, parse-checked (null = DATA variant). */
|
|
173
|
+
postponed: string | null;
|
|
174
|
+
/**
|
|
175
|
+
* The capture snapshot: on the entry, or still arriving on its own promise
|
|
176
|
+
* after a prelude-first read (SegmentCacheStore.readShellDocument), which
|
|
177
|
+
* only the tail awaits.
|
|
178
|
+
*/
|
|
179
|
+
snapshot: ShellSnapshotRecord[] | undefined | Promise<ShellSnapshotRecord[] | undefined>;
|
|
180
|
+
/** Why a prelude-first read's snapshot is missing (ShellDocumentRead). */
|
|
181
|
+
snapshotFailure?: Promise<ShellSnapshotFailure | undefined>;
|
|
182
|
+
/** The prelude-first read's stats, when perf metrics are on. */
|
|
183
|
+
stats?: ShellReadStats;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Pre-commit integrity gate for a document HIT, and its only prelude decode.
|
|
187
|
+
* A stored entry whose prelude is not decodable base64 or whose postponed blob
|
|
188
|
+
* is not parseable JSON would otherwise throw AFTER the 200 + full static
|
|
189
|
+
* prelude flushed (`resumeShellHTML` parses in the tail) — the client gets a
|
|
190
|
+
* visually complete page that never hydrates, re-served on every request
|
|
191
|
+
* until the entry ages out. Returning null here turns a corrupt entry
|
|
192
|
+
* (store-layer fault) into a plain MISS the recapture overwrites. The decoded
|
|
193
|
+
* bytes are what serveShellHit enqueues, so the check costs no second decode.
|
|
194
|
+
* A prelude-first `read` (SegmentCacheStore.readShellDocument) already carries
|
|
195
|
+
* the raw bytes and a pending snapshot, so only its postponed blob is checked.
|
|
196
|
+
*/
|
|
197
|
+
export declare function openShellDocument(entry: ShellCacheEntry, read?: Pick<ShellDocumentRead, "prelude" | "snapshot" | "snapshotFailure" | "stats">): ShellDocument | null;
|
|
198
|
+
/**
|
|
199
|
+
* Prelude enqueue granularity. A streaming compressor in front of the worker
|
|
200
|
+
* emits its first byte only after compressing the whole write it was handed:
|
|
201
|
+
* one 629 KB enqueue delayed the first compressed byte by the full prelude's
|
|
202
|
+
* compression time (brotli-6 5.1 ms, gzip-6 1.1 ms in Node zlib with a flush
|
|
203
|
+
* per write; workerd's CompressionStream emits nothing until the whole write is
|
|
204
|
+
* compressed), while 32 KB chunks emit within 0.1 ms and cost slightly more
|
|
205
|
+
* in total compression (0.55-0.70 ms) than one write (issue #941).
|
|
206
|
+
*/
|
|
207
|
+
export declare const SHELL_PRELUDE_CHUNK_BYTES: number;
|
|
140
208
|
/** True when the store implements the shell entry family. */
|
|
141
209
|
export declare function hasShellFamily(store: SegmentCacheStore | undefined): store is SegmentCacheStore & {
|
|
142
210
|
getShell: NonNullable<SegmentCacheStore["getShell"]>;
|
|
@@ -152,8 +220,22 @@ export declare function hasShellFamily(store: SegmentCacheStore | undefined): st
|
|
|
152
220
|
export interface ShellTailTiming {
|
|
153
221
|
key: string;
|
|
154
222
|
outcome: "complete" | "redirect" | "error";
|
|
223
|
+
/** The capture snapshot available to the tail (read and parsed). */
|
|
224
|
+
snapshotMs?: number;
|
|
225
|
+
/** Snapshot bytes read after the commit (prelude-first stores). */
|
|
226
|
+
snapshotBytes?: number;
|
|
227
|
+
/** Snapshot bytes read after the commit, excluding the parse. */
|
|
228
|
+
snapshotReadMs?: number;
|
|
229
|
+
/** Snapshot JSON.parse CPU (reads 0 on a deployed worker: see bytes). */
|
|
230
|
+
snapshotParseMs?: number;
|
|
231
|
+
/** Snapshot records by family, e.g. `segment:1/item:5` (no commas: it rides a Server-Timing desc). */
|
|
232
|
+
snapshotRecords?: string;
|
|
233
|
+
/** Records the capture pruned from the snapshot, same format (ShellCacheEntry.prunedRecords). */
|
|
234
|
+
snapshotPruned?: string;
|
|
155
235
|
/** Loader-family seed decode (only when the entry carried a snapshot). */
|
|
156
236
|
seedMs?: number;
|
|
237
|
+
/** The seed decode's own duration: Flight deserialization, CPU only. */
|
|
238
|
+
seedCpuMs?: number;
|
|
157
239
|
/** Tail router.match() settled. */
|
|
158
240
|
matchMs?: number;
|
|
159
241
|
/** Tail stream (resume output) handed to the response stream. */
|
|
@@ -168,12 +250,13 @@ export interface ShellTailTiming {
|
|
|
168
250
|
tailBytes?: number;
|
|
169
251
|
}
|
|
170
252
|
/**
|
|
171
|
-
* Compact single-line form for the console log and the
|
|
172
|
-
*
|
|
173
|
-
* escaping needed.
|
|
253
|
+
* Compact single-line form for the console log and the Server-Timing mirror's
|
|
254
|
+
* `desc`: alphanumerics and `=`, `-`, `:`, `/` only (records=segment:1 pruned=item:5),
|
|
255
|
+
* so no quoted-string escaping is needed. Offsets are from the commit; the
|
|
256
|
+
* `-cpu` fields are CPU-only durations.
|
|
174
257
|
*/
|
|
175
258
|
export declare function describeShellTailTiming(timing: ShellTailTiming): string;
|
|
176
|
-
/** Buffer one terminal tail timing for the
|
|
259
|
+
/** Buffer one terminal tail timing for the Server-Timing mirror. */
|
|
177
260
|
export declare function publishShellTailTiming(timing: ShellTailTiming): void;
|
|
178
261
|
/**
|
|
179
262
|
* Consume (read-and-clear) the buffered tail timing for `key`, so one tail
|
|
@@ -198,3 +281,43 @@ export declare function warnShellStoreMissingOnce(key: string): void;
|
|
|
198
281
|
* the missing-store warning above (an undeclared route stays silent).
|
|
199
282
|
*/
|
|
200
283
|
export declare function warnPprNonceActiveOnce(key: string): void;
|
|
284
|
+
/**
|
|
285
|
+
* Dev only (the caller gates on NODE_ENV): warn once per route that the
|
|
286
|
+
* route's cache() entry reduces an explicit `ppr.ttl` or `ppr.swr`. `shell`
|
|
287
|
+
* is what the capture stores for a record written just now
|
|
288
|
+
* (shell-capture.ts capShellWindow), so the message states the values the
|
|
289
|
+
* shell actually gets. A ppr value the cap leaves whole (or raises: the
|
|
290
|
+
* cap can move a record's stale time into the shell's swr) is silent, and
|
|
291
|
+
* so is a ppr config that sets no ttl/swr.
|
|
292
|
+
*/
|
|
293
|
+
export declare function warnPprWindowCappedOnce(routeName: string, ppr: {
|
|
294
|
+
ttl?: number;
|
|
295
|
+
swr?: number;
|
|
296
|
+
}, shell: {
|
|
297
|
+
ttl: number;
|
|
298
|
+
swr: number;
|
|
299
|
+
}, cache: {
|
|
300
|
+
ttl: number;
|
|
301
|
+
swr: number;
|
|
302
|
+
}): void;
|
|
303
|
+
/**
|
|
304
|
+
* Whether a partitioned request needs no build-shell probe: its route was
|
|
305
|
+
* already warned, or its path was already found without one.
|
|
306
|
+
*/
|
|
307
|
+
export declare function partitionBuildShellCheckDone(pathname: string, routeName: string | undefined): boolean;
|
|
308
|
+
/**
|
|
309
|
+
* Record a partitioned request's build-shell probe, and warn once per route
|
|
310
|
+
* when it found one: the route has a build-time shell its requests cannot
|
|
311
|
+
* read. The route's request partition (a `cache({ key })` enclosing it, at
|
|
312
|
+
* any depth (#970), or the store's keyGenerator) keys the shell per
|
|
313
|
+
* partition, and the build captured only
|
|
314
|
+
* the default one, so each partition captures at runtime. Same
|
|
315
|
+
* declared-intent-cannot-be-honored doctrine as the warnings above; a path
|
|
316
|
+
* with no build shell stays silent.
|
|
317
|
+
*/
|
|
318
|
+
export declare function notePartitionBuildShellCheck(pathname: string, routeName: string | undefined, found: boolean): void;
|
|
319
|
+
/**
|
|
320
|
+
* @internal Reset the serve path's once-per-key warnings and buffered tail
|
|
321
|
+
* timings. Tests only (testing/serve-shell-request.ts resetShellTestState).
|
|
322
|
+
*/
|
|
323
|
+
export declare function resetShellServeStateForTests(): void;
|