| import { FetchStrategy } from './types' | |
| import type { | |
| NormalizedPathname, | |
| NormalizedSearch, | |
| NormalizedNextUrl, | |
| } from './cache-key' | |
| import type { RouteTree } from './cache' | |
| import { Fallback, type FallbackType } from './cache-map' | |
| import { HEAD_REQUEST_KEY } from '../../../shared/lib/segment-cache/segment-value-encoding' | |
| type Opaque<T, K> = T & { __brand: K } | |
| /** | |
| * A linked-list of all the params (or other param-like) inputs that a cache | |
| * entry may vary by. This is used by the CacheMap module to reuse cache entries | |
| * across different param values. If a param has a value of Fallback, it means | |
| * the cache entry is reusable for all possible values of that param. See | |
| * cache-map.ts for details. | |
| * | |
| * A segment's vary path is a pure function of a segment's position in a | |
| * particular route tree and the (post-rewrite) URL that is being queried. More | |
| * concretely, successive queries of the cache for the same segment always use | |
| * the same vary path. | |
| * | |
| * A route's vary path is simpler: it's comprised of the pathname, search | |
| * string, and Next-URL header. | |
| */ | |
| export type VaryPath = { | |
| value: string | null | FallbackType | |
| parent: VaryPath | null | |
| } | |
| // Because it's so important for vary paths to line up across cache accesses, | |
| // we use opaque type aliases to ensure these are only created within | |
| // this module. | |
| // requestKey -> searchParams -> nextUrl | |
| export type RouteVaryPath = Opaque< | |
| { | |
| value: NormalizedPathname | |
| parent: { | |
| value: NormalizedSearch | |
| parent: { | |
| value: NormalizedNextUrl | null | FallbackType | |
| parent: null | |
| } | |
| } | |
| }, | |
| 'RouteVaryPath' | |
| > | |
| // requestKey -> pathParams | |
| export type LayoutVaryPath = Opaque< | |
| { | |
| value: string | |
| parent: PartialSegmentVaryPath | null | |
| }, | |
| 'LayoutVaryPath' | |
| > | |
| // requestKey -> searchParams -> pathParams | |
| export type PageVaryPath = Opaque< | |
| { | |
| value: string | |
| parent: { | |
| value: NormalizedSearch | FallbackType | |
| parent: PartialSegmentVaryPath | null | |
| } | |
| }, | |
| 'PageVaryPath' | |
| > | |
| export type SegmentVaryPath = LayoutVaryPath | PageVaryPath | |
| // Intermediate type used when building a vary path during a recursive traversal | |
| // of the route tree. | |
| export type PartialSegmentVaryPath = Opaque<VaryPath, 'PartialSegmentVaryPath'> | |
| export function getRouteVaryPath( | |
| pathname: NormalizedPathname, | |
| search: NormalizedSearch, | |
| nextUrl: NormalizedNextUrl | null | |
| ): RouteVaryPath { | |
| // requestKey -> searchParams -> nextUrl | |
| const varyPath: VaryPath = { | |
| value: pathname, | |
| parent: { | |
| value: search, | |
| parent: { | |
| value: nextUrl, | |
| parent: null, | |
| }, | |
| }, | |
| } | |
| return varyPath as RouteVaryPath | |
| } | |
| export function getFulfilledRouteVaryPath( | |
| pathname: NormalizedPathname, | |
| search: NormalizedSearch, | |
| nextUrl: NormalizedNextUrl | null, | |
| couldBeIntercepted: boolean | |
| ): RouteVaryPath { | |
| // This is called when a route's data is fulfilled. The cache entry will be | |
| // re-keyed based on which inputs the response varies by. | |
| // requestKey -> searchParams -> nextUrl | |
| const varyPath: VaryPath = { | |
| value: pathname, | |
| parent: { | |
| value: search, | |
| parent: { | |
| value: couldBeIntercepted ? nextUrl : Fallback, | |
| parent: null, | |
| }, | |
| }, | |
| } | |
| return varyPath as RouteVaryPath | |
| } | |
| export function appendLayoutVaryPath( | |
| parentPath: PartialSegmentVaryPath | null, | |
| cacheKey: string | |
| ): PartialSegmentVaryPath { | |
| const varyPathPart: VaryPath = { | |
| value: cacheKey, | |
| parent: parentPath, | |
| } | |
| return varyPathPart as PartialSegmentVaryPath | |
| } | |
| export function finalizeLayoutVaryPath( | |
| requestKey: string, | |
| varyPath: PartialSegmentVaryPath | null | |
| ): LayoutVaryPath { | |
| const layoutVaryPath: VaryPath = { | |
| value: requestKey, | |
| parent: varyPath, | |
| } | |
| return layoutVaryPath as LayoutVaryPath | |
| } | |
| export function finalizePageVaryPath( | |
| requestKey: string, | |
| renderedSearch: NormalizedSearch, | |
| varyPath: PartialSegmentVaryPath | null | |
| ): PageVaryPath { | |
| // Unlike layouts, a page segment's vary path also includes the search string. | |
| // requestKey -> searchParams -> pathParams | |
| const pageVaryPath: VaryPath = { | |
| value: requestKey, | |
| parent: { | |
| value: renderedSearch, | |
| parent: varyPath, | |
| }, | |
| } | |
| return pageVaryPath as PageVaryPath | |
| } | |
| export function finalizeMetadataVaryPath( | |
| pageRequestKey: string, | |
| renderedSearch: NormalizedSearch, | |
| varyPath: PartialSegmentVaryPath | null | |
| ): PageVaryPath { | |
| // The metadata "segment" is not a real segment because it doesn't exist in | |
| // the normal structure of the route tree, but in terms of caching, it | |
| // behaves like a page segment because it varies by all the same params as | |
| // a page. | |
| // | |
| // To keep the protocol for querying the server simple, the request key for | |
| // the metadata does not include any path information. It's unnecessary from | |
| // the server's perspective, because unlike page segments, there's only one | |
| // metadata response per URL, i.e. there's no need to distinguish multiple | |
| // parallel pages. | |
| // | |
| // However, this means the metadata request key is insufficient for | |
| // caching the the metadata in the client cache, because on the client we | |
| // use the request key to distinguish the metadata entry from all other | |
| // page's metadata entries. | |
| // | |
| // So instead we create a simulated request key based on the page segment. | |
| // Conceptually this is equivalent to the request key the server would have | |
| // assigned the metadata segment if it treated it as part of the actual | |
| // route structure. | |
| // If there are multiple parallel pages, we use whichever is the first one. | |
| // This is fine because the only difference between request keys for | |
| // different parallel pages are things like route groups and parallel | |
| // route slots. As long as it's always the same one, it doesn't matter. | |
| const pageVaryPath: VaryPath = { | |
| // Append the actual metadata request key to the page request key. Note | |
| // that we're not using a separate vary path part; it's unnecessary because | |
| // these are not conceptually separate inputs. | |
| value: pageRequestKey + HEAD_REQUEST_KEY, | |
| parent: { | |
| value: renderedSearch, | |
| parent: varyPath, | |
| }, | |
| } | |
| return pageVaryPath as PageVaryPath | |
| } | |
| export function getSegmentVaryPathForRequest( | |
| fetchStrategy: FetchStrategy, | |
| tree: RouteTree | |
| ): SegmentVaryPath { | |
| // This is used for storing pending requests in the cache. We want to choose | |
| // the most generic vary path based on the strategy used to fetch it, i.e. | |
| // static/PPR versus runtime prefetching, so that it can be reused as much | |
| // as possible. | |
| // | |
| // We may be able to re-key the response to something even more generic once | |
| // we receive it — for example, if the server tells us that the response | |
| // doesn't vary on a particular param — but even before we send the request, | |
| // we know some params are reusable based on the fetch strategy alone. For | |
| // example, a static prefetch will never vary on search params. | |
| // | |
| // The original vary path with all the params filled in is stored on the | |
| // route tree object. We will clone this one to create a new vary path | |
| // where certain params are replaced with Fallback. | |
| // | |
| // This result of this function is not stored anywhere. It's only used to | |
| // access the cache a single time. | |
| // | |
| // TODO: Rather than create a new list object just to access the cache, the | |
| // plan is to add the concept of a "vary mask". This will represent all the | |
| // params that can be treated as Fallback. (Or perhaps the inverse.) | |
| const originalVaryPath = tree.varyPath | |
| // Only page segments (and the special "metadata" segment, which is treated | |
| // like a page segment for the purposes of caching) may contain search | |
| // params. There's no reason to include them in the vary path otherwise. | |
| if (tree.isPage) { | |
| // Only a runtime prefetch will include search params in the vary path. | |
| // Static prefetches never include search params, so they can be reused | |
| // across all possible search param values. | |
| const doesVaryOnSearchParams = | |
| fetchStrategy === FetchStrategy.Full || | |
| fetchStrategy === FetchStrategy.PPRRuntime | |
| if (!doesVaryOnSearchParams) { | |
| // The response from the the server will not vary on search params. Clone | |
| // the end of the original vary path to replace the search params | |
| // with Fallback. | |
| // | |
| // requestKey -> searchParams -> pathParams | |
| // ^ This part gets replaced with Fallback | |
| const searchParamsVaryPath = (originalVaryPath as PageVaryPath).parent | |
| const pathParamsVaryPath = searchParamsVaryPath.parent | |
| const patchedVaryPath: VaryPath = { | |
| value: originalVaryPath.value, | |
| parent: { | |
| value: Fallback, | |
| parent: pathParamsVaryPath, | |
| }, | |
| } | |
| return patchedVaryPath as SegmentVaryPath | |
| } | |
| } | |
| // The request does vary on search params. We don't need to modify anything. | |
| return originalVaryPath as SegmentVaryPath | |
| } | |
| export function clonePageVaryPathWithNewSearchParams( | |
| originalVaryPath: PageVaryPath, | |
| newSearch: NormalizedSearch | |
| ): PageVaryPath { | |
| // requestKey -> searchParams -> pathParams | |
| // ^ This part gets replaced with newSearch | |
| const searchParamsVaryPath = originalVaryPath.parent | |
| const clonedVaryPath: VaryPath = { | |
| value: originalVaryPath.value, | |
| parent: { | |
| value: newSearch, | |
| parent: searchParamsVaryPath.parent, | |
| }, | |
| } | |
| return clonedVaryPath as PageVaryPath | |
| } | |