@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/shell-capture.ts
CHANGED
|
@@ -41,7 +41,9 @@ import {
|
|
|
41
41
|
SHELL_CAPTURE_TASK_HARD_CAP_MS,
|
|
42
42
|
resetShellWarningsForTests,
|
|
43
43
|
warnOnce,
|
|
44
|
+
type ShellCaptureRefusal,
|
|
44
45
|
} from "./shell-capture-constants.js";
|
|
46
|
+
import { noteWarmWrite } from "../prerender/warm-request.js";
|
|
45
47
|
import { INTERNAL_RANGO_DEBUG } from "../internal-debug.js";
|
|
46
48
|
import { observePhase, PHASES } from "../router/instrument.js";
|
|
47
49
|
import type { TraceSpan } from "../router/tracing.js";
|
|
@@ -75,6 +77,7 @@ import type {
|
|
|
75
77
|
CachedEntryData,
|
|
76
78
|
ShellCacheEntry,
|
|
77
79
|
SegmentCacheStore,
|
|
80
|
+
ShellSnapshotHandlesValue,
|
|
78
81
|
ShellSnapshotRecord,
|
|
79
82
|
} from "../cache/types.js";
|
|
80
83
|
import {
|
|
@@ -83,6 +86,7 @@ import {
|
|
|
83
86
|
} from "../router/segment-resolution/loader-snapshot.js";
|
|
84
87
|
import {
|
|
85
88
|
RecordingShellStore,
|
|
89
|
+
SHELL_HANDLES_RECORD_KEY,
|
|
86
90
|
SnapshotOnlySegmentStore,
|
|
87
91
|
countSnapshotFamilies,
|
|
88
92
|
estimateShellEntryBytes,
|
|
@@ -90,10 +94,17 @@ import {
|
|
|
90
94
|
hasDocRecord,
|
|
91
95
|
pruneShellSnapshot,
|
|
92
96
|
} from "../cache/shell-snapshot.js";
|
|
97
|
+
import {
|
|
98
|
+
captureOwnedHandles,
|
|
99
|
+
encodeHandles,
|
|
100
|
+
} from "../cache/handle-snapshot.js";
|
|
93
101
|
import type { HandlerContext } from "./handler-context.js";
|
|
94
102
|
import type { SSRModule } from "./types.js";
|
|
95
103
|
import { buildFullPayload, payloadInitialTheme } from "./full-payload.js";
|
|
96
|
-
import {
|
|
104
|
+
import {
|
|
105
|
+
recordedHandleData,
|
|
106
|
+
resolveDeferredHandleValues,
|
|
107
|
+
} from "../handles/deferred-resolution.js";
|
|
97
108
|
import { renderRscFlightStage } from "./render-pipeline.js";
|
|
98
109
|
import { stripInternalParams } from "../router/handler-context.js";
|
|
99
110
|
|
|
@@ -482,6 +493,20 @@ function refuseOnCaptureGuard(
|
|
|
482
493
|
return true;
|
|
483
494
|
}
|
|
484
495
|
|
|
496
|
+
/** The refusal a tripped capture guard stands for (refuseOnCaptureGuard). */
|
|
497
|
+
function guardRefusal(derivedCtx: RequestContext<any>): ShellCaptureRefusal {
|
|
498
|
+
return derivedCtx._dynamic ? "dynamic" : "identity";
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/** A refused attempt; the reason rides its event (ShellCaptureDebugEvent.refusal). */
|
|
502
|
+
function refuse(
|
|
503
|
+
stats: Pick<ShellCaptureDebugEvent, "refusal">,
|
|
504
|
+
refusal: ShellCaptureRefusal,
|
|
505
|
+
): "refused" {
|
|
506
|
+
stats.refusal = refusal;
|
|
507
|
+
return "refused";
|
|
508
|
+
}
|
|
509
|
+
|
|
485
510
|
/** The no-shell cause for handler output that missed the capture deadline. */
|
|
486
511
|
const HANDLER_OUTPUT_TIMEOUT_REASON: string =
|
|
487
512
|
"the handler output (a promise it passes or pushes, or an async server " +
|
|
@@ -658,6 +683,8 @@ export interface ShellCaptureDebugEvent {
|
|
|
658
683
|
untaggedBake?: true;
|
|
659
684
|
/** Outcome reported by a store that supports shell-write acknowledgements. */
|
|
660
685
|
storeWrite?: "stored" | "invalidated" | "uncacheable";
|
|
686
|
+
/** Why a `refused` attempt refused. */
|
|
687
|
+
refusal?: ShellCaptureRefusal;
|
|
661
688
|
/** Consecutive failure count in the key's backoff entry, when one exists. */
|
|
662
689
|
backoffFailures?: number;
|
|
663
690
|
/** Ms remaining in the key's backoff window, when one exists. */
|
|
@@ -719,6 +746,7 @@ export function describeShellCaptureEvent(
|
|
|
719
746
|
if (event.storeWrite !== undefined) {
|
|
720
747
|
parts.push(`store-write=${event.storeWrite}`);
|
|
721
748
|
}
|
|
749
|
+
if (event.refusal !== undefined) parts.push(`refusal=${event.refusal}`);
|
|
722
750
|
if (event.backoffFailures !== undefined) {
|
|
723
751
|
parts.push(`backoff-failures=${event.backoffFailures}`);
|
|
724
752
|
}
|
|
@@ -1042,6 +1070,15 @@ export interface ShellCaptureDescriptor {
|
|
|
1042
1070
|
* re-render the page and drop the isolate's memo of the shell just stored.
|
|
1043
1071
|
*/
|
|
1044
1072
|
storedSeqAtRead?: number;
|
|
1073
|
+
/**
|
|
1074
|
+
* A `router.prerender()` warm's capture (rsc-rendering.ts shellServePlan):
|
|
1075
|
+
* one explicit call that must store, so the guards that shed a herd of
|
|
1076
|
+
* visitors do not apply to it. scheduleShellCapture skips the in-flight
|
|
1077
|
+
* dedup and the refused-capture backoff, and attemptCapture waits longer on
|
|
1078
|
+
* the write barrier. Every correctness guard, the inert-store skip and the
|
|
1079
|
+
* queue limits still decide.
|
|
1080
|
+
*/
|
|
1081
|
+
force?: true;
|
|
1045
1082
|
}
|
|
1046
1083
|
|
|
1047
1084
|
/** Per key, the sequence number of its latest stored capture (bounded). */
|
|
@@ -1086,7 +1123,10 @@ export function scheduleShellCapture(
|
|
|
1086
1123
|
descriptor: ShellCaptureDescriptor,
|
|
1087
1124
|
): void {
|
|
1088
1125
|
const key = descriptor.key;
|
|
1089
|
-
|
|
1126
|
+
// A forced capture installs its own token below: a visitor's capture that
|
|
1127
|
+
// started before the warm may have read data from before the change the
|
|
1128
|
+
// warm publishes, and the queue runs it first, so the warm stores last.
|
|
1129
|
+
const inFlight = descriptor.force ? undefined : inFlightCaptures.get(key);
|
|
1090
1130
|
if (inFlight) {
|
|
1091
1131
|
if (Date.now() - inFlight.startedAt <= SHELL_CAPTURE_TASK_HARD_CAP_MS) {
|
|
1092
1132
|
publishCaptureDebugEvent(descriptor, { key, outcome: "skip-in-flight" });
|
|
@@ -1107,7 +1147,7 @@ export function scheduleShellCapture(
|
|
|
1107
1147
|
}
|
|
1108
1148
|
// Refused/failed within the window → skip the doomed re-render (one probe per
|
|
1109
1149
|
// key per window per isolate). Expired entries self-evict inside the check.
|
|
1110
|
-
if (isCaptureBackedOff(key)) {
|
|
1150
|
+
if (!descriptor.force && isCaptureBackedOff(key)) {
|
|
1111
1151
|
publishCaptureDebugEvent(descriptor, {
|
|
1112
1152
|
key,
|
|
1113
1153
|
outcome: "skip-backoff",
|
|
@@ -1436,6 +1476,7 @@ type CaptureAttemptStats = Pick<
|
|
|
1436
1476
|
| "snapshotSkipped"
|
|
1437
1477
|
| "untaggedBake"
|
|
1438
1478
|
| "storeWrite"
|
|
1479
|
+
| "refusal"
|
|
1439
1480
|
| "recordSettleMs"
|
|
1440
1481
|
| "entryBytes"
|
|
1441
1482
|
> & {
|
|
@@ -1597,7 +1638,7 @@ async function runShellCapture(
|
|
|
1597
1638
|
export type CaptureRecordOutcome =
|
|
1598
1639
|
| { kind: "record" | "prerender"; match: MatchResult }
|
|
1599
1640
|
| { kind: "timeout"; reason: string }
|
|
1600
|
-
| { kind: "refused" };
|
|
1641
|
+
| { kind: "refused"; refusal: ShellCaptureRefusal };
|
|
1601
1642
|
|
|
1602
1643
|
/**
|
|
1603
1644
|
* Record-first capture: the doc record is both the settle signal and the
|
|
@@ -1643,7 +1684,13 @@ export async function settleCaptureRecord(
|
|
|
1643
1684
|
const loaderRecords = derivedCtx._shellCaptureLoaderRecords;
|
|
1644
1685
|
const settled = await raceDeadline(
|
|
1645
1686
|
Promise.all([
|
|
1646
|
-
freshHandleStore
|
|
1687
|
+
freshHandleStore
|
|
1688
|
+
.getData()
|
|
1689
|
+
.then((data) =>
|
|
1690
|
+
resolveDeferredHandleValues(
|
|
1691
|
+
recordedHandleData(freshHandleStore, data),
|
|
1692
|
+
),
|
|
1693
|
+
),
|
|
1647
1694
|
derivation.handlerPushesSettled(),
|
|
1648
1695
|
loaderRecords && loaderRecords.size > 0
|
|
1649
1696
|
? Promise.allSettled([...loaderRecords.values()])
|
|
@@ -1655,7 +1702,7 @@ export async function settleCaptureRecord(
|
|
|
1655
1702
|
// write starts (no encode for a capture that already failed).
|
|
1656
1703
|
if (!settled.done) {
|
|
1657
1704
|
if (refuseOnCaptureGuard(capture.key, derivedCtx))
|
|
1658
|
-
return { kind: "refused" };
|
|
1705
|
+
return { kind: "refused", refusal: guardRefusal(derivedCtx) };
|
|
1659
1706
|
return { kind: "timeout", reason: HANDLER_OUTPUT_TIMEOUT_REASON };
|
|
1660
1707
|
}
|
|
1661
1708
|
|
|
@@ -1676,7 +1723,9 @@ export async function settleCaptureRecord(
|
|
|
1676
1723
|
|
|
1677
1724
|
// Capture guards first: a tripped guard usually also fails the record,
|
|
1678
1725
|
// and its own message names the cause.
|
|
1679
|
-
if (refuseOnCaptureGuard(capture.key, derivedCtx))
|
|
1726
|
+
if (refuseOnCaptureGuard(capture.key, derivedCtx)) {
|
|
1727
|
+
return { kind: "refused", refusal: guardRefusal(derivedCtx) };
|
|
1728
|
+
}
|
|
1680
1729
|
|
|
1681
1730
|
const docKey = derivedCtx._shellImplicitCache?.docKey;
|
|
1682
1731
|
const record =
|
|
@@ -1694,7 +1743,7 @@ export async function settleCaptureRecord(
|
|
|
1694
1743
|
return { kind: "timeout", reason: HANDLER_OUTPUT_TIMEOUT_REASON };
|
|
1695
1744
|
}
|
|
1696
1745
|
warnCaptureRefusedOnce(capture.key, NO_DOC_RECORD_REASON);
|
|
1697
|
-
return { kind: "refused" };
|
|
1746
|
+
return { kind: "refused", refusal: "no-record" };
|
|
1698
1747
|
}
|
|
1699
1748
|
|
|
1700
1749
|
// A handle encode that timed out stores "" (handle-snapshot.ts
|
|
@@ -1708,7 +1757,7 @@ export async function settleCaptureRecord(
|
|
|
1708
1757
|
"record. A promise inside a Map, Set, or class instance is not awaited " +
|
|
1709
1758
|
"before the encode; push plain objects and arrays.",
|
|
1710
1759
|
);
|
|
1711
|
-
return { kind: "refused" };
|
|
1760
|
+
return { kind: "refused", refusal: "handles" };
|
|
1712
1761
|
}
|
|
1713
1762
|
|
|
1714
1763
|
const { fragmentSegments } = await import("../cache/segment-codec.js");
|
|
@@ -1805,8 +1854,21 @@ async function attemptCapture(
|
|
|
1805
1854
|
// skipped, cache-store middleware's write path gated off by state.cacheHit), so
|
|
1806
1855
|
// prelude, snapshot, and ring-3 agree on the foreground's generation. Runs per
|
|
1807
1856
|
// attempt (the retry re-checks; already-settled promises are free).
|
|
1857
|
+
//
|
|
1858
|
+
// A forced capture (a warm) is not latency-bound and must not bake an entry
|
|
1859
|
+
// its own foreground is still replacing, so it waits for as long as the task
|
|
1860
|
+
// hard cap leaves after the capture budget.
|
|
1808
1861
|
const barrierStart = performance.now();
|
|
1809
|
-
await settleTrackedBackgroundTasks(
|
|
1862
|
+
await settleTrackedBackgroundTasks(
|
|
1863
|
+
reqCtx,
|
|
1864
|
+
descriptor.force
|
|
1865
|
+
? Math.max(
|
|
1866
|
+
SHELL_CAPTURE_WRITE_BARRIER_MS,
|
|
1867
|
+
SHELL_CAPTURE_TASK_HARD_CAP_MS -
|
|
1868
|
+
(descriptor.captureTimeout ?? SHELL_CAPTURE_MAX_WAIT_MS),
|
|
1869
|
+
)
|
|
1870
|
+
: SHELL_CAPTURE_WRITE_BARRIER_MS,
|
|
1871
|
+
);
|
|
1810
1872
|
stats.barrierWaitMs = Math.round(performance.now() - barrierStart);
|
|
1811
1873
|
|
|
1812
1874
|
const derivation = deriveShellCaptureContext(
|
|
@@ -1863,7 +1925,7 @@ async function attemptCapture(
|
|
|
1863
1925
|
stats.recordSettleMs,
|
|
1864
1926
|
);
|
|
1865
1927
|
}
|
|
1866
|
-
if (settled.kind === "refused") return
|
|
1928
|
+
if (settled.kind === "refused") return refuse(stats, settled.refusal);
|
|
1867
1929
|
if (settled.kind === "timeout") {
|
|
1868
1930
|
stats.noShellCause = settled.reason;
|
|
1869
1931
|
return "no-shell";
|
|
@@ -1961,8 +2023,8 @@ export function deriveShellCaptureContext(
|
|
|
1961
2023
|
// (settleCaptureRecord) can wait for its promises, top-level and nested,
|
|
1962
2024
|
// before the doc record encodes the handles a HIT replays. A route
|
|
1963
2025
|
// cache() record's restore (handle-snapshot.ts restoreHandles) lands
|
|
1964
|
-
// here too: a value it restores
|
|
1965
|
-
//
|
|
2026
|
+
// here too: a value it restores as a placeholder keeps that loader as
|
|
2027
|
+
// owner, so a HIT restores it as that loader's (pushRestored).
|
|
1966
2028
|
// - DSL-LOADER pushes. Only bake-lane { ssr: false } loaders and the
|
|
1967
2029
|
// loaders they await via ctx.use execute at capture (live loaders are
|
|
1968
2030
|
// masked), their pushes are in the captured HTML (<head> title/meta,
|
|
@@ -1970,28 +2032,33 @@ export function deriveShellCaptureContext(
|
|
|
1970
2032
|
// loader (loader-cache.ts), so the stored record carries them: each
|
|
1971
2033
|
// settled, thenable-free push from a loader body keeps that loader as
|
|
1972
2034
|
// `owner` (HandleStore.push), the record stores it
|
|
1973
|
-
// (CachedEntryData.handleOwners), and
|
|
1974
|
-
//
|
|
1975
|
-
//
|
|
1976
|
-
//
|
|
1977
|
-
//
|
|
1978
|
-
//
|
|
2035
|
+
// (CachedEntryData.handleOwners), and a replay that serves the loader
|
|
2036
|
+
// from its pin restores it through pushRestored (restoreHandles). Those
|
|
2037
|
+
// copies stand: a promise-carrying bake-lane loader that runs on the
|
|
2038
|
+
// replay reads the store, so its settled pushes (and those replayed
|
|
2039
|
+
// inside its body) are dropped and only its thenable ones are added. A
|
|
2040
|
+
// loader the replay does not serve from a pin is a hole: one the route
|
|
2041
|
+
// also runs on the live lane, or one whose pin the entry lost. Its run
|
|
2042
|
+
// replaces its copies (pushPlaceholder, #936), those of the
|
|
1979
2043
|
// dependencies it awaits included (they are credited to it). Nested
|
|
1980
2044
|
// thenables stay masked (the promise shape is the liveness
|
|
1981
2045
|
// declaration, mask-nested.ts). A deferred (thenable) push,
|
|
1982
2046
|
// one with masked nested promises, or one outside a loader body (a
|
|
1983
2047
|
// loader-cache replay names its loader: pushReplayed below) stays
|
|
1984
2048
|
// tagged (HandleStore.push loaderPush) so the
|
|
1985
|
-
// handle encode cannot stall on a never-resolving mask
|
|
1986
|
-
//
|
|
1987
|
-
//
|
|
1988
|
-
//
|
|
2049
|
+
// handle encode cannot stall on a never-resolving mask. A tagged push
|
|
2050
|
+
// is out of the record (captureHandles) and out of the capture's own
|
|
2051
|
+
// render (resolvedHandleStream `recordedOnly`, issue #1035): the
|
|
2052
|
+
// prelude is rendered from what the record keeps, which is what a HIT
|
|
2053
|
+
// hydrates with. The loader records then carry `runs: 1` so a HIT runs
|
|
2054
|
+
// those bodies for them, and what they push reaches the client after
|
|
2055
|
+
// hydration (HandleStore.freezeDocumentSnapshot).
|
|
1989
2056
|
const handlerPushSettles: Promise<void>[] = [];
|
|
1990
2057
|
// A replay (a loader-cache HIT, loader-cache.ts replayLoaderHandles, or a
|
|
1991
2058
|
// route cache() record's owned values, restoreHandles) re-pushes a loader's
|
|
1992
|
-
// recorded values through pushReplayed, which
|
|
1993
|
-
// loader body: name that loader for the funnel so the
|
|
1994
|
-
// under it too.
|
|
2059
|
+
// recorded values through pushReplayed or pushPlaceholder, which call
|
|
2060
|
+
// push() outside any loader body: name that loader for the funnel so the
|
|
2061
|
+
// record keeps them under it too.
|
|
1995
2062
|
let replayOwner: string | undefined;
|
|
1996
2063
|
// The live-lane loader whose body a push is made in, at any depth, unless
|
|
1997
2064
|
// a bake-lane loader's body is nearer (createMatchContextForFull sets the
|
|
@@ -2011,15 +2078,17 @@ export function deriveShellCaptureContext(
|
|
|
2011
2078
|
: undefined)
|
|
2012
2079
|
);
|
|
2013
2080
|
};
|
|
2014
|
-
const
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2081
|
+
for (const copy of ["pushReplayed", "pushPlaceholder"] as const) {
|
|
2082
|
+
const raw = freshHandleStore[copy].bind(freshHandleStore);
|
|
2083
|
+
freshHandleStore[copy] = (handleName, segmentId, value, loaderId) => {
|
|
2084
|
+
replayOwner = loaderId;
|
|
2085
|
+
try {
|
|
2086
|
+
raw(handleName, segmentId, value, loaderId);
|
|
2087
|
+
} finally {
|
|
2088
|
+
replayOwner = undefined;
|
|
2089
|
+
}
|
|
2090
|
+
};
|
|
2091
|
+
}
|
|
2023
2092
|
const rawCapturePush = freshHandleStore.push.bind(freshHandleStore);
|
|
2024
2093
|
freshHandleStore.push = (
|
|
2025
2094
|
handleName: string,
|
|
@@ -2043,8 +2112,11 @@ export function deriveShellCaptureContext(
|
|
|
2043
2112
|
let owner: string | undefined;
|
|
2044
2113
|
if (isThenable(value)) {
|
|
2045
2114
|
// Deferred (thenable) loader pushes always keep the tag — the
|
|
2046
|
-
// carve-out below is for settled values only.
|
|
2047
|
-
|
|
2115
|
+
// carve-out below is for settled values only. Promise.resolve first: a
|
|
2116
|
+
// loader cache() entry replays a deferred push as the thenable Flight
|
|
2117
|
+
// decoded it to, whose then() returns nothing, and the slot was
|
|
2118
|
+
// `undefined` (the empty element of issue #1035).
|
|
2119
|
+
masked = Promise.resolve(value).then(mask);
|
|
2048
2120
|
} else {
|
|
2049
2121
|
masked = mask(value);
|
|
2050
2122
|
if (!maskedNestedThenable) {
|
|
@@ -2112,6 +2184,7 @@ export function deriveShellCaptureContext(
|
|
|
2112
2184
|
// Own list: the capture's render errors refuse the capture only, and the
|
|
2113
2185
|
// foreground's never reach it.
|
|
2114
2186
|
derivedCtx._renderErrors = [];
|
|
2187
|
+
derivedCtx._recoveredHandlerErrors = undefined;
|
|
2115
2188
|
derivedCtx._shellCaptureRun = true;
|
|
2116
2189
|
// Own dynamic() latch. The inherited method writes the FOREGROUND context
|
|
2117
2190
|
// (its closure), which already served, and nothing read it for a runtime
|
|
@@ -2230,7 +2303,9 @@ async function captureAndStoreShell(
|
|
|
2230
2303
|
// (boundary-less segment — lands in the catch) or is swallowed into
|
|
2231
2304
|
// per-loader error UI (the render completes — caught after the try).
|
|
2232
2305
|
const refuseOnGuardTrip = (): "refused" | undefined =>
|
|
2233
|
-
refuseOnCaptureGuard(capture.key, reqCtx)
|
|
2306
|
+
refuseOnCaptureGuard(capture.key, reqCtx)
|
|
2307
|
+
? refuse(stats, guardRefusal(reqCtx))
|
|
2308
|
+
: undefined;
|
|
2234
2309
|
|
|
2235
2310
|
// Dev diagnostics for a no-shell attempt (warnNullCaptureOnce): the stacks of
|
|
2236
2311
|
// the tasks React reports as still pending at the capture's abort. React's
|
|
@@ -2346,7 +2421,7 @@ async function captureAndStoreShell(
|
|
|
2346
2421
|
"Fix the loader, or drop its ssr: false to move it to the live lane.",
|
|
2347
2422
|
PPR_LANE_HINT,
|
|
2348
2423
|
);
|
|
2349
|
-
return "
|
|
2424
|
+
return refuse(stats, "loader");
|
|
2350
2425
|
}
|
|
2351
2426
|
// The container itself never settled: it is a hole (under an ancestor
|
|
2352
2427
|
// boundary) or the trivial-prelude gate already fired. Omit — no pin.
|
|
@@ -2363,7 +2438,7 @@ async function captureAndStoreShell(
|
|
|
2363
2438
|
"Drop its ssr: false to move it to the live lane, or move the decision into middleware.",
|
|
2364
2439
|
PPR_LANE_HINT,
|
|
2365
2440
|
);
|
|
2366
|
-
return "
|
|
2441
|
+
return refuse(stats, "loader");
|
|
2367
2442
|
}
|
|
2368
2443
|
// Past the hole check: this container settled with real material that
|
|
2369
2444
|
// bakes into the shell prelude (independent of the snapshot pin below).
|
|
@@ -2403,6 +2478,21 @@ async function captureAndStoreShell(
|
|
|
2403
2478
|
}
|
|
2404
2479
|
}
|
|
2405
2480
|
|
|
2481
|
+
// Built before the render-error check below so its encode errors refuse
|
|
2482
|
+
// the capture (ShellSnapshotHandlesValue).
|
|
2483
|
+
const docKey = reqCtx._shellImplicitCache?.docKey;
|
|
2484
|
+
let prerenderHandles: ShellSnapshotHandlesValue | undefined;
|
|
2485
|
+
if (!capture.navigationOnly && !hasDocRecord(snapshot, docKey)) {
|
|
2486
|
+
// The store is sealed (settleCaptureRecord) and settled (quiesce).
|
|
2487
|
+
const { handles, owners } = captureOwnedHandles(reqCtx._handleStore);
|
|
2488
|
+
prerenderHandles = {
|
|
2489
|
+
handles: await encodeHandles(handles, (error) => {
|
|
2490
|
+
reqCtx._renderErrors?.push(error);
|
|
2491
|
+
}),
|
|
2492
|
+
handleOwners: owners,
|
|
2493
|
+
};
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2406
2496
|
// A shell component that threw inside a Suspense boundary does not reject
|
|
2407
2497
|
// the capture: Flight and Fizz report it through onError and the prelude
|
|
2408
2498
|
// carries the errored boundary, which every HIT would serve (issue #915).
|
|
@@ -2416,7 +2506,6 @@ async function captureAndStoreShell(
|
|
|
2416
2506
|
// Store only what a HIT reads (issue #941): the doc record and, for a
|
|
2417
2507
|
// document entry, the bake-lane loader pins. Before the size guards, so
|
|
2418
2508
|
// they measure what is stored.
|
|
2419
|
-
const docKey = reqCtx._shellImplicitCache?.docKey;
|
|
2420
2509
|
let prunedRecords: string | undefined;
|
|
2421
2510
|
const { kept, pruned } = pruneShellSnapshot(
|
|
2422
2511
|
snapshot,
|
|
@@ -2454,6 +2543,15 @@ async function captureAndStoreShell(
|
|
|
2454
2543
|
stats.snapshotSkipped = true;
|
|
2455
2544
|
}
|
|
2456
2545
|
}
|
|
2546
|
+
// Exempt from maxSnapshotBytes: the HIT needs it for its HTML and
|
|
2547
|
+
// hydration data to match.
|
|
2548
|
+
if (prerenderHandles) {
|
|
2549
|
+
snapshot.push({
|
|
2550
|
+
family: "handles",
|
|
2551
|
+
key: SHELL_HANDLES_RECORD_KEY,
|
|
2552
|
+
value: prerenderHandles,
|
|
2553
|
+
});
|
|
2554
|
+
}
|
|
2457
2555
|
|
|
2458
2556
|
// settleCaptureRecord refused every capture without a doc record except a
|
|
2459
2557
|
// prerender-served one (the prerender store supplies its handler layer on
|
|
@@ -2515,7 +2613,7 @@ async function captureAndStoreShell(
|
|
|
2515
2613
|
"Shrink the page the shell bakes, or move large regions under a live " +
|
|
2516
2614
|
"loader's boundary.",
|
|
2517
2615
|
);
|
|
2518
|
-
return "
|
|
2616
|
+
return refuse(stats, "size");
|
|
2519
2617
|
}
|
|
2520
2618
|
|
|
2521
2619
|
// Missing tags are valid: the shell follows TTL/SWR-only invalidation. Expose
|
|
@@ -2587,7 +2685,7 @@ async function captureAndStoreShell(
|
|
|
2587
2685
|
record,
|
|
2588
2686
|
`${sinceWriteMs} ms after this capture wrote it`,
|
|
2589
2687
|
);
|
|
2590
|
-
return "
|
|
2688
|
+
return refuse(stats, "record-expired");
|
|
2591
2689
|
}
|
|
2592
2690
|
stats.expired = {
|
|
2593
2691
|
route,
|
|
@@ -2613,7 +2711,7 @@ async function captureAndStoreShell(
|
|
|
2613
2711
|
"If capture code deterministically calls updateTag() on its own shell tag, move that mutation out of the render; " +
|
|
2614
2712
|
"otherwise every generation is invalidated before it can be served.",
|
|
2615
2713
|
);
|
|
2616
|
-
return "
|
|
2714
|
+
return refuse(stats, "invalidated");
|
|
2617
2715
|
}
|
|
2618
2716
|
if (storeWrite === "uncacheable") {
|
|
2619
2717
|
// The store declared the entry unstorable under its current
|
|
@@ -2628,8 +2726,9 @@ async function captureAndStoreShell(
|
|
|
2628
2726
|
"the write as permanently refusable, so the key is backed off. See the cache store's " +
|
|
2629
2727
|
"own warning for the specific limit and remedy.",
|
|
2630
2728
|
);
|
|
2631
|
-
return "
|
|
2729
|
+
return refuse(stats, "uncacheable");
|
|
2632
2730
|
}
|
|
2731
|
+
noteWarmWrite(reqCtx, "shell");
|
|
2633
2732
|
} catch (error) {
|
|
2634
2733
|
// Best-effort: a failed put must never throw out of the background task.
|
|
2635
2734
|
reportCacheError(
|
package/src/rsc/shell-serve.ts
CHANGED
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
resetShellWarningsForTests,
|
|
36
36
|
warnOnce,
|
|
37
37
|
} from "./shell-capture-constants.js";
|
|
38
|
+
import { routerKeyPrefix } from "../cache/cache-key-utils.js";
|
|
38
39
|
|
|
39
40
|
export {
|
|
40
41
|
PPR_REPLAY_STATUS_HEADER,
|
|
@@ -529,8 +530,9 @@ export function warnPprWindowCappedOnce(
|
|
|
529
530
|
|
|
530
531
|
/**
|
|
531
532
|
* Paths a partitioned request found without a build shell. Build shells are
|
|
532
|
-
* per path (a param route prerenders some of its paths), so a
|
|
533
|
-
* kept per path, not per route; cleared when full, so
|
|
533
|
+
* per router and path (a param route prerenders some of its paths), so a
|
|
534
|
+
* negative is kept per router and path, not per route; cleared when full, so
|
|
535
|
+
* it stays bounded.
|
|
534
536
|
*/
|
|
535
537
|
const pathsWithoutBuildShell = new Set<string>();
|
|
536
538
|
const PATHS_WITHOUT_BUILD_SHELL_MAX = 1_000;
|
|
@@ -540,12 +542,14 @@ const PATHS_WITHOUT_BUILD_SHELL_MAX = 1_000;
|
|
|
540
542
|
* already warned, or its path was already found without one.
|
|
541
543
|
*/
|
|
542
544
|
export function partitionBuildShellCheckDone(
|
|
545
|
+
routerId: string,
|
|
543
546
|
pathname: string,
|
|
544
547
|
routeName: string | undefined,
|
|
545
548
|
): boolean {
|
|
549
|
+
const router = routerKeyPrefix(routerId);
|
|
546
550
|
return (
|
|
547
|
-
hasWarnedOnce("partition-build-shell", routeName ?? pathname) ||
|
|
548
|
-
pathsWithoutBuildShell.has(pathname)
|
|
551
|
+
hasWarnedOnce("partition-build-shell", router + (routeName ?? pathname)) ||
|
|
552
|
+
pathsWithoutBuildShell.has(router + pathname)
|
|
549
553
|
);
|
|
550
554
|
}
|
|
551
555
|
|
|
@@ -560,21 +564,23 @@ export function partitionBuildShellCheckDone(
|
|
|
560
564
|
* with no build shell stays silent.
|
|
561
565
|
*/
|
|
562
566
|
export function notePartitionBuildShellCheck(
|
|
567
|
+
routerId: string,
|
|
563
568
|
pathname: string,
|
|
564
569
|
routeName: string | undefined,
|
|
565
570
|
found: boolean,
|
|
566
571
|
): void {
|
|
572
|
+
const router = routerKeyPrefix(routerId);
|
|
567
573
|
if (!found) {
|
|
568
574
|
if (pathsWithoutBuildShell.size >= PATHS_WITHOUT_BUILD_SHELL_MAX) {
|
|
569
575
|
pathsWithoutBuildShell.clear();
|
|
570
576
|
}
|
|
571
|
-
pathsWithoutBuildShell.add(pathname);
|
|
577
|
+
pathsWithoutBuildShell.add(router + pathname);
|
|
572
578
|
return;
|
|
573
579
|
}
|
|
574
580
|
// Concurrent first requests can both probe; one of them warns.
|
|
575
581
|
warnOnce(
|
|
576
582
|
"partition-build-shell",
|
|
577
|
-
routeName ?? pathname,
|
|
583
|
+
router + (routeName ?? pathname),
|
|
578
584
|
() =>
|
|
579
585
|
`[rango] Route ${routeName ? `"${routeName}" ` : ""}("${pathname}") has a ` +
|
|
580
586
|
"build-time shell, but its request partition (a cache({ key }) enclosing the route, " +
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SSR module loader a handler created without `loadSSRModule` falls back
|
|
3
|
+
* to in tests (`router.fetch` creates its handler that way). A leaf with no
|
|
4
|
+
* runtime imports: testing/serve-shell-request.ts installs its stub here when
|
|
5
|
+
* it loads, without importing rsc/handler.ts (which binds build-only virtual
|
|
6
|
+
* modules a flight-only test config does not alias).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { LoadSSRModule } from "./types.js";
|
|
10
|
+
|
|
11
|
+
let testLoader: LoadSSRModule | undefined;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @internal Tests only: `router.prerender()` warms through `router.fetch`,
|
|
15
|
+
* and the rsc Vitest project cannot run `import.meta.viteRsc.loadModule`.
|
|
16
|
+
*/
|
|
17
|
+
export function setDefaultSSRModuleLoaderForTests(
|
|
18
|
+
loader: LoadSSRModule | undefined,
|
|
19
|
+
): void {
|
|
20
|
+
testLoader = loader;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** @internal Undefined everywhere but in tests. */
|
|
24
|
+
export function defaultSSRModuleLoaderForTests(): LoadSSRModule | undefined {
|
|
25
|
+
return testLoader;
|
|
26
|
+
}
|
package/src/rsc/types.ts
CHANGED
|
@@ -97,6 +97,14 @@ export interface RscPayload {
|
|
|
97
97
|
locationState?: Record<string, unknown>;
|
|
98
98
|
};
|
|
99
99
|
returnValue?: { ok: boolean; data: unknown };
|
|
100
|
+
/**
|
|
101
|
+
* useActionState form state of a progressive-enhancement POST re-render
|
|
102
|
+
* (the value the SSR render received). Present only on that document; the
|
|
103
|
+
* browser entry passes it to hydrateRoot so the hydrated hook matches the
|
|
104
|
+
* server HTML instead of resetting to its initial state. A promise so a
|
|
105
|
+
* state Flight cannot serialize rejects this slot alone.
|
|
106
|
+
*/
|
|
107
|
+
formState?: Promise<ReactFormState>;
|
|
100
108
|
}
|
|
101
109
|
|
|
102
110
|
/**
|
|
@@ -357,13 +365,11 @@ export interface CreateRSCHandlerOptions<
|
|
|
357
365
|
cache?: HandlerCacheConfig | ((env: TEnv) => HandlerCacheConfig);
|
|
358
366
|
|
|
359
367
|
/**
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
* Defaults to the auto-generated VERSION from `rsc-router:version` virtual module.
|
|
364
|
-
* Only set this if you need a custom versioning strategy.
|
|
368
|
+
* Version override for this handler. Same meaning as
|
|
369
|
+
* `createRouter({ version })`, which it takes precedence over: this exact
|
|
370
|
+
* value is used for both of the router's versions.
|
|
365
371
|
*
|
|
366
|
-
* @default
|
|
372
|
+
* @default createRouter({ version }), else the router's build versions
|
|
367
373
|
*/
|
|
368
374
|
version?: string;
|
|
369
375
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
const IS_BROWSER = typeof window !== "undefined";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* What a RouteContentWrapper receives as `content`.
|
|
7
|
+
*
|
|
8
|
+
* Browser: hand over the settled value. React treats a promise as fulfilled
|
|
9
|
+
* only after it has read it, and a boundary on screen handed a promise it has
|
|
10
|
+
* not read, in a render that cannot wait, shows its fallback. A component that
|
|
11
|
+
* is already a promise (a Flight-streamed segment) is returned as is, since
|
|
12
|
+
* it can really be pending.
|
|
13
|
+
*
|
|
14
|
+
* Server: a fresh `Promise.resolve(component)` per call, so Suspense emits the
|
|
15
|
+
* fallback in the streamed HTML. A shared resolved promise would carry React's
|
|
16
|
+
* `.status` across requests and skip it.
|
|
17
|
+
*
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
export function getBoundaryContent(component: ReactNode): ReactNode {
|
|
21
|
+
if (component instanceof Promise || IS_BROWSER) {
|
|
22
|
+
return component;
|
|
23
|
+
}
|
|
24
|
+
return Promise.resolve(component);
|
|
25
|
+
}
|