@rangojs/router 0.20.0 → 0.22.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 +39 -15
- package/dist/types/browser/event-controller.d.ts +49 -0
- package/dist/types/browser/history-state.d.ts +16 -4
- package/dist/types/browser/navigation-store.d.ts +9 -0
- package/dist/types/browser/navigation-transaction.d.ts +7 -0
- package/dist/types/browser/partial-update.d.ts +2 -1
- package/dist/types/browser/prefetch/default-strategy.d.ts +16 -20
- package/dist/types/browser/rango-state.d.ts +3 -1
- package/dist/types/browser/react/NavigationProvider.d.ts +14 -1
- package/dist/types/browser/react/context.d.ts +25 -0
- package/dist/types/browser/react/location-state-shared.d.ts +84 -10
- package/dist/types/browser/react/location-state.d.ts +7 -2
- package/dist/types/browser/rsc-router.d.ts +13 -2
- package/dist/types/browser/types.d.ts +10 -0
- package/dist/types/build/generate-manifest.d.ts +2 -0
- package/dist/types/cache/background-task.d.ts +22 -0
- package/dist/types/cache/cache-key-utils.d.ts +50 -5
- package/dist/types/cache/cache-policy.d.ts +17 -0
- package/dist/types/cache/cache-scope.d.ts +5 -6
- package/dist/types/cache/cf/cf-cache-store.d.ts +34 -7
- package/dist/types/cache/cf/cf-cache-types.d.ts +12 -4
- package/dist/types/cache/document-cache.d.ts +8 -5
- package/dist/types/cache/handle-snapshot.d.ts +59 -50
- package/dist/types/cache/index.d.ts +3 -1
- package/dist/types/cache/invalidation-order.d.ts +14 -0
- package/dist/types/cache/memory-segment-store.d.ts +33 -1
- package/dist/types/cache/shell-snapshot.d.ts +29 -4
- package/dist/types/cache/store-scope.d.ts +22 -0
- package/dist/types/cache/tag-invalidation.d.ts +7 -6
- package/dist/types/cache/types.d.ts +84 -24
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +35 -17
- package/dist/types/client-urls/navigation.d.ts +7 -1
- package/dist/types/client-urls/optimistic-location.d.ts +10 -4
- package/dist/types/errors.d.ts +6 -0
- package/dist/types/handles/deferred-resolution.d.ts +18 -1
- package/dist/types/internal-suspense-audit.d.ts +1 -0
- package/dist/types/loader-store.d.ts +4 -8
- package/dist/types/outlet-provider.d.ts +2 -1
- package/dist/types/prerender/build-shell-capture.d.ts +8 -6
- package/dist/types/prerender/cloudflare.d.ts +49 -0
- package/dist/types/prerender/create-prerender-trigger.d.ts +121 -0
- package/dist/types/prerender/index.d.ts +12 -0
- package/dist/types/prerender/memory-prerender-store.d.ts +29 -0
- package/dist/types/prerender/on-demand.d.ts +272 -0
- package/dist/types/prerender/producer-guard.d.ts +33 -0
- package/dist/types/prerender/shell-manifest-key.d.ts +1 -18
- package/dist/types/prerender/warm-request.d.ts +65 -0
- package/dist/types/prerender/warm.d.ts +65 -0
- package/dist/types/prerender/writable-store.d.ts +170 -0
- package/dist/types/prerender.d.ts +27 -0
- package/dist/types/reverse.d.ts +1 -1
- package/dist/types/route-content-wrapper.d.ts +3 -1
- package/dist/types/router/error-handling.d.ts +2 -0
- package/dist/types/router/handler-context.d.ts +1 -1
- package/dist/types/router/match-middleware/cache-lookup.d.ts +15 -1
- package/dist/types/router/navigation-snapshot.d.ts +8 -1
- package/dist/types/router/pattern-matching.d.ts +2 -0
- package/dist/types/router/prefetch-default.d.ts +4 -4
- package/dist/types/router/prerender-match.d.ts +23 -1
- package/dist/types/router/route-trie-builder.d.ts +8 -1
- package/dist/types/router/router-context.d.ts +1 -0
- package/dist/types/router/router-interfaces.d.ts +32 -1
- package/dist/types/router/router-options.d.ts +39 -5
- package/dist/types/router/segment-resolution/fresh.d.ts +7 -0
- package/dist/types/router/segment-resolution/loader-cache.d.ts +23 -10
- package/dist/types/router/segment-resolution/revalidation.d.ts +5 -1
- package/dist/types/router/segment-resolution/streamed-handler-boundary.d.ts +43 -0
- package/dist/types/router/trie-matching.d.ts +2 -0
- package/dist/types/router-versions.d.ts +39 -0
- package/dist/types/rsc/handler-context.d.ts +5 -0
- package/dist/types/rsc/shell-build-manifest.d.ts +9 -6
- package/dist/types/rsc/shell-capture-constants.d.ts +20 -5
- package/dist/types/rsc/shell-capture.d.ts +14 -1
- package/dist/types/rsc/shell-serve.d.ts +2 -2
- package/dist/types/rsc/ssr-module-loader.d.ts +15 -0
- package/dist/types/rsc/types.d.ts +12 -6
- package/dist/types/segment-boundary-content.d.ts +17 -0
- package/dist/types/segment-loader-promise.d.ts +2 -6
- package/dist/types/server/build-version-table.d.ts +53 -0
- package/dist/types/server/context.d.ts +12 -4
- package/dist/types/server/handle-store.d.ts +89 -46
- package/dist/types/server/request-context.d.ts +84 -9
- package/dist/types/suspense-audit-react.d.ts +26 -0
- package/dist/types/suspense-audit.d.ts +157 -0
- package/dist/types/testing/action-form.d.ts +34 -0
- package/dist/types/testing/build-versions.d.ts +32 -0
- package/dist/types/testing/dom.entry.d.ts +1 -1
- package/dist/types/testing/flight.entry.d.ts +3 -1
- package/dist/types/testing/index.d.ts +6 -0
- package/dist/types/testing/internal/seed-vars.d.ts +7 -2
- package/dist/types/testing/internal/server-modules.d.ts +8 -0
- package/dist/types/testing/location-state-key.d.ts +5 -1
- package/dist/types/testing/render-route.d.ts +77 -6
- package/dist/types/testing/serve-shell-request.d.ts +55 -4
- package/dist/types/testing/shell-status.d.ts +14 -6
- package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +9 -6
- package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
- package/dist/types/thenable-status.d.ts +6 -0
- package/dist/types/types/handler-context.d.ts +15 -5
- package/dist/types/types/route-entry.d.ts +5 -0
- package/dist/types/urls/path-helper.d.ts +18 -0
- package/dist/types/vite/discovery/build-versions.d.ts +393 -0
- package/dist/types/vite/discovery/installed-packages.d.ts +26 -0
- package/dist/types/vite/discovery/router-versions-phase.d.ts +52 -0
- package/dist/types/vite/discovery/shell-prerender-phase.d.ts +7 -17
- package/dist/types/vite/discovery/state.d.ts +25 -0
- package/dist/types/vite/encryption-key.d.ts +18 -0
- package/dist/types/vite/inject-client-debug.d.ts +6 -25
- package/dist/types/vite/plugin-types.d.ts +27 -0
- package/dist/types/vite/plugins/{version-injector.d.ts → entry-bootstrap-injector.d.ts} +4 -6
- package/dist/types/vite/plugins/expose-ids/export-analysis.d.ts +1 -0
- package/dist/types/vite/plugins/expose-ids/router-transform.d.ts +14 -0
- package/dist/types/vite/plugins/stable-rsc-output.d.ts +51 -0
- package/dist/types/vite/plugins/version-plugin.d.ts +9 -11
- package/dist/types/vite/plugins/virtual-entries.d.ts +18 -1
- package/dist/types/vite/router-discovery.d.ts +6 -0
- package/dist/types/vite/utils/bundle-analysis.d.ts +6 -2
- package/dist/types/vite/utils/client-chunks.d.ts +2 -0
- package/dist/types/vite/utils/compare-strings.d.ts +7 -0
- package/dist/types/vite/utils/package-resolution.d.ts +8 -0
- package/dist/types/vite/utils/prerender-utils.d.ts +2 -0
- package/dist/vite/index.js +1707 -931
- package/package.json +12 -3
- package/skills/cache-guide/SKILL.md +102 -37
- package/skills/caching/SKILL.md +56 -9
- package/skills/catalog.json +1 -1
- package/skills/client-urls/SKILL.md +16 -0
- package/skills/cloudflare/SKILL.md +133 -1
- package/skills/comparison/references/framework-comparison.md +17 -13
- package/skills/deployment-caching/SKILL.md +33 -12
- package/skills/document-cache/SKILL.md +16 -14
- package/skills/hooks/SKILL.md +25 -0
- package/skills/hooks/handle-and-actions.md +5 -0
- package/skills/hooks/navigation.md +38 -0
- package/skills/hooks/state.md +213 -11
- package/skills/hooks/urls.md +40 -1
- package/skills/host-router/SKILL.md +31 -0
- package/skills/intercept/SKILL.md +6 -0
- package/skills/loader/SKILL.md +25 -5
- package/skills/middleware/SKILL.md +2 -0
- package/skills/migrate-nextjs/SKILL.md +82 -9
- package/skills/ppr/SKILL.md +150 -48
- package/skills/prerender/SKILL.md +651 -10
- package/skills/rango/SKILL.md +9 -7
- package/skills/response-routes/SKILL.md +5 -5
- package/skills/route/SKILL.md +29 -0
- package/skills/router-setup/SKILL.md +15 -1
- package/skills/testing/SKILL.md +32 -27
- package/skills/testing/cache-prerender.md +18 -17
- package/skills/testing/client-components.md +193 -8
- package/skills/testing/handles.md +2 -0
- package/skills/testing/loader.md +1 -1
- package/skills/testing/middleware.md +1 -1
- package/skills/testing/render-handler.md +18 -18
- package/skills/testing/serve-shell-request.md +167 -22
- package/skills/testing/server-actions.md +17 -17
- package/skills/testing/server-tree.md +1 -1
- package/skills/typesafety/generated-files-and-cli.md +1 -1
- package/skills/use-cache/SKILL.md +25 -10
- package/skills/vercel/SKILL.md +5 -3
- package/skills/view-transitions/SKILL.md +9 -4
- package/src/bin/rango.ts +1 -1
- package/src/browser/event-controller.ts +119 -2
- package/src/browser/history-state.ts +39 -17
- package/src/browser/link-interceptor.ts +2 -2
- package/src/browser/navigation-bridge.ts +104 -41
- package/src/browser/navigation-store.ts +26 -0
- package/src/browser/navigation-transaction.ts +24 -12
- package/src/browser/network-error-handler.ts +2 -0
- package/src/browser/partial-update.ts +58 -14
- package/src/browser/prefetch/default-strategy.ts +22 -42
- package/src/browser/rango-state.ts +3 -1
- package/src/browser/react/Link.tsx +37 -43
- package/src/browser/react/NavigationProvider.tsx +85 -1
- package/src/browser/react/context.ts +30 -0
- package/src/browser/react/location-state-shared.ts +200 -34
- package/src/browser/react/location-state.ts +28 -77
- package/src/browser/react/use-action.ts +4 -1
- package/src/browser/react/use-handle.ts +3 -0
- package/src/browser/react/use-link-status.ts +4 -3
- package/src/browser/react/use-navigation.ts +4 -1
- package/src/browser/react/use-params.ts +4 -1
- package/src/browser/react/use-pathname.ts +4 -1
- package/src/browser/react/use-search-params.ts +5 -3
- package/src/browser/react/use-segments.ts +36 -54
- package/src/browser/rsc-router.tsx +93 -31
- package/src/browser/segment-reconciler.ts +3 -3
- package/src/browser/server-action-bridge.ts +43 -7
- package/src/browser/types.ts +11 -0
- package/src/build/generate-manifest.ts +17 -0
- package/src/build/merge-full-manifests.ts +5 -0
- package/src/build/route-trie.ts +1 -0
- package/src/cache/background-task.ts +61 -0
- package/src/cache/cache-key-utils.ts +91 -12
- package/src/cache/cache-policy.ts +11 -2
- package/src/cache/cache-runtime.ts +23 -23
- package/src/cache/cache-scope.ts +48 -36
- package/src/cache/cf/cf-cache-store.ts +91 -66
- package/src/cache/cf/cf-cache-types.ts +12 -4
- package/src/cache/document-cache.ts +35 -14
- package/src/cache/handle-snapshot.ts +118 -86
- package/src/cache/index.ts +5 -0
- package/src/cache/invalidation-order.ts +53 -0
- package/src/cache/memory-segment-store.ts +52 -1
- package/src/cache/shell-snapshot.ts +52 -5
- package/src/cache/store-scope.ts +36 -0
- package/src/cache/tag-invalidation.ts +7 -5
- package/src/cache/types.ts +90 -24
- package/src/cache/vercel/vercel-cache-store.ts +68 -26
- package/src/client-urls/client-root.tsx +1 -0
- package/src/client-urls/navigation.ts +10 -0
- package/src/client-urls/optimistic-location.ts +10 -4
- package/src/client.tsx +40 -3
- package/src/errors.ts +13 -6
- package/src/handles/deferred-resolution.ts +36 -0
- package/src/internal-suspense-audit.ts +12 -0
- package/src/loader-store.ts +4 -9
- package/src/outlet-provider.tsx +25 -1
- package/src/prerender/build-shell-capture.ts +11 -9
- package/src/prerender/cloudflare.ts +130 -0
- package/src/prerender/create-prerender-trigger.ts +780 -0
- package/src/prerender/index.ts +39 -0
- package/src/prerender/memory-prerender-store.ts +115 -0
- package/src/prerender/on-demand.ts +339 -0
- package/src/prerender/producer-guard.ts +66 -0
- package/src/prerender/shell-manifest-key.ts +12 -17
- package/src/prerender/warm-request.ts +154 -0
- package/src/prerender/warm.ts +193 -0
- package/src/prerender/writable-store.ts +278 -0
- package/src/prerender.ts +29 -0
- package/src/reverse.ts +1 -1
- package/src/route-content-wrapper.tsx +107 -13
- package/src/router/error-handling.ts +12 -12
- package/src/router/find-match.ts +1 -0
- package/src/router/handler-context.ts +2 -0
- package/src/router/intercept-resolution.ts +15 -8
- package/src/router/loader-resolution.ts +2 -10
- package/src/router/manifest.ts +2 -0
- package/src/router/match-middleware/background-revalidation.ts +2 -0
- package/src/router/match-middleware/cache-lookup.ts +239 -54
- package/src/router/navigation-snapshot.ts +25 -6
- package/src/router/pattern-matching.ts +14 -0
- package/src/router/prefetch-default.ts +4 -4
- package/src/router/prerender-match.ts +460 -352
- package/src/router/request-classification.ts +15 -12
- package/src/router/route-trie-builder.ts +9 -0
- package/src/router/router-context.ts +1 -0
- package/src/router/router-interfaces.ts +35 -1
- package/src/router/router-options.ts +45 -5
- package/src/router/segment-resolution/fresh.ts +20 -19
- package/src/router/segment-resolution/loader-cache.ts +110 -77
- package/src/router/segment-resolution/revalidation.ts +34 -24
- package/src/router/segment-resolution/streamed-handler-boundary.ts +133 -0
- package/src/router/trie-matching.ts +3 -0
- package/src/router-versions.ts +60 -0
- package/src/router.ts +107 -1
- package/src/rsc/full-payload.ts +6 -2
- package/src/rsc/handler-context.ts +5 -0
- package/src/rsc/handler.ts +64 -7
- package/src/rsc/progressive-enhancement.ts +5 -1
- package/src/rsc/response-cache-serve.ts +10 -4
- package/src/rsc/rsc-rendering.ts +65 -12
- package/src/rsc/shell-build-manifest.ts +17 -12
- package/src/rsc/shell-capture-constants.ts +39 -7
- package/src/rsc/shell-capture.ts +144 -45
- package/src/rsc/shell-serve.ts +12 -6
- package/src/rsc/ssr-module-loader.ts +26 -0
- package/src/rsc/types.ts +12 -6
- package/src/segment-boundary-content.ts +25 -0
- package/src/segment-loader-promise.ts +28 -38
- package/src/segment-system.tsx +117 -57
- package/src/server/build-version-table.ts +130 -0
- package/src/server/context.ts +40 -17
- package/src/server/cookie-store.ts +5 -0
- package/src/server/handle-store.ts +357 -127
- package/src/server/request-context.ts +149 -12
- package/src/ssr/ssr-root.tsx +32 -13
- package/src/suspense-audit-react.tsx +98 -0
- package/src/suspense-audit.ts +1030 -0
- package/src/testing/action-form.ts +66 -0
- package/src/testing/build-versions.ts +64 -0
- package/src/testing/dispatch.ts +6 -0
- package/src/testing/dom.entry.ts +1 -0
- package/src/testing/flight-runtime.d.ts +27 -0
- package/src/testing/flight.entry.ts +4 -0
- package/src/testing/index.ts +22 -0
- package/src/testing/internal/context.ts +2 -1
- package/src/testing/internal/seed-vars.ts +14 -2
- package/src/testing/internal/server-modules.ts +32 -0
- package/src/testing/location-state-key.ts +18 -8
- package/src/testing/render-handler.ts +2 -1
- package/src/testing/render-route.tsx +293 -46
- package/src/testing/serve-shell-request.ts +262 -37
- package/src/testing/shell-status.ts +17 -6
- package/src/testing/vitest-stubs/plugin-rsc.ts +37 -11
- package/src/testing/vitest-stubs/version.ts +4 -1
- package/src/thenable-status.ts +10 -0
- package/src/types/handler-context.ts +15 -5
- package/src/types/route-entry.ts +6 -0
- package/src/urls/path-helper.ts +69 -9
- package/src/use-loader.tsx +57 -30
- package/src/vite/discovery/build-versions.ts +1281 -0
- package/src/vite/discovery/bundle-postprocess.ts +4 -0
- package/src/vite/discovery/discover-routers.ts +15 -0
- package/src/vite/discovery/installed-packages.ts +159 -0
- package/src/vite/discovery/prerender-collection.ts +38 -1
- package/src/vite/discovery/router-versions-phase.ts +336 -0
- package/src/vite/discovery/shell-prerender-phase.ts +33 -43
- package/src/vite/discovery/state.ts +36 -1
- package/src/vite/discovery/virtual-module-codegen.ts +7 -2
- package/src/vite/encryption-key.ts +101 -9
- package/src/vite/inject-client-debug.ts +42 -20
- package/src/vite/plugin-types.ts +28 -0
- package/src/vite/plugins/{version-injector.ts → entry-bootstrap-injector.ts} +7 -22
- package/src/vite/plugins/expose-ids/export-analysis.ts +1 -1
- package/src/vite/plugins/expose-ids/router-transform.ts +36 -15
- package/src/vite/plugins/expose-internal-ids.ts +12 -3
- package/src/vite/plugins/stable-rsc-output.ts +147 -0
- package/src/vite/plugins/version-plugin.ts +19 -24
- package/src/vite/plugins/version.d.ts +17 -2
- package/src/vite/plugins/virtual-entries.ts +39 -6
- package/src/vite/rango.ts +28 -3
- package/src/vite/router-discovery.ts +96 -13
- package/src/vite/utils/bundle-analysis.ts +39 -7
- package/src/vite/utils/client-chunks.ts +5 -1
- package/src/vite/utils/compare-strings.ts +9 -0
- package/src/vite/utils/package-resolution.ts +9 -0
- package/src/vite/utils/prerender-utils.ts +4 -1
- package/src/vite/utils/shared-utils.ts +1 -1
- package/dist/types/segment-content-promise.d.ts +0 -13
- package/src/segment-content-promise.ts +0 -67
package/src/rsc/handler.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
/// <reference types="@vitejs/plugin-rsc/types" />
|
|
2
|
-
/// <reference path="../vite/plugins/version.d.ts" />
|
|
3
2
|
/**
|
|
4
3
|
* RSC Request Handler
|
|
5
4
|
*
|
|
@@ -16,6 +15,7 @@ import {
|
|
|
16
15
|
getRequestContext,
|
|
17
16
|
_getRequestContext,
|
|
18
17
|
createRequestContext,
|
|
18
|
+
captureRequestEntryContext,
|
|
19
19
|
} from "../server/request-context.js";
|
|
20
20
|
import * as rscDeps from "@vitejs/plugin-rsc/rsc/server";
|
|
21
21
|
import type {
|
|
@@ -47,7 +47,10 @@ import {
|
|
|
47
47
|
type ResponseRouteMatch,
|
|
48
48
|
} from "./response-route-handler.js";
|
|
49
49
|
import { nonce as nonceToken, resolveProviderNonce } from "./nonce.js";
|
|
50
|
-
import {
|
|
50
|
+
import {
|
|
51
|
+
resolvePrerenderVersion,
|
|
52
|
+
resolveRouterVersions,
|
|
53
|
+
} from "../server/build-version-table.js";
|
|
51
54
|
import type { ErrorPhase } from "../types.js";
|
|
52
55
|
import type { RouterRequestInput } from "../router/router-interfaces.js";
|
|
53
56
|
import {
|
|
@@ -87,6 +90,8 @@ import {
|
|
|
87
90
|
} from "./origin-guard.js";
|
|
88
91
|
import { handleRscRendering } from "./rsc-rendering.js";
|
|
89
92
|
import { withoutShellMissMarker } from "./shell-serve.js";
|
|
93
|
+
import { readWarmMark } from "../prerender/warm-request.js";
|
|
94
|
+
import { defaultSSRModuleLoaderForTests } from "./ssr-module-loader.js";
|
|
90
95
|
import {
|
|
91
96
|
withTimeout,
|
|
92
97
|
isTimeoutEnabled,
|
|
@@ -169,7 +174,18 @@ export function createRSCHandler<
|
|
|
169
174
|
TEnv = unknown,
|
|
170
175
|
TRoutes extends Record<string, string> = Record<string, string>,
|
|
171
176
|
>(options: CreateRSCHandlerOptions<TEnv, TRoutes>) {
|
|
172
|
-
const { router,
|
|
177
|
+
const { router, nonce: nonceProvider } = options;
|
|
178
|
+
// A consumer-set version (this option, else createRouter({ version })) is
|
|
179
|
+
// used for both; otherwise the router's own pair from the build.
|
|
180
|
+
const versions = resolveRouterVersions(
|
|
181
|
+
router.id,
|
|
182
|
+
options.version ?? router.version,
|
|
183
|
+
);
|
|
184
|
+
// Everything the handler itself versions is tied to the HTML a tab holds:
|
|
185
|
+
// payload metadata (what the browser echoes as `_rsc_v`), the reload check,
|
|
186
|
+
// shell stamping and gating, the rango state value. The data version is only
|
|
187
|
+
// a cache-key prefix, read by the stores from the request context.
|
|
188
|
+
const version = versions.document;
|
|
173
189
|
|
|
174
190
|
// Handler-owned registry of explicit per-scope stores from cache({ store }).
|
|
175
191
|
// Lives in the closure so it is scoped per handler (multi-router deployments
|
|
@@ -195,7 +211,13 @@ export function createRSCHandler<
|
|
|
195
211
|
// In dev mode Vite may hot-reload the module, so skip memoization.
|
|
196
212
|
const rawLoadSSRModule: LoadSSRModule =
|
|
197
213
|
options.loadSSRModule ??
|
|
198
|
-
(() =>
|
|
214
|
+
(() =>
|
|
215
|
+
// The test seam (ssr-module-loader.ts). Gated on the build mode, not
|
|
216
|
+
// NODE_ENV: the rsc Vitest project forces NODE_ENV=production, its mode
|
|
217
|
+
// is "test", and a production build folds the whole branch away.
|
|
218
|
+
((import.meta as any).env?.MODE !== "production" &&
|
|
219
|
+
defaultSSRModuleLoaderForTests()?.()) ||
|
|
220
|
+
import.meta.viteRsc.loadModule("ssr", "index"));
|
|
199
221
|
let _ssrModulePromise: Promise<SSRModule> | undefined;
|
|
200
222
|
const loadSSRModule: LoadSSRModule =
|
|
201
223
|
process.env.NODE_ENV === "production"
|
|
@@ -442,12 +464,17 @@ export function createRSCHandler<
|
|
|
442
464
|
input: RouterRequestInput<TEnv> = {},
|
|
443
465
|
): Promise<Response> {
|
|
444
466
|
const handlerStart = performance.now();
|
|
467
|
+
// Before any router scope: see captureRequestEntryContext.
|
|
468
|
+
const runAtRequestEntry = captureRequestEntryContext();
|
|
445
469
|
// A degraded PPR HIT's reload carries the forced-MISS marker: keep only
|
|
446
470
|
// the flag (set on the request context below) and drop the marker before
|
|
447
471
|
// anything reads the request, so middleware, handlers, loaders, cache
|
|
448
472
|
// keys and the SSR search seed see the URL the visitor asked for.
|
|
449
473
|
const unmarkedRequest = withoutShellMissMarker(incomingRequest);
|
|
450
474
|
const request = unmarkedRequest ?? incomingRequest;
|
|
475
|
+
// A router.prerender() warm: the mark is the incoming Request object
|
|
476
|
+
// itself (prerender/warm-request.ts), read before anything rebuilds it.
|
|
477
|
+
const warm = readWarmMark(incomingRequest);
|
|
451
478
|
// Create the metrics store at handler start so handler:total has startTime=0
|
|
452
479
|
// and all metrics are relative to the request entry point.
|
|
453
480
|
const earlyMetricsStore = router.debugPerformance
|
|
@@ -499,10 +526,12 @@ export function createRSCHandler<
|
|
|
499
526
|
let searchParamsFilter: SearchParamsFilter | undefined;
|
|
500
527
|
const cacheOption = options.cache ?? router.cache;
|
|
501
528
|
if (cacheOption && !url.searchParams.has("__no_cache")) {
|
|
529
|
+
// A warm writes to the store the trigger checked the scope of.
|
|
502
530
|
const cacheConfig =
|
|
503
|
-
|
|
531
|
+
warm?.cacheConfig ??
|
|
532
|
+
(typeof cacheOption === "function"
|
|
504
533
|
? cacheOption(env, executionCtx)
|
|
505
|
-
: cacheOption;
|
|
534
|
+
: cacheOption);
|
|
506
535
|
|
|
507
536
|
if (cacheConfig.enabled !== false) {
|
|
508
537
|
cacheStore = cacheConfig.store;
|
|
@@ -512,6 +541,28 @@ export function createRSCHandler<
|
|
|
512
541
|
}
|
|
513
542
|
}
|
|
514
543
|
|
|
544
|
+
// Resolve the writable prerender store (durable overlay) for the serve path.
|
|
545
|
+
// Same factory-or-object shape as cache, resolved per request from env/ctx
|
|
546
|
+
// (never memoized). The version resolves per request, as router.prerender()
|
|
547
|
+
// resolves it per refresh, so both sides key off the same table state.
|
|
548
|
+
let resolvedPrerender:
|
|
549
|
+
| import("../prerender/on-demand.js").ResolvedPrerender<any>
|
|
550
|
+
| undefined;
|
|
551
|
+
const prerenderOption = router._prerenderConfig;
|
|
552
|
+
if (prerenderOption) {
|
|
553
|
+
const prerenderConfig =
|
|
554
|
+
typeof prerenderOption === "function"
|
|
555
|
+
? prerenderOption(env, executionCtx)
|
|
556
|
+
: prerenderOption;
|
|
557
|
+
if (prerenderConfig?.store) {
|
|
558
|
+
resolvedPrerender = {
|
|
559
|
+
config: prerenderConfig,
|
|
560
|
+
routerId: router.id,
|
|
561
|
+
version: resolvePrerenderVersion(router.id, router.version),
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
515
566
|
// Route manifest is populated at startup via the virtual module
|
|
516
567
|
// (virtual:rsc-router/routes-manifest). In build/production, it's inlined
|
|
517
568
|
// into the bundle. In dev mode (Node), the discovery plugin populates it
|
|
@@ -566,15 +617,21 @@ export function createRSCHandler<
|
|
|
566
617
|
url,
|
|
567
618
|
variables,
|
|
568
619
|
cacheStore,
|
|
620
|
+
prerender: resolvedPrerender,
|
|
569
621
|
searchParamsFilter,
|
|
570
622
|
explicitTaggedStores,
|
|
571
623
|
cacheProfiles: router.cacheProfiles,
|
|
572
624
|
executionContext: executionCtx,
|
|
573
625
|
themeConfig: router.themeConfig,
|
|
574
626
|
stateCookieName: router.resolvedStateCookieName,
|
|
575
|
-
|
|
627
|
+
versions,
|
|
576
628
|
});
|
|
577
629
|
if (unmarkedRequest) requestContext._shellForcedMiss = true;
|
|
630
|
+
requestContext._runAtRequestEntry = runAtRequestEntry;
|
|
631
|
+
if (warm) {
|
|
632
|
+
requestContext._prerenderWarm = warm;
|
|
633
|
+
requestContext._renderErrors = warm.renderErrors;
|
|
634
|
+
}
|
|
578
635
|
// Gate on the SAME enabled-semantics withTimeout uses (isTimeoutEnabled):
|
|
579
636
|
// a `renderStartMs: 0` / negative opt-out disables the timeout, so the
|
|
580
637
|
// driver's cursor bookkeeping (which only the timeout reads) must be off too.
|
|
@@ -438,6 +438,11 @@ function renderPeResponse<TEnv>(
|
|
|
438
438
|
strictMode: ctx.router.strictMode,
|
|
439
439
|
initialTheme: payloadInitialTheme(getRequestContext()),
|
|
440
440
|
},
|
|
441
|
+
// The same decoded value the SSR render receives, never a second decode:
|
|
442
|
+
// hydrateRoot needs it on the client or useActionState resets. A promise,
|
|
443
|
+
// so a state Flight cannot serialize (a class instance) rejects only this
|
|
444
|
+
// slot instead of failing the payload root and the whole document.
|
|
445
|
+
...(reactFormState ? { formState: Promise.resolve(reactFormState) } : {}),
|
|
441
446
|
};
|
|
442
447
|
|
|
443
448
|
const trace = getRequestContext()._activeRoutine;
|
|
@@ -470,7 +475,6 @@ function renderPeResponse<TEnv>(
|
|
|
470
475
|
{
|
|
471
476
|
// metricsStore=undefined is safe: the handler already stashed the early
|
|
472
477
|
// SSR setup promise, so this reuses it instead of starting setup again.
|
|
473
|
-
// reactFormState travels through the SSR option, not RscPayload.
|
|
474
478
|
html: createSsrHtmlStage({
|
|
475
479
|
ctx,
|
|
476
480
|
request,
|
|
@@ -33,9 +33,11 @@ import {
|
|
|
33
33
|
finalizeResponse,
|
|
34
34
|
} from "./helpers.js";
|
|
35
35
|
import { reportCacheError } from "../cache/cache-error.js";
|
|
36
|
-
import {
|
|
36
|
+
import { requestKeyBase } from "../cache/cache-key-utils.js";
|
|
37
|
+
import { isWarmReplace, noteWarmWrite } from "../prerender/warm-request.js";
|
|
37
38
|
import {
|
|
38
39
|
executionStart,
|
|
40
|
+
markResponseStart,
|
|
39
41
|
predatesInvalidation,
|
|
40
42
|
type ExecutionStart,
|
|
41
43
|
} from "../cache/tag-invalidation.js";
|
|
@@ -104,7 +106,7 @@ export async function serveResponseRouteWithCache(
|
|
|
104
106
|
// Default key: response:{type}: + host-namespaced base (sorted search, reserved
|
|
105
107
|
// _rsc*/__* params excluded). Same composition as document/segment tiers so
|
|
106
108
|
// the host-namespacing and search-normalization rules cannot drift.
|
|
107
|
-
let cacheKey = `response:${responseType}:${
|
|
109
|
+
let cacheKey = `response:${responseType}:${requestKeyBase(reqCtx, url.host, url.pathname, url.searchParams, undefined, reqCtx._searchParamsFilter)}`;
|
|
108
110
|
|
|
109
111
|
// Keyed like the segment record (CacheScope.resolveKeyFrom): with key()
|
|
110
112
|
// results on the cache() chain (a full override, namespaced, #975) or an
|
|
@@ -180,15 +182,19 @@ export async function serveResponseRouteWithCache(
|
|
|
180
182
|
}
|
|
181
183
|
await store2.putResponse!(
|
|
182
184
|
cacheKey,
|
|
183
|
-
fresh,
|
|
185
|
+
markResponseStart(fresh, start),
|
|
184
186
|
cacheScope!.ttl,
|
|
185
187
|
cacheScope!.swr,
|
|
186
188
|
responseTags,
|
|
187
189
|
);
|
|
190
|
+
noteWarmWrite(reqCtx, "response");
|
|
188
191
|
};
|
|
189
192
|
|
|
190
193
|
try {
|
|
191
|
-
|
|
194
|
+
// A router.prerender() warm takes the miss branch and replaces the entry.
|
|
195
|
+
const cached = isWarmReplace(reqCtx)
|
|
196
|
+
? null
|
|
197
|
+
: await store.getResponse(cacheKey);
|
|
192
198
|
if (cached && canServeCached(cached.response)) {
|
|
193
199
|
if (!cached.shouldRevalidate) {
|
|
194
200
|
return applyPreHandlerCallbacks(cached.response);
|
package/src/rsc/rsc-rendering.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
buildShellLoaderSeed,
|
|
19
19
|
countSnapshotFamilies,
|
|
20
20
|
hasDocRecord,
|
|
21
|
+
shellPrerenderHandles,
|
|
21
22
|
} from "../cache/shell-snapshot.js";
|
|
22
23
|
import { appendMetric } from "../router/metrics.js";
|
|
23
24
|
import type { MetricsStore } from "../server/context.js";
|
|
@@ -93,6 +94,10 @@ import {
|
|
|
93
94
|
type DevShellLookup,
|
|
94
95
|
} from "./shell-build-manifest.js";
|
|
95
96
|
import { contextGet } from "../context-var.js";
|
|
97
|
+
import {
|
|
98
|
+
isWarmReplace,
|
|
99
|
+
noteWarmShellEvent,
|
|
100
|
+
} from "../prerender/warm-request.js";
|
|
96
101
|
import {
|
|
97
102
|
resolveSwrWindow,
|
|
98
103
|
staleShellRecaptureDue,
|
|
@@ -465,6 +470,13 @@ function* shellServePlan<TEnv>(
|
|
|
465
470
|
): RoutinePlan<ShellServeOutcome> {
|
|
466
471
|
const { ctx, request, env, url, isPartial, handleStore, nonce, reqCtx } =
|
|
467
472
|
input;
|
|
473
|
+
const manifestEntry = reqCtx._classifiedRoute?.manifestEntry;
|
|
474
|
+
|
|
475
|
+
// A router.prerender() warm (prerender/warm-request.ts) reports the shell of
|
|
476
|
+
// a ppr route: "not-eligible" stands for every gate below that passes, until
|
|
477
|
+
// a HIT or a capture event overwrites it.
|
|
478
|
+
const warm = reqCtx._prerenderWarm;
|
|
479
|
+
if (warm && resolvePprConfig(manifestEntry)) warm.shell ??= "not-eligible";
|
|
468
480
|
|
|
469
481
|
if (
|
|
470
482
|
isPartial ||
|
|
@@ -474,9 +486,11 @@ function* shellServePlan<TEnv>(
|
|
|
474
486
|
) {
|
|
475
487
|
return { kind: "pass" };
|
|
476
488
|
}
|
|
477
|
-
const manifestEntry = reqCtx._classifiedRoute?.manifestEntry;
|
|
478
489
|
const pprConfig = resolvePprConfig(manifestEntry);
|
|
479
490
|
if (!pprConfig) return { kind: "pass" };
|
|
491
|
+
// In replace mode the request is a MISS without reading a shell, and its
|
|
492
|
+
// capture is forced (ShellCaptureDescriptor.force).
|
|
493
|
+
const warmReplace = isWarmReplace(reqCtx);
|
|
480
494
|
// A degraded HIT's reload (shellReloadScript) renders like a cache miss:
|
|
481
495
|
// no shell read, no capture, so it can never degrade again. The handler
|
|
482
496
|
// stripped the marker from the request and left this flag.
|
|
@@ -487,7 +501,7 @@ function* shellServePlan<TEnv>(
|
|
|
487
501
|
// other visitor. Every step below, a HIT included, therefore runs without
|
|
488
502
|
// one.
|
|
489
503
|
const store = reqCtx._cacheStore;
|
|
490
|
-
const baseKey = buildShellKey(url, reqCtx._searchParamsFilter);
|
|
504
|
+
const baseKey = buildShellKey(ctx.router.id, url, reqCtx._searchParamsFilter);
|
|
491
505
|
if (activeRequestNonce(nonce, reqCtx) !== undefined) {
|
|
492
506
|
// Declared intent that cannot be honored deserves a diagnostic (unlike an
|
|
493
507
|
// undeclared route, which is silent): a ppr route gated off by an active
|
|
@@ -551,12 +565,16 @@ function* shellServePlan<TEnv>(
|
|
|
551
565
|
// A MISS schedules its capture after rendering: taken before the read, this
|
|
552
566
|
// lets that capture skip itself when another request's capture stored the
|
|
553
567
|
// shell in between (ShellCaptureDescriptor.storedSeqAtRead).
|
|
554
|
-
|
|
568
|
+
// A warm's capture is its own: one another request stored meanwhile does
|
|
569
|
+
// not cancel it, so it takes no sequence.
|
|
570
|
+
const storedSeqAtRead = warmReplace ? undefined : lastStoredCaptureSeq(key);
|
|
555
571
|
// SSR setup starts before route handling, so read the shell before joining it
|
|
556
572
|
// to overlap the remaining setup work with cache I/O.
|
|
557
|
-
const cached =
|
|
558
|
-
|
|
559
|
-
|
|
573
|
+
const cached = warmReplace
|
|
574
|
+
? null
|
|
575
|
+
: yield* step("shell-read", () =>
|
|
576
|
+
readShellEntry(store, key, reqCtx, pprConfig.tags),
|
|
577
|
+
);
|
|
560
578
|
|
|
561
579
|
// allReady (ssr.resolveStreaming) bypasses PPR entirely: buffering defeats
|
|
562
580
|
// streaming, so bots/SEO crawlers get one complete axis-1 document. These
|
|
@@ -579,6 +597,15 @@ function* shellServePlan<TEnv>(
|
|
|
579
597
|
pprConfig,
|
|
580
598
|
store,
|
|
581
599
|
);
|
|
600
|
+
if (warm) {
|
|
601
|
+
// The warm learns its capture's outcome from the capture's own events,
|
|
602
|
+
// ahead of the router's debugShellCapture sink.
|
|
603
|
+
const routerSink = descriptor.debugSink;
|
|
604
|
+
descriptor.debugSink = (event) => {
|
|
605
|
+
noteWarmShellEvent(warm, event);
|
|
606
|
+
routerSink?.(event);
|
|
607
|
+
};
|
|
608
|
+
}
|
|
582
609
|
// MISS (no entry, invalid reactVersion, a tombstone, or store read
|
|
583
610
|
// failure): axis 1 + a background capture scheduled once the response is
|
|
584
611
|
// known servable.
|
|
@@ -587,6 +614,10 @@ function* shellServePlan<TEnv>(
|
|
|
587
614
|
descriptor: { ...descriptor, storedSeqAtRead },
|
|
588
615
|
ssrModule,
|
|
589
616
|
};
|
|
617
|
+
// Neither the stored shell nor the build shell is read: a warm renders.
|
|
618
|
+
if (warmReplace) {
|
|
619
|
+
return { ...miss, descriptor: { ...descriptor, force: true } };
|
|
620
|
+
}
|
|
590
621
|
|
|
591
622
|
// A HIT replays the handler layer from the entry's doc record (`docKey`),
|
|
592
623
|
// or, for a Prerender route, from the prerender store. An entry that has
|
|
@@ -612,6 +643,9 @@ function* shellServePlan<TEnv>(
|
|
|
612
643
|
"[ShellServe] getShell",
|
|
613
644
|
);
|
|
614
645
|
} else {
|
|
646
|
+
// An onlyIfStale warm found a servable shell; a recapture scheduled
|
|
647
|
+
// below overwrites this through the capture's events.
|
|
648
|
+
if (warm) warm.shell = "fresh";
|
|
615
649
|
// Stale (SWR) hit: serve the stale shell now, recapture in the
|
|
616
650
|
// background (stampede-guarded + backoff inside scheduleShellCapture),
|
|
617
651
|
// at most once per SHELL_MIN_RECAPTURE_INTERVAL_MS of the shell's age.
|
|
@@ -680,17 +714,23 @@ function* shellServePlan<TEnv>(
|
|
|
680
714
|
// route warned. A search-bearing URL never reads one: nothing to probe.
|
|
681
715
|
const routeName = reqCtx._classifiedRoute?.routeKey;
|
|
682
716
|
if (
|
|
683
|
-
!partitionBuildShellCheckDone(url.pathname, routeName) &&
|
|
717
|
+
!partitionBuildShellCheckDone(ctx.router.id, url.pathname, routeName) &&
|
|
684
718
|
shellSearchSeed(url, reqCtx._searchParamsFilter) === ""
|
|
685
719
|
) {
|
|
686
720
|
const found = yield* step("build-shell-check", () =>
|
|
687
|
-
hasBuildShell(url.pathname, devShellLookup),
|
|
721
|
+
hasBuildShell(ctx.router.id, url.pathname, devShellLookup),
|
|
722
|
+
);
|
|
723
|
+
notePartitionBuildShellCheck(
|
|
724
|
+
ctx.router.id,
|
|
725
|
+
url.pathname,
|
|
726
|
+
routeName,
|
|
727
|
+
found,
|
|
688
728
|
);
|
|
689
|
-
notePartitionBuildShellCheck(url.pathname, routeName, found);
|
|
690
729
|
}
|
|
691
730
|
} else {
|
|
692
731
|
buildHit = yield* step("build-shell-lookup", () =>
|
|
693
732
|
lookupBuildShell(
|
|
733
|
+
ctx.router.id,
|
|
694
734
|
url,
|
|
695
735
|
ctx.version,
|
|
696
736
|
store,
|
|
@@ -705,6 +745,7 @@ function* shellServePlan<TEnv>(
|
|
|
705
745
|
? openShellDocumentMetered(reqCtx, { entry: buildHit.entry })
|
|
706
746
|
: null;
|
|
707
747
|
if (buildHit && buildDocument) {
|
|
748
|
+
if (warm) warm.shell = "fresh";
|
|
708
749
|
// Past ppr.ttl: still serve the baked entry, recapture upgrades it.
|
|
709
750
|
if (buildHit.stale) {
|
|
710
751
|
yield* handoff("shell-recapture", () =>
|
|
@@ -1336,7 +1377,7 @@ async function matchPartialWithPprReplay<TEnv>(
|
|
|
1336
1377
|
// null and the seeded record could not have been consulted, which used to
|
|
1337
1378
|
// read as a misleading `snapshot-miss` AFTER two wasted getShell reads.
|
|
1338
1379
|
// Same predicate as resolveNavigation, decided before any store I/O.
|
|
1339
|
-
if (!getNavigationContextHeader(request)) {
|
|
1380
|
+
if (!getNavigationContextHeader(request, url.origin)) {
|
|
1340
1381
|
return runMatch({ outcome: "BYPASS", reason: "no-navigation-context" });
|
|
1341
1382
|
}
|
|
1342
1383
|
// Fragment passthrough (#700), extended to partial replay. Navigation
|
|
@@ -1398,7 +1439,7 @@ async function matchPartialWithPprReplay<TEnv>(
|
|
|
1398
1439
|
// The visitor's own partition (resolveShellPartition): replay never
|
|
1399
1440
|
// crosses into another partition's shell.
|
|
1400
1441
|
const key = await resolveRequestShellKey(
|
|
1401
|
-
buildShellKey(url, reqCtx._searchParamsFilter),
|
|
1442
|
+
buildShellKey(ctx.router.id, url, reqCtx._searchParamsFilter),
|
|
1402
1443
|
routeSnapshot,
|
|
1403
1444
|
store,
|
|
1404
1445
|
reqCtx,
|
|
@@ -1745,6 +1786,17 @@ function serveShellHit(
|
|
|
1745
1786
|
// close over that same context or a streaming tail inherits the base
|
|
1746
1787
|
// context's premature non-streaming handle snapshot.
|
|
1747
1788
|
wireRenderBarrier(tailCtx, handleStore);
|
|
1789
|
+
// The tail's render barrier resolves once its record is replayed and
|
|
1790
|
+
// before a loader runs (cache-lookup.ts withCacheLookup, yieldFromStore).
|
|
1791
|
+
// The store then holds what the prelude was rendered from, so that is
|
|
1792
|
+
// the snapshot the document hydrates with; what this request's loaders
|
|
1793
|
+
// do to it afterwards reaches the client after hydration
|
|
1794
|
+
// (HandleStore.freezeDocumentSnapshot, issue #1035).
|
|
1795
|
+
const resolveRenderBarrier = tailCtx._resolveRenderBarrier;
|
|
1796
|
+
tailCtx._resolveRenderBarrier = (segments) => {
|
|
1797
|
+
handleStore.freezeDocumentSnapshot();
|
|
1798
|
+
resolveRenderBarrier(segments);
|
|
1799
|
+
};
|
|
1748
1800
|
return tailCtx;
|
|
1749
1801
|
};
|
|
1750
1802
|
|
|
@@ -1885,7 +1937,7 @@ function serveShellHit(
|
|
|
1885
1937
|
// fresh; a promise-free bake-lane loader is served from its pin
|
|
1886
1938
|
// (_shellLoaderSeed, loader-cache.ts). A Prerender route's tail takes
|
|
1887
1939
|
// the handler layer from the prerender store before this scope is
|
|
1888
|
-
// consulted
|
|
1940
|
+
// consulted, and the loader pushes from `prerenderHandles`.
|
|
1889
1941
|
seededCtx._shellImplicitCache = {
|
|
1890
1942
|
ttl: descriptor.ttl,
|
|
1891
1943
|
swr: descriptor.swr,
|
|
@@ -1893,6 +1945,7 @@ function serveShellHit(
|
|
|
1893
1945
|
keyPrefix: "doc",
|
|
1894
1946
|
docTail: true,
|
|
1895
1947
|
fixedDocKey: entry.docKey,
|
|
1948
|
+
prerenderHandles: shellPrerenderHandles(records),
|
|
1896
1949
|
};
|
|
1897
1950
|
// Fragment splice (issue #700): store hits in THIS tail emit their stored
|
|
1898
1951
|
// segment fragments verbatim (expanded by segment-fragments.ts). The flag
|
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
* served through the same serveShellHit as a captured one.
|
|
12
12
|
*
|
|
13
13
|
* Lifecycle:
|
|
14
|
-
* - No expiry until the next deploy
|
|
15
|
-
*
|
|
14
|
+
* - No expiry until the next deploy: every build ships its own manifest, and
|
|
15
|
+
* its entries carry that build's document version for the router, which is
|
|
16
|
+
* what the buildVersion gate compares.
|
|
16
17
|
* - `ppr.ttl` drives STALENESS ONLY: past createdAt + ttl the entry still
|
|
17
18
|
* serves, but a runtime recapture is scheduled — SWR is the UPGRADE path
|
|
18
19
|
* from build entry to fresher runtime entry, not the bootstrap path. The
|
|
@@ -54,7 +55,7 @@ export interface BuildShellEntry {
|
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
interface ShellManifestModule {
|
|
57
|
-
/** Manifest key (
|
|
58
|
+
/** Manifest key (router id + pathname, shell-manifest-key.ts) -> asset specifier. */
|
|
58
59
|
default: Record<string, string>;
|
|
59
60
|
loadShellAsset: (spec: string) => Promise<{ default: BuildShellEntry }>;
|
|
60
61
|
}
|
|
@@ -90,9 +91,8 @@ function loadManifest(): Promise<ShellManifestModule | null> {
|
|
|
90
91
|
* Only the per-request gates (tag markers, staleness) stay outside the memo;
|
|
91
92
|
* the document HIT decodes the prelude once per serve (openShellDocument),
|
|
92
93
|
* partial replay never does. `null` memoizes a failed verdict —
|
|
93
|
-
* deterministically invalid.
|
|
94
|
-
*
|
|
95
|
-
* never loads a manifest).
|
|
94
|
+
* deterministically invalid. Keyed by the asking router's document version as
|
|
95
|
+
* well as the spec: the verdict is that version's.
|
|
96
96
|
*/
|
|
97
97
|
const validatedSpecs = new Map<string, ValidatedBuildShellEntry | null>();
|
|
98
98
|
|
|
@@ -110,7 +110,8 @@ async function validatedManifestRecord(
|
|
|
110
110
|
spec: string,
|
|
111
111
|
buildVersion: string,
|
|
112
112
|
): Promise<ValidatedBuildShellEntry | undefined> {
|
|
113
|
-
|
|
113
|
+
const memoKey = `${buildVersion}\u0000${spec}`;
|
|
114
|
+
let verdict = validatedSpecs.get(memoKey);
|
|
114
115
|
if (verdict === undefined) {
|
|
115
116
|
const record = (await mod.loadShellAsset(spec)).default;
|
|
116
117
|
verdict =
|
|
@@ -118,7 +119,7 @@ async function validatedManifestRecord(
|
|
|
118
119
|
hasIntactShellPayload(record.entry)
|
|
119
120
|
? (record as ValidatedBuildShellEntry)
|
|
120
121
|
: null;
|
|
121
|
-
validatedSpecs.set(
|
|
122
|
+
validatedSpecs.set(memoKey, verdict);
|
|
122
123
|
}
|
|
123
124
|
return verdict ?? undefined;
|
|
124
125
|
}
|
|
@@ -267,9 +268,11 @@ async function fetchDevShellEntry(
|
|
|
267
268
|
* request is search-less, like the entries lookupBuildShell serves): a
|
|
268
269
|
* manifest entry for the path in production, a Prerender route in dev. No
|
|
269
270
|
* asset load and no dev capture: it only decides whether a request that
|
|
270
|
-
* cannot read the build shell deserves a warning.
|
|
271
|
+
* cannot read the build shell deserves a warning. The manifest entry is the
|
|
272
|
+
* one staged for the router `routerId` (shell-manifest-key.ts).
|
|
271
273
|
*/
|
|
272
274
|
export async function hasBuildShell(
|
|
275
|
+
routerId: string,
|
|
273
276
|
pathname: string,
|
|
274
277
|
dev?: DevShellLookup,
|
|
275
278
|
): Promise<boolean> {
|
|
@@ -277,11 +280,12 @@ export async function hasBuildShell(
|
|
|
277
280
|
return dev?.isPrerenderRoute === true;
|
|
278
281
|
}
|
|
279
282
|
const mod = await loadManifest();
|
|
280
|
-
return mod?.default[buildShellManifestKey(pathname)] !== undefined;
|
|
283
|
+
return mod?.default[buildShellManifestKey(routerId, pathname)] !== undefined;
|
|
281
284
|
}
|
|
282
285
|
|
|
283
286
|
/**
|
|
284
287
|
* Look up the baked shell entry for a request, applying every serve gate:
|
|
288
|
+
* the serving router's own entry only (`routerId`, shell-manifest-key.ts),
|
|
285
289
|
* search-less requests only (the build captured the bare pathname; a
|
|
286
290
|
* search-bearing URL has its own shell identity owned by runtime capture),
|
|
287
291
|
* version validity, payload integrity, and tag-invalidation markers. Returns
|
|
@@ -294,6 +298,7 @@ export async function hasBuildShell(
|
|
|
294
298
|
* traffic the shell was prerendered for.
|
|
295
299
|
*/
|
|
296
300
|
export async function lookupBuildShell(
|
|
301
|
+
routerId: string,
|
|
297
302
|
url: URL,
|
|
298
303
|
buildVersion: string,
|
|
299
304
|
store: SegmentCacheStore,
|
|
@@ -311,7 +316,7 @@ export async function lookupBuildShell(
|
|
|
311
316
|
if (hasManifest) {
|
|
312
317
|
const mod = await loadManifest();
|
|
313
318
|
if (!mod) return null;
|
|
314
|
-
const spec = mod.default[buildShellManifestKey(url.pathname)];
|
|
319
|
+
const spec = mod.default[buildShellManifestKey(routerId, url.pathname)];
|
|
315
320
|
if (!spec) return null;
|
|
316
321
|
record = await validatedManifestRecord(mod, spec, buildVersion);
|
|
317
322
|
} else if (dev) {
|
|
@@ -338,7 +343,7 @@ export async function lookupBuildShell(
|
|
|
338
343
|
// diagnostic; the route keeps runtime-capture semantics.
|
|
339
344
|
warnOnce(
|
|
340
345
|
"build-shell-tag-check",
|
|
341
|
-
buildShellManifestKey(url.pathname),
|
|
346
|
+
buildShellManifestKey(routerId, url.pathname),
|
|
342
347
|
() =>
|
|
343
348
|
`[rango] Build-time shell for "${url.pathname}" carries cache tags, but ` +
|
|
344
349
|
"the app cache store cannot answer tag-invalidation history durably " +
|
|
@@ -6,7 +6,10 @@
|
|
|
6
6
|
* pulling the capture orchestration module or shell-serve.ts (React).
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
routerKeyPrefix,
|
|
11
|
+
sortedSearchString,
|
|
12
|
+
} from "../cache/cache-key-utils.js";
|
|
10
13
|
import type { SearchParamsFilter } from "../cache/search-params-filter.js";
|
|
11
14
|
|
|
12
15
|
/**
|
|
@@ -85,13 +88,14 @@ export function shellSearchSeed(url: URL, filter?: SearchParamsFilter): string {
|
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
/**
|
|
88
|
-
* Shell cache key: host + pathname + sorted search + a `:shell`
|
|
89
|
-
* (so it can never collide with a document-cache key; the
|
|
90
|
-
* the shell family internally).
|
|
91
|
+
* Shell cache key: router id + host + pathname + sorted search + a `:shell`
|
|
92
|
+
* namespace suffix (so it can never collide with a document-cache key; the
|
|
93
|
+
* store further isolates the shell family internally).
|
|
91
94
|
*
|
|
92
95
|
* The key includes the request HOST: in a multi-tenant host-router deployment
|
|
93
96
|
* (one worker, one shared KV/runtime-cache store) a host-less key would serve
|
|
94
|
-
* tenant A's captured shell to tenant B's users.
|
|
97
|
+
* tenant A's captured shell to tenant B's users. It starts with the serving
|
|
98
|
+
* ROUTER for the same reason (cache-key-utils.ts, the router rule).
|
|
95
99
|
*
|
|
96
100
|
* `filter` is the request's compiled `cache.searchParams` config
|
|
97
101
|
* (ctx._searchParamsFilter): excluded params collapse onto one shell slot.
|
|
@@ -99,8 +103,12 @@ export function shellSearchSeed(url: URL, filter?: SearchParamsFilter): string {
|
|
|
99
103
|
* and lookup makes every shell request a permanent miss. The testing helper
|
|
100
104
|
* `shellCacheKey` (testing/shell-status.ts) builds its key with it too.
|
|
101
105
|
*/
|
|
102
|
-
export function buildShellKey(
|
|
103
|
-
|
|
106
|
+
export function buildShellKey(
|
|
107
|
+
routerId: string,
|
|
108
|
+
url: URL,
|
|
109
|
+
filter?: SearchParamsFilter,
|
|
110
|
+
): string {
|
|
111
|
+
return `${routerKeyPrefix(routerId)}${url.host}${url.pathname}${shellSearchSeed(url, filter)}:shell`;
|
|
104
112
|
}
|
|
105
113
|
|
|
106
114
|
/**
|
|
@@ -173,6 +181,30 @@ export const POST_QUIESCE_TASK_HOPS = 16;
|
|
|
173
181
|
*/
|
|
174
182
|
export const SHELL_CAPTURE_TASK_HARD_CAP_MS = 25_000;
|
|
175
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Why a capture attempt refused to store its shell
|
|
186
|
+
* (ShellCaptureDebugEvent.refusal, `caches.refusal` on a `router.prerender()`
|
|
187
|
+
* result): `identity` (a request-scoped read: cookies(), headers(), the
|
|
188
|
+
* theme, a `{ cache: false }` variable), `dynamic` (`ctx.dynamic()`),
|
|
189
|
+
* `loader` (a `{ ssr: false }` loader rejected, or settled with
|
|
190
|
+
* redirect()/notFound()), `no-record` (no doc record: the route's cache()
|
|
191
|
+
* refused the write, or the handler output failed), `handles` (a pushed
|
|
192
|
+
* handle value did not finish encoding), `size` (over the store's value
|
|
193
|
+
* limit), `record-expired` (the route cache() record the capture wrote ran
|
|
194
|
+
* out first), `invalidated` (a tag of the shell was invalidated after the
|
|
195
|
+
* capture started), `uncacheable` (the store cannot hold the entry).
|
|
196
|
+
*/
|
|
197
|
+
export type ShellCaptureRefusal =
|
|
198
|
+
| "identity"
|
|
199
|
+
| "dynamic"
|
|
200
|
+
| "loader"
|
|
201
|
+
| "no-record"
|
|
202
|
+
| "handles"
|
|
203
|
+
| "size"
|
|
204
|
+
| "record-expired"
|
|
205
|
+
| "invalidated"
|
|
206
|
+
| "uncacheable";
|
|
207
|
+
|
|
176
208
|
/**
|
|
177
209
|
* Shared PPR loader-lane hint, appended to every capture warning or error
|
|
178
210
|
* whose fix depends on the lane (shell-capture.ts warnings, the cookie-store.ts
|