next
Version:
The React Framework
120 lines (119 loc) • 5.85 kB
TypeScript
import type { InitialRSCPayload, Segment } from '../../../shared/lib/app-router-types';
import { RenderStage } from '../staged-rendering';
import type { ValidationBoundaryTracking } from './boundary-tracking';
import { type LoaderTree } from '../../lib/app-dir-module';
import type { GetDynamicParamFromSegment } from '../app-render';
import type { Instant } from '../../../build/segment-config/app/app-segment-config';
import { Readable } from 'node:stream';
import type { NextParsedUrlQuery } from '../../request-meta';
type ClientReferenceManifest = Record<string, any>;
/** Used to identify a segment. Conceptually similar to request keys in the Client Segment Cache. */
export type SegmentPath = string & {
_tag: 'SegmentPath';
};
/**
* Isomorphic to a FlightRouterState, but with extra data attached.
* Carries the segment path for each segment so we can easily get it from the cache.
* */
export type RouteTree = {
path: SegmentPath;
segment: Segment;
module: null | {
type: 'layout' | 'page';
instantConfig: Instant | null;
conventionPath: string;
createInstantStack: (() => Error) | null;
};
slots: {
[parallelRouteKey: string]: RouteTree;
} | null;
};
export type SegmentStage = RenderStage.Static | RenderStage.Runtime | RenderStage.Dynamic;
export type StageChunks = Record<SegmentStage, Uint8Array[]>;
export type StageEndTimes = {
[RenderStage.Static]: number;
[RenderStage.Runtime]: number;
};
/**
* Splits an existing staged stream (represented as arrays of chunks)
* into separate staged streams (also in arrays-of-chunks form), one for each segment.
* */
export declare function collectStagedSegmentData(fullPageChunks: StageChunks, fullPageDebugChunks: Uint8Array[] | null, startTime: number, hasRuntimePrefetch: boolean, clientReferenceManifest: ClientReferenceManifest): Promise<{
cache: SegmentCache;
payload: InitialRSCPayload;
stageEndTimes: StageEndTimes;
}>;
/**
* Creates a late-release stream for a given payload.
* When `renderSignal` is triggered, the stream will release late chunks
* to provide extra debug info.
* */
export declare function createCombinedPayloadStream(payload: InitialRSCPayload, extraChunksAbortController: AbortController, renderSignal: AbortSignal, clientReferenceManifest: ClientReferenceManifest, startTime: number, isDebugChannelEnabled: boolean): Promise<{
stream: Readable;
debugStream: Readable | null;
}>;
export type SegmentCache = {
head: SegmentCacheItem | null;
segments: Map<SegmentPath, SegmentCacheItem>;
};
type SegmentCacheItem = {
chunks: StageChunks;
debugChunks: Uint8Array[] | null;
};
/**
* Walks the LoaderTree to discover validation depth bounds.
*
* Each route group between URL segments represents a potential
* shared/new boundary in a client navigation. When a user navigates
* between sibling routes that share a route group layout, that
* layout is already mounted — its Suspense boundaries are revealed
* and don't cover new content below. By tracking the max group
* depth at each URL depth, we can iterate all possible group
* boundaries and validate that blocking code is always covered by
* Suspense in the new tree. This is conservative: some boundaries
* may not correspond to real navigations (e.g. a route group with
* no siblings), but it ensures we don't miss real violations.
*
* The max is taken across all parallel slots. When slots have
* different numbers of groups, the deepest slot determines the
* iteration range. Shallower slots simply stay entirely shared
* at group depths beyond their own group count — they run out
* of groups before reaching the boundary, so their content
* remains in the Dynamic stage.
*
* Returns an array where:
* - length = max URL depth (number of URL-consuming segments)
* - array[i] = max group depth at URL depth i (number of route group
* segments between this URL depth and the next)
*
* For example, a tree like:
* '' / (outer) / (inner) / dashboard / page
* returns [2, 0] — URL depth 0 (root) has 2 group layers before
* the next URL segment (dashboard), and URL depth 1 (dashboard) has
* 0 group layers before the leaf.
*/
export declare function discoverValidationDepths(loaderTree: LoaderTree): number[];
/**
* Builds a combined RSC payload for validation at a given URL depth.
*
* Walks the LoaderTree directly, loading modules and counting
* URL-contributing layouts. When `depth` URL segments have been
* consumed, the boundary flips from shared (dynamic stage) to new
* (static/runtime stage). As the new subtree is built, we check for
* instant configs. If none are found, returns null — no validation
* needed at this depth or deeper.
*
* This combines module loading, tree walking, config discovery, and
* payload construction into a single pass.
*/
export type ValidationPayloadResult = {
payload: InitialRSCPayload;
/** Whether errors from this payload could be ambiguous between runtime
* API access (cookies, headers) and uncached IO (connection, fetch).
* True when some segments used Static stage. False when all segments
* used Runtime stage and errors are definitively from uncached IO. */
hasAmbiguousErrors: boolean;
createInstantStack: (() => Error) | null;
};
export declare function createCombinedPayloadAtDepth(initialRSCPayload: InitialRSCPayload, cache: SegmentCache, initialLoaderTree: LoaderTree, getDynamicParamFromSegment: GetDynamicParamFromSegment, query: NextParsedUrlQuery | null, depth: number, groupDepth: number, releaseSignal: AbortSignal, boundaryState: ValidationBoundaryTracking, clientReferenceManifest: ClientReferenceManifest, stageEndTimes: StageEndTimes, useRuntimeStageForPartialSegments: boolean): Promise<ValidationPayloadResult | null>;
export {};