@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
|
@@ -61,6 +61,8 @@ export interface GeneratedManifest {
|
|
|
61
61
|
prerenderRoutes?: string[];
|
|
62
62
|
/** Route names wrapped with Passthrough() (live handler for runtime fallback) */
|
|
63
63
|
passthroughRoutes?: string[];
|
|
64
|
+
/** Route names opted into on-demand prerender (Prerender(..., { onDemand })) */
|
|
65
|
+
onDemandRoutes?: string[];
|
|
64
66
|
/** Route name → response type for non-RSC routes */
|
|
65
67
|
responseTypeRoutes?: Record<string, string>;
|
|
66
68
|
/** Route name -> search schema descriptor for typed URL helpers */
|
|
@@ -172,6 +174,7 @@ async function buildPrefixTreeNode(
|
|
|
172
174
|
prerenderRoutes?: string[],
|
|
173
175
|
prerenderDefs?: Record<string, any>,
|
|
174
176
|
passthroughRoutes?: string[],
|
|
177
|
+
onDemandRoutes?: string[],
|
|
175
178
|
responseTypeRoutes?: Record<string, string>,
|
|
176
179
|
routeSearchSchemas?: Record<string, Record<string, string>>,
|
|
177
180
|
routerId?: string,
|
|
@@ -264,6 +267,13 @@ async function buildPrefixTreeNode(
|
|
|
264
267
|
passthroughRoutes.push(name);
|
|
265
268
|
}
|
|
266
269
|
}
|
|
270
|
+
if (
|
|
271
|
+
onDemandRoutes &&
|
|
272
|
+
entry.type === "route" &&
|
|
273
|
+
entry.isOnDemand === true
|
|
274
|
+
) {
|
|
275
|
+
onDemandRoutes.push(name);
|
|
276
|
+
}
|
|
267
277
|
}
|
|
268
278
|
}
|
|
269
279
|
|
|
@@ -289,6 +299,7 @@ async function buildPrefixTreeNode(
|
|
|
289
299
|
prerenderRoutes,
|
|
290
300
|
prerenderDefs,
|
|
291
301
|
passthroughRoutes,
|
|
302
|
+
onDemandRoutes,
|
|
292
303
|
responseTypeRoutes,
|
|
293
304
|
routeSearchSchemas,
|
|
294
305
|
routerId,
|
|
@@ -455,6 +466,7 @@ export async function generateManifestFull<TEnv>(
|
|
|
455
466
|
const prerenderRoutes: string[] = [];
|
|
456
467
|
const prerenderDefs: Record<string, any> = {};
|
|
457
468
|
const passthroughRoutes: string[] = [];
|
|
469
|
+
const onDemandRoutes: string[] = [];
|
|
458
470
|
const responseTypeRoutes: Record<string, string> = {};
|
|
459
471
|
for (const [name, entry] of manifest) {
|
|
460
472
|
if (entry.type === "route" && entry.isPrerender) {
|
|
@@ -465,6 +477,9 @@ export async function generateManifestFull<TEnv>(
|
|
|
465
477
|
if (entry.isPassthrough === true) {
|
|
466
478
|
passthroughRoutes.push(name);
|
|
467
479
|
}
|
|
480
|
+
if (entry.isOnDemand === true) {
|
|
481
|
+
onDemandRoutes.push(name);
|
|
482
|
+
}
|
|
468
483
|
}
|
|
469
484
|
if (entry.type === "route" && entry.responseType) {
|
|
470
485
|
responseTypeRoutes[name] = entry.responseType;
|
|
@@ -485,6 +500,7 @@ export async function generateManifestFull<TEnv>(
|
|
|
485
500
|
prerenderRoutes,
|
|
486
501
|
prerenderDefs,
|
|
487
502
|
passthroughRoutes,
|
|
503
|
+
onDemandRoutes,
|
|
488
504
|
responseTypeRoutes,
|
|
489
505
|
routeSearchSchemas,
|
|
490
506
|
options?.routerId,
|
|
@@ -502,6 +518,7 @@ export async function generateManifestFull<TEnv>(
|
|
|
502
518
|
prerenderRoutes: prerenderRoutes.length > 0 ? prerenderRoutes : undefined,
|
|
503
519
|
passthroughRoutes:
|
|
504
520
|
passthroughRoutes.length > 0 ? passthroughRoutes : undefined,
|
|
521
|
+
onDemandRoutes: onDemandRoutes.length > 0 ? onDemandRoutes : undefined,
|
|
505
522
|
responseTypeRoutes:
|
|
506
523
|
Object.keys(responseTypeRoutes).length > 0
|
|
507
524
|
? responseTypeRoutes
|
|
@@ -136,6 +136,11 @@ export function mergeFullManifests(
|
|
|
136
136
|
manifest.passthroughRoutes,
|
|
137
137
|
overwrittenRoutes,
|
|
138
138
|
),
|
|
139
|
+
onDemandRoutes: mergeRouteList(
|
|
140
|
+
merged.onDemandRoutes,
|
|
141
|
+
manifest.onDemandRoutes,
|
|
142
|
+
overwrittenRoutes,
|
|
143
|
+
),
|
|
139
144
|
responseTypeRoutes: mergeRouteRecord(
|
|
140
145
|
merged.responseTypeRoutes,
|
|
141
146
|
manifest.responseTypeRoutes,
|
package/src/build/route-trie.ts
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* skipping.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import type { ExecutionContext } from "../types/request-scope.js";
|
|
11
|
+
|
|
10
12
|
interface WaitUntilHost {
|
|
11
13
|
waitUntil?: (fn: () => Promise<void>) => void;
|
|
12
14
|
}
|
|
@@ -85,3 +87,62 @@ export async function settleGrowing(
|
|
|
85
87
|
}
|
|
86
88
|
return true;
|
|
87
89
|
}
|
|
90
|
+
|
|
91
|
+
/** Macrotask turns `awaitLaterMillisecond` waits at most. */
|
|
92
|
+
export const LATER_MILLISECOND_MAX_TURNS = 10;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Yield until `Date.now()` passes `since`, so a store's whole-millisecond tag
|
|
96
|
+
* gate (putShell vs an invalidation made just before) sees a later
|
|
97
|
+
* millisecond. Bounded: on Cloudflare the clock stands still between I/O
|
|
98
|
+
* events, so an unbounded wait could hang. At the bound the caller proceeds
|
|
99
|
+
* and a store may refuse the write, which its result reports.
|
|
100
|
+
*/
|
|
101
|
+
export async function awaitLaterMillisecond(
|
|
102
|
+
since: number,
|
|
103
|
+
maxTurns: number = LATER_MILLISECOND_MAX_TURNS,
|
|
104
|
+
): Promise<void> {
|
|
105
|
+
for (let turn = 0; turn < maxTurns && Date.now() === since; turn++) {
|
|
106
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 0));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** An ExecutionContext that keeps every promise handed to `waitUntil`. */
|
|
111
|
+
export interface CollectingExecutionContext extends ExecutionContext {
|
|
112
|
+
readonly tasks: Promise<unknown>[];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Wrap the caller's ExecutionContext (or none) so a caller can wait for the
|
|
117
|
+
* request's background work: the deferred cache writes, the shell capture, a
|
|
118
|
+
* store's own writes. Every other member reads through to `inner`, bound to
|
|
119
|
+
* it (workerd's methods reject a foreign `this`).
|
|
120
|
+
*/
|
|
121
|
+
export function createCollectingExecutionContext(
|
|
122
|
+
inner?: ExecutionContext,
|
|
123
|
+
): CollectingExecutionContext {
|
|
124
|
+
const tasks: Promise<unknown>[] = [];
|
|
125
|
+
const own: CollectingExecutionContext = {
|
|
126
|
+
tasks,
|
|
127
|
+
waitUntil(promise) {
|
|
128
|
+
const task = Promise.resolve(promise);
|
|
129
|
+
// The list only waits; the host (or the log below) owns the failure.
|
|
130
|
+
tasks.push(task.catch(() => {}));
|
|
131
|
+
if (inner) inner.waitUntil(task);
|
|
132
|
+
else {
|
|
133
|
+
task.catch((error) =>
|
|
134
|
+
console.error("[waitUntil] Background task failed:", error),
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
passThroughOnException() {},
|
|
139
|
+
};
|
|
140
|
+
if (!inner) return own;
|
|
141
|
+
return new Proxy(own, {
|
|
142
|
+
get(target, prop) {
|
|
143
|
+
if (prop in target) return target[prop as keyof typeof target];
|
|
144
|
+
const value = (inner as unknown as Record<PropertyKey, unknown>)[prop];
|
|
145
|
+
return typeof value === "function" ? value.bind(inner) : value;
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
}
|
|
@@ -22,7 +22,7 @@ import type { SearchParamsFilter } from "./search-params-filter.js";
|
|
|
22
22
|
*/
|
|
23
23
|
const RESERVED_SEARCH_PARAMS = new Set(["__no_cache", "__rsc", "__html"]);
|
|
24
24
|
|
|
25
|
-
function isReservedSearchParam(key: string): boolean {
|
|
25
|
+
export function isReservedSearchParam(key: string): boolean {
|
|
26
26
|
return key.startsWith("_rsc") || RESERVED_SEARCH_PARAMS.has(key);
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -65,20 +65,37 @@ export function sortedRouteParams(
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
|
-
*
|
|
68
|
+
* THE ROUTER RULE (#1065). A key that names one router's output for a host
|
|
69
|
+
* and path starts with the router part, `{encodeURIComponent(routerId)}@`. A
|
|
70
|
+
* key that names a function and its arguments, or a tag, does not.
|
|
69
71
|
*
|
|
70
|
-
*
|
|
71
|
-
* (
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
72
|
+
* - With the part: PPR shells and the build-shell manifest (buildShellKey,
|
|
73
|
+
* buildShellManifestKey); route cache() records, the document cache and
|
|
74
|
+
* response routes (cacheKeyBase); a loader's own cache() entry; a
|
|
75
|
+
* `"use cache"` entry of a call that takes a ctx (a ctx is one router's:
|
|
76
|
+
* its variables, env and reverse() map).
|
|
77
|
+
* - Without: a `"use cache"` entry of plain arguments or a bare Request
|
|
78
|
+
* (requestArgKey); tag markers; a key the app builds itself
|
|
79
|
+
* (`cache({ key })`, a `keyGenerator`).
|
|
76
80
|
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
81
|
+
* Why: a host and path do not name a router. A `hostOverride` cookie picks
|
|
82
|
+
* the router and forwards the request unmodified, and a `router.prerender()`
|
|
83
|
+
* warm requests whatever origin it resolved, so two routers on one store
|
|
84
|
+
* built one key and read each other's entry.
|
|
85
|
+
*
|
|
86
|
+
* The id (router.ts) has to be the same in every isolate and in build-time
|
|
87
|
+
* discovery: an explicit `id` and the injected `$$id` are, the `router_{n}`
|
|
88
|
+
* fallback is for a single router only (discover-routers.ts warns about
|
|
89
|
+
* more). Encoded, the id holds no `@` or `/`, and a host holds no `@`, so a
|
|
90
|
+
* key has a router part exactly when its first `@` comes before its first
|
|
91
|
+
* `/`: no id can be chosen to spell another router's key.
|
|
80
92
|
*/
|
|
81
|
-
export function
|
|
93
|
+
export function routerKeyPrefix(routerId: string): string {
|
|
94
|
+
return `${encodeURIComponent(routerId)}@`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** `{host}{pathname}[:params][?search]`: what follows the router part. */
|
|
98
|
+
function hostPathKey(
|
|
82
99
|
host: string,
|
|
83
100
|
pathname: string,
|
|
84
101
|
searchParams?: URLSearchParams,
|
|
@@ -96,6 +113,68 @@ export function cacheKeyBase(
|
|
|
96
113
|
return key;
|
|
97
114
|
}
|
|
98
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Key base of one router's output (the router rule, routerKeyPrefix):
|
|
118
|
+
* `${routerId}@${host}${pathname}[:params][?search]`.
|
|
119
|
+
*
|
|
120
|
+
* The ONE composition of the namespacing rule, shared by the segment tier
|
|
121
|
+
* (cache-scope.ts), the document tier (document-cache.ts), the response tier
|
|
122
|
+
* (rsc/response-cache-serve.ts) and the loader cache so the rule cannot drift
|
|
123
|
+
* between them. Host prefixing matters because VercelCacheStore /
|
|
124
|
+
* MemorySegmentCacheStore key by the raw string (only CFCacheStore adds host
|
|
125
|
+
* internally) -- on a single function serving multiple domains an
|
|
126
|
+
* un-namespaced key bleeds tenant A's cached response to tenant B.
|
|
127
|
+
*
|
|
128
|
+
* Output is BYTE-STABLE by contract: changing the composition silently
|
|
129
|
+
* invalidates every persisted cache entry on upgrade. Callers append their own
|
|
130
|
+
* tier-specific suffixes (`:rsc`/`:html`, segment hash) after this base.
|
|
131
|
+
*/
|
|
132
|
+
export function cacheKeyBase(
|
|
133
|
+
routerId: string,
|
|
134
|
+
host: string,
|
|
135
|
+
pathname: string,
|
|
136
|
+
searchParams?: URLSearchParams,
|
|
137
|
+
params?: Record<string, string>,
|
|
138
|
+
filter?: SearchParamsFilter,
|
|
139
|
+
): string {
|
|
140
|
+
return `${routerKeyPrefix(routerId)}${hostPathKey(host, pathname, searchParams, params, filter)}`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* cacheKeyBase for the request context `ctx`. rsc/handler.ts sets `_routerId`
|
|
145
|
+
* before the request scope opens and derived contexts inherit it, so a served
|
|
146
|
+
* request always has one. Only a hand-built context has none (a unit test,
|
|
147
|
+
* the runLoader / renderHandler primitives): its key has no router part,
|
|
148
|
+
* which no router's key equals (routerKeyPrefix).
|
|
149
|
+
*/
|
|
150
|
+
export function requestKeyBase(
|
|
151
|
+
ctx: { readonly _routerId?: string } | null | undefined,
|
|
152
|
+
host: string,
|
|
153
|
+
pathname: string,
|
|
154
|
+
searchParams?: URLSearchParams,
|
|
155
|
+
params?: Record<string, string>,
|
|
156
|
+
filter?: SearchParamsFilter,
|
|
157
|
+
): string {
|
|
158
|
+
const routerId = ctx?._routerId;
|
|
159
|
+
return routerId === undefined
|
|
160
|
+
? hostPathKey(host, pathname, searchParams, params, filter)
|
|
161
|
+
: cacheKeyBase(routerId, host, pathname, searchParams, params, filter);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The `"use cache"` key part of a bare Request argument: its URL, with no
|
|
166
|
+
* router part (the router rule, routerKeyPrefix).
|
|
167
|
+
*/
|
|
168
|
+
export function requestArgKey(url: URL, filter?: SearchParamsFilter): string {
|
|
169
|
+
return hostPathKey(
|
|
170
|
+
url.host,
|
|
171
|
+
url.pathname,
|
|
172
|
+
url.searchParams,
|
|
173
|
+
undefined,
|
|
174
|
+
filter,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
99
178
|
/**
|
|
100
179
|
* The prefix of a `key()` result's part in a record key (#975), and of a
|
|
101
180
|
* loader's own `key()` result after its `loader:<id>:` namespace (#1009,
|
|
@@ -13,6 +13,15 @@ import type { RequestContext } from "../server/request-context.js";
|
|
|
13
13
|
import { normalizeTags } from "./cache-tag.js";
|
|
14
14
|
import { reportCacheError } from "./cache-error.js";
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* How long a follower trusts an in-flight leader before evicting it and
|
|
18
|
+
* running fresh. High enough that a slow-but-healthy upstream never triggers
|
|
19
|
+
* duplicate work (a legitimate cached call taking >15s is already pathological);
|
|
20
|
+
* low enough to bound the blast radius of a wedged leader to seconds, not the
|
|
21
|
+
* isolate lifetime. Aligned with SHELL_CAPTURE_MAX_WAIT_MS.
|
|
22
|
+
*/
|
|
23
|
+
export const IN_FLIGHT_LEADER_MAX_WAIT_MS = 15_000;
|
|
24
|
+
|
|
16
25
|
/**
|
|
17
26
|
* Default TTL for route-level cache() DSL and loader cache.
|
|
18
27
|
* Applied when neither the cache options nor the store defaults specify a TTL.
|
|
@@ -184,8 +193,8 @@ export function resolveTagsOption<TEnv>(
|
|
|
184
193
|
* and on CFCacheStore would also cost a wasted KV marker read per request.
|
|
185
194
|
* Returns undefined when nothing usable remains, keeping the entry header-free.
|
|
186
195
|
*/
|
|
187
|
-
function normalizeTagList(tags: string[]): string[] | undefined {
|
|
188
|
-
const out = normalizeTags(tags);
|
|
196
|
+
export function normalizeTagList(tags: string[]): string[] | undefined {
|
|
197
|
+
const out = [...new Set(normalizeTags(tags))];
|
|
189
198
|
return out.length > 0 ? out : undefined;
|
|
190
199
|
}
|
|
191
200
|
|
|
@@ -42,7 +42,11 @@ import {
|
|
|
42
42
|
} from "./handle-snapshot.js";
|
|
43
43
|
import { startHandleCapture, useCacheRecordKey } from "./handle-capture.js";
|
|
44
44
|
import { isHandle } from "../handle.js";
|
|
45
|
-
import {
|
|
45
|
+
import {
|
|
46
|
+
requestArgKey,
|
|
47
|
+
requestKeyBase,
|
|
48
|
+
sortedSearchString,
|
|
49
|
+
} from "./cache-key-utils.js";
|
|
46
50
|
import { encodeKV } from "../encode-kv.js";
|
|
47
51
|
import { runBackground } from "./background-task.js";
|
|
48
52
|
import { observePhase, PHASES } from "../router/instrument.js";
|
|
@@ -60,6 +64,8 @@ import {
|
|
|
60
64
|
type CacheExecScope,
|
|
61
65
|
} from "./cache-exec-scope.js";
|
|
62
66
|
import { reportCacheError } from "./cache-error.js";
|
|
67
|
+
import { IN_FLIGHT_LEADER_MAX_WAIT_MS } from "./cache-policy.js";
|
|
68
|
+
import { isWarmReplace, noteWarmWrite } from "../prerender/warm-request.js";
|
|
63
69
|
import {
|
|
64
70
|
executionStart,
|
|
65
71
|
invalidatedSince,
|
|
@@ -314,15 +320,6 @@ interface InFlightExecution {
|
|
|
314
320
|
|
|
315
321
|
const inFlightExecutions = new Map<string, InFlightExecution>();
|
|
316
322
|
|
|
317
|
-
/**
|
|
318
|
-
* How long a follower trusts an in-flight leader before evicting it and
|
|
319
|
-
* running fresh. High enough that a slow-but-healthy upstream never triggers
|
|
320
|
-
* duplicate work (a legitimate cached call taking >15s is already pathological);
|
|
321
|
-
* low enough to bound the blast radius of a wedged leader to seconds, not the
|
|
322
|
-
* isolate lifetime. Aligned with SHELL_CAPTURE_MAX_WAIT_MS.
|
|
323
|
-
*/
|
|
324
|
-
const IN_FLIGHT_LEADER_MAX_WAIT_MS = 15_000;
|
|
325
|
-
|
|
326
323
|
/** Distinguishes a leader timeout from a leader rejection in raceLeader. */
|
|
327
324
|
const LEADER_TIMED_OUT = Symbol("leader-timed-out");
|
|
328
325
|
|
|
@@ -499,9 +496,11 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
499
496
|
}
|
|
500
497
|
if (ctx.params && typeof ctx.params === "object") {
|
|
501
498
|
// Include host to prevent cross-host cache collisions (same
|
|
502
|
-
// pattern as route-level cache-scope.ts key generation)
|
|
499
|
+
// pattern as route-level cache-scope.ts key generation), led by the
|
|
500
|
+
// router part: a ctx is one router's (cache-key-utils.ts, the
|
|
501
|
+
// router rule).
|
|
503
502
|
if (ctx.url?.host) {
|
|
504
|
-
keyArgs.push(ctx.url.host);
|
|
503
|
+
keyArgs.push(requestKeyBase(requestCtx, ctx.url.host, ""));
|
|
505
504
|
}
|
|
506
505
|
// Include route name to prevent collisions when the same cached
|
|
507
506
|
// function is reused across routes with identical pathname/params
|
|
@@ -529,15 +528,8 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
529
528
|
} else if (arg instanceof Request) {
|
|
530
529
|
// A raw Request (ctx.request) is request-scoped like ctx: fold in its
|
|
531
530
|
// URL with the same host-namespacing and search normalization.
|
|
532
|
-
const url = new URL(arg.url);
|
|
533
531
|
keyArgs.push(
|
|
534
|
-
|
|
535
|
-
url.host,
|
|
536
|
-
url.pathname,
|
|
537
|
-
url.searchParams,
|
|
538
|
-
undefined,
|
|
539
|
-
requestCtx?._searchParamsFilter,
|
|
540
|
-
),
|
|
532
|
+
requestArgKey(new URL(arg.url), requestCtx?._searchParamsFilter),
|
|
541
533
|
);
|
|
542
534
|
} else if (arg != null && arg === requestCtx?.env) {
|
|
543
535
|
// The request's env is left out: constant per deployment, and its
|
|
@@ -617,8 +609,12 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
617
609
|
return result;
|
|
618
610
|
}
|
|
619
611
|
|
|
620
|
-
// Cache lookup
|
|
621
|
-
|
|
612
|
+
// Cache lookup. A router.prerender() warm reads a miss and leads its own
|
|
613
|
+
// execution below: a leader that started before the warm was called may
|
|
614
|
+
// be reading data from before the change the warm publishes, and a
|
|
615
|
+
// follower writes nothing of its own.
|
|
616
|
+
const warmReplace = isWarmReplace(requestCtx);
|
|
617
|
+
const cached = warmReplace ? null : await store.getItem(cacheKey);
|
|
622
618
|
|
|
623
619
|
// Serve a cached entry on the hit path: deserialize the stored value,
|
|
624
620
|
// replay handle data (gated on tainted args), and surface the entry's tags
|
|
@@ -767,7 +763,9 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
767
763
|
ttl: profile.ttl,
|
|
768
764
|
swr: profile.swr,
|
|
769
765
|
tags: freshTags.length > 0 ? freshTags : undefined,
|
|
766
|
+
startedAt: start.at,
|
|
770
767
|
});
|
|
768
|
+
noteWarmWrite(requestCtx, "item");
|
|
771
769
|
}
|
|
772
770
|
},
|
|
773
771
|
),
|
|
@@ -806,7 +804,7 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
806
804
|
// against its OWN handle store (gated on ITS hasTaintedArgs), and records
|
|
807
805
|
// tags into its OWN request — no deserialized result is shared across
|
|
808
806
|
// requests. The store write stays exactly once (the leader's).
|
|
809
|
-
let existing = inFlightExecutions.get(cacheKey);
|
|
807
|
+
let existing = warmReplace ? undefined : inFlightExecutions.get(cacheKey);
|
|
810
808
|
while (existing) {
|
|
811
809
|
const remainingMs =
|
|
812
810
|
IN_FLIGHT_LEADER_MAX_WAIT_MS - (Date.now() - existing.registeredAt);
|
|
@@ -1022,7 +1020,9 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
1022
1020
|
ttl: profile.ttl,
|
|
1023
1021
|
swr: profile.swr,
|
|
1024
1022
|
tags: tags.length > 0 ? tags : undefined,
|
|
1023
|
+
startedAt: start.at,
|
|
1025
1024
|
});
|
|
1025
|
+
noteWarmWrite(requestCtx, "item");
|
|
1026
1026
|
} catch (writeError) {
|
|
1027
1027
|
requestCtx?._reportBackgroundError?.(writeError, "cache-write");
|
|
1028
1028
|
}
|
package/src/cache/cache-scope.ts
CHANGED
|
@@ -38,12 +38,11 @@ import {
|
|
|
38
38
|
import {
|
|
39
39
|
captureHandles,
|
|
40
40
|
captureHandleOwners,
|
|
41
|
-
|
|
41
|
+
restoreRecordHandles,
|
|
42
42
|
type OwnedPushDelivery,
|
|
43
43
|
encodeHandles,
|
|
44
|
-
decodeHandles,
|
|
45
44
|
} from "./handle-snapshot.js";
|
|
46
|
-
import {
|
|
45
|
+
import { composeCacheKeys, requestKeyBase } from "./cache-key-utils.js";
|
|
47
46
|
import {
|
|
48
47
|
DEFAULT_ROUTE_TTL,
|
|
49
48
|
isFiniteNonNegativeSeconds,
|
|
@@ -53,6 +52,7 @@ import {
|
|
|
53
52
|
} from "./cache-policy.js";
|
|
54
53
|
import type { RequestContext } from "../server/request-context.js";
|
|
55
54
|
import { runIdentityExempt } from "./cache-exec-scope.js";
|
|
55
|
+
import { isWarmReplace, noteWarmWrite } from "../prerender/warm-request.js";
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Narrow the request's route-record window (RequestContext._routeRecordWindow)
|
|
@@ -138,7 +138,7 @@ function getDefaultRouteCacheKey(
|
|
|
138
138
|
? "intercept"
|
|
139
139
|
: (prefixOverride ?? (isPartial ? "partial" : "doc"));
|
|
140
140
|
|
|
141
|
-
return `${prefix}:${
|
|
141
|
+
return `${prefix}:${requestKeyBase(ctx, host, pathname, searchParams, params, ctx?._searchParamsFilter)}`;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
// ============================================================================
|
|
@@ -735,15 +735,14 @@ export class CacheScope {
|
|
|
735
735
|
* @param pathname - URL pathname for cache key generation
|
|
736
736
|
* @param params - Route params for cache key generation
|
|
737
737
|
* @param isIntercept - Whether this is an intercept navigation (uses different cache key)
|
|
738
|
-
* @param ownedPushes -
|
|
739
|
-
* loader-
|
|
740
|
-
* _claimLoaderPushes, and on a document HIT tail the owners it restores)
|
|
738
|
+
* @param ownedPushes - What the record is to each loader's handle pushes
|
|
739
|
+
* (withCacheLookup: loader-cache.ts loaderPins), called on a hit
|
|
741
740
|
*/
|
|
742
741
|
async lookupRoute(
|
|
743
742
|
pathname: string,
|
|
744
743
|
params: Record<string, string>,
|
|
745
744
|
isIntercept?: boolean,
|
|
746
|
-
ownedPushes?: OwnedPushDelivery,
|
|
745
|
+
ownedPushes?: () => OwnedPushDelivery,
|
|
747
746
|
): Promise<{
|
|
748
747
|
segments: ResolvedSegment[];
|
|
749
748
|
shouldRevalidate: boolean;
|
|
@@ -779,7 +778,7 @@ export class CacheScope {
|
|
|
779
778
|
pathname: string,
|
|
780
779
|
params: Record<string, string>,
|
|
781
780
|
isIntercept?: boolean,
|
|
782
|
-
ownedPushes?: OwnedPushDelivery,
|
|
781
|
+
ownedPushes?: () => OwnedPushDelivery,
|
|
783
782
|
): Promise<CacheRouteLookupOutcome> {
|
|
784
783
|
if (!this.enabled) return { status: "bypass" };
|
|
785
784
|
if (!this.conditionAllows("read")) return { status: "bypass" };
|
|
@@ -787,6 +786,12 @@ export class CacheScope {
|
|
|
787
786
|
const store = this.getStore();
|
|
788
787
|
if (!store) return { status: "bypass" };
|
|
789
788
|
|
|
789
|
+
// A router.prerender() warm renders as on a cold cache: the opt-outs
|
|
790
|
+
// above still answer `bypass`, and the miss lets the write path replace
|
|
791
|
+
// the record under the key it resolves (cacheRoute).
|
|
792
|
+
const ambientContext = _getRequestContext();
|
|
793
|
+
if (isWarmReplace(ambientContext)) return { status: "miss" };
|
|
794
|
+
|
|
790
795
|
// Resolve cache key INSIDE the try so a throwing consumer key() (or a
|
|
791
796
|
// store.keyGenerator) degrades to a cache miss (return null -> render
|
|
792
797
|
// uncached) instead of crashing the foreground render. resolveCacheKey
|
|
@@ -827,7 +832,6 @@ export class CacheScope {
|
|
|
827
832
|
// seeded record, which a document tail turns into the degrade
|
|
828
833
|
// (ShellRecordUnavailableError).
|
|
829
834
|
let segments: ResolvedSegment[];
|
|
830
|
-
const ambientContext = _getRequestContext();
|
|
831
835
|
try {
|
|
832
836
|
const codec = await import("./segment-codec.js");
|
|
833
837
|
segments = ambientContext?._shellFragmentPayload
|
|
@@ -870,27 +874,24 @@ export class CacheScope {
|
|
|
870
874
|
cached.tags,
|
|
871
875
|
);
|
|
872
876
|
|
|
873
|
-
//
|
|
874
|
-
//
|
|
875
|
-
//
|
|
876
|
-
//
|
|
877
|
+
// Before the handle replay: the implicit doc scope's marker arms the
|
|
878
|
+
// loader pins of a navigation replay here (matchPartialWithPprReplay),
|
|
879
|
+
// and `ownedPushes` reads them to decide which loader-owned values
|
|
880
|
+
// stand. The segments decoded, so the record serves from here on.
|
|
881
|
+
await this.onHit?.();
|
|
882
|
+
|
|
877
883
|
const handleStore = _getRequestContext()?._handleStore;
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
handleStore,
|
|
890
|
-
cached.handleOwners,
|
|
891
|
-
ownedPushes,
|
|
892
|
-
);
|
|
893
|
-
}
|
|
884
|
+
// An empty handles string means the route pushed none (the common
|
|
885
|
+
// case); a decode failure skips the restore but keeps the segments.
|
|
886
|
+
if (handleStore) {
|
|
887
|
+
await restoreRecordHandles(
|
|
888
|
+
handleStore,
|
|
889
|
+
cached,
|
|
890
|
+
ownedPushes?.(),
|
|
891
|
+
this.boundary !== undefined
|
|
892
|
+
? new Set(segments.map((s) => s.id))
|
|
893
|
+
: undefined,
|
|
894
|
+
);
|
|
894
895
|
}
|
|
895
896
|
|
|
896
897
|
if (INTERNAL_RANGO_DEBUG) {
|
|
@@ -905,7 +906,6 @@ export class CacheScope {
|
|
|
905
906
|
if (ambientContext)
|
|
906
907
|
this.noteRecordWindow(ambientContext, cached.expiresAt, false);
|
|
907
908
|
|
|
908
|
-
await this.onHit?.();
|
|
909
909
|
return { status: "hit", result: { segments, shouldRevalidate } };
|
|
910
910
|
} catch (error) {
|
|
911
911
|
// Covers a store.get() failure AND a throwing consumer key()/keyGenerator
|
|
@@ -1105,15 +1105,25 @@ export class CacheScope {
|
|
|
1105
1105
|
// the tree) or a rejected handle value as an error row and completes
|
|
1106
1106
|
// normally; stored, every HIT would render that error until expiry.
|
|
1107
1107
|
if (flightErrors.length > 0) throw flightErrors[0];
|
|
1108
|
+
// A streamed handler recovered into its boundary fallback resolves, so
|
|
1109
|
+
// Flight reports nothing; refuse like a Flight error row.
|
|
1110
|
+
const recovered = requestCtx._recoveredHandlerErrors;
|
|
1111
|
+
if (recovered?.size) {
|
|
1112
|
+
const failed = nonLoaderSegments.find((s) => recovered.has(s.id));
|
|
1113
|
+
if (failed) throw recovered.get(failed.id);
|
|
1114
|
+
}
|
|
1108
1115
|
|
|
1116
|
+
const recordTags = collectRecordTags(requestCtx, tags, [
|
|
1117
|
+
...nonLoaderSegments.map((s) => s.id),
|
|
1118
|
+
handlesOwnerId,
|
|
1119
|
+
]);
|
|
1109
1120
|
const data: CachedEntryData = {
|
|
1110
1121
|
segments: serializedSegments,
|
|
1111
1122
|
handles: encodedHandles,
|
|
1112
1123
|
expiresAt: Date.now() + ttl * 1000,
|
|
1113
|
-
tags:
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
]),
|
|
1124
|
+
tags: recordTags,
|
|
1125
|
+
// The stamp is the render's start, not the write's (#1068).
|
|
1126
|
+
...(start && recordTags?.length ? { taggedAt: start.at } : {}),
|
|
1117
1127
|
};
|
|
1118
1128
|
if (handleOwners && encodedHandles) data.handleOwners = handleOwners;
|
|
1119
1129
|
|
|
@@ -1127,6 +1137,8 @@ export class CacheScope {
|
|
|
1127
1137
|
}
|
|
1128
1138
|
|
|
1129
1139
|
await store.set(key, data, ttl, swr);
|
|
1140
|
+
// The implicit doc scope's record lives in the shell entry only.
|
|
1141
|
+
if (!this.isShellImplicitDocScope) noteWarmWrite(requestCtx, "record");
|
|
1130
1142
|
|
|
1131
1143
|
if (INTERNAL_RANGO_DEBUG) {
|
|
1132
1144
|
const segmentTypes = nonLoaderSegments.map((s) =>
|
|
@@ -1175,7 +1187,7 @@ function collectRecordTags(
|
|
|
1175
1187
|
* enclosing scopes on another store (#974) and of a scope without its own
|
|
1176
1188
|
* `key()` (#970)); with none, the app store's keyGenerator result
|
|
1177
1189
|
* given the document default key. Undefined (synchronously, with no work)
|
|
1178
|
-
* when neither applies: the shell key stays
|
|
1190
|
+
* when neither applies: the shell key stays unpartitioned (buildShellKey).
|
|
1179
1191
|
* Resolves to null when the partition is the default key itself (a
|
|
1180
1192
|
* keyGenerator that returns it unchanged): that partitions nothing, so the
|
|
1181
1193
|
* shell stays unpartitioned and keeps its build shell. The shell's key, its
|