UNPKG

next

Version:

The React Framework

268 lines (267 loc) • 15.2 kB
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 {};