vinext 1.2.1 → 1.3.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 +12 -9
- package/dist/build/client-build-config.d.ts +64 -3
- package/dist/build/client-build-config.js +131 -4
- package/dist/build/client-reference-groups.d.ts +163 -0
- package/dist/build/client-reference-groups.js +534 -0
- package/dist/build/module-dependency-cache.d.ts +31 -0
- package/dist/build/module-dependency-cache.js +51 -1
- package/dist/build/next-client-runtime-manifests.d.ts +10 -1
- package/dist/build/next-client-runtime-manifests.js +19 -4
- package/dist/build/nitro-public-files.d.ts +24 -0
- package/dist/build/nitro-public-files.js +60 -0
- package/dist/build/pages-client-assets-module.js +1 -1
- package/dist/build/prerender-paths.js +5 -2
- package/dist/build/server-actions-encryption-key.d.ts +23 -0
- package/dist/build/server-actions-encryption-key.js +101 -0
- package/dist/build/ssr-manifest.d.ts +3 -0
- package/dist/build/ssr-manifest.js +10 -1
- package/dist/client/client-rewrite-params.d.ts +9 -0
- package/dist/client/client-rewrite-params.js +20 -0
- package/dist/client/dev-error-overlay.js +2 -1
- package/dist/client/navigation-runtime.d.ts +25 -2
- package/dist/client/navigation-runtime.js +42 -11
- package/dist/client/vinext-next-data.d.ts +0 -1
- package/dist/config/config-matchers.d.ts +15 -40
- package/dist/config/config-matchers.js +183 -255
- package/dist/config/config-source.d.ts +34 -0
- package/dist/config/config-source.js +141 -0
- package/dist/config/header-params.d.ts +29 -0
- package/dist/config/header-params.js +106 -0
- package/dist/config/next-config.d.ts +7 -5
- package/dist/config/next-config.js +120 -7
- package/dist/entries/app-browser-entry.d.ts +14 -1
- package/dist/entries/app-browser-entry.js +26 -36
- package/dist/entries/app-rsc-entry.d.ts +2 -0
- package/dist/entries/app-rsc-entry.js +92 -57
- package/dist/entries/app-rsc-manifest.d.ts +10 -0
- package/dist/entries/app-rsc-manifest.js +20 -1
- package/dist/entries/app-ssr-entry.d.ts +4 -1
- package/dist/entries/app-ssr-entry.js +9 -2
- package/dist/entries/pages-client-entry.d.ts +1 -0
- package/dist/entries/pages-client-entry.js +6 -6
- package/dist/entries/pages-server-entry.js +17 -14
- package/dist/index.d.ts +11 -0
- package/dist/index.js +239 -83
- package/dist/init.js +17 -0
- package/dist/plugins/optimize-imports.d.ts +25 -2
- package/dist/plugins/optimize-imports.js +106 -20
- package/dist/plugins/rsc-client-reference-loaders.d.ts +45 -1
- package/dist/plugins/rsc-client-reference-loaders.js +336 -18
- package/dist/plugins/use-cache-callable.d.ts +5 -0
- package/dist/plugins/use-cache-callable.js +30 -8
- package/dist/plugins/worker-image-imports.d.ts +1 -0
- package/dist/plugins/worker-image-imports.js +1 -1
- package/dist/routing/app-route-graph.d.ts +1 -1
- package/dist/routing/browser-route-manifest.d.ts +43 -0
- package/dist/routing/browser-route-manifest.js +195 -0
- package/dist/routing/hashed-route-patterns.d.ts +4 -0
- package/dist/routing/hashed-route-patterns.js +22 -0
- package/dist/routing/optimistic-route-trie.d.ts +27 -0
- package/dist/routing/optimistic-route-trie.js +102 -0
- package/dist/routing/route-validation.d.ts +6 -1
- package/dist/routing/route-validation.js +26 -10
- package/dist/server/app-action-forwarding.d.ts +8 -0
- package/dist/server/app-action-forwarding.js +15 -1
- package/dist/server/app-browser-entry.js +147 -60
- package/dist/server/app-browser-error.d.ts +3 -1
- package/dist/server/app-browser-error.js +7 -2
- package/dist/server/app-browser-interception-context.d.ts +2 -2
- package/dist/server/app-browser-mpa-navigation.d.ts +1 -0
- package/dist/server/app-browser-mpa-navigation.js +6 -5
- package/dist/server/app-browser-navigation-controller.d.ts +17 -6
- package/dist/server/app-browser-navigation-controller.js +88 -26
- package/dist/server/app-browser-state.d.ts +2 -2
- package/dist/server/app-browser-visible-commit.d.ts +4 -4
- package/dist/server/app-client-reference-preloader.d.ts +23 -0
- package/dist/server/app-client-reference-preloader.js +30 -7
- package/dist/server/app-fallback-renderer.js +2 -1
- package/dist/server/app-middleware.d.ts +6 -0
- package/dist/server/app-middleware.js +8 -10
- package/dist/server/app-optimistic-routing.d.ts +8 -10
- package/dist/server/app-optimistic-routing.js +4 -95
- package/dist/server/app-page-boundary-render.js +8 -1
- package/dist/server/app-page-cache-render.js +1 -0
- package/dist/server/app-page-dispatch.js +2 -0
- package/dist/server/app-page-render.js +3 -1
- package/dist/server/app-page-rsc-render-status.d.ts +1 -0
- package/dist/server/app-page-rsc-render-status.js +1 -0
- package/dist/server/app-page-search-params-observation.d.ts +1 -4
- package/dist/server/app-page-search-params-observation.js +8 -10
- package/dist/server/app-page-stream.d.ts +7 -0
- package/dist/server/app-page-stream.js +1 -0
- package/dist/server/app-pages-bridge.d.ts +2 -1
- package/dist/server/app-pages-bridge.js +11 -7
- package/dist/server/app-prerender-static-params.js +3 -2
- package/dist/server/app-render-dependency.js +42 -2
- package/dist/server/app-request-stage-independent-entry.js +2 -2
- package/dist/server/app-response-stage-entry.js +3 -3
- package/dist/server/app-route-handler-dispatch.js +3 -1
- package/dist/server/app-route-handler-execution.d.ts +2 -0
- package/dist/server/app-route-handler-execution.js +9 -2
- package/dist/server/app-route-handler-middleware-context.d.ts +2 -1
- package/dist/server/app-route-handler-middleware-context.js +3 -3
- package/dist/server/app-route-handler-response.d.ts +2 -1
- package/dist/server/app-route-handler-response.js +2 -2
- package/dist/server/app-route-handler-runtime.d.ts +2 -0
- package/dist/server/app-route-handler-runtime.js +20 -3
- package/dist/server/app-route-module-loader.js +7 -6
- package/dist/server/app-route-tree.d.ts +20 -0
- package/dist/server/app-route-tree.js +18 -0
- package/dist/server/app-router-entry.js +2 -2
- package/dist/server/app-rsc-combined-handler.js +8 -1
- package/dist/server/app-rsc-errors.d.ts +7 -0
- package/dist/server/app-rsc-errors.js +9 -2
- package/dist/server/app-rsc-handler.d.ts +4 -1
- package/dist/server/app-rsc-handler.js +146 -130
- package/dist/server/app-rsc-response-finalizer.d.ts +5 -0
- package/dist/server/app-rsc-response-finalizer.js +4 -1
- package/dist/server/app-rsc-response-stage.js +3 -1
- package/dist/server/app-rsc-route-matching.d.ts +1 -0
- package/dist/server/app-rsc-route-matching.js +1 -1
- package/dist/server/app-server-action-execution.d.ts +23 -6
- package/dist/server/app-server-action-execution.js +50 -56
- package/dist/server/app-ssr-entry.d.ts +6 -0
- package/dist/server/app-ssr-entry.js +16 -7
- package/dist/server/app-ssr-stream.d.ts +7 -0
- package/dist/server/app-ssr-stream.js +13 -1
- package/dist/server/config-headers.js +12 -3
- package/dist/server/isolate-shared-promise.d.ts +10 -0
- package/dist/server/isolate-shared-promise.js +33 -0
- package/dist/server/isr-cache.js +3 -2
- package/dist/server/middleware-matcher-pattern.d.ts +0 -1
- package/dist/server/middleware-matcher-pattern.js +11 -140
- package/dist/server/middleware-matcher.js +5 -6
- package/dist/server/middleware-response-headers.d.ts +31 -0
- package/dist/server/middleware-response-headers.js +57 -1
- package/dist/server/middleware-runtime.js +26 -13
- package/dist/server/navigation-planner.d.ts +2 -2
- package/dist/server/nitro-public-files-plugin.d.ts +38 -0
- package/dist/server/nitro-public-files-plugin.js +106 -0
- package/dist/server/nitro-public-files.d.ts +21 -0
- package/dist/server/nitro-public-files.js +39 -0
- package/dist/server/pages-asset-tags.d.ts +5 -0
- package/dist/server/pages-asset-tags.js +9 -2
- package/dist/server/pages-client-assets.d.ts +4 -1
- package/dist/server/pages-i18n.d.ts +18 -0
- package/dist/server/pages-i18n.js +18 -2
- package/dist/server/pages-node-compat.d.ts +5 -3
- package/dist/server/pages-node-compat.js +35 -16
- package/dist/server/pages-request-pipeline.d.ts +6 -1
- package/dist/server/pages-request-pipeline.js +31 -35
- package/dist/server/pages-request-stage-entry.js +16 -6
- package/dist/server/pages-response-stage-entry.js +1 -1
- package/dist/server/pages-revalidate.js +1 -0
- package/dist/server/pages-router-entry.js +1 -1
- package/dist/server/prod-server.d.ts +2 -2
- package/dist/server/prod-server.js +71 -33
- package/dist/server/proxy-trust.d.ts +6 -0
- package/dist/server/proxy-trust.js +12 -6
- package/dist/server/request-pipeline.d.ts +5 -0
- package/dist/server/request-pipeline.js +12 -1
- package/dist/server/server-reference-loader.js +10 -1
- package/dist/server/static-file-cache.js +15 -3
- package/dist/server/worker-utils.js +1 -1
- package/dist/shims/cache-handler.js +3 -2
- package/dist/shims/cache-request-state.d.ts +40 -3
- package/dist/shims/cache-request-state.js +70 -5
- package/dist/shims/cache-runtime.js +47 -19
- package/dist/shims/cache.d.ts +2 -2
- package/dist/shims/cache.js +17 -8
- package/dist/shims/cdn-purge-routes.d.ts +24 -0
- package/dist/shims/cdn-purge-routes.js +77 -0
- package/dist/shims/dynamic.js +17 -4
- package/dist/shims/form.js +8 -3
- package/dist/shims/image-props.d.ts +93 -0
- package/dist/shims/image-props.js +323 -0
- package/dist/shims/image.d.ts +4 -18
- package/dist/shims/image.js +4 -319
- package/dist/shims/internal/headers-state.d.ts +7 -0
- package/dist/shims/internal/headers-state.js +14 -2
- package/dist/shims/internal/hybrid-client-route-owner-direct.d.ts +4 -1
- package/dist/shims/internal/hybrid-client-route-owner-direct.js +31 -14
- package/dist/shims/internal/hybrid-client-route-owner.js +8 -8
- package/dist/shims/link.js +5 -3
- package/dist/shims/navigation.d.ts +1 -1
- package/dist/shims/navigation.js +17 -5
- package/dist/shims/og.js +35 -10
- package/dist/shims/request-context.d.ts +13 -0
- package/dist/shims/router.js +19 -40
- package/dist/shims/server.d.ts +31 -4
- package/dist/shims/server.js +58 -10
- package/dist/shims/thenable-params.d.ts +6 -1
- package/dist/shims/thenable-params.js +11 -2
- package/dist/shims/unified-request-context.d.ts +5 -0
- package/dist/shims/unified-request-context.js +2 -0
- package/dist/utils/asset-prefix.d.ts +7 -0
- package/dist/utils/asset-prefix.js +10 -1
- package/dist/utils/client-build-manifest.js +12 -5
- package/dist/utils/client-runtime-metadata.d.ts +1 -0
- package/dist/utils/client-runtime-metadata.js +15 -2
- package/dist/utils/lazy-chunks.d.ts +4 -0
- package/dist/utils/manifest-paths.d.ts +14 -0
- package/dist/utils/manifest-paths.js +22 -1
- package/dist/utils/path-to-regexp.d.ts +21 -0
- package/dist/{server/middleware-path-to-regexp.js → utils/path-to-regexp.js} +14 -11
- package/dist/utils/plugin-react-version.d.ts +15 -0
- package/dist/utils/plugin-react-version.js +64 -0
- package/dist/utils/public-routes.d.ts +2 -0
- package/dist/utils/public-routes.js +18 -7
- package/package.json +1 -1
- package/dist/server/middleware-path-to-regexp.d.ts +0 -13
- package/dist/utils/regex-safety.d.ts +0 -17
- package/dist/utils/regex-safety.js +0 -789
|
@@ -11,6 +11,11 @@ type FinalizeAppRscResponseOptions = {
|
|
|
11
11
|
* default-locale URLs (issue #1336, item 4).
|
|
12
12
|
*/
|
|
13
13
|
i18nConfig: NextI18nConfig | null;
|
|
14
|
+
/**
|
|
15
|
+
* Whether the root keeps its trailing slash when the default locale is
|
|
16
|
+
* spliced in (`shouldRestoreLocaleRootTrailingSlash` on the raw request).
|
|
17
|
+
*/
|
|
18
|
+
restoreLocaleRootTrailingSlash?: boolean;
|
|
14
19
|
/**
|
|
15
20
|
* Original pre-middleware request context.
|
|
16
21
|
* Next.js evaluates config header has/missing conditions against the
|
|
@@ -32,7 +32,10 @@ async function applyAppRscConfigHeaders(headers, request, options) {
|
|
|
32
32
|
let pathname = url.pathname;
|
|
33
33
|
const hadBasePath = !options.basePath || hasBasePath(pathname, options.basePath);
|
|
34
34
|
pathname = stripBasePath(pathname, options.basePath);
|
|
35
|
-
const matchPathname = options.i18nConfig ? normalizeDefaultLocalePathname(pathname, options.i18nConfig, {
|
|
35
|
+
const matchPathname = options.i18nConfig ? normalizeDefaultLocalePathname(pathname, options.i18nConfig, {
|
|
36
|
+
hostname: url.hostname,
|
|
37
|
+
restoreRootTrailingSlash: options.restoreLocaleRootTrailingSlash
|
|
38
|
+
}) : pathname;
|
|
36
39
|
const { applyConfigHeadersToResponse } = await import("./config-headers.js");
|
|
37
40
|
applyConfigHeadersToResponse(headers, {
|
|
38
41
|
configHeaders: options.configHeaders,
|
|
@@ -71,9 +71,10 @@ function rematchAppWorkerResponseStageRoute(options, normalized, props) {
|
|
|
71
71
|
async function renderAppWorkerResponseStage(options, rawRequest, ctx, props, stageOptions) {
|
|
72
72
|
if (props.protocolVersion !== 11 || props.buildId !== options.buildId) return new Response("Incompatible vinext App response stage", { status: 409 });
|
|
73
73
|
if (props.kind === "app-full-request") return new Response("Invalid vinext App response stage", { status: 400 });
|
|
74
|
+
const rawRequestSignal = rawRequest.signal;
|
|
74
75
|
options.registerCacheAdapters();
|
|
75
|
-
await options.ensureInstrumentation?.();
|
|
76
76
|
const executionContext = isExecutionContextLike(ctx) ? ctx : getRequestExecutionContext() ?? null;
|
|
77
|
+
await options.ensureInstrumentation?.(executionContext);
|
|
77
78
|
const filteredHeaders = executionContext?.isInternalPagesRevalidation ? new Headers(rawRequest.headers) : filterInternalHeaders(rawRequest.headers);
|
|
78
79
|
filteredHeaders.delete(VINEXT_REVALIDATE_HOST_HEADER);
|
|
79
80
|
const request = cloneRequestWithHeaders(rawRequest, filteredHeaders);
|
|
@@ -103,6 +104,7 @@ async function renderAppWorkerResponseStage(options, rawRequest, ctx, props, sta
|
|
|
103
104
|
const requestContext = createRequestContext({
|
|
104
105
|
headersContext,
|
|
105
106
|
executionContext,
|
|
107
|
+
requestSignal: executionContext?.requestSignal ?? rawRequestSignal,
|
|
106
108
|
unstableCacheRevalidation: "background"
|
|
107
109
|
});
|
|
108
110
|
const middlewareContext = {
|
|
@@ -120,6 +120,7 @@ type AppRscInterceptLookupEntry = {
|
|
|
120
120
|
slotId: string | null;
|
|
121
121
|
targetRouteGraphId: string | null;
|
|
122
122
|
};
|
|
123
|
+
export declare function isAppRouteHandlerRoute(route: Pick<AppRscRouteForMatching, "__loadRouteHandler" | "routeHandler">): boolean;
|
|
123
124
|
/**
|
|
124
125
|
* @param decodedPathnamePatterns Static page patterns that answer their
|
|
125
126
|
* segment-decoded pathname (built by `buildAppDecodedPathnamePatterns`).
|
|
@@ -282,4 +282,4 @@ function pickPatternParams(params, patternParts) {
|
|
|
282
282
|
return picked;
|
|
283
283
|
}
|
|
284
284
|
//#endregion
|
|
285
|
-
export { SIBLING_PAGE_INTERCEPT_SLOT_KEY, createAppRscRouteMatcher };
|
|
285
|
+
export { SIBLING_PAGE_INTERCEPT_SLOT_KEY, createAppRscRouteMatcher, isAppRouteHandlerRoute };
|
|
@@ -19,8 +19,10 @@ type AppServerActionErrorReporter = (error: unknown, request: {
|
|
|
19
19
|
renderSource: "react-server-components-payload";
|
|
20
20
|
revalidateReason: undefined;
|
|
21
21
|
}) => void;
|
|
22
|
-
|
|
23
|
-
type
|
|
22
|
+
/** React's `decodeAction()`; bound references resolve through `serverManifest`. */
|
|
23
|
+
type AppServerActionDecoder = (body: FormData, serverManifest: object) => Promise<unknown>;
|
|
24
|
+
/** React's `decodeFormState()`, which decodes the bound references again. */
|
|
25
|
+
type AppServerActionFormStateDecoder = (actionResult: unknown, body: FormData, serverManifest: object) => Promise<ReactFormState | undefined>;
|
|
24
26
|
type ReadFormDataWithLimit = (request: Request, maxBytes: number) => Promise<FormData>;
|
|
25
27
|
type ReadBodyWithLimit = (request: Request, maxBytes: number) => Promise<string>;
|
|
26
28
|
type AppServerActionReturnValue = {
|
|
@@ -136,19 +138,22 @@ export type HandleProgressiveServerActionRequestOptions = {
|
|
|
136
138
|
cleanPathname: string;
|
|
137
139
|
clearRequestContext: () => void;
|
|
138
140
|
contentType: string;
|
|
141
|
+
/** plugin-rsc's server manifest, which resolves any export of a server-reference module. */
|
|
142
|
+
createServerManifest: () => object;
|
|
139
143
|
decodeAction: AppServerActionDecoder;
|
|
140
144
|
decodeFormState: AppServerActionFormStateDecoder;
|
|
141
145
|
getAndClearPendingCookies: () => string[];
|
|
142
146
|
getDraftModeCookieHeader: () => string | null | undefined;
|
|
143
147
|
forwardAction?: (actionId: string) => Promise<Response | null>;
|
|
144
|
-
|
|
148
|
+
/** Whether these Server Actions belong to the current route. */
|
|
149
|
+
validateActionReferences: (actionIds: readonly string[]) => boolean;
|
|
145
150
|
/**
|
|
146
151
|
* Whether the posted-to route resolves to an App Router *page* (as opposed to
|
|
147
152
|
* a route handler or no match). Multipart form POSTs to a page are always
|
|
148
153
|
* server-action attempts in Next.js, so a body that decodes to no action must
|
|
149
154
|
* surface as 404 action-not-found rather than rendering the page. Route
|
|
150
|
-
* handlers
|
|
151
|
-
*
|
|
155
|
+
* handlers and Pages routes never reach this dispatch (#3886); unmatched
|
|
156
|
+
* routes fall through. See issue #1340.
|
|
152
157
|
*/
|
|
153
158
|
hasPageRoute: boolean;
|
|
154
159
|
maxActionBodySize: number;
|
|
@@ -176,7 +181,13 @@ export type HandleServerActionRscRequestOptions<TElement, TRoute extends AppServ
|
|
|
176
181
|
createPayloadRouteId: (pathname: string, interceptionContext: string | null) => string;
|
|
177
182
|
createRscOnErrorHandler: (request: Request, pathname: string, pattern: string, overrides?: AppRenderErrorContextOverrides) => (error: unknown) => unknown;
|
|
178
183
|
createTemporaryReferenceSet: () => TTemporaryReferences;
|
|
179
|
-
|
|
184
|
+
/** plugin-rsc's server manifest, which resolves any export of a server-reference module. */
|
|
185
|
+
createServerManifest: () => object;
|
|
186
|
+
/**
|
|
187
|
+
* React's `decodeReply()`. Every server reference in the arguments resolves
|
|
188
|
+
* through `serverManifest`, so the manifest bounds which functions can decode.
|
|
189
|
+
*/
|
|
190
|
+
decodeReply: (body: string | FormData, serverManifest: object, options: DecodeServerActionReplyOptions<TTemporaryReferences>) => Promise<unknown[]> | unknown[];
|
|
180
191
|
draftModeSecret: string;
|
|
181
192
|
/**
|
|
182
193
|
* Hydrate a route's lazy page/route-handler modules before reading
|
|
@@ -192,6 +203,12 @@ export type HandleServerActionRscRequestOptions<TElement, TRoute extends AppServ
|
|
|
192
203
|
isEdgeRuntime?: boolean;
|
|
193
204
|
isRscRequest: boolean;
|
|
194
205
|
loadServerAction: (actionId: string) => Promise<unknown>;
|
|
206
|
+
/**
|
|
207
|
+
* Whether these Server Actions belong to the current route. Next.js resolves
|
|
208
|
+
* argument references through the current page's server module map, so an
|
|
209
|
+
* action owned by another route must not decode into a callable function.
|
|
210
|
+
*/
|
|
211
|
+
validateActionReferences: (actionIds: readonly string[]) => boolean;
|
|
195
212
|
/**
|
|
196
213
|
* Match an action redirect target. Must use *request* route identity — the
|
|
197
214
|
* raw, undecoded pathname — because the target is rendered as if the client
|
|
@@ -9,6 +9,8 @@ import { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS
|
|
|
9
9
|
import { internalServerErrorResponse, payloadTooLargeResponse } from "./http-error-responses.js";
|
|
10
10
|
import { attachRequestCfMetadata, validateCsrfOrigin, validateServerActionPayload } from "./request-pipeline.js";
|
|
11
11
|
import { splitPathSegments } from "../routing/utils.js";
|
|
12
|
+
import { getRequestExecutionContext } from "../shims/request-context.js";
|
|
13
|
+
import { shareIsolatePromise } from "./isolate-shared-promise.js";
|
|
12
14
|
import { APP_RSC_RENDER_MODE_NAVIGATION } from "./app-rsc-render-mode.js";
|
|
13
15
|
import { isDraftModeRequest, setHeadersContext } from "../shims/headers.js";
|
|
14
16
|
import { _drainPendingRevalidations, getAndClearActionRevalidationKind } from "../shims/cache-request-state.js";
|
|
@@ -43,50 +45,47 @@ import { markAppRscResponseConfigHeadersApplied } from "./app-rsc-response-final
|
|
|
43
45
|
*
|
|
44
46
|
* The result is awaited inside the callback because `decodeReply()` returns a
|
|
45
47
|
* lazy thenable that only resolves (and imports) its references once awaited.
|
|
48
|
+
*
|
|
49
|
+
* The imports are shared with every later request that names the same modules,
|
|
50
|
+
* and the server-reference loader cannot see this request's execution context
|
|
51
|
+
* once it has been left, so the request is kept alive here until they settle.
|
|
52
|
+
* From here a request that starts a load cannot be told apart from one that
|
|
53
|
+
* joins it, so each keeps its own decode alive; it awaits that decode anyway.
|
|
46
54
|
*/
|
|
47
55
|
function loadOutsideRequestScopes(load) {
|
|
48
|
-
|
|
56
|
+
const ctx = getRequestExecutionContext();
|
|
57
|
+
return shareIsolatePromise(runOutsideRequestScopes(async () => await load()), ctx);
|
|
49
58
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const rootMetadata = chunks.get("0");
|
|
64
|
-
if (typeof rootMetadata !== "object" || rootMetadata === null || typeof Reflect.get(rootMetadata, "id") !== "string") return null;
|
|
65
|
-
const actionId = Reflect.get(rootMetadata, "id");
|
|
66
|
-
const referencedActionIds = /* @__PURE__ */ new Set([actionId]);
|
|
67
|
-
for (const chunk of chunks.values()) {
|
|
68
|
-
const values = [chunk];
|
|
69
|
-
while (values.length > 0) {
|
|
70
|
-
const value = values.pop();
|
|
71
|
-
if (typeof value === "string") {
|
|
72
|
-
if (!value.startsWith("$h")) continue;
|
|
73
|
-
const [encodedChunkId, ...path] = value.slice(2).split(":");
|
|
74
|
-
const chunkId = Number.parseInt(encodedChunkId, 16);
|
|
75
|
-
if (!Number.isSafeInteger(chunkId) || chunkId < 0) return null;
|
|
76
|
-
let metadata = chunks.get(String(chunkId));
|
|
77
|
-
for (const segment of path) {
|
|
78
|
-
if (typeof metadata !== "object" || metadata === null || !Object.hasOwn(metadata, segment)) return null;
|
|
79
|
-
metadata = Reflect.get(metadata, segment);
|
|
80
|
-
}
|
|
81
|
-
if (typeof metadata !== "object" || metadata === null || typeof Reflect.get(metadata, "id") !== "string") return null;
|
|
82
|
-
referencedActionIds.add(Reflect.get(metadata, "id"));
|
|
83
|
-
} else if (typeof value === "object" && value !== null) values.push(...Object.values(value));
|
|
84
|
-
}
|
|
59
|
+
/**
|
|
60
|
+
* Read the action a bound `$ACTION_REF_` submission names, which Next.js checks
|
|
61
|
+
* before decoding (`areAllActionIdsValid()`). The bound arguments are left to
|
|
62
|
+
* React, which resolves their references through the route-scoped manifest.
|
|
63
|
+
*/
|
|
64
|
+
function readBoundProgressiveActionId(body, referencePrefix) {
|
|
65
|
+
const descriptors = body.getAll(`$ACTION_${referencePrefix}:0`);
|
|
66
|
+
if (descriptors.length !== 1 || typeof descriptors[0] !== "string") return null;
|
|
67
|
+
let descriptor;
|
|
68
|
+
try {
|
|
69
|
+
descriptor = JSON.parse(descriptors[0]);
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
85
72
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
73
|
+
const actionId = typeof descriptor === "object" && descriptor !== null ? Reflect.get(descriptor, "id") : void 0;
|
|
74
|
+
return typeof actionId === "string" ? actionId : null;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Scope the manifest React resolves server references through to the current
|
|
78
|
+
* route's Server Actions, as Next.js scopes its server module map to the
|
|
79
|
+
* current page. React then fails to resolve any other id wherever a payload
|
|
80
|
+
* carries it, so that module never loads. A denied lookup throws because React
|
|
81
|
+
* retries a missing `module#a#b` as module `module#a` with export `b`, which
|
|
82
|
+
* would reach a non-action export of an owned module.
|
|
83
|
+
*/
|
|
84
|
+
function createRouteServerManifest(manifest, validateActionReferences) {
|
|
85
|
+
return new Proxy(manifest, { get(target, id, receiver) {
|
|
86
|
+
if (typeof id === "string" && !validateActionReferences([id])) throw new Error(`Server reference "${id}" is not a Server Action of this route.`);
|
|
87
|
+
return Reflect.get(target, id, receiver);
|
|
88
|
+
} });
|
|
90
89
|
}
|
|
91
90
|
function prepareActionPageRerenderContext(options) {
|
|
92
91
|
if (options.dynamicConfig === "force-static" || options.dynamicConfig === "error") {
|
|
@@ -572,32 +571,26 @@ async function handleProgressiveServerActionRequest(options) {
|
|
|
572
571
|
let actionKey = null;
|
|
573
572
|
for (const key of body.keys()) if (key.startsWith("$ACTION_ID_") || key.startsWith("$ACTION_REF_")) actionKey = key;
|
|
574
573
|
let directActionId = null;
|
|
575
|
-
let referencedActionIds = [];
|
|
576
574
|
let invalidDirectActionReference = false;
|
|
577
|
-
if (actionKey?.startsWith("$ACTION_ID_"))
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
const referencePrefix = actionKey.slice(12);
|
|
582
|
-
const reference = parseBoundProgressiveActionReference(body, referencePrefix);
|
|
583
|
-
if (reference) {
|
|
584
|
-
directActionId = reference.actionId;
|
|
585
|
-
referencedActionIds = reference.referencedActionIds;
|
|
586
|
-
} else invalidDirectActionReference = true;
|
|
575
|
+
if (actionKey?.startsWith("$ACTION_ID_")) directActionId = actionKey.slice(11);
|
|
576
|
+
else if (actionKey?.startsWith("$ACTION_REF_")) {
|
|
577
|
+
directActionId = readBoundProgressiveActionId(body, actionKey.slice(12));
|
|
578
|
+
invalidDirectActionReference = directActionId === null;
|
|
587
579
|
}
|
|
588
580
|
if (invalidDirectActionReference) return createActionNotFoundResponse(null, {
|
|
589
581
|
clearRequestContext: options.clearRequestContext,
|
|
590
582
|
getAndClearPendingCookies: options.getAndClearPendingCookies
|
|
591
583
|
});
|
|
592
|
-
if (directActionId && options.forwardAction) {
|
|
584
|
+
if (directActionId !== null && options.forwardAction) {
|
|
593
585
|
const forwardResponse = await options.forwardAction(directActionId);
|
|
594
586
|
if (forwardResponse) return forwardResponse;
|
|
595
587
|
}
|
|
596
|
-
if (
|
|
588
|
+
if (directActionId !== null && !options.validateActionReferences([directActionId])) return createActionNotFoundResponse(directActionId, {
|
|
597
589
|
clearRequestContext: options.clearRequestContext,
|
|
598
590
|
getAndClearPendingCookies: options.getAndClearPendingCookies
|
|
599
591
|
});
|
|
600
|
-
const
|
|
592
|
+
const serverManifest = createRouteServerManifest(options.createServerManifest(), options.validateActionReferences);
|
|
593
|
+
const action = await loadOutsideRequestScopes(() => options.decodeAction(body, serverManifest));
|
|
601
594
|
if (!isAppServerActionFunction(action)) {
|
|
602
595
|
if (options.hasPageRoute) return createActionNotFoundResponse(null, {
|
|
603
596
|
clearRequestContext: options.clearRequestContext,
|
|
@@ -676,7 +669,7 @@ async function handleProgressiveServerActionRequest(options) {
|
|
|
676
669
|
};
|
|
677
670
|
return {
|
|
678
671
|
kind: "form-state",
|
|
679
|
-
formState: await loadOutsideRequestScopes(() => options.decodeFormState(actionResult, body)) ?? null,
|
|
672
|
+
formState: await loadOutsideRequestScopes(() => options.decodeFormState(actionResult, body, serverManifest)) ?? null,
|
|
680
673
|
pendingCookies: actionPendingCookies,
|
|
681
674
|
draftCookie: actionDraftCookie,
|
|
682
675
|
revalidationKind
|
|
@@ -821,8 +814,9 @@ async function handleServerActionRscRequest(options) {
|
|
|
821
814
|
});
|
|
822
815
|
action = loadedAction;
|
|
823
816
|
}
|
|
817
|
+
const serverManifest = createRouteServerManifest(options.createServerManifest(), options.validateActionReferences);
|
|
824
818
|
const temporaryReferences = options.createTemporaryReferenceSet();
|
|
825
|
-
const args = await loadOutsideRequestScopes(() => options.decodeReply(body, { temporaryReferences }));
|
|
819
|
+
const args = await loadOutsideRequestScopes(() => options.decodeReply(body, serverManifest, { temporaryReferences }));
|
|
826
820
|
let returnValue;
|
|
827
821
|
let actionRedirect = null;
|
|
828
822
|
let actionStatus = 200;
|
|
@@ -25,6 +25,12 @@ export declare function handleSsr(rscStream: ReadableStream<Uint8Array>, navCont
|
|
|
25
25
|
value: Promise<ArrayBuffer> | null;
|
|
26
26
|
};
|
|
27
27
|
pprFallbackShellSignal?: AbortSignal;
|
|
28
|
+
/**
|
|
29
|
+
* Pattern of the route this render shows, whose client references load
|
|
30
|
+
* before it starts, or null for a render without a route (a route-miss
|
|
31
|
+
* 404). Omitted, every client reference loads first.
|
|
32
|
+
*/
|
|
33
|
+
preloadRoutePattern?: string | null;
|
|
28
34
|
formState?: ReactFormState | null;
|
|
29
35
|
basePath?: string;
|
|
30
36
|
/**
|
|
@@ -15,14 +15,15 @@ import "./app-elements.js";
|
|
|
15
15
|
import { ServerInsertedHTMLContext, clearServerInsertedHTML, getBfcacheIdMapContext, registerServerInsertedHTMLCallback, renderServerInsertedHTML, setNavigationContext } from "../shims/navigation-context-state.js";
|
|
16
16
|
import "../shims/navigation-server.js";
|
|
17
17
|
import DefaultGlobalError from "../shims/default-global-error.js";
|
|
18
|
-
import { BfcacheIdentityMapContext
|
|
18
|
+
import { BfcacheIdentityMapContext } from "../shims/slot.js";
|
|
19
19
|
import { getNextErrorDigest } from "./next-error-digest.js";
|
|
20
20
|
import { createSsrErrorMetaRenderer } from "./app-ssr-error-meta.js";
|
|
21
21
|
import { createRenderStreamCancellation, isAppRenderAbortError } from "./app-render-abort-error.js";
|
|
22
22
|
import { RSC_FORM_STATE_GLOBAL } from "../client/browser-globals.js";
|
|
23
|
-
import { createNavigationRuntimeRenderedSearchScript, createNavigationRuntimeRscMetadataScript, createRscEmbedTransform, createTickBufferedTransform, waitAtLeastOneReactRenderTask } from "./app-ssr-stream.js";
|
|
23
|
+
import { createNavigationRuntimeRenderedSearchScript, createNavigationRuntimeRscCompatibilityScript, createNavigationRuntimeRscMetadataScript, createRscEmbedTransform, createTickBufferedTransform, waitAtLeastOneReactRenderTask } from "./app-ssr-stream.js";
|
|
24
24
|
import { createInitialBfcacheMaps } from "./app-bfcache-identity.js";
|
|
25
|
-
import {
|
|
25
|
+
import { createAppRouteTree } from "./app-route-tree.js";
|
|
26
|
+
import { createClientReferencePreloader, resolveRouteClientReferences } from "./app-client-reference-preloader.js";
|
|
26
27
|
import { createClientPageSsrSearchParamsSource } from "./app-page-search-params-observation.js";
|
|
27
28
|
import { runWithRootParamsScope } from "../shims/root-params.js";
|
|
28
29
|
import { runWithNavigationContext } from "../shims/navigation-state.js";
|
|
@@ -36,7 +37,7 @@ import { Fragment, createElement, use } from "react";
|
|
|
36
37
|
import { renderToReadableStream, renderToStaticMarkup } from "react-dom/server.edge";
|
|
37
38
|
import { preinitModule } from "react-dom";
|
|
38
39
|
import { createFromReadableStream } from "@vitejs/plugin-rsc/ssr";
|
|
39
|
-
import clientReferences from "virtual:vite-rsc/client-references";
|
|
40
|
+
import clientReferences, { routeClientReferences } from "virtual:vite-rsc/client-references";
|
|
40
41
|
import pagesClientAssets from "virtual:vinext-pages-client-assets";
|
|
41
42
|
//#region src/server/app-ssr-entry.ts
|
|
42
43
|
setPagesClientAssets(pagesClientAssets);
|
|
@@ -164,7 +165,11 @@ function buildHeadInjectionHtml(navContext, bootstrapModuleUrl, formState, inser
|
|
|
164
165
|
pathname: navContext.pathname,
|
|
165
166
|
searchParams: [...navContext.searchParams.entries()]
|
|
166
167
|
};
|
|
167
|
-
|
|
168
|
+
const rscMetadataScript = createInlineScriptTag(createNavigationRuntimeRscMetadataScript(navContext.params, navPayload, dynamicStaleTimeSeconds, searchParamsFromBrowser), scriptNonce);
|
|
169
|
+
const compatibilityScript = createNavigationRuntimeRscCompatibilityScript();
|
|
170
|
+
const rscCompatibilityScript = compatibilityScript === "" ? "" : createInlineScriptTag(compatibilityScript, scriptNonce);
|
|
171
|
+
const formStateScript = formState === null ? "" : createInlineScriptTag("self[" + safeJsonStringify(RSC_FORM_STATE_GLOBAL) + "]=" + safeJsonStringify(formState), scriptNonce);
|
|
172
|
+
return rscMetadataScript + rscCompatibilityScript + formStateScript + buildModulePreloadHtml(bootstrapModuleUrl, scriptNonce) + insertedHTML + fontHTML;
|
|
168
173
|
}
|
|
169
174
|
function requireNavigationContext(navContext) {
|
|
170
175
|
if (!navContext) throw new Error("App SSR requires navigation context for BFCache state keys");
|
|
@@ -189,7 +194,7 @@ async function handleSsr(rscStream, navContext, fontData, options) {
|
|
|
189
194
|
isPprFallbackShell: options?.pprFallbackShellSignal !== void 0
|
|
190
195
|
})
|
|
191
196
|
};
|
|
192
|
-
await clientReferencePreloader.preload();
|
|
197
|
+
await clientReferencePreloader.preload(options?.pprFallbackShellSignal ? void 0 : resolveRouteClientReferences(routeClientReferences, options?.preloadRoutePattern));
|
|
193
198
|
setNavigationContext(ssrNavigationContext);
|
|
194
199
|
clearServerInsertedHTML();
|
|
195
200
|
const cleanup = () => {
|
|
@@ -235,7 +240,11 @@ async function handleSsr(rscStream, navContext, fontData, options) {
|
|
|
235
240
|
elements,
|
|
236
241
|
metadata
|
|
237
242
|
});
|
|
238
|
-
const routeTree =
|
|
243
|
+
const routeTree = createAppRouteTree({
|
|
244
|
+
elements,
|
|
245
|
+
historyCommit: null,
|
|
246
|
+
routeId: metadata.routeId
|
|
247
|
+
});
|
|
239
248
|
const identityMapTree = createElement(BfcacheIdentityMapContext.Provider, { value: bfcacheMaps.identities }, routeTree);
|
|
240
249
|
return BfcacheIdMapContext ? createElement(BfcacheIdMapContext.Provider, { value: bfcacheMaps.bfcacheIds }, identityMapTree) : identityMapTree;
|
|
241
250
|
}
|
|
@@ -32,6 +32,13 @@ export declare function createNavigationRuntimeRscMetadataScript(params: Record<
|
|
|
32
32
|
pathname: string;
|
|
33
33
|
searchParams: [string, string][];
|
|
34
34
|
}, dynamicStaleTimeSeconds?: number, searchParamsFromBrowser?: boolean): string;
|
|
35
|
+
/**
|
|
36
|
+
* The compatibility ID of the server rendering this page. The browser compares
|
|
37
|
+
* RSC responses against it, so it is sent with the page rather than inlined
|
|
38
|
+
* into the client bundle, where a per-build value would rename chunks on every
|
|
39
|
+
* build. Static export skips the check, so its pages omit it.
|
|
40
|
+
*/
|
|
41
|
+
export declare function createNavigationRuntimeRscCompatibilityScript(): string;
|
|
35
42
|
/**
|
|
36
43
|
* The query a client page read during SSR, for a render that turned dynamic
|
|
37
44
|
* after its head told the browser to read the query from its own URL.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createInlineScriptTag, escapeHtmlAttr, htmlTokenListContains, safeJsonStringify } from "./html.js";
|
|
2
|
+
import { getVinextRscCompatibilityId } from "./app-rsc-cache-busting.js";
|
|
2
3
|
import { bytesToBase64, concatUint8Arrays } from "./app-rsc-embedded-chunks.js";
|
|
3
4
|
import { NAVIGATION_RUNTIME_SYMBOL_DESCRIPTION } from "../client/browser-globals.js";
|
|
4
5
|
//#region src/server/app-ssr-stream.ts
|
|
@@ -14,6 +15,17 @@ function createNavigationRuntimeRscMetadataScript(params, nav, dynamicStaleTimeS
|
|
|
14
15
|
return "Object.assign(" + navigationRuntimeRscBootstrapExpression() + ",{params:" + safeJsonStringify(params) + ",nav:" + safeJsonStringify(nav) + (searchParamsFromBrowser === void 0 ? "" : ",searchParamsFromBrowser:" + safeJsonStringify(searchParamsFromBrowser)) + (dynamicStaleTimeSeconds === void 0 ? "" : ",dynamicStaleTimeSeconds:" + safeJsonStringify(dynamicStaleTimeSeconds)) + "})";
|
|
15
16
|
}
|
|
16
17
|
/**
|
|
18
|
+
* The compatibility ID of the server rendering this page. The browser compares
|
|
19
|
+
* RSC responses against it, so it is sent with the page rather than inlined
|
|
20
|
+
* into the client bundle, where a per-build value would rename chunks on every
|
|
21
|
+
* build. Static export skips the check, so its pages omit it.
|
|
22
|
+
*/
|
|
23
|
+
function createNavigationRuntimeRscCompatibilityScript() {
|
|
24
|
+
const compatibilityId = getVinextRscCompatibilityId();
|
|
25
|
+
if (compatibilityId === null || process.env.NODE_ENV === "production" && process.env.__NEXT_CONFIG_OUTPUT === "export") return "";
|
|
26
|
+
return "Object.assign(" + navigationRuntimeRscBootstrapExpression() + ",{compatibilityId:" + safeJsonStringify(compatibilityId) + "})";
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
17
29
|
* The query a client page read during SSR, for a render that turned dynamic
|
|
18
30
|
* after its head told the browser to read the query from its own URL.
|
|
19
31
|
*/
|
|
@@ -474,4 +486,4 @@ function createTickBufferedTransform(rscEmbed, injectHTML = "", injectAfterHeadO
|
|
|
474
486
|
});
|
|
475
487
|
}
|
|
476
488
|
//#endregion
|
|
477
|
-
export { createNavigationRuntimeRenderedSearchScript, createNavigationRuntimeRscMetadataScript, createRscEmbedTransform, createTickBufferedTransform, fixPreloadAs, navigationRuntimeRscBootstrapExpression, waitAtLeastOneReactRenderTask };
|
|
489
|
+
export { createNavigationRuntimeRenderedSearchScript, createNavigationRuntimeRscCompatibilityScript, createNavigationRuntimeRscMetadataScript, createRscEmbedTransform, createTickBufferedTransform, fixPreloadAs, navigationRuntimeRscBootstrapExpression, waitAtLeastOneReactRenderTask };
|
|
@@ -76,10 +76,12 @@ function appendVaryHeaderRecord(headers, value) {
|
|
|
76
76
|
function applyConfigHeadersToResponse(responseHeaders, options) {
|
|
77
77
|
const matched = retainLastSingularConfigValues(matchHeaders(options.pathname, options.configHeaders, options.requestContext, options.basePathState, options.recordCacheability === false ? void 0 : markConditionalConfigHeaderCacheability));
|
|
78
78
|
if (options.recordCacheability !== false) markExplicitConfigResponseVeto(matched);
|
|
79
|
+
const configCookies = [];
|
|
79
80
|
for (const header of matched) {
|
|
80
81
|
const lowerName = header.key.toLowerCase();
|
|
81
82
|
if (lowerName === "x-vinext-special-error-status") continue;
|
|
82
|
-
if (lowerName === "
|
|
83
|
+
if (lowerName === "set-cookie") configCookies.push(header.value);
|
|
84
|
+
else if (lowerName === "link") {
|
|
83
85
|
if (options.middlewareHeaders?.get(lowerName)) continue;
|
|
84
86
|
const postConfigLink = options.appendToPostConfigLink ? responseHeaders.get(lowerName) : null;
|
|
85
87
|
responseHeaders.set(header.key, postConfigLink ? `${header.value}, ${postConfigLink}` : header.value);
|
|
@@ -88,15 +90,22 @@ function applyConfigHeadersToResponse(responseHeaders, options) {
|
|
|
88
90
|
else if (ADDITIVE_CONFIG_HEADER_NAMES.has(lowerName)) responseHeaders.append(header.key, header.value);
|
|
89
91
|
else if ((typeof options.overwriteExisting === "function" ? options.overwriteExisting(lowerName) : options.overwriteExisting?.has(lowerName)) || !responseHeaders.has(lowerName)) responseHeaders.set(header.key, header.value);
|
|
90
92
|
}
|
|
93
|
+
if (configCookies.length > 0 && !options.middlewareHeaders?.get("set-cookie")) {
|
|
94
|
+
const responseCookies = responseHeaders.getSetCookie();
|
|
95
|
+
responseHeaders.delete("set-cookie");
|
|
96
|
+
for (const cookie of [...configCookies, ...responseCookies]) responseHeaders.append("set-cookie", cookie);
|
|
97
|
+
}
|
|
91
98
|
}
|
|
92
99
|
/** Apply matched next.config.js headers to an early response header record. */
|
|
93
100
|
function applyConfigHeadersToHeaderRecord(headers, options) {
|
|
94
101
|
const matched = retainLastSingularConfigValues(matchHeaders(options.pathname, options.configHeaders, options.requestContext, options.basePathState, options.recordCacheability === false ? void 0 : markConditionalConfigHeaderCacheability));
|
|
95
102
|
if (options.recordCacheability !== false) markExplicitConfigResponseVeto(matched);
|
|
103
|
+
const middlewareSetsCookie = Boolean(options.middlewareHeaders?.get("set-cookie")) || findHeaderRecordKey(headers, "set-cookie") !== void 0;
|
|
96
104
|
for (const header of matched) {
|
|
97
105
|
const lowerName = header.key.toLowerCase();
|
|
98
|
-
if (lowerName === "set-cookie")
|
|
99
|
-
|
|
106
|
+
if (lowerName === "set-cookie") {
|
|
107
|
+
if (!middlewareSetsCookie) appendHeaderRecord(headers, lowerName, header.value);
|
|
108
|
+
} else if (lowerName === "vary") appendVaryHeaderRecord(headers, header.value);
|
|
100
109
|
else if (findHeaderRecordKey(headers, lowerName) === void 0) headers[lowerName] = header.value;
|
|
101
110
|
}
|
|
102
111
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ExecutionContextLike } from "../shims/request-context.js";
|
|
2
|
+
//#region src/server/isolate-shared-promise.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Register `promise` with the creating request's `waitUntil()` and return it.
|
|
5
|
+
*
|
|
6
|
+
* @param ctx - The creating request's execution context. Defaults to the one
|
|
7
|
+
* in scope, for callers inside `runWithExecutionContext()`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function shareIsolatePromise<T>(promise: Promise<T>, ctx?: Partial<Pick<ExecutionContextLike, "waitUntil">> | null): Promise<T>;
|
|
10
|
+
//#endregion
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { getRequestExecutionContext } from "../shims/request-context.js";
|
|
2
|
+
//#region src/server/isolate-shared-promise.ts
|
|
3
|
+
/**
|
|
4
|
+
* Keep the request that creates an isolate-shared promise alive until it
|
|
5
|
+
* settles.
|
|
6
|
+
*
|
|
7
|
+
* workerd binds the I/O a promise waits on (timers, fetches, dynamic
|
|
8
|
+
* `import()` under the legacy module registry) to the request that started it.
|
|
9
|
+
* If that request is canceled first, for example because the client navigated
|
|
10
|
+
* away or aborted a prefetch, the continuations are dropped and the promise
|
|
11
|
+
* never settles. A promise memoized for the isolate is awaited by every later
|
|
12
|
+
* request too, so they would all wait forever. `waitUntil()` keeps the creating
|
|
13
|
+
* request's context running after cancellation, so the work finishes for
|
|
14
|
+
* everyone.
|
|
15
|
+
*
|
|
16
|
+
* Call this only where the shared promise is created. Later requests that
|
|
17
|
+
* merely await it need nothing.
|
|
18
|
+
*/
|
|
19
|
+
function ignore() {}
|
|
20
|
+
/**
|
|
21
|
+
* Register `promise` with the creating request's `waitUntil()` and return it.
|
|
22
|
+
*
|
|
23
|
+
* @param ctx - The creating request's execution context. Defaults to the one
|
|
24
|
+
* in scope, for callers inside `runWithExecutionContext()`.
|
|
25
|
+
*/
|
|
26
|
+
function shareIsolatePromise(promise, ctx) {
|
|
27
|
+
(typeof ctx?.waitUntil === "function" ? ctx : getRequestExecutionContext())?.waitUntil?.(promise.then(ignore, ignore));
|
|
28
|
+
return promise;
|
|
29
|
+
}
|
|
30
|
+
const SHARE_ISOLATE_PROMISE_KEY = Symbol.for("vinext.shareIsolatePromise");
|
|
31
|
+
globalThis[SHARE_ISOLATE_PROMISE_KEY] ??= shareIsolatePromise;
|
|
32
|
+
//#endregion
|
|
33
|
+
export { shareIsolatePromise };
|
package/dist/server/isr-cache.js
CHANGED
|
@@ -4,6 +4,7 @@ import { getRequestExecutionContext } from "../shims/request-context.js";
|
|
|
4
4
|
import { isInterceptionMatchedUrlPath } from "./normalize-path.js";
|
|
5
5
|
import { reportRequestError } from "./instrumentation.js";
|
|
6
6
|
import { getCdnCacheAdapter } from "../shims/cdn-cache.js";
|
|
7
|
+
import { shareIsolatePromise } from "./isolate-shared-promise.js";
|
|
7
8
|
import { normalizeMountedSlotsHeader } from "./app-mounted-slots-header.js";
|
|
8
9
|
import { APP_RSC_RENDER_MODE_NAVIGATION, getRscRenderModeCacheVariant } from "./app-rsc-render-mode.js";
|
|
9
10
|
import { getRevalidateSecret, isOnDemandRevalidateRequest, isRevalidateSecret } from "./revalidation-request.js";
|
|
@@ -93,9 +94,9 @@ const pendingOnDemandRegenerations = _g[_PENDING_ON_DEMAND_REGEN_KEY] ??= /* @__
|
|
|
93
94
|
function coalesceOnDemandRevalidation(key, renderFn) {
|
|
94
95
|
const pending = pendingOnDemandRegenerations.get(key);
|
|
95
96
|
if (pending) return pending;
|
|
96
|
-
const promise = Promise.resolve().then(renderFn).finally(() => {
|
|
97
|
+
const promise = shareIsolatePromise(Promise.resolve().then(renderFn).finally(() => {
|
|
97
98
|
if (pendingOnDemandRegenerations.get(key) === promise) pendingOnDemandRegenerations.delete(key);
|
|
98
|
-
});
|
|
99
|
+
}));
|
|
99
100
|
pendingOnDemandRegenerations.set(key, promise);
|
|
100
101
|
return promise;
|
|
101
102
|
}
|