268 lines
15 KiB
TypeScript
268 lines
15 KiB
TypeScript
import type { FlightData, Segment as FlightRouterStateSegment } from '../../../shared/lib/app-router-types';
|
|
import { type VaryParams, type VaryParamsThenable } from '../../../shared/lib/segment-cache/vary-params-decoding';
|
|
import { type RSCResponse } from '../router-reducer/fetch-server-response';
|
|
import { type PrefetchTask, type PrefetchSubtaskResult } from './scheduler';
|
|
import { type SegmentVaryPath, type PageVaryPath, type LayoutVaryPath } from './vary-path';
|
|
import type { NormalizedPathname, NormalizedSearch, RouteCacheKey } from './cache-key';
|
|
import { type UnknownMapEntry } from './cache-map';
|
|
import { type SegmentRequestKey } from '../../../shared/lib/segment-cache/segment-value-encoding';
|
|
import type { FlightRouterState } from '../../../shared/lib/app-router-types';
|
|
import { type NormalizedFlightData } from '../../flight-data-helpers';
|
|
import { FetchStrategy } from './types';
|
|
import { type NavigationSeed } from './navigation';
|
|
/**
|
|
* Ensures a minimum stale time of 30s to avoid issues where the server sends a too
|
|
* short-lived stale time, which would prevent anything from being prefetched.
|
|
*/
|
|
export declare function getStaleTimeMs(staleTimeSeconds: number): number;
|
|
type RouteTreeShared = {
|
|
requestKey: SegmentRequestKey;
|
|
segment: FlightRouterStateSegment;
|
|
refreshState: RefreshState | null;
|
|
slots: null | {
|
|
[parallelRouteKey: string]: RouteTree;
|
|
};
|
|
prefetchHints: number;
|
|
};
|
|
export type RefreshState = {
|
|
canonicalUrl: string;
|
|
renderedSearch: NormalizedSearch;
|
|
};
|
|
type LayoutRouteTree = RouteTreeShared & {
|
|
isPage: false;
|
|
varyPath: LayoutVaryPath;
|
|
};
|
|
type PageRouteTree = RouteTreeShared & {
|
|
isPage: true;
|
|
varyPath: PageVaryPath;
|
|
};
|
|
export type RouteTree = LayoutRouteTree | PageRouteTree;
|
|
type RouteCacheEntryShared = {
|
|
couldBeIntercepted: boolean;
|
|
ref: UnknownMapEntry | null;
|
|
size: number;
|
|
staleAt: number;
|
|
version: number;
|
|
};
|
|
/**
|
|
* Tracks the status of a cache entry as it progresses from no data (Empty),
|
|
* waiting for server data (Pending), and finished (either Fulfilled or
|
|
* Rejected depending on the response from the server.
|
|
*/
|
|
export declare const enum EntryStatus {
|
|
Empty = 0,
|
|
Pending = 1,
|
|
Fulfilled = 2,
|
|
Rejected = 3
|
|
}
|
|
export type PendingRouteCacheEntry = RouteCacheEntryShared & {
|
|
status: EntryStatus.Empty | EntryStatus.Pending;
|
|
blockedTasks: Set<PrefetchTask> | null;
|
|
canonicalUrl: null;
|
|
renderedSearch: null;
|
|
tree: null;
|
|
metadata: null;
|
|
supportsPerSegmentPrefetching: false;
|
|
};
|
|
type RejectedRouteCacheEntry = RouteCacheEntryShared & {
|
|
status: EntryStatus.Rejected;
|
|
blockedTasks: Set<PrefetchTask> | null;
|
|
canonicalUrl: null;
|
|
renderedSearch: null;
|
|
tree: null;
|
|
metadata: null;
|
|
supportsPerSegmentPrefetching: boolean;
|
|
};
|
|
export type FulfilledRouteCacheEntry = RouteCacheEntryShared & {
|
|
status: EntryStatus.Fulfilled;
|
|
blockedTasks: null;
|
|
canonicalUrl: string;
|
|
renderedSearch: NormalizedSearch;
|
|
tree: RouteTree;
|
|
metadata: RouteTree;
|
|
supportsPerSegmentPrefetching: boolean;
|
|
hasDynamicRewrite: boolean;
|
|
};
|
|
export type RouteCacheEntry = PendingRouteCacheEntry | FulfilledRouteCacheEntry | RejectedRouteCacheEntry;
|
|
type SegmentCacheEntryShared = {
|
|
fetchStrategy: FetchStrategy;
|
|
ref: UnknownMapEntry | null;
|
|
size: number;
|
|
staleAt: number;
|
|
version: number;
|
|
};
|
|
export type EmptySegmentCacheEntry = SegmentCacheEntryShared & {
|
|
status: EntryStatus.Empty;
|
|
rsc: null;
|
|
isPartial: true;
|
|
promise: null;
|
|
};
|
|
export type PendingSegmentCacheEntry = SegmentCacheEntryShared & {
|
|
status: EntryStatus.Pending;
|
|
rsc: null;
|
|
isPartial: boolean;
|
|
promise: null | PromiseWithResolvers<FulfilledSegmentCacheEntry | null>;
|
|
};
|
|
type RejectedSegmentCacheEntry = SegmentCacheEntryShared & {
|
|
status: EntryStatus.Rejected;
|
|
rsc: null;
|
|
isPartial: true;
|
|
promise: null;
|
|
};
|
|
export type FulfilledSegmentCacheEntry = SegmentCacheEntryShared & {
|
|
status: EntryStatus.Fulfilled;
|
|
rsc: React.ReactNode | null;
|
|
isPartial: boolean;
|
|
promise: null;
|
|
};
|
|
export type SegmentCacheEntry = EmptySegmentCacheEntry | PendingSegmentCacheEntry | RejectedSegmentCacheEntry | FulfilledSegmentCacheEntry;
|
|
export type NonEmptySegmentCacheEntry = Exclude<SegmentCacheEntry, EmptySegmentCacheEntry>;
|
|
export declare function getCurrentRouteCacheVersion(): number;
|
|
export declare function getCurrentSegmentCacheVersion(): number;
|
|
/**
|
|
* Invalidates all prefetch cache entries (both route and segment caches).
|
|
*
|
|
* After invalidation, triggers re-prefetching of visible links and notifies
|
|
* invalidation listeners.
|
|
*/
|
|
export declare function invalidateEntirePrefetchCache(nextUrl: string | null, tree: FlightRouterState): void;
|
|
/**
|
|
* Invalidates all route cache entries. Route entries contain the tree structure
|
|
* (which segments exist at a given URL) but not the segment data itself.
|
|
*
|
|
* After invalidation, triggers re-prefetching of visible links and notifies
|
|
* invalidation listeners.
|
|
*/
|
|
export declare function invalidateRouteCacheEntries(nextUrl: string | null, tree: FlightRouterState): void;
|
|
/**
|
|
* Invalidates all segment cache entries. Segment entries contain the actual
|
|
* RSC data for each segment.
|
|
*
|
|
* After invalidation, triggers re-prefetching of visible links and notifies
|
|
* invalidation listeners.
|
|
*/
|
|
export declare function invalidateSegmentCacheEntries(nextUrl: string | null, tree: FlightRouterState): void;
|
|
export declare function pingInvalidationListeners(nextUrl: string | null, tree: FlightRouterState): void;
|
|
export declare function readRouteCacheEntry(now: number, key: RouteCacheKey): RouteCacheEntry | null;
|
|
export declare function readSegmentCacheEntry(now: number, varyPath: SegmentVaryPath): SegmentCacheEntry | null;
|
|
export declare function waitForSegmentCacheEntry(pendingEntry: PendingSegmentCacheEntry): Promise<FulfilledSegmentCacheEntry | null>;
|
|
/**
|
|
* Checks if an entry for a route exists in the cache. If so, it returns the
|
|
* entry, If not, it adds an empty entry to the cache and returns it.
|
|
*/
|
|
export declare function readOrCreateRouteCacheEntry(now: number, task: PrefetchTask, key: RouteCacheKey): RouteCacheEntry;
|
|
export declare function deprecated_requestOptimisticRouteCacheEntry(now: number, requestedUrl: URL, nextUrl: string | null): FulfilledRouteCacheEntry | null;
|
|
/**
|
|
* Checks if an entry for a segment exists in the cache. If so, it returns the
|
|
* entry, If not, it adds an empty entry to the cache and returns it.
|
|
*/
|
|
export declare function readOrCreateSegmentCacheEntry(now: number, fetchStrategy: FetchStrategy, tree: RouteTree): SegmentCacheEntry;
|
|
export declare function readOrCreateRevalidatingSegmentEntry(now: number, fetchStrategy: FetchStrategy, tree: RouteTree): SegmentCacheEntry;
|
|
export declare function overwriteRevalidatingSegmentCacheEntry(now: number, fetchStrategy: FetchStrategy, tree: RouteTree): EmptySegmentCacheEntry;
|
|
export declare function upsertSegmentEntry(now: number, varyPath: SegmentVaryPath, candidateEntry: SegmentCacheEntry): SegmentCacheEntry | null;
|
|
export declare function createDetachedSegmentCacheEntry(now: number): EmptySegmentCacheEntry;
|
|
export declare function upgradeToPendingSegment(emptyEntry: EmptySegmentCacheEntry, fetchStrategy: FetchStrategy): PendingSegmentCacheEntry;
|
|
export declare function attemptToFulfillDynamicSegmentFromBFCache(now: number, segment: EmptySegmentCacheEntry, tree: RouteTree): FulfilledSegmentCacheEntry | null;
|
|
/**
|
|
* Attempts to replace an existing segment cache entry with data from the
|
|
* bfcache. Unlike `attemptToFulfillDynamicSegmentFromBFCache` (which fills an
|
|
* empty entry), this creates a new entry and upserts it, so it works even when
|
|
* the segment is already fulfilled.
|
|
*/
|
|
export declare function attemptToUpgradeSegmentFromBFCache(now: number, tree: RouteTree): FulfilledSegmentCacheEntry | null;
|
|
export declare function createMetadataRouteTree(metadataVaryPath: PageVaryPath): RouteTree;
|
|
export declare function fulfillRouteCacheEntry(now: number, entry: PendingRouteCacheEntry, tree: RouteTree, metadataVaryPath: PageVaryPath, couldBeIntercepted: boolean, canonicalUrl: string, supportsPerSegmentPrefetching: boolean): FulfilledRouteCacheEntry;
|
|
export declare function writeRouteIntoCache(now: number, pathname: NormalizedPathname, nextUrl: string | null, tree: RouteTree, metadataVaryPath: PageVaryPath, couldBeIntercepted: boolean, canonicalUrl: string, supportsPerSegmentPrefetching: boolean): FulfilledRouteCacheEntry;
|
|
/**
|
|
* Marks a route cache entry as having a dynamic rewrite. Called when we
|
|
* discover that a route pattern has dynamic rewrite behavior - i.e., we used
|
|
* an optimistic route tree for prediction, but the server responded with a
|
|
* different rendered pathname.
|
|
*
|
|
* Once marked, attempts to use this entry as a template for prediction will
|
|
* bail out to server resolution.
|
|
*/
|
|
export declare function markRouteEntryAsDynamicRewrite(entry: FulfilledRouteCacheEntry): void;
|
|
type RouteTreeAccumulator = {
|
|
metadataVaryPath: PageVaryPath | null;
|
|
};
|
|
export declare function convertRootFlightRouterStateToRouteTree(flightRouterState: FlightRouterState, renderedSearch: NormalizedSearch, acc: RouteTreeAccumulator): RouteTree;
|
|
export declare function convertReusedFlightRouterStateToRouteTree(parentRouteTree: RouteTree, parallelRouteKey: string, flightRouterState: FlightRouterState, renderedSearch: NormalizedSearch, acc: RouteTreeAccumulator): RouteTree;
|
|
export declare function convertRouteTreeToFlightRouterState(routeTree: RouteTree): FlightRouterState;
|
|
export declare function fetchRouteOnCacheMiss(entry: PendingRouteCacheEntry, key: RouteCacheKey): Promise<PrefetchSubtaskResult<null> | null>;
|
|
export declare function fetchSegmentOnCacheMiss(route: FulfilledRouteCacheEntry, segmentCacheEntry: PendingSegmentCacheEntry, routeKey: RouteCacheKey, tree: RouteTree): Promise<PrefetchSubtaskResult<FulfilledSegmentCacheEntry> | null>;
|
|
export declare function fetchInlinedSegmentsOnCacheMiss(route: FulfilledRouteCacheEntry, routeKey: RouteCacheKey, tree: RouteTree, spawnedEntries: Map<SegmentRequestKey, PendingSegmentCacheEntry>): Promise<PrefetchSubtaskResult<null> | null>;
|
|
export declare function fetchSegmentPrefetchesUsingDynamicRequest(task: PrefetchTask, route: FulfilledRouteCacheEntry, fetchStrategy: FetchStrategy.LoadingBoundary | FetchStrategy.PPRRuntime | FetchStrategy.Full, dynamicRequestTree: FlightRouterState, spawnedEntries: Map<SegmentRequestKey, PendingSegmentCacheEntry>): Promise<PrefetchSubtaskResult<null> | null>;
|
|
export declare function writeDynamicRenderResponseIntoCache(now: number, fetchStrategy: FetchStrategy.LoadingBoundary | FetchStrategy.PPR | FetchStrategy.PPRRuntime | FetchStrategy.Full, flightDatas: NormalizedFlightData[], buildId: string | undefined, isResponsePartial: boolean, headVaryParams: VaryParams | null, staleAt: number, navigationSeed: NavigationSeed, spawnedEntries: Map<SegmentRequestKey, PendingSegmentCacheEntry> | null): Array<FulfilledSegmentCacheEntry> | null;
|
|
/**
|
|
* Checks whether the new fetch strategy is likely to provide more content than the old one.
|
|
*
|
|
* Generally, when an app uses dynamic data, a "more specific" fetch strategy is expected to provide more content:
|
|
* - `LoadingBoundary` only provides static layouts
|
|
* - `PPR` can provide shells for each segment (even for segments that use dynamic data)
|
|
* - `PPRRuntime` can additionally include content that uses searchParams, params, or cookies
|
|
* - `Full` includes all the content, even if it uses dynamic data
|
|
*
|
|
* However, it's possible that a more specific fetch strategy *won't* give us more content if:
|
|
* - a segment is fully static
|
|
* (then, `PPR`/`PPRRuntime`/`Full` will all yield equivalent results)
|
|
* - providing searchParams/params/cookies doesn't reveal any more content, e.g. because of an `await connection()`
|
|
* (then, `PPR` and `PPRRuntime` will yield equivalent results, only `Full` will give us more)
|
|
* Because of this, when comparing two segments, we should also check if the existing segment is partial.
|
|
* If it's not partial, then there's no need to prefetch it again, even using a "more specific" strategy.
|
|
* There's currently no way to know if `PPRRuntime` will yield more data that `PPR`, so we have to assume it will.
|
|
*
|
|
* Also note that, in practice, we don't expect to be comparing `LoadingBoundary` to `PPR`/`PPRRuntime`,
|
|
* because a non-PPR-enabled route wouldn't ever use the latter strategies. It might however use `Full`.
|
|
*/
|
|
export declare function canNewFetchStrategyProvideMoreContent(currentStrategy: FetchStrategy, newStrategy: FetchStrategy): boolean;
|
|
/**
|
|
* Reads the stale time from an async iterable or a response header and
|
|
* returns a staleAt timestamp.
|
|
*
|
|
* TODO: Buffer the response and then read the iterable values
|
|
* synchronously, similar to readVaryParams. This would avoid the need to
|
|
* make this async, and we could also use it in
|
|
* writeDynamicTreeResponseIntoCache. This will also be needed when React
|
|
* starts leaving async iterables hanging when the outer RSC stream is
|
|
* aborted e.g. due to sync I/O (with unstable_allowPartialStream).
|
|
*/
|
|
export declare function getStaleAt(now: number, staleTimeIterable: AsyncIterable<number> | undefined, response?: RSCResponse<unknown>): Promise<number>;
|
|
/**
|
|
* Writes the static stage of a navigation response into the segment cache.
|
|
* When `isResponsePartial` is false, segments are written as non-partial with
|
|
* `FetchStrategy.Full` so no dynamic follow-up is needed. Default segments
|
|
* are skipped (by `writeSeedDataIntoCache`) to avoid caching fallback content
|
|
* that would block refreshes from overwriting with dynamic data.
|
|
*/
|
|
export declare function writeStaticStageResponseIntoCache(now: number, flightData: FlightData, buildId: string | undefined, headVaryParamsThenable: VaryParamsThenable | null, staleAt: number, baseTree: FlightRouterState, renderedSearch: string, isResponsePartial: boolean): void;
|
|
/**
|
|
* Decodes an embedded runtime prefetch Flight stream, normalizes the flight
|
|
* data, and derives a `NavigationSeed` from the base tree.
|
|
*
|
|
* Returns `null` if the response triggers an MPA navigation.
|
|
*/
|
|
export declare function processRuntimePrefetchStream(now: number, runtimePrefetchStream: ReadableStream<Uint8Array>, baseTree: FlightRouterState, renderedSearch: string): Promise<{
|
|
flightDatas: NormalizedFlightData[];
|
|
navigationSeed: NavigationSeed;
|
|
buildId: string | undefined;
|
|
isResponsePartial: boolean;
|
|
headVaryParams: VaryParams | null;
|
|
staleAt: number;
|
|
} | null>;
|
|
/**
|
|
* Strips the leading isPartial byte from an RSC response stream.
|
|
*
|
|
* The server prepends a single byte: '~' (0x7e) for partial, '#' (0x23) for
|
|
* complete. These bytes cannot appear as the first byte of a valid RSC Flight
|
|
* response (Flight rows start with a hex digit or ':').
|
|
*
|
|
* If the first byte is not a recognized marker, the stream is returned intact
|
|
* and `isPartial` is determined by the cachedNavigations experimental flag.
|
|
*/
|
|
export declare function stripIsPartialByte(stream: ReadableStream<Uint8Array>): Promise<{
|
|
stream: ReadableStream<Uint8Array>;
|
|
isPartial: boolean;
|
|
}>;
|
|
export {};
|