@rangojs/router 0.24.4 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/bin/rango.js +363 -39
- package/dist/testing/vitest.js +37 -4
- package/dist/types/browser/event-controller.d.ts +24 -5
- package/dist/types/browser/intercept-utils.d.ts +5 -2
- package/dist/types/browser/merge-segment-loaders.d.ts +0 -23
- package/dist/types/browser/navigation-transaction.d.ts +5 -5
- package/dist/types/browser/partial-update.d.ts +21 -13
- package/dist/types/browser/prefetch/cache.d.ts +8 -0
- package/dist/types/browser/rango-marker.d.ts +7 -0
- package/dist/types/browser/react/filter-segment-order.d.ts +10 -0
- package/dist/types/browser/react/use-handle.d.ts +20 -0
- package/dist/types/browser/react/use-href.d.ts +6 -0
- package/dist/types/browser/react/use-router.d.ts +6 -1
- package/dist/types/browser/response-adapter.d.ts +5 -3
- package/dist/types/browser/scroll-restoration.d.ts +9 -3
- package/dist/types/browser/segment-reconciler.d.ts +10 -13
- package/dist/types/browser/types.d.ts +38 -8
- package/dist/types/build/route-types/per-module-writer.d.ts +12 -0
- package/dist/types/cache/cf/cf-cache-store.d.ts +0 -2
- package/dist/types/cache/cf/cf-cache-types.d.ts +0 -16
- package/dist/types/cache/cf/cf-shell-frame.d.ts +2 -0
- package/dist/types/cache/handle-snapshot.d.ts +2 -2
- package/dist/types/cache/types.d.ts +16 -6
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +1 -15
- package/dist/types/client-urls/client-urls.d.ts +6 -2
- package/dist/types/client-urls/revalidate-registry.d.ts +1 -1
- package/dist/types/client-urls/server-projection.d.ts +9 -5
- package/dist/types/client-urls/types.d.ts +29 -16
- package/dist/types/client.d.ts +2 -2
- package/dist/types/client.rsc.d.ts +2 -2
- package/dist/types/decode-loader-results.d.ts +28 -7
- package/dist/types/handles/deferred-resolution.d.ts +3 -2
- package/dist/types/handles/meta.d.ts +5 -0
- package/dist/types/href-client.d.ts +3 -1
- package/dist/types/index.rsc.d.ts +1 -1
- package/dist/types/loader-redirect.d.ts +2 -3
- package/dist/types/outlet-context.d.ts +7 -6
- package/dist/types/prefetch-markers.d.ts +14 -5
- package/dist/types/prerender/build-shell-capture.d.ts +1 -2
- package/dist/types/prerender/writable-store.d.ts +2 -3
- package/dist/types/route-content-wrapper.d.ts +17 -14
- package/dist/types/route-definition/helpers-types.d.ts +7 -2
- package/dist/types/route-definition/resolve-handler-use.d.ts +6 -1
- package/dist/types/route-definition/rewrite.d.ts +48 -5
- package/dist/types/route-types.d.ts +2 -2
- package/dist/types/router/handler-context.d.ts +14 -2
- package/dist/types/router/intercept-resolution.d.ts +19 -46
- package/dist/types/router/lazy-includes.d.ts +7 -0
- package/dist/types/router/match-middleware/intercept-resolution.d.ts +2 -2
- package/dist/types/router/middleware.d.ts +0 -16
- package/dist/types/router/navigation-snapshot.d.ts +2 -0
- package/dist/types/router/prefetch-cache-ttl.d.ts +8 -11
- package/dist/types/router/revalidation.d.ts +8 -1
- package/dist/types/router/router-context.d.ts +4 -16
- package/dist/types/router/router-interfaces.d.ts +0 -11
- package/dist/types/router/router-options.d.ts +4 -27
- package/dist/types/router/segment-resolution/mask-nested.d.ts +5 -3
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +31 -8
- package/dist/types/router/segment-resolution/revalidation.d.ts +38 -4
- package/dist/types/router/segment-resolution.d.ts +1 -1
- package/dist/types/router/segment-wrappers.d.ts +3 -5
- package/dist/types/router/substitute-pattern-params.d.ts +3 -2
- package/dist/types/router/tracing.d.ts +2 -2
- package/dist/types/router-versions.d.ts +8 -6
- package/dist/types/rsc/full-payload.d.ts +14 -2
- package/dist/types/rsc/helpers.d.ts +8 -0
- package/dist/types/rsc/manifest-init.d.ts +20 -0
- package/dist/types/rsc/prefetch-render-error.d.ts +22 -0
- package/dist/types/rsc/progressive-enhancement.d.ts +20 -0
- package/dist/types/rsc/rewrite-passes.d.ts +12 -2
- package/dist/types/rsc/server-action.d.ts +6 -0
- package/dist/types/rsc/shell-capture-constants.d.ts +14 -4
- package/dist/types/rsc/shell-capture.d.ts +23 -2
- package/dist/types/rsc/types.d.ts +5 -8
- package/dist/types/segment-system.d.ts +15 -0
- package/dist/types/server/build-version-table.d.ts +10 -13
- package/dist/types/server/context.d.ts +90 -6
- package/dist/types/server/handle-store.d.ts +17 -6
- package/dist/types/server/request-context.d.ts +45 -7
- package/dist/types/server/request-headers.d.ts +12 -0
- package/dist/types/server/rewrite-trail.d.ts +8 -1
- package/dist/types/testing/build-versions.d.ts +0 -3
- package/dist/types/testing/e2e/index.d.ts +1 -1
- package/dist/types/testing/flight.entry.d.ts +1 -1
- package/dist/types/testing/index.d.ts +1 -1
- package/dist/types/testing/internal/client-urls-projection.d.ts +4 -3
- package/dist/types/testing/internal/seed-vars.d.ts +4 -4
- package/dist/types/testing/render-route.d.ts +21 -11
- package/dist/types/testing/run-loader.d.ts +7 -0
- package/dist/types/testing/run-middleware.d.ts +7 -0
- package/dist/types/testing/run-revalidate.d.ts +5 -4
- package/dist/types/testing/serve-shell-request.d.ts +42 -5
- package/dist/types/testing/shell-status.d.ts +17 -0
- package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
- package/dist/types/testing/vitest.d.ts +10 -1
- package/dist/types/thenable-status.d.ts +0 -6
- package/dist/types/types/handler-context.d.ts +12 -1
- package/dist/types/types/loader-types.d.ts +10 -4
- package/dist/types/types/route-entry.d.ts +2 -1
- package/dist/types/urls/path-helper-types.d.ts +4 -2
- package/dist/types/vite/discovery/client-urls-projection.d.ts +55 -5
- package/dist/types/vite/discovery/state.d.ts +20 -0
- package/dist/types/vite/plugins/client-urls-revalidate-hoist.d.ts +28 -16
- package/dist/types/vite/plugins/hoist/source.d.ts +8 -7
- package/dist/types/vite/plugins/virtual-entries.d.ts +6 -0
- package/dist/types/vite/utils/prerender-utils.d.ts +7 -0
- package/dist/vite/index.js +6819 -6555
- package/package.json +1 -1
- package/skills/app-structure/SKILL.md +255 -0
- package/skills/app-structure/content-site.md +333 -0
- package/skills/app-structure/e-commerce.md +435 -0
- package/skills/app-structure/large-app.md +885 -0
- package/skills/app-structure/multi-app.md +280 -0
- package/skills/breadcrumbs/SKILL.md +6 -1
- package/skills/cache-guide/SKILL.md +4 -8
- package/skills/caching/SKILL.md +6 -6
- package/skills/catalog.json +12 -0
- package/skills/client-urls/SKILL.md +185 -32
- package/skills/comparison/references/framework-comparison.md +1 -1
- package/skills/composability/SKILL.md +10 -8
- package/skills/handler-use/SKILL.md +48 -9
- package/skills/hooks/handle-and-actions.md +104 -0
- package/skills/hooks/navigation.md +24 -10
- package/skills/hooks/urls.md +1 -1
- package/skills/host-router/SKILL.md +1 -1
- package/skills/i18n/SKILL.md +2 -1
- package/skills/intercept/SKILL.md +60 -29
- package/skills/layout/SKILL.md +6 -3
- package/skills/links/SKILL.md +151 -17
- package/skills/loader/SKILL.md +42 -19
- package/skills/middleware/SKILL.md +111 -15
- package/skills/observability/SKILL.md +11 -0
- package/skills/parallel/SKILL.md +5 -5
- package/skills/ppr/SKILL.md +103 -52
- package/skills/prerender/SKILL.md +4 -5
- package/skills/rango/SKILL.md +3 -0
- package/skills/response-routes/SKILL.md +4 -1
- package/skills/router-setup/SKILL.md +0 -2
- package/skills/scripts/SKILL.md +12 -1
- package/skills/server-actions/SKILL.md +60 -20
- package/skills/testing/SKILL.md +1 -0
- package/skills/testing/client-components.md +13 -10
- package/skills/testing/loader.md +25 -24
- package/skills/testing/middleware.md +11 -11
- package/skills/testing/render-handler.md +17 -17
- package/skills/testing/response-routes.md +4 -1
- package/skills/testing/serve-shell-request.md +86 -22
- package/skills/testing/server-actions.md +16 -16
- package/skills/ux-architecture/SKILL.md +980 -0
- package/skills/vercel/SKILL.md +1 -1
- package/skills/view-transitions/SKILL.md +43 -10
- package/src/__augment-tests__/augmented.check.ts +28 -0
- package/src/browser/event-controller.ts +44 -18
- package/src/browser/intercept-utils.ts +3 -2
- package/src/browser/merge-segment-loaders.ts +0 -85
- package/src/browser/navigation-bridge.ts +112 -84
- package/src/browser/navigation-client.ts +20 -15
- package/src/browser/navigation-store.ts +4 -2
- package/src/browser/navigation-transaction.ts +9 -8
- package/src/browser/partial-update.ts +90 -32
- package/src/browser/prefetch/cache.ts +8 -0
- package/src/browser/prefetch/fetch.ts +13 -3
- package/src/browser/rango-marker.ts +13 -0
- package/src/browser/react/NavigationProvider.tsx +25 -7
- package/src/browser/react/filter-segment-order.ts +13 -0
- package/src/browser/react/use-action.ts +9 -15
- package/src/browser/react/use-handle.ts +63 -71
- package/src/browser/react/use-href.tsx +24 -5
- package/src/browser/react/use-link-status.ts +1 -1
- package/src/browser/react/use-navigation.ts +30 -40
- package/src/browser/react/use-router.ts +37 -6
- package/src/browser/react/use-segments.ts +8 -12
- package/src/browser/response-adapter.ts +11 -5
- package/src/browser/rsc-router.tsx +3 -1
- package/src/browser/scroll-restoration.ts +34 -20
- package/src/browser/segment-reconciler.ts +11 -29
- package/src/browser/server-action-bridge.ts +52 -16
- package/src/browser/types.ts +40 -8
- package/src/build/generate-manifest.ts +12 -1
- package/src/build/route-types/codegen.ts +6 -2
- package/src/build/route-types/per-module-writer.ts +94 -61
- package/src/cache/cache-runtime.ts +9 -0
- package/src/cache/cache-scope.ts +2 -1
- package/src/cache/cf/cf-cache-store.ts +6 -7
- package/src/cache/cf/cf-cache-types.ts +0 -17
- package/src/cache/cf/cf-shell-frame.ts +3 -0
- package/src/cache/handle-snapshot.ts +2 -2
- package/src/cache/types.ts +16 -6
- package/src/cache/vercel/vercel-cache-store.ts +8 -19
- package/src/client-urls/client-urls.ts +202 -153
- package/src/client-urls/revalidate-registry.ts +12 -3
- package/src/client-urls/server-projection.ts +137 -66
- package/src/client-urls/types.ts +30 -15
- package/src/client.rsc.tsx +11 -1
- package/src/client.tsx +9 -1
- package/src/decode-loader-results.ts +64 -31
- package/src/handles/deferred-resolution.ts +3 -2
- package/src/handles/meta.ts +5 -0
- package/src/href-client.ts +3 -1
- package/src/index.rsc.ts +1 -0
- package/src/index.ts +12 -4
- package/src/loader-redirect.tsx +2 -3
- package/src/outlet-context.ts +7 -6
- package/src/prefetch-markers.ts +15 -5
- package/src/prerender/build-shell-capture.ts +1 -2
- package/src/prerender/writable-store.ts +2 -3
- package/src/reverse.ts +3 -3
- package/src/route-content-wrapper.tsx +19 -17
- package/src/route-definition/dsl-helpers.ts +41 -21
- package/src/route-definition/helpers-types.ts +7 -2
- package/src/route-definition/resolve-handler-use.ts +23 -10
- package/src/route-definition/rewrite.ts +86 -19
- package/src/route-types.ts +1 -2
- package/src/router/find-match.ts +37 -1
- package/src/router/handler-context.ts +85 -40
- package/src/router/intercept-resolution.ts +161 -307
- package/src/router/lazy-includes.ts +43 -27
- package/src/router/loader-resolution.ts +21 -1
- package/src/router/manifest.ts +15 -10
- package/src/router/match-api.ts +11 -1
- package/src/router/match-middleware/background-revalidation.ts +2 -4
- package/src/router/match-middleware/cache-lookup.ts +16 -20
- package/src/router/match-middleware/intercept-resolution.ts +41 -60
- package/src/router/match-result.ts +31 -2
- package/src/router/middleware.ts +6 -147
- package/src/router/navigation-snapshot.ts +38 -2
- package/src/router/prefetch-cache-ttl.ts +10 -18
- package/src/router/prerender-match.ts +2 -1
- package/src/router/revalidation.ts +24 -3
- package/src/router/router-context.ts +5 -18
- package/src/router/router-interfaces.ts +0 -13
- package/src/router/router-options.ts +4 -28
- package/src/router/segment-resolution/fresh.ts +6 -2
- package/src/router/segment-resolution/helpers.ts +7 -1
- package/src/router/segment-resolution/loader-cache.ts +4 -3
- package/src/router/segment-resolution/mask-nested.ts +5 -3
- package/src/router/segment-resolution/prefetch-deferral.ts +62 -12
- package/src/router/segment-resolution/revalidation.ts +75 -35
- package/src/router/segment-resolution.ts +0 -1
- package/src/router/segment-wrappers.ts +8 -13
- package/src/router/substitute-pattern-params.ts +3 -2
- package/src/router/tracing.ts +2 -2
- package/src/router-versions.ts +7 -7
- package/src/router.ts +5 -17
- package/src/rsc/full-payload.ts +26 -4
- package/src/rsc/handler.ts +86 -48
- package/src/rsc/helpers.ts +13 -0
- package/src/rsc/manifest-init.ts +56 -3
- package/src/rsc/prefetch-render-error.ts +38 -0
- package/src/rsc/progressive-enhancement.ts +161 -211
- package/src/rsc/rewrite-passes.ts +56 -9
- package/src/rsc/rsc-rendering.ts +79 -23
- package/src/rsc/server-action.ts +25 -2
- package/src/rsc/shell-capture-constants.ts +26 -3
- package/src/rsc/shell-capture.ts +137 -51
- package/src/rsc/types.ts +5 -9
- package/src/segment-system.tsx +125 -119
- package/src/server/build-version-table.ts +12 -24
- package/src/server/context.ts +160 -7
- package/src/server/handle-store.ts +80 -12
- package/src/server/request-context.ts +46 -6
- package/src/server/request-headers.ts +32 -0
- package/src/server/rewrite-trail.ts +55 -9
- package/src/ssr/ssr-root.tsx +6 -10
- package/src/testing/build-versions.ts +0 -3
- package/src/testing/dispatch.ts +5 -8
- package/src/testing/e2e/index.ts +1 -0
- package/src/testing/flight.entry.ts +1 -0
- package/src/testing/index.ts +1 -0
- package/src/testing/internal/client-urls-projection.ts +31 -11
- package/src/testing/internal/seed-vars.ts +4 -4
- package/src/testing/render-route.tsx +83 -40
- package/src/testing/run-loader.ts +26 -2
- package/src/testing/run-middleware.ts +10 -0
- package/src/testing/run-revalidate.ts +13 -6
- package/src/testing/serve-shell-request.ts +79 -9
- package/src/testing/shell-status.ts +40 -0
- package/src/testing/vitest-stubs/version.ts +1 -0
- package/src/testing/vitest.ts +74 -7
- package/src/thenable-status.ts +0 -11
- package/src/types/handler-context.ts +12 -1
- package/src/types/loader-types.ts +10 -4
- package/src/types/route-entry.ts +2 -1
- package/src/urls/include-helper.ts +38 -11
- package/src/urls/path-helper-types.ts +4 -2
- package/src/vite/discovery/client-urls-projection.ts +205 -16
- package/src/vite/discovery/discover-routers.ts +2 -2
- package/src/vite/discovery/prerender-collection.ts +2 -6
- package/src/vite/discovery/shell-prerender-phase.ts +1 -5
- package/src/vite/discovery/state.ts +26 -0
- package/src/vite/plugins/client-urls-revalidate-hoist.ts +190 -77
- package/src/vite/plugins/expose-internal-ids.ts +46 -40
- package/src/vite/plugins/hoist/source.ts +8 -7
- package/src/vite/plugins/transition-when-hoist.ts +1 -0
- package/src/vite/plugins/version-plugin.ts +7 -0
- package/src/vite/plugins/version.d.ts +7 -0
- package/src/vite/plugins/virtual-entries.ts +20 -0
- package/src/vite/rango.ts +4 -1
- package/src/vite/router-discovery.ts +39 -7
- package/src/vite/utils/prerender-utils.ts +17 -0
package/src/rsc/rsc-rendering.ts
CHANGED
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
renderRscFlightStage,
|
|
41
41
|
renderRscResponse,
|
|
42
42
|
} from "./render-pipeline.js";
|
|
43
|
+
import { markPrefetchOnRenderError } from "./prefetch-render-error.js";
|
|
43
44
|
import {
|
|
44
45
|
createRoutineTrace,
|
|
45
46
|
handoff,
|
|
@@ -51,7 +52,7 @@ import {
|
|
|
51
52
|
import type { HandlerContext } from "./handler-context.js";
|
|
52
53
|
import { attachTransitionWhen } from "./attach-transition-when.js";
|
|
53
54
|
import { publicRouteName } from "../route-name.js";
|
|
54
|
-
import { buildFullPayload,
|
|
55
|
+
import { buildFullPayload, payloadLocation } from "./full-payload.js";
|
|
55
56
|
import {
|
|
56
57
|
capShellWindow,
|
|
57
58
|
lastStoredCaptureSeq,
|
|
@@ -94,6 +95,8 @@ import {
|
|
|
94
95
|
type DevShellLookup,
|
|
95
96
|
} from "./shell-build-manifest.js";
|
|
96
97
|
import { contextGet } from "../context-var.js";
|
|
98
|
+
import { rewriteShellAudience, rewriteTrail } from "../server/rewrite-trail.js";
|
|
99
|
+
import { visitorShellKey } from "./shell-capture-constants.js";
|
|
97
100
|
import { PREFETCH_HEADER } from "../prefetch-markers.js";
|
|
98
101
|
import {
|
|
99
102
|
isWarmReplace,
|
|
@@ -155,10 +158,52 @@ function describePprReplayStatus(status: PprReplayStatus): string {
|
|
|
155
158
|
: `bypass:${status.reason}`;
|
|
156
159
|
}
|
|
157
160
|
|
|
161
|
+
/**
|
|
162
|
+
* The shell key before the route's own partition, or undefined: a request
|
|
163
|
+
* middleware rewrote reads and captures no shell unless its rewrite chose
|
|
164
|
+
* `.shell()`, so a URL's shared shell is read and written only by requests
|
|
165
|
+
* on that URL (#1146).
|
|
166
|
+
*/
|
|
167
|
+
interface RequestShellBase {
|
|
168
|
+
baseKey: string;
|
|
169
|
+
/** shellSearchSeed of the URL the key names. */
|
|
170
|
+
searchSeed: string;
|
|
171
|
+
/** A visitor-keyed shell's location (ShellCacheEntry.pathname). */
|
|
172
|
+
pathname?: string;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function requestShellBase(
|
|
176
|
+
ctx: HandlerContext<any>,
|
|
177
|
+
reqCtx: RequestContext<any>,
|
|
178
|
+
url: URL,
|
|
179
|
+
): RequestShellBase | undefined {
|
|
180
|
+
const filter = reqCtx._searchParamsFilter;
|
|
181
|
+
const trail = rewriteTrail(reqCtx);
|
|
182
|
+
const shell = rewriteShellAudience(reqCtx);
|
|
183
|
+
if (trail && shell === "visitor") {
|
|
184
|
+
const visitor = new URL(trail[0]!);
|
|
185
|
+
return {
|
|
186
|
+
baseKey: visitorShellKey(
|
|
187
|
+
ctx.router.id,
|
|
188
|
+
visitor,
|
|
189
|
+
new URL(trail[trail.length - 1]!),
|
|
190
|
+
filter,
|
|
191
|
+
),
|
|
192
|
+
searchSeed: shellSearchSeed(visitor, filter),
|
|
193
|
+
pathname: visitor.pathname,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
if (trail && shell !== "target") return undefined;
|
|
197
|
+
return {
|
|
198
|
+
baseKey: buildShellKey(ctx.router.id, url, filter),
|
|
199
|
+
searchSeed: shellSearchSeed(url, filter),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
158
203
|
function createShellCaptureDescriptor(
|
|
159
204
|
ctx: HandlerContext<any>,
|
|
160
205
|
key: string,
|
|
161
|
-
|
|
206
|
+
base: RequestShellBase,
|
|
162
207
|
originSeed: string,
|
|
163
208
|
pprConfig: ResolvedPprConfig,
|
|
164
209
|
store: SegmentCacheStore<any>,
|
|
@@ -166,7 +211,8 @@ function createShellCaptureDescriptor(
|
|
|
166
211
|
): ShellCaptureDescriptor {
|
|
167
212
|
return {
|
|
168
213
|
key,
|
|
169
|
-
searchSeed,
|
|
214
|
+
searchSeed: base.searchSeed,
|
|
215
|
+
pathname: base.pathname,
|
|
170
216
|
// Derived from the same request URL as the key (the callers compute
|
|
171
217
|
// both next to each other, so they cannot drift): seeding the origin
|
|
172
218
|
// keeps origin-dependent static markup (Link's data-external)
|
|
@@ -499,13 +545,15 @@ function* shellServePlan<TEnv>(
|
|
|
499
545
|
// no shell read, no capture, so it can never degrade again. The handler
|
|
500
546
|
// stripped the marker from the request and left this flag.
|
|
501
547
|
if (reqCtx._shellForcedMiss) return { kind: "pass" };
|
|
548
|
+
const base = requestShellBase(ctx, reqCtx, url);
|
|
549
|
+
if (!base) return { kind: "pass" };
|
|
502
550
|
|
|
503
551
|
// A per-request CSP nonce pins the route to axis 1: a shared shell would
|
|
504
552
|
// freeze the capture request's nonce and CSP would reject it for every
|
|
505
553
|
// other visitor. Every step below, a HIT included, therefore runs without
|
|
506
554
|
// one.
|
|
507
555
|
const store = reqCtx._cacheStore;
|
|
508
|
-
const baseKey =
|
|
556
|
+
const { baseKey } = base;
|
|
509
557
|
if (activeRequestNonce(nonce, reqCtx) !== undefined) {
|
|
510
558
|
// Declared intent that cannot be honored deserves a diagnostic (unlike an
|
|
511
559
|
// undeclared route, which is silent): a ppr route gated off by an active
|
|
@@ -596,7 +644,7 @@ function* shellServePlan<TEnv>(
|
|
|
596
644
|
const descriptor = createShellCaptureDescriptor(
|
|
597
645
|
ctx,
|
|
598
646
|
key,
|
|
599
|
-
|
|
647
|
+
base,
|
|
600
648
|
url.origin,
|
|
601
649
|
pprConfig,
|
|
602
650
|
store,
|
|
@@ -626,10 +674,13 @@ function* shellServePlan<TEnv>(
|
|
|
626
674
|
// A HIT replays the handler layer from the entry's doc record (`docKey`),
|
|
627
675
|
// or, for a Prerender route, from the prerender store. An entry that has
|
|
628
676
|
// neither is a MISS: serving it would run handlers after the commit.
|
|
677
|
+
// The entry must have rendered this request's location: the browser
|
|
678
|
+
// hydrates against the address bar.
|
|
629
679
|
if (
|
|
630
680
|
cached &&
|
|
631
681
|
!cached.entry.navigationOnly &&
|
|
632
682
|
(cached.entry.docKey !== undefined || route?.matched?.pr === true) &&
|
|
683
|
+
cached.entry.pathname === base.pathname &&
|
|
633
684
|
isValidShellHit(cached.entry, ctx.version)
|
|
634
685
|
) {
|
|
635
686
|
const document = openShellDocumentMetered(reqCtx, cached);
|
|
@@ -713,7 +764,9 @@ function* shellServePlan<TEnv>(
|
|
|
713
764
|
// compile-time constant).
|
|
714
765
|
const devShellLookup = resolveDevShellLookup(reqCtx, pprConfig);
|
|
715
766
|
let buildHit: Awaited<ReturnType<typeof lookupBuildShell>> = null;
|
|
716
|
-
if (
|
|
767
|
+
if (base.pathname !== undefined) {
|
|
768
|
+
// A build shell is the target's: never served under a visitor key.
|
|
769
|
+
} else if (key !== baseKey) {
|
|
717
770
|
// Probed once per path without a build shell, and not again once the
|
|
718
771
|
// route warned. A search-bearing URL never reads one: nothing to probe.
|
|
719
772
|
const routeName = reqCtx._classifiedRoute?.routeKey;
|
|
@@ -845,7 +898,7 @@ function* preparePayloadPlan<TEnv>(
|
|
|
845
898
|
kind: "payload",
|
|
846
899
|
payload: {
|
|
847
900
|
metadata: {
|
|
848
|
-
|
|
901
|
+
...payloadLocation(reqCtx, url),
|
|
849
902
|
// routerId is serialized on every payload (including within-session
|
|
850
903
|
// ones) so the frontend can read the current app/router identity. It
|
|
851
904
|
// always equals the current app's id: a cross-app navigation is
|
|
@@ -1209,16 +1262,6 @@ function renderPreparedRscResponse<TEnv>(
|
|
|
1209
1262
|
if (sourceScoped) {
|
|
1210
1263
|
rscHeaders["x-rsc-prefetch-scope"] = "source";
|
|
1211
1264
|
}
|
|
1212
|
-
// Enable browser HTTP caching for prefetch responses only.
|
|
1213
|
-
// Requires a prefetch request (X-Rango-Prefetch, sent by Link prefetch
|
|
1214
|
-
// fetch), a source-agnostic response (see sourceScoped above), and a
|
|
1215
|
-
// configured cache-control value (false disables caching).
|
|
1216
|
-
if (reqCtx._requestKind === "prefetch" && isPartial && !sourceScoped) {
|
|
1217
|
-
const cc = ctx.router.prefetchCacheControl;
|
|
1218
|
-
if (cc) {
|
|
1219
|
-
rscHeaders["cache-control"] = cc;
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
1265
|
// prefetch: false (docs/design/prefetch-false.md, R7). A response that
|
|
1223
1266
|
// carries deferred units answers one page's prefetch, and a fill is the
|
|
1224
1267
|
// second half of one adoption: neither is reused by a shared cache or the
|
|
@@ -1248,7 +1291,7 @@ function renderPreparedRscResponse<TEnv>(
|
|
|
1248
1291
|
reqCtx._activeRoutine &&
|
|
1249
1292
|
createRenderStageTraceBridge(reqCtx._activeRoutine),
|
|
1250
1293
|
};
|
|
1251
|
-
|
|
1294
|
+
const rendered = renderRscResponse(
|
|
1252
1295
|
{
|
|
1253
1296
|
ctx,
|
|
1254
1297
|
request,
|
|
@@ -1272,6 +1315,11 @@ function renderPreparedRscResponse<TEnv>(
|
|
|
1272
1315
|
}),
|
|
1273
1316
|
},
|
|
1274
1317
|
);
|
|
1318
|
+
// A prefetch whose render reported an error is not kept (#1170).
|
|
1319
|
+
if (reqCtx._requestKind !== "prefetch" || !isPartial) return rendered;
|
|
1320
|
+
return rendered.then((response) =>
|
|
1321
|
+
markPrefetchOnRenderError(response, reqCtx._renderErrors ?? []),
|
|
1322
|
+
);
|
|
1275
1323
|
}
|
|
1276
1324
|
|
|
1277
1325
|
/**
|
|
@@ -1286,6 +1334,8 @@ function scheduleNavigationShellCapture<TEnv>(
|
|
|
1286
1334
|
const { ctx, request, env, url, reqCtx } = input;
|
|
1287
1335
|
const pprConfig = resolvePprConfig(reqCtx._classifiedRoute?.manifestEntry)!;
|
|
1288
1336
|
const store = reqCtx._cacheStore!;
|
|
1337
|
+
const base = requestShellBase(ctx, reqCtx, url);
|
|
1338
|
+
if (!base) return;
|
|
1289
1339
|
scheduleShellCapture(
|
|
1290
1340
|
ctx,
|
|
1291
1341
|
request,
|
|
@@ -1305,7 +1355,7 @@ function scheduleNavigationShellCapture<TEnv>(
|
|
|
1305
1355
|
createShellCaptureDescriptor(
|
|
1306
1356
|
ctx,
|
|
1307
1357
|
captureKey,
|
|
1308
|
-
|
|
1358
|
+
base,
|
|
1309
1359
|
url.origin,
|
|
1310
1360
|
pprConfig,
|
|
1311
1361
|
store,
|
|
@@ -1464,10 +1514,13 @@ async function matchPartialWithPprReplay<TEnv>(
|
|
|
1464
1514
|
return runMatch({ outcome: "BYPASS", reason: "cache-disabled" });
|
|
1465
1515
|
}
|
|
1466
1516
|
|
|
1517
|
+
const base = requestShellBase(ctx, reqCtx, url);
|
|
1518
|
+
if (!base) return runMatch({ outcome: "BYPASS", reason: "rewritten" });
|
|
1519
|
+
|
|
1467
1520
|
// The visitor's own partition (resolveShellPartition): replay never
|
|
1468
1521
|
// crosses into another partition's shell.
|
|
1469
1522
|
const key = await resolveRequestShellKey(
|
|
1470
|
-
|
|
1523
|
+
base.baseKey,
|
|
1471
1524
|
routeSnapshot,
|
|
1472
1525
|
store,
|
|
1473
1526
|
reqCtx,
|
|
@@ -1806,6 +1859,9 @@ function serveShellHit(
|
|
|
1806
1859
|
descriptor: ShellCaptureDescriptor,
|
|
1807
1860
|
): Response {
|
|
1808
1861
|
const { entry, prelude: preludeBytes } = document;
|
|
1862
|
+
// A visitor-keyed shell shows the visitor's URL: the document cache keys
|
|
1863
|
+
// this response by the target.
|
|
1864
|
+
if (entry.pathname !== undefined) reqCtx._payloadVisitorLocation = true;
|
|
1809
1865
|
const metricsStore = reqCtx._metricsStore;
|
|
1810
1866
|
// Per-stage tail timing for the `ppr:tail` Server-Timing mirror and the
|
|
1811
1867
|
// perf console line: always in dev, in production only when this request
|
|
@@ -1867,8 +1923,8 @@ function serveShellHit(
|
|
|
1867
1923
|
payload.metadata.initialTheme = entry.initialTheme as
|
|
1868
1924
|
| import("../theme/types.js").Theme
|
|
1869
1925
|
| undefined;
|
|
1870
|
-
// Same for the location: the
|
|
1871
|
-
payload.metadata.pathname = url.pathname;
|
|
1926
|
+
// Same for the location: the key's URL, or a visitor-keyed shell's.
|
|
1927
|
+
payload.metadata.pathname = entry.pathname ?? url.pathname;
|
|
1872
1928
|
}
|
|
1873
1929
|
// Full Flight render per request: hydration needs the whole payload (there
|
|
1874
1930
|
// is no Flight-side resume — a React limitation, not ours).
|
|
@@ -1911,7 +1967,7 @@ function serveShellHit(
|
|
|
1911
1967
|
nonce: undefined,
|
|
1912
1968
|
// The shell key's own search — identical to the capture seed for
|
|
1913
1969
|
// this key, so the resume tree matches the captured tree.
|
|
1914
|
-
search:
|
|
1970
|
+
search: descriptor.searchSeed,
|
|
1915
1971
|
// The HIT request's origin — same host as the capture's (key-scoped).
|
|
1916
1972
|
origin: url.origin,
|
|
1917
1973
|
// The document cache reads the list before storing this composite.
|
package/src/rsc/server-action.ts
CHANGED
|
@@ -24,6 +24,7 @@ import { observePhase, PHASES } from "../router/instrument.js";
|
|
|
24
24
|
import { resolveActionRefId } from "../router/is-action.js";
|
|
25
25
|
import type { TraceSpan } from "../router/tracing.js";
|
|
26
26
|
import { attachTransitionWhen } from "./attach-transition-when.js";
|
|
27
|
+
import { payloadLocation } from "./full-payload.js";
|
|
27
28
|
import { publicRouteName } from "../route-name.js";
|
|
28
29
|
import type { RscPayload } from "./types.js";
|
|
29
30
|
import {
|
|
@@ -323,6 +324,28 @@ export async function executeServerAction<TEnv>(
|
|
|
323
324
|
};
|
|
324
325
|
}
|
|
325
326
|
|
|
327
|
+
/**
|
|
328
|
+
* The continuation for a rewrite pass after the action ran (#1145): a failed
|
|
329
|
+
* action's error boundary is re-matched on the target, since the one pass 1
|
|
330
|
+
* matched (or missed) is the rewriting route's.
|
|
331
|
+
*/
|
|
332
|
+
export async function continuationForRewritePass<TEnv>(
|
|
333
|
+
ctx: HandlerContext<TEnv>,
|
|
334
|
+
request: Request,
|
|
335
|
+
env: TEnv,
|
|
336
|
+
continuation: ActionContinuation,
|
|
337
|
+
): Promise<ActionContinuation> {
|
|
338
|
+
if (continuation.returnValue.ok) return continuation;
|
|
339
|
+
const errorBoundary =
|
|
340
|
+
(await ctx.router.matchError(
|
|
341
|
+
request,
|
|
342
|
+
{ env },
|
|
343
|
+
continuation.actionContext.actionResult,
|
|
344
|
+
"route",
|
|
345
|
+
)) ?? undefined;
|
|
346
|
+
return { ...continuation, errorBoundary };
|
|
347
|
+
}
|
|
348
|
+
|
|
326
349
|
/**
|
|
327
350
|
* Phase 2: Revalidate after action.
|
|
328
351
|
*
|
|
@@ -513,7 +536,7 @@ function renderActionBoundaryResponse<TEnv>(
|
|
|
513
536
|
|
|
514
537
|
const errorPayload: RscPayload = {
|
|
515
538
|
metadata: {
|
|
516
|
-
|
|
539
|
+
...payloadLocation(reqCtx, url),
|
|
517
540
|
// routerId exposed for the frontend (current app identity); see
|
|
518
541
|
// rsc-rendering.ts partial branch.
|
|
519
542
|
routerId: ctx.router.id,
|
|
@@ -576,7 +599,7 @@ function renderRevalidationResponse<TEnv>(
|
|
|
576
599
|
|
|
577
600
|
const payload: RscPayload = {
|
|
578
601
|
metadata: {
|
|
579
|
-
|
|
602
|
+
...payloadLocation(reqCtx, url),
|
|
580
603
|
// routerId exposed for the frontend (current app identity); see
|
|
581
604
|
// rsc-rendering.ts partial branch.
|
|
582
605
|
routerId: ctx.router.id,
|
|
@@ -67,6 +67,8 @@ export const PPR_REPLAY_BYPASS_REASONS = [
|
|
|
67
67
|
"no-segment-snapshot",
|
|
68
68
|
"snapshot-miss",
|
|
69
69
|
"explicit-cache-hit",
|
|
70
|
+
// A rewritten request whose rewrite chose no `.shell()`.
|
|
71
|
+
"rewritten",
|
|
70
72
|
] as const;
|
|
71
73
|
|
|
72
74
|
/** One of {@link PPR_REPLAY_BYPASS_REASONS}. */
|
|
@@ -126,6 +128,26 @@ export function partitionShellKey(key: string, partition: string): string {
|
|
|
126
128
|
return `${key}|${encodeURIComponent(partition)}`;
|
|
127
129
|
}
|
|
128
130
|
|
|
131
|
+
/**
|
|
132
|
+
* The shell key of a `rewrite(...).shell("visitor")` request: the visitor's
|
|
133
|
+
* URL, partitioned by `rw:` + the target's path and search (both searches
|
|
134
|
+
* filtered and sorted, shellSearchSeed). The visitor's URL keeps two public
|
|
135
|
+
* URLs of one target apart; the target keeps a conditional rewrite (an A/B
|
|
136
|
+
* arm, a lock) apart from the URL's own shell. The route's own partition
|
|
137
|
+
* composes after it.
|
|
138
|
+
*/
|
|
139
|
+
export function visitorShellKey(
|
|
140
|
+
routerId: string,
|
|
141
|
+
visitor: URL,
|
|
142
|
+
target: URL,
|
|
143
|
+
filter?: SearchParamsFilter,
|
|
144
|
+
): string {
|
|
145
|
+
return partitionShellKey(
|
|
146
|
+
buildShellKey(routerId, visitor, filter),
|
|
147
|
+
`rw:${target.pathname}${shellSearchSeed(target, filter)}`,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
129
151
|
/**
|
|
130
152
|
* The navigation-only entry beside a document shell key (a cold partial
|
|
131
153
|
* request's capture, rsc-rendering.ts). A suffix after the (encoded)
|
|
@@ -186,9 +208,10 @@ export const SHELL_CAPTURE_TASK_HARD_CAP_MS = 25_000;
|
|
|
186
208
|
* (ShellCaptureDebugEvent.refusal, `caches.refusal` on a `router.prerender()`
|
|
187
209
|
* result): `identity` (a request-scoped read: cookies(), headers(), the
|
|
188
210
|
* theme, a `{ cache: false }` variable), `dynamic` (`ctx.dynamic()`),
|
|
189
|
-
* `loader` (a `{ ssr: false }` loader rejected,
|
|
190
|
-
* redirect()/notFound()
|
|
191
|
-
*
|
|
211
|
+
* `loader` (a `{ ssr: false }` loader rejected, settled with
|
|
212
|
+
* redirect()/notFound(), or a promise it pushed rejected),
|
|
213
|
+
* `no-record` (no doc record: the route's cache() refused the write, or the
|
|
214
|
+
* handler output failed), `handles` (a pushed
|
|
192
215
|
* handle value did not finish encoding), `size` (over the store's value
|
|
193
216
|
* limit), `record-expired` (the route cache() record the capture wrote ran
|
|
194
217
|
* out first), `invalidated` (a tag of the shell was invalidated after the
|
package/src/rsc/shell-capture.ts
CHANGED
|
@@ -508,6 +508,11 @@ function refuse(
|
|
|
508
508
|
return "refused";
|
|
509
509
|
}
|
|
510
510
|
|
|
511
|
+
/** A bake-lane loader's promise push, as a refusal names it. */
|
|
512
|
+
function describeLoaderPromisePush(push: LoaderPromisePush): string {
|
|
513
|
+
return `the promise the loader "${push.loaderId}" pushed to the handle "${push.handleName}"`;
|
|
514
|
+
}
|
|
515
|
+
|
|
511
516
|
/** The no-shell cause for handler output that missed the capture deadline. */
|
|
512
517
|
const HANDLER_OUTPUT_TIMEOUT_REASON: string =
|
|
513
518
|
"the handler output (a promise it passes or pushes, or an async server " +
|
|
@@ -1063,6 +1068,13 @@ export interface ShellCaptureDescriptor {
|
|
|
1063
1068
|
debugSink?: (event: ShellCaptureDebugEvent) => void;
|
|
1064
1069
|
/** Store the snapshot for navigation replay, never the captured HTML prelude. */
|
|
1065
1070
|
navigationOnly?: true;
|
|
1071
|
+
/**
|
|
1072
|
+
* Set for a shell keyed by the visitor's URL (`rewrite(...).shell("visitor")`,
|
|
1073
|
+
* visitorShellKey): the capture renders the visitor's location
|
|
1074
|
+
* (`RequestContext._shellCaptureForVisitor`) and stores it as
|
|
1075
|
+
* ShellCacheEntry.pathname.
|
|
1076
|
+
*/
|
|
1077
|
+
pathname?: string;
|
|
1066
1078
|
/**
|
|
1067
1079
|
* lastStoredCaptureSeq(key) when a document request read the store and
|
|
1068
1080
|
* missed. The request schedules its capture only after rendering the MISS,
|
|
@@ -1660,8 +1672,9 @@ export type CaptureRecordOutcome =
|
|
|
1660
1672
|
* component showed one value in the prelude and another on every HIT.
|
|
1661
1673
|
*
|
|
1662
1674
|
* Order, all bounded by the one capture deadline (`ppr.captureTimeout`):
|
|
1663
|
-
* top-level handle promises
|
|
1664
|
-
*
|
|
1675
|
+
* top-level handle promises (a bake-lane loader's pushed promises among
|
|
1676
|
+
* them, #1182), the handler pushes' nested promises, and the bake-lane
|
|
1677
|
+
* loader containers settle first (the record encodes the handles
|
|
1665
1678
|
* a HIT restores, and its handle encode has its own 5s timeout that must
|
|
1666
1679
|
* never be the one that fires); then the capture's onResponse callbacks fire
|
|
1667
1680
|
* with a synthetic 200 (the implicit doc scope's cacheRoute, an explicit
|
|
@@ -1704,7 +1717,33 @@ export async function settleCaptureRecord(
|
|
|
1704
1717
|
if (!settled.done) {
|
|
1705
1718
|
if (refuseOnCaptureGuard(capture.key, derivedCtx))
|
|
1706
1719
|
return { kind: "refused", refusal: guardRefusal(derivedCtx) };
|
|
1707
|
-
|
|
1720
|
+
const pending = derivation.loaderPromisePushes.find(
|
|
1721
|
+
(p) => p.state === "pending",
|
|
1722
|
+
);
|
|
1723
|
+
return {
|
|
1724
|
+
kind: "timeout",
|
|
1725
|
+
reason: pending
|
|
1726
|
+
? `${describeLoaderPromisePush(pending)} did not settle within ppr.captureTimeout`
|
|
1727
|
+
: HANDLER_OUTPUT_TIMEOUT_REASON,
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
// A rejected value must not bake, and the record's encode would fail on
|
|
1731
|
+
// it anyway: refuse before the write starts. A guard trip in that promise
|
|
1732
|
+
// names the read instead.
|
|
1733
|
+
const rejected = derivation.loaderPromisePushes.find(
|
|
1734
|
+
(p) => p.state === "rejected",
|
|
1735
|
+
);
|
|
1736
|
+
if (rejected) {
|
|
1737
|
+
if (refuseOnCaptureGuard(capture.key, derivedCtx))
|
|
1738
|
+
return { kind: "refused", refusal: guardRefusal(derivedCtx) };
|
|
1739
|
+
warnCaptureRefusedOnce(
|
|
1740
|
+
capture.key,
|
|
1741
|
+
`${describeLoaderPromisePush(rejected)} rejected during capture; a ` +
|
|
1742
|
+
"failed value must not bake into the shared shell. Catch it and push " +
|
|
1743
|
+
"a fallback, or push it from a loader without ssr: false.",
|
|
1744
|
+
PPR_LANE_HINT,
|
|
1745
|
+
);
|
|
1746
|
+
return { kind: "refused", refusal: "loader" };
|
|
1708
1747
|
}
|
|
1709
1748
|
|
|
1710
1749
|
const callbacks = derivedCtx._onResponseCallbacks?.splice(0) ?? [];
|
|
@@ -1878,6 +1917,9 @@ async function attemptCapture(
|
|
|
1878
1917
|
descriptor.navigationOnly ? { request, url } : undefined,
|
|
1879
1918
|
);
|
|
1880
1919
|
const { derivedCtx, freshHandleStore } = derivation;
|
|
1920
|
+
if (descriptor.pathname !== undefined) {
|
|
1921
|
+
derivedCtx._shellCaptureForVisitor = true;
|
|
1922
|
+
}
|
|
1881
1923
|
// The capture generation starts before matching or any snapshot read
|
|
1882
1924
|
// (ShellCacheEntry.createdAt): a tag invalidated after it wins.
|
|
1883
1925
|
const captureStartedAt = Date.now();
|
|
@@ -1996,6 +2038,20 @@ export interface CaptureContextDerivation {
|
|
|
1996
2038
|
* record encodes its handles, so that encode never races a pending value.
|
|
1997
2039
|
*/
|
|
1998
2040
|
handlerPushesSettled: () => Promise<void>;
|
|
2041
|
+
/**
|
|
2042
|
+
* Every promise a bake-lane loader body pushed (#1182), in push order.
|
|
2043
|
+
* The record-first step waits for them (they are in the recorded cut) and
|
|
2044
|
+
* names the loader and the handle of one that rejects or misses the
|
|
2045
|
+
* deadline.
|
|
2046
|
+
*/
|
|
2047
|
+
loaderPromisePushes: readonly LoaderPromisePush[];
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
/** A promise a bake-lane loader pushed at capture, owned by that loader. */
|
|
2051
|
+
export interface LoaderPromisePush {
|
|
2052
|
+
loaderId: string;
|
|
2053
|
+
handleName: string;
|
|
2054
|
+
state: "pending" | "fulfilled" | "rejected";
|
|
1999
2055
|
}
|
|
2000
2056
|
|
|
2001
2057
|
/**
|
|
@@ -2031,30 +2087,35 @@ export function deriveShellCaptureContext(
|
|
|
2031
2087
|
// masked), their pushes are in the captured HTML (<head> title/meta,
|
|
2032
2088
|
// useHandle echoes), and a HIT does not run a promise-free bake-lane
|
|
2033
2089
|
// loader (loader-cache.ts), so the stored record carries them: each
|
|
2034
|
-
//
|
|
2035
|
-
// `owner` (HandleStore.push), the record stores it
|
|
2090
|
+
// push from a loader body whose value, once settled, is thenable-free
|
|
2091
|
+
// keeps that loader as `owner` (HandleStore.push), the record stores it
|
|
2036
2092
|
// (CachedEntryData.handleOwners), and a replay that serves the loader
|
|
2037
|
-
// from its pin restores it through pushRestored (restoreHandles).
|
|
2038
|
-
//
|
|
2039
|
-
//
|
|
2040
|
-
//
|
|
2041
|
-
//
|
|
2042
|
-
//
|
|
2043
|
-
//
|
|
2044
|
-
//
|
|
2045
|
-
//
|
|
2046
|
-
//
|
|
2047
|
-
//
|
|
2048
|
-
//
|
|
2049
|
-
//
|
|
2050
|
-
//
|
|
2051
|
-
//
|
|
2052
|
-
//
|
|
2053
|
-
//
|
|
2054
|
-
//
|
|
2055
|
-
//
|
|
2093
|
+
// from its pin restores it through pushRestored (restoreHandles). A
|
|
2094
|
+
// pushed promise (push(promise), .defer()) is judged by its settled
|
|
2095
|
+
// value, as push(await promise) would be (#1182): the record-first step
|
|
2096
|
+
// waits for it under the capture deadline, and one that rejects or
|
|
2097
|
+
// misses the deadline refuses the capture. Those copies stand: a
|
|
2098
|
+
// promise-carrying bake-lane loader that runs on the replay reads the
|
|
2099
|
+
// store, so its pushes that settle thenable-free (and those replayed
|
|
2100
|
+
// inside its body) are dropped and only the ones holding a promise are
|
|
2101
|
+
// added. A loader the replay does not serve from a pin is a hole: one
|
|
2102
|
+
// the route also runs on the live lane, or one whose pin the entry
|
|
2103
|
+
// lost. Its run replaces its copies (pushPlaceholder, #936), those of
|
|
2104
|
+
// the dependencies it awaits included (they are credited to it). Nested
|
|
2105
|
+
// thenables stay masked (the promise shape is the liveness declaration,
|
|
2106
|
+
// mask-nested.ts). A push whose settled value holds a masked nested
|
|
2107
|
+
// promise, one credited to a live-lane body, or one no body or replay
|
|
2108
|
+
// names (a loader-cache replay names its loader: pushReplayed below)
|
|
2109
|
+
// stays tagged (HandleStore.push loaderPush) so the handle encode cannot
|
|
2110
|
+
// stall on a never-resolving mask. A tagged push is out of the record
|
|
2111
|
+
// (captureHandles) and out of the capture's own render
|
|
2112
|
+
// (resolvedHandleStream `recordedOnly`, issue #1035): the prelude is
|
|
2113
|
+
// rendered from what the record keeps, which is what a HIT hydrates
|
|
2114
|
+
// with. The loader records then carry `runs: 1` so a HIT runs those
|
|
2115
|
+
// bodies for them, and what they push reaches the client after
|
|
2056
2116
|
// hydration (HandleStore.freezeDocumentSnapshot).
|
|
2057
2117
|
const handlerPushSettles: Promise<void>[] = [];
|
|
2118
|
+
const loaderPromisePushes: LoaderPromisePush[] = [];
|
|
2058
2119
|
// A replay (a loader-cache HIT, loader-cache.ts replayLoaderHandles, or a
|
|
2059
2120
|
// route cache() record's owned values, restoreHandles) re-pushes a loader's
|
|
2060
2121
|
// recorded values through pushReplayed or pushPlaceholder, which call
|
|
@@ -2108,39 +2169,62 @@ export function deriveShellCaptureContext(
|
|
|
2108
2169
|
if (report.thenable) maskedNestedThenable = true;
|
|
2109
2170
|
return masked;
|
|
2110
2171
|
};
|
|
2172
|
+
// The loader the push is credited to (see the funnel comment above). A
|
|
2173
|
+
// push under a live-lane loader's body, at any depth, is that hole's
|
|
2174
|
+
// output: its live run replaces it on a HIT and a navigation. Otherwise
|
|
2175
|
+
// the first loader around the push that the route registers decides: a
|
|
2176
|
+
// bake-lane one keeps the push under the innermost body, and a
|
|
2177
|
+
// dependency on neither lane is walked past. A replay of a hole's push
|
|
2178
|
+
// is that hole's, unless a live-lane body around it takes it first.
|
|
2179
|
+
const live = liveLaneOwner();
|
|
2180
|
+
const bodyLoaderId = live ?? getCurrentLoaderBodyId() ?? replayOwner;
|
|
2111
2181
|
let masked: unknown;
|
|
2112
|
-
let loaderPush = true;
|
|
2113
2182
|
let owner: string | undefined;
|
|
2114
|
-
if (isThenable(value)) {
|
|
2115
|
-
// Deferred (thenable) loader pushes always keep the tag — the
|
|
2116
|
-
// carve-out below is for settled values only. Promise.resolve first: a
|
|
2117
|
-
// loader cache() entry replays a deferred push as the thenable Flight
|
|
2118
|
-
// decoded it to, whose then() returns nothing, and the slot was
|
|
2119
|
-
// `undefined` (the empty element of issue #1035).
|
|
2120
|
-
masked = Promise.resolve(value).then(mask);
|
|
2121
|
-
} else {
|
|
2183
|
+
if (!isThenable(value)) {
|
|
2122
2184
|
masked = mask(value);
|
|
2123
|
-
if (!maskedNestedThenable)
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2185
|
+
if (!maskedNestedThenable) owner = bodyLoaderId;
|
|
2186
|
+
} else if (live === undefined && bodyLoaderId !== undefined) {
|
|
2187
|
+
// Owned now, judged by its settled value (funnel comment above).
|
|
2188
|
+
// Promise.resolve first: a loader cache() entry replays a deferred
|
|
2189
|
+
// push as the thenable Flight decoded it to, whose then() returns
|
|
2190
|
+
// nothing (issue #1035). The retag runs inside the slot's own then(),
|
|
2191
|
+
// so it lands before the wait on it (settleCaptureRecord) resumes and
|
|
2192
|
+
// the record encodes. The state handler is attached now, which also
|
|
2193
|
+
// marks a rejection as handled.
|
|
2194
|
+
const slot: Promise<unknown> = Promise.resolve(value).then((settled) => {
|
|
2195
|
+
const judged = mask(settled);
|
|
2196
|
+
if (maskedNestedThenable) {
|
|
2197
|
+
freshHandleStore.unrecordPush(handleName, segmentId, slot);
|
|
2198
|
+
derivedCtx._shellCaptureUnrecordedLoaderPush = true;
|
|
2199
|
+
}
|
|
2200
|
+
return judged;
|
|
2201
|
+
});
|
|
2202
|
+
const push: LoaderPromisePush = {
|
|
2203
|
+
loaderId: bodyLoaderId,
|
|
2204
|
+
handleName,
|
|
2205
|
+
state: "pending",
|
|
2206
|
+
};
|
|
2207
|
+
slot.then(
|
|
2208
|
+
() => {
|
|
2209
|
+
push.state = "fulfilled";
|
|
2210
|
+
},
|
|
2211
|
+
() => {
|
|
2212
|
+
push.state = "rejected";
|
|
2213
|
+
},
|
|
2214
|
+
);
|
|
2215
|
+
loaderPromisePushes.push(push);
|
|
2216
|
+
masked = slot;
|
|
2217
|
+
owner = bodyLoaderId;
|
|
2218
|
+
} else {
|
|
2219
|
+
// A hole's promise, or one no body or replay names: tagged. Nothing
|
|
2220
|
+
// awaits the slot at capture, so its rejection is handled here.
|
|
2221
|
+
const tagged = Promise.resolve(value).then(mask);
|
|
2222
|
+
tagged.catch(() => {});
|
|
2223
|
+
masked = tagged;
|
|
2141
2224
|
}
|
|
2142
2225
|
// A push the record cannot keep reaches a HIT only from a live run of
|
|
2143
2226
|
// its loader, so the loader records ask for one (runs: 1).
|
|
2227
|
+
const loaderPush = owner === undefined;
|
|
2144
2228
|
if (loaderPush) derivedCtx._shellCaptureUnrecordedLoaderPush = true;
|
|
2145
2229
|
rawCapturePush(handleName, segmentId, masked, loaderPush, owner);
|
|
2146
2230
|
};
|
|
@@ -2261,6 +2345,7 @@ export function deriveShellCaptureContext(
|
|
|
2261
2345
|
freshHandleStore,
|
|
2262
2346
|
handlerPushesSettled: () =>
|
|
2263
2347
|
Promise.all(handlerPushSettles).then(() => undefined),
|
|
2348
|
+
loaderPromisePushes,
|
|
2264
2349
|
};
|
|
2265
2350
|
}
|
|
2266
2351
|
|
|
@@ -2647,6 +2732,7 @@ async function captureAndStoreShell(
|
|
|
2647
2732
|
// default, #971). The serve tail replays it so the resume tree
|
|
2648
2733
|
// matches the frozen prelude — see ShellCacheEntry.initialTheme.
|
|
2649
2734
|
initialTheme: payloadInitialTheme(reqCtx),
|
|
2735
|
+
pathname: capture.pathname,
|
|
2650
2736
|
snapshot,
|
|
2651
2737
|
prunedRecords: snapshot.length > 0 ? prunedRecords : undefined,
|
|
2652
2738
|
// The canonical doc segment record's key, published by the doc
|
package/src/rsc/types.ts
CHANGED
|
@@ -18,6 +18,11 @@ import type { ResolvedThemeConfig, Theme } from "../theme/types.js";
|
|
|
18
18
|
export interface RscPayload {
|
|
19
19
|
metadata?: {
|
|
20
20
|
pathname: string;
|
|
21
|
+
/**
|
|
22
|
+
* Path and search of the URL the route rendered for, when middleware
|
|
23
|
+
* rewrote the request there (full-payload.ts payloadLocation).
|
|
24
|
+
*/
|
|
25
|
+
rewriteTarget?: string;
|
|
21
26
|
segments: ResolvedSegment[];
|
|
22
27
|
/** Router instance ID. When this changes between navigations, the client
|
|
23
28
|
* discards cached segments and does a full tree replacement (app switch). */
|
|
@@ -364,15 +369,6 @@ export interface CreateRSCHandlerOptions<
|
|
|
364
369
|
*/
|
|
365
370
|
cache?: HandlerCacheConfig | ((env: TEnv) => HandlerCacheConfig);
|
|
366
371
|
|
|
367
|
-
/**
|
|
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.
|
|
371
|
-
*
|
|
372
|
-
* @default createRouter({ version }), else the router's build versions
|
|
373
|
-
*/
|
|
374
|
-
version?: string;
|
|
375
|
-
|
|
376
372
|
/**
|
|
377
373
|
* Nonce provider for Content Security Policy (CSP).
|
|
378
374
|
*
|