UNPKG

next

Version:

The React Framework

2,695 lines 149 kB
"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
0 && (module.exports = {
    EntryStatus: null,
    MetadataOnlyRequestTree: null,
    attemptToFulfillDynamicSegmentFromBFCache: null,
    attemptToUpgradeSegmentFromBFCache: null,
    canNewFetchStrategyProvideMoreContent: null,
    convertReusedFlightRouterStateToRouteTree: null,
    convertRootFlightRouterStateToRouteTree: null,
    convertRouteTreeToFlightRouterState: null,
    createDetachedSegmentCacheEntry: null,
    createMetadataRouteTree: null,
    createNonTaskyPrefetchResponseStream: null,
    deprecated_requestOptimisticRouteCacheEntry: null,
    fetchRouteOnCacheMiss: null,
    fetchSegmentPrefetchesUsingDynamicRequest: null,
    fetchSegmentsOnCacheMiss: null,
    fulfillRouteCacheEntry: null,
    getCurrentRouteCacheVersion: null,
    getCurrentSegmentCacheVersion: null,
    getStaleTimeMs: null,
    invalidateEntirePrefetchCache: null,
    invalidateRouteCacheEntries: null,
    invalidateSegmentCacheEntries: null,
    markRouteEntryAsDynamicRewrite: null,
    overwriteRevalidatingSegmentCacheEntry: null,
    pingInvalidationListeners: null,
    processRuntimePrefetchStream: null,
    readOrCreateRevalidatingSegmentEntry: null,
    readOrCreateRouteCacheEntry: null,
    readOrCreateSegmentCacheEntry: null,
    readRouteCacheEntry: null,
    readSegmentCacheEntryForNavigation: null,
    resolveStaleAt: null,
    segmentCacheMap: null,
    stripIsPartialByte: null,
    upgradeToPendingSegment: null,
    upsertSegmentEntry: null,
    waitForSegmentCacheEntry: null,
    writeDynamicRenderResponseIntoCache: null,
    writePrerenderResponseIntoCache: null,
    writeRouteIntoCache: null
});
function _export(target, all) {
    for(var name in all)Object.defineProperty(target, name, {
        enumerable: true,
        get: all[name]
    });
}
_export(exports, {
    EntryStatus: function() {
        return _cachemap.EntryStatus;
    },
    MetadataOnlyRequestTree: function() {
        return MetadataOnlyRequestTree;
    },
    attemptToFulfillDynamicSegmentFromBFCache: function() {
        return attemptToFulfillDynamicSegmentFromBFCache;
    },
    attemptToUpgradeSegmentFromBFCache: function() {
        return attemptToUpgradeSegmentFromBFCache;
    },
    canNewFetchStrategyProvideMoreContent: function() {
        return canNewFetchStrategyProvideMoreContent;
    },
    convertReusedFlightRouterStateToRouteTree: function() {
        return convertReusedFlightRouterStateToRouteTree;
    },
    convertRootFlightRouterStateToRouteTree: function() {
        return convertRootFlightRouterStateToRouteTree;
    },
    convertRouteTreeToFlightRouterState: function() {
        return convertRouteTreeToFlightRouterState;
    },
    createDetachedSegmentCacheEntry: function() {
        return createDetachedSegmentCacheEntry;
    },
    createMetadataRouteTree: function() {
        return createMetadataRouteTree;
    },
    createNonTaskyPrefetchResponseStream: function() {
        return createNonTaskyPrefetchResponseStream;
    },
    deprecated_requestOptimisticRouteCacheEntry: function() {
        return deprecated_requestOptimisticRouteCacheEntry;
    },
    fetchRouteOnCacheMiss: function() {
        return fetchRouteOnCacheMiss;
    },
    fetchSegmentPrefetchesUsingDynamicRequest: function() {
        return fetchSegmentPrefetchesUsingDynamicRequest;
    },
    fetchSegmentsOnCacheMiss: function() {
        return fetchSegmentsOnCacheMiss;
    },
    fulfillRouteCacheEntry: function() {
        return fulfillRouteCacheEntry;
    },
    getCurrentRouteCacheVersion: function() {
        return getCurrentRouteCacheVersion;
    },
    getCurrentSegmentCacheVersion: function() {
        return getCurrentSegmentCacheVersion;
    },
    getStaleTimeMs: function() {
        return getStaleTimeMs;
    },
    invalidateEntirePrefetchCache: function() {
        return invalidateEntirePrefetchCache;
    },
    invalidateRouteCacheEntries: function() {
        return invalidateRouteCacheEntries;
    },
    invalidateSegmentCacheEntries: function() {
        return invalidateSegmentCacheEntries;
    },
    markRouteEntryAsDynamicRewrite: function() {
        return markRouteEntryAsDynamicRewrite;
    },
    overwriteRevalidatingSegmentCacheEntry: function() {
        return overwriteRevalidatingSegmentCacheEntry;
    },
    pingInvalidationListeners: function() {
        return pingInvalidationListeners;
    },
    processRuntimePrefetchStream: function() {
        return processRuntimePrefetchStream;
    },
    readOrCreateRevalidatingSegmentEntry: function() {
        return readOrCreateRevalidatingSegmentEntry;
    },
    readOrCreateRouteCacheEntry: function() {
        return readOrCreateRouteCacheEntry;
    },
    readOrCreateSegmentCacheEntry: function() {
        return readOrCreateSegmentCacheEntry;
    },
    readRouteCacheEntry: function() {
        return readRouteCacheEntry;
    },
    readSegmentCacheEntryForNavigation: function() {
        return readSegmentCacheEntryForNavigation;
    },
    resolveStaleAt: function() {
        return resolveStaleAt;
    },
    segmentCacheMap: function() {
        return segmentCacheMap;
    },
    stripIsPartialByte: function() {
        return stripIsPartialByte;
    },
    upgradeToPendingSegment: function() {
        return upgradeToPendingSegment;
    },
    upsertSegmentEntry: function() {
        return upsertSegmentEntry;
    },
    waitForSegmentCacheEntry: function() {
        return waitForSegmentCacheEntry;
    },
    writeDynamicRenderResponseIntoCache: function() {
        return writeDynamicRenderResponseIntoCache;
    },
    writePrerenderResponseIntoCache: function() {
        return writePrerenderResponseIntoCache;
    },
    writeRouteIntoCache: function() {
        return writeRouteIntoCache;
    }
});
const _approutertypes = require("../../../shared/lib/app-router-types");
const _varyparamsdecoding = require("../../../shared/lib/segment-cache/vary-params-decoding");
const _approuterheaders = require("../app-router-headers");
const _fetchserverresponse = require("../router-reducer/fetch-server-response");
const _fetch = require("./fetch");
const _scheduler = require("./scheduler");
const _varypath = require("./vary-path");
const _createhreffromurl = require("../router-reducer/create-href-from-url");
const _cachekey = require("./cache-key");
const _routeparams = require("../../route-params");
const _cachemap = require("./cache-map");
const _segmentvalueencoding = require("../../../shared/lib/segment-cache/segment-value-encoding");
const _flightdatahelpers = require("../../flight-data-helpers");
const _navigatereducer = require("../router-reducer/reducers/navigate-reducer");
const _links = require("../links");
const _segment = require("../../../shared/lib/segment");
const _types = require("./types");
const _promisewithresolvers = require("../../../shared/lib/promise-with-resolvers");
const _bfcache = require("./bfcache");
const _optimisticroutes = require("./optimistic-routes");
const _navigation = require("./navigation");
const _navigationbuildid = require("../../navigation-build-id");
const _constants = require("../../../lib/constants");
function getStaleTimeMs(staleTimeSeconds) {
    return Math.max(staleTimeSeconds, 30) * 1000;
}
const isOutputExportMode = process.env.NODE_ENV === 'production' && process.env.__NEXT_CONFIG_OUTPUT === 'export';
const MetadataOnlyRequestTree = [
    '',
    {},
    null,
    'metadata-only'
];
const routeCacheMap = (0, _cachemap.createCacheMap)();
const segmentCacheMap = (0, _cachemap.createCacheMap)();
// All invalidation listeners for the whole cache are tracked in single set.
// Since we don't yet support tag or path-based invalidation, there's no point
// tracking them any more granularly than this. Once we add granular
// invalidation, that may change, though generally the model is to just notify
// the listeners and allow the caller to poll the prefetch cache with a new
// prefetch task if desired.
let invalidationListeners = null;
// Incrementing counters used to track cache invalidations. Route and segment
// caches have separate versions so they can be invalidated independently.
// Invalidation does not eagerly evict anything from the cache; entries are
// lazily evicted when read.
let currentRouteCacheVersion = 0;
let currentSegmentCacheVersion = 0;
function getCurrentRouteCacheVersion() {
    return currentRouteCacheVersion;
}
function getCurrentSegmentCacheVersion() {
    return currentSegmentCacheVersion;
}
function invalidateEntirePrefetchCache(nextUrl, tree) {
    currentRouteCacheVersion++;
    currentSegmentCacheVersion++;
    (0, _links.pingVisibleLinks)(nextUrl, tree);
    pingInvalidationListeners(nextUrl, tree);
}
function invalidateRouteCacheEntries(nextUrl, tree) {
    currentRouteCacheVersion++;
    (0, _links.pingVisibleLinks)(nextUrl, tree);
    pingInvalidationListeners(nextUrl, tree);
}
function invalidateSegmentCacheEntries(nextUrl, tree) {
    currentSegmentCacheVersion++;
    (0, _links.pingVisibleLinks)(nextUrl, tree);
    pingInvalidationListeners(nextUrl, tree);
}
function attachInvalidationListener(task) {
    // This function is called whenever a prefetch task reads a cache entry. If
    // the task has an onInvalidate function associated with it — i.e. the one
    // optionally passed to router.prefetch(onInvalidate) — then we attach that
    // listener to the every cache entry that the task reads. Then, if an entry
    // is invalidated, we call the function.
    if (task.onInvalidate !== null) {
        if (invalidationListeners === null) {
            invalidationListeners = new Set([
                task
            ]);
        } else {
            invalidationListeners.add(task);
        }
    }
}
function notifyInvalidationListener(task) {
    const onInvalidate = task.onInvalidate;
    if (onInvalidate !== null) {
        // Clear the callback from the task object to guarantee it's not called more
        // than once.
        task.onInvalidate = null;
        // This is a user-space function, so we must wrap in try/catch.
        try {
            onInvalidate();
        } catch (error) {
            if (typeof reportError === 'function') {
                reportError(error);
            } else {
                console.error(error);
            }
        }
    }
}
function pingInvalidationListeners(nextUrl, tree) {
    // The rough equivalent of pingVisibleLinks, but for onInvalidate callbacks.
    // This is called when the Next-Url or the base tree changes, since those
    // may affect the result of a prefetch task. It's also called after a
    // cache invalidation.
    if (invalidationListeners !== null) {
        const tasks = invalidationListeners;
        invalidationListeners = null;
        for (const task of tasks){
            if ((0, _scheduler.isPrefetchTaskDirty)(task, nextUrl, tree)) {
                notifyInvalidationListener(task);
            }
        }
    }
}
function readRouteCacheEntry(now, key) {
    const varyPath = (0, _varypath.getRouteVaryPath)(key.pathname, key.search, key.nextUrl);
    const isRevalidation = false;
    const existingEntry = (0, _cachemap.getFromCacheMap)(now, getCurrentRouteCacheVersion(), routeCacheMap, varyPath, isRevalidation, false);
    if (existingEntry !== null) {
        return existingEntry;
    }
    // No cache hit. Attempt to construct from template using the new
    // optimistic routing mechanism (pattern-based matching).
    if (process.env.__NEXT_OPTIMISTIC_ROUTING) {
        return (0, _optimisticroutes.matchKnownRoute)(now, key.pathname, key.search);
    }
    return null;
}
function readSegmentCacheEntryForNavigation(now, // The map the navigation is bound to: a locked navigation's driving-task
// map, or the shared map otherwise.
map, varyPath, restrictToShell = false) {
    const isRevalidation = false;
    let lookupVaryPath = varyPath;
    if (process.env.__NEXT_EXPOSE_TESTING_API && restrictToShell) {
        // Instant Navigation Testing API: we're navigating to a link that 1) has
        // Partial Prefetching enabled, and 2) does not have a prefetch prop set.
        // Only the shell may render, not anything that varies on concrete route
        // params.
        lookupVaryPath = (0, _varypath.getShellSegmentVaryPath)(varyPath);
    }
    // Prefer a Fulfilled entry (e.g. a cached shell) over a more-specific
    // Pending/Rejected one so it renders immediately instead of blocking on an
    // in-flight entry.
    const fulfilled = (0, _cachemap.getFromCacheMap)(now, getCurrentSegmentCacheVersion(), map, lookupVaryPath, isRevalidation, true);
    if (fulfilled !== null) {
        return fulfilled;
    }
    return (0, _cachemap.getFromCacheMap)(now, getCurrentSegmentCacheVersion(), map, lookupVaryPath, isRevalidation, false);
}
function readRevalidatingSegmentCacheEntry(now, map, varyPath) {
    const isRevalidation = true;
    return (0, _cachemap.getFromCacheMap)(now, getCurrentSegmentCacheVersion(), map, varyPath, isRevalidation, false);
}
function waitForSegmentCacheEntry(pendingEntry) {
    // Because the entry is pending, there's already a in-progress request.
    // Attach a promise to the entry that will resolve when the server responds.
    let promiseWithResolvers = pendingEntry.promise;
    if (promiseWithResolvers === null) {
        promiseWithResolvers = pendingEntry.promise = (0, _promisewithresolvers.createPromiseWithResolvers)();
    } else {
    // There's already a promise we can use
    }
    return promiseWithResolvers.promise;
}
function createDetachedRouteCacheEntry() {
    return {
        canonicalUrl: null,
        status: _cachemap.EntryStatus.Empty,
        blockedTasks: null,
        tree: null,
        metadata: null,
        // This is initialized to true because we don't know yet whether the route
        // could be intercepted. It's only set to false once we receive a response
        // from the server.
        couldBeIntercepted: true,
        // Similarly, we don't yet know if the route supports PPR.
        supportsPerSegmentPrefetching: false,
        hasDynamicRewrite: false,
        renderedSearch: null,
        // Map-related fields
        ref: null,
        size: 0,
        // Since this is an empty entry, there's no reason to ever evict it. It will
        // be updated when the data is populated.
        staleAt: Infinity,
        version: getCurrentRouteCacheVersion()
    };
}
function readOrCreateRouteCacheEntry(now, task, key) {
    attachInvalidationListener(task);
    const existingEntry = readRouteCacheEntry(now, key);
    if (existingEntry !== null) {
        return existingEntry;
    }
    // Create a pending entry and add it to the cache.
    const pendingEntry = createDetachedRouteCacheEntry();
    const varyPath = (0, _varypath.getRouteVaryPath)(key.pathname, key.search, key.nextUrl);
    const isRevalidation = false;
    (0, _cachemap.setInCacheMap)(routeCacheMap, varyPath, pendingEntry, isRevalidation);
    return pendingEntry;
}
function deprecated_requestOptimisticRouteCacheEntry(now, requestedUrl, nextUrl) {
    // This function is called during a navigation when there was no matching
    // route tree in the prefetch cache. Before de-opting to a blocking,
    // unprefetched navigation, we will first attempt to construct an "optimistic"
    // route tree by checking the cache for similar routes.
    //
    // Check if there's a route with the same pathname, but with different
    // search params. We can then base our optimistic route tree on this entry.
    //
    // Conceptually, we are simulating what would happen if we did perform a
    // prefetch the requested URL, under the assumption that the server will
    // not redirect or rewrite the request in a different manner than the
    // base route tree. This assumption might not hold, in which case we'll have
    // to recover when we perform the dynamic navigation request. However, this
    // is what would happen if a route were dynamically rewritten/redirected
    // in between the prefetch and the navigation. So the logic needs to exist
    // to handle this case regardless.
    // Look for a route with the same pathname, but with an empty search string.
    // TODO: There's nothing inherently special about the empty search string;
    // it's chosen somewhat arbitrarily, with the rationale that it's the most
    // likely one to exist. But we should update this to match _any_ search
    // string. The plan is to generalize this logic alongside other improvements
    // related to "fallback" cache entries.
    const requestedSearch = requestedUrl.search;
    if (requestedSearch === '') {
        // The caller would have already checked if a route with an empty search
        // string is in the cache. So we can bail out here.
        return null;
    }
    const urlWithoutSearchParams = new URL(requestedUrl);
    urlWithoutSearchParams.search = '';
    const routeWithNoSearchParams = readRouteCacheEntry(now, (0, _cachekey.createCacheKey)(urlWithoutSearchParams.href, nextUrl));
    if (routeWithNoSearchParams === null || routeWithNoSearchParams.status !== _cachemap.EntryStatus.Fulfilled) {
        // Bail out of constructing an optimistic route tree. This will result in
        // a blocking, unprefetched navigation.
        return null;
    }
    // Now we have a base route tree we can "patch" with our optimistic values.
    // Optimistically assume that redirects for the requested pathname do
    // not vary on the search string. Therefore, if the base route was
    // redirected to a different search string, then the optimistic route
    // should be redirected to the same search string. Otherwise, we use
    // the requested search string.
    const canonicalUrlForRouteWithNoSearchParams = new URL(routeWithNoSearchParams.canonicalUrl, requestedUrl.origin);
    const optimisticCanonicalSearch = canonicalUrlForRouteWithNoSearchParams.search !== '' ? canonicalUrlForRouteWithNoSearchParams.search : requestedSearch;
    // Similarly, optimistically assume that rewrites for the requested
    // pathname do not vary on the search string. Therefore, if the base
    // route was rewritten to a different search string, then the optimistic
    // route should be rewritten to the same search string. Otherwise, we use
    // the requested search string.
    const optimisticRenderedSearch = routeWithNoSearchParams.renderedSearch !== '' ? routeWithNoSearchParams.renderedSearch : requestedSearch;
    const optimisticUrl = new URL(routeWithNoSearchParams.canonicalUrl, location.origin);
    optimisticUrl.search = optimisticCanonicalSearch;
    const optimisticCanonicalUrl = (0, _createhreffromurl.createHrefFromUrl)(optimisticUrl);
    const optimisticRouteTree = deprecated_createOptimisticRouteTree(routeWithNoSearchParams.tree, optimisticRenderedSearch);
    const optimisticMetadataTree = deprecated_createOptimisticRouteTree(routeWithNoSearchParams.metadata, optimisticRenderedSearch);
    // Clone the base route tree, and override the relevant fields with our
    // optimistic values.
    const optimisticEntry = {
        canonicalUrl: optimisticCanonicalUrl,
        status: _cachemap.EntryStatus.Fulfilled,
        // This isn't cloned because it's instance-specific
        blockedTasks: null,
        tree: optimisticRouteTree,
        metadata: optimisticMetadataTree,
        couldBeIntercepted: routeWithNoSearchParams.couldBeIntercepted,
        supportsPerSegmentPrefetching: routeWithNoSearchParams.supportsPerSegmentPrefetching,
        hasDynamicRewrite: routeWithNoSearchParams.hasDynamicRewrite,
        // Override the rendered search with the optimistic value.
        renderedSearch: optimisticRenderedSearch,
        // Map-related fields
        ref: null,
        size: 0,
        staleAt: routeWithNoSearchParams.staleAt,
        version: routeWithNoSearchParams.version
    };
    // Do not insert this entry into the cache. It only exists so we can
    // perform the current navigation. Just return it to the caller.
    return optimisticEntry;
}
function deprecated_createOptimisticRouteTree(tree, newRenderedSearch) {
    // Create a new route tree that identical to the original one except for
    // the rendered search string, which is contained in the vary path.
    let clonedSlots = null;
    const originalSlots = tree.slots;
    if (originalSlots !== null) {
        clonedSlots = new Map();
        for (const [parallelRouteKey, childTree] of originalSlots){
            clonedSlots.set(parallelRouteKey, deprecated_createOptimisticRouteTree(childTree, newRenderedSearch));
        }
    }
    // We only need to clone the vary path if the route is a page.
    if (tree.isPage) {
        // The shell vary path Fallbacks search params, so it's unaffected by the
        // new rendered search and can be reused as-is.
        return {
            requestKey: tree.requestKey,
            segment: tree.segment,
            shellVaryPath: tree.shellVaryPath,
            refreshState: tree.refreshState,
            varyPath: (0, _varypath.clonePageVaryPathWithNewSearchParams)(tree.varyPath, newRenderedSearch),
            isPage: true,
            slots: clonedSlots,
            prefetchHints: tree.prefetchHints
        };
    }
    return {
        requestKey: tree.requestKey,
        segment: tree.segment,
        shellVaryPath: tree.shellVaryPath,
        refreshState: tree.refreshState,
        varyPath: tree.varyPath,
        isPage: false,
        slots: clonedSlots,
        prefetchHints: tree.prefetchHints
    };
}
function readOrCreateSegmentCacheEntry(now, // The map the calling task operates in (`PrefetchTask.segmentCacheMap`,
// captured when the task was scheduled).
map, fetchStrategy, tree) {
    const existingEntry = (0, _cachemap.getFromCacheMap)(now, getCurrentSegmentCacheVersion(), map, tree.varyPath, false, false);
    if (existingEntry !== null) {
        return existingEntry;
    }
    return insertEmptySegmentCacheEntry(now, map, fetchStrategy, tree);
}
/**
 * Creates an empty segment cache entry and inserts it into the cache, keyed
 * at the vary path a request made with the given fetch strategy is stored
 * under. The stale time is set to a default value; the actual stale time will
 * be set when the entry is fulfilled with data from the server response.
 */ function insertEmptySegmentCacheEntry(now, map, fetchStrategy, tree) {
    const varyPathForRequest = (0, _varypath.getSegmentVaryPathForRequest)(fetchStrategy, tree);
    const emptyEntry = createDetachedSegmentCacheEntry(now);
    const isRevalidation = false;
    (0, _cachemap.setInCacheMap)(map, varyPathForRequest, emptyEntry, isRevalidation);
    return emptyEntry;
}
function readOrCreateRevalidatingSegmentEntry(now, // The map the calling task operates in (`PrefetchTask.segmentCacheMap`).
map, fetchStrategy, tree) {
    // This function is called when we've already confirmed that a particular
    // segment is cached, but we want to perform another request anyway in case it
    // returns more complete and/or fresher data than we already have. The logic
    // for deciding whether to replace the existing entry is handled elsewhere;
    // this function just handles retrieving a cache entry that we can use to
    // track the revalidation.
    //
    // The reason revalidations are stored in the cache is because we need to be
    // able to dedupe multiple revalidation requests. The reason they have to be
    // handled specially is because we shouldn't overwrite a "normal" entry if
    // one exists at the same keypath. So, for each internal cache location, there
    // is a special "revalidation" slot that is used solely for this purpose.
    //
    // You can think of it as if all the revalidation entries were stored in a
    // separate cache map from the canonical entries, and then transfered to the
    // canonical cache map once the request is complete — this isn't how it's
    // actually implemented, since it's more efficient to store them in the same
    // data structure as the normal entries, but that's how it's modeled
    // conceptually.
    // TODO: Once we implement Fallback behavior for params, where an entry is
    // re-keyed based on response information, we'll need to account for the
    // possibility that the keypath of the previous entry is more generic than
    // the keypath of the revalidating entry. In other words, the server could
    // return a less generic entry upon revalidation. For now, though, this isn't
    // a concern because the keypath is based solely on the prefetch strategy,
    // not on data contained in the response.
    const existingEntry = readRevalidatingSegmentCacheEntry(now, map, tree.varyPath);
    if (existingEntry !== null) {
        return existingEntry;
    }
    // Create a pending entry and add it to the cache. The stale time is set to a
    // default value; the actual stale time will be set when the entry is
    // fulfilled with data from the server response.
    const varyPathForRequest = (0, _varypath.getSegmentVaryPathForRequest)(fetchStrategy, tree);
    const pendingEntry = createDetachedSegmentCacheEntry(now);
    const isRevalidation = true;
    (0, _cachemap.setInCacheMap)(map, varyPathForRequest, pendingEntry, isRevalidation);
    return pendingEntry;
}
function overwriteRevalidatingSegmentCacheEntry(now, // The map the calling task operates in (`PrefetchTask.segmentCacheMap`).
map, fetchStrategy, tree) {
    // This function is called when we've already decided to replace an existing
    // revalidation entry. Create a new entry and write it into the cache,
    // overwriting the previous value. The stale time is set to a default value;
    // the actual stale time will be set when the entry is fulfilled with data
    // from the server response.
    const varyPathForRequest = (0, _varypath.getSegmentVaryPathForRequest)(fetchStrategy, tree);
    const pendingEntry = createDetachedSegmentCacheEntry(now);
    const isRevalidation = true;
    (0, _cachemap.setInCacheMap)(map, varyPathForRequest, pendingEntry, isRevalidation);
    return pendingEntry;
}
/**
 * Whether an existing cache entry is preferred over an incoming candidate —
 * i.e. the candidate does NOT supersede it. (On an exact tie — same fetch
 * strategy, same partialness — this returns false, so the candidate replaces
 * the existing entry.) This is the precedence rule used both when deciding
 * whether an upsert may replace the entry at its own keypath, and when
 * deciding whether an entry at a more specific keypath may be evicted because
 * it shadows a just-inserted candidate (see `evictShadowingSegmentEntries`).
 *
 * Note that "less/more specific" in the comments below refers to fetch
 * strategy content tiers (how much content a strategy can produce), not the
 * vary-path specificity the eviction docs are concerned with.
 */ function isExistingSegmentEntryPreferred(existingEntry, candidateEntry) {
    return(// We fetched the new segment using a different, less specific fetch
    // strategy than the segment we already have in the cache, so it can't
    // have more content.
    candidateEntry.fetchStrategy !== existingEntry.fetchStrategy && !canNewFetchStrategyProvideMoreContent(existingEntry.fetchStrategy, candidateEntry.fetchStrategy) || // The existing entry isn't partial, but the new one is.
    // (TODO: can this be true if `candidateEntry.fetchStrategy >= existingEntry.fetchStrategy`?)
    !existingEntry.isPartial && candidateEntry.isPartial);
}
function upsertSegmentEntry(now, // The map the whole upsert (existing-entry read, insert, shadow eviction)
// operates in. Prefetch response-write paths pass the spawning task's map
// (`PrefetchTask.segmentCacheMap`), so a response that lands after a
// testing-lock scope boundary still writes into the map its entries
// live in.
map, varyPath, candidateEntry, // The fully concrete vary path a read for this segment position resolves
// against (all concrete param values, i.e. `tree.varyPath`) — the most
// specific path a read would use. Note this is the opposite of the
// generalized keying path that `getSegmentVaryPathForRequest` computes.
// Used to detect and evict stale entries at more specific keypaths that
// would otherwise shadow the candidate. Pass null when there's no request
// context; the shadow check is skipped.
lookupVaryPath) {
    // We have a new entry that has not yet been inserted into the cache. Before
    // we do so, we need to confirm whether it takes precedence over the existing
    // entry (if one exists).
    // TODO: We should not upsert an entry if its key was invalidated in the time
    // since the request was made. We can do that by passing the "owner" entry to
    // this function and confirming it's the same as `existingEntry`.
    if ((0, _cachemap.isValueExpired)(now, getCurrentSegmentCacheVersion(), candidateEntry)) {
        // The entry is expired. We cannot upsert it.
        return null;
    }
    const existingEntry = (0, _cachemap.getFromCacheMap)(now, getCurrentSegmentCacheVersion(), map, varyPath, false, false);
    if (existingEntry !== null) {
        // Don't replace a more specific segment with a less-specific one. A case where this
        // might happen is if the existing segment was fetched via
        // `<Link prefetch={true}>`.
        if (isExistingSegmentEntryPreferred(existingEntry, candidateEntry)) {
            // The candidate does not supersede the existing entry. Leave the
            // existing entry in place and discard the candidate by not inserting it.
            //
            // We must not mutate the candidate here (e.g. downgrade it to Rejected or
            // null out its `rsc`). The caller does not transfer exclusive ownership
            // of it: it may already have been fulfilled, resolving its promise to a
            // waiter that holds the entry and reads `rsc` off it later. A navigation
            // seed is such a waiter, via `waitForSegmentCacheEntry`. Nulling `rsc`
            // after the fact resolves that read to `null`, so the waiter loses the
            // data it was about to render. Declining to insert it is enough: the
            // existing entry stays canonical, and the candidate keeps its valid (if
            // less complete) data for any waiter that already took it.
            return null;
        }
        // Ping any tasks blocked on the existing entry before replacing it so they
        // re-run and pick up the new entry. Without this, tasks waiting on the
        // existing Empty/Pending entry would be stranded — the new fulfilled
        // candidate has no blockedTasks of its own.
        if (existingEntry.status === _cachemap.EntryStatus.Empty || existingEntry.status === _cachemap.EntryStatus.Pending) {
            pingBlockedTasks(existingEntry);
        }
    // Replace the existing entry by writing the candidate over its keypath
    // below (the same mechanism `overwriteRevalidatingSegmentCacheEntry`
    // uses). We intentionally do NOT call `deleteFromCacheMap` first: deleting
    // vacates the canonical slot, and `deleteMapEntry` promotes a pending
    // Revalidation-slot entry into the vacated slot — which the immediate
    // insert below would then silently overwrite. The in-flight revalidation
    // would vanish from the map, so the next scheduler pass would find an
    // empty revalidation slot and spawn a duplicate request instead of
    // deduping against it. Replacing in place never vacates the slot, so
    // promotion never runs and the pending revalidating entry stays in its
    // Revalidation slot where `readOrCreateRevalidatingSegmentEntry`'s dedupe
    // finds it.
    //
    // The displaced entry's map/LRU accounting is handled by the replacement
    // itself: `setMapEntryValue` drops the displaced value's `ref` and
    // `updateLruSize` swaps its size for the candidate's, which is exactly
    // what delete-then-insert did.
    }
    const isRevalidation = false;
    (0, _cachemap.setInCacheMap)(map, varyPath, candidateEntry, isRevalidation);
    if (lookupVaryPath !== null) {
        evictShadowingSegmentEntries(now, map, lookupVaryPath, candidateEntry);
    }
    return candidateEntry;
}
/**
 * Evicts stale entries at more specific keypaths that shadow a just-inserted
 * candidate entry.
 *
 * A response can be written to the cache at a MORE GENERIC vary path than the
 * path the request was issued against — for example, the server may report
 * that a segment doesn't vary on a param, so the entry is re-keyed with that
 * param as Fallback. Meanwhile, an older, less useful entry can exist at a
 * more specific path within the same fallback chain — for example, a partial
 * shell entry keyed with root params concrete (see
 * `getShellSegmentVaryPath`). Because segment lookup is
 * most-specific-match-wins, every subsequent read at the concrete request
 * path keeps returning the stale specific entry, and the more complete
 * generic entry is unreachable from that URL. That both wastes the completed
 * request and can loop: a prefetch task that revalidated the segment reads
 * back the same stale entry, decides it needs to revalidate again, and
 * repeats forever.
 *
 * The upsert is the one moment we know the ordering between the two entries:
 * the candidate was produced by a request for this segment position, and
 * `lookupVaryPath` is the fully concrete path a read for that position
 * resolves against, so any entry that a read at that path would return in the
 * candidate's stead is directly comparable to it. If such an entry is settled
 * and the candidate supersedes it — under the same precedence rules the
 * upsert applies at its own keypath — we know we never want to match against
 * it again, so delete it, making the candidate reachable.
 *
 * Non-settled entries are never evicted here: a Pending entry is owned by an
 * in-flight request that will settle it, and an Empty entry is a placeholder
 * that a scheduler pass may still claim and upgrade.
 */ function evictShadowingSegmentEntries(now, map, lookupVaryPath, candidateEntry) {
    // There can in principle be multiple shadowing entries at successively less
    // specific keypaths, so loop until the read returns the candidate (or an
    // entry we don't supersede). Each iteration re-reads and re-checks from
    // scratch (in part because `deleteFromCacheMap` can promote a settled
    // Revalidation-slot value into the just-vacated slot, surfacing a new entry
    // at the same keypath). Each iteration deletes an entry from the map, so
    // the loop terminates naturally; the bound is defensive, and 32 is far
    // beyond any real fallback chain, which is bounded by the vary
    // path's length.
    for(let i = 0; i < 32; i++){
        const shadowEntry = (0, _cachemap.getFromCacheMap)(now, getCurrentSegmentCacheVersion(), map, lookupVaryPath, false, false);
        if (shadowEntry === null || shadowEntry === candidateEntry) {
            // The candidate is reachable from the lookup path (or the read missed
            // entirely, e.g. because the candidate expired). Done.
            return;
        }
        if (shadowEntry.status !== _cachemap.EntryStatus.Fulfilled && shadowEntry.status !== _cachemap.EntryStatus.Rejected) {
            // Only settled entries may be evicted. A Pending entry is held by an
            // in-flight request and will settle on its own.
            return;
        }
        if (isExistingSegmentEntryPreferred(shadowEntry, candidateEntry)) {
            // The shadowing entry is preferred over the candidate (e.g. it's a
            // complete entry fetched with a more specific strategy). Leave it —
            // reads at this path should keep matching it.
            return;
        }
        // The candidate supersedes the shadowing entry. Evict it. Settled entries
        // shouldn't have blocked tasks (Fulfilled always has `blockedTasks:
        // null`, and Rejected entries were pinged at rejection), but ping
        // defensively before deleting, matching the upsert-evict pattern above.
        pingBlockedTasks(shadowEntry);
        (0, _cachemap.deleteFromCacheMap)(shadowEntry);
    }
}
function createDetachedSegmentCacheEntry(now) {
    // Default stale time for pending segment cache entries. The actual stale time
    // is set when the entry is fulfilled with data from the server response.
    const staleAt = now + 30 * 1000;
    const emptyEntry = {
        status: _cachemap.EntryStatus.Empty,
        blockedTasks: null,
        // Default to assuming the fetch strategy will be PPR. This will be updated
        // when a fetch is actually initiated.
        fetchStrategy: _types.FetchStrategy.PPR,
        rsc: null,
        isPartial: true,
        isUpgradeableISRFallback: false,
        promise: null,
        // Map-related fields
        ref: null,
        size: 0,
        staleAt,
        version: 0
    };
    return emptyEntry;
}
function upgradeToPendingSegment(emptyEntry, fetchStrategy) {
    const pendingEntry = emptyEntry;
    pendingEntry.status = _cachemap.EntryStatus.Pending;
    pendingEntry.fetchStrategy = fetchStrategy;
    if (fetchStrategy === _types.FetchStrategy.Full) {
        // We can assume the response will contain the full segment data. Set this
        // to false so we know it's OK to omit this segment from any navigation
        // requests that may happen while the data is still pending.
        pendingEntry.isPartial = false;
    }
    // Set the version here, since this is right before the request is initiated.
    // The next time the segment cache version is incremented, the entry will
    // effectively be evicted. This happens before initiating the request, rather
    // than when receiving the response, because it's guaranteed to happen
    // before the data is read on the server.
    pendingEntry.version = getCurrentSegmentCacheVersion();
    return pendingEntry;
}
function attemptToFulfillDynamicSegmentFromBFCache(now, segment, tree) {
    // Attempts to fulfill an empty segment cache entry using data from the
    // bfcache. This is only valid during a Full prefetch (i.e. one that includes
    // dynamic data), because the bfcache stores data from navigations which
    // always include dynamic data.
    // We always use the canonical vary path when checking the bfcache. This is
    // the same operation we'd use to access the cache during a
    // regular navigation.
    const varyPath = tree.varyPath;
    // Read from the BFCache without expiring it (pass -1). We check freshness
    // ourselves using navigatedAt, because the BFCache's staleAt may have been
    // overridden by a per-page unstable_dynamicStaleTime and can't be used to
    // derive the original request time.
    const bfcacheEntry = (0, _bfcache.readFromBFCache)(varyPath);
    if (bfcacheEntry !== null) {
        // The stale time for dynamic prefetches (default: 5 mins) is different
        // from the stale time for regular navigations (default: 0 secs). Use
        // navigatedAt to compute the correct expiry for prefetch purposes.
        const dynamicPrefetchStaleAt = bfcacheEntry.navigatedAt + _navigatereducer.STATIC_STALETIME_MS;
        if (now > dynamicPrefetchStaleAt) {
            return null;
        }
        const pendingSegment = upgradeToPendingSegment(segment, _types.FetchStrategy.Full);
        const isPartial = false;
        return fulfillSegmentCacheEntry(pendingSegment, bfcacheEntry.rsc, dynamicPrefetchStaleAt, isPartial, // bfcache data is concrete, never an ISR fallback.
        false, _types.FetchStrategy.Full);
    }
    return null;
}
function attemptToUpgradeSegmentFromBFCache(now, // The map the calling task operates in (`PrefetchTask.segmentCacheMap`).
map, tree) {
    const varyPath = tree.varyPath;
    const bfcacheEntry = (0, _bfcache.readFromBFCache)(varyPath);
    if (bfcacheEntry !== null) {
        const dynamicPrefetchStaleAt = bfcacheEntry.navigatedAt + _navigatereducer.STATIC_STALETIME_MS;
        if (now > dynamicPrefetchStaleAt) {
            return null;
        }
        const pendingSegment = upgradeToPendingSegment(createDetachedSegmentCacheEntry(now), _types.FetchStrategy.Full);
        const isPartial = false;
        const newEntry = fulfillSegmentCacheEntry(pendingSegment, bfcacheEntry.rsc, dynamicPrefetchStaleAt, isPartial, // bfcache data is concrete, never an ISR fallback.
        false, _types.FetchStrategy.Full);
        const segmentVaryPath = (0, _varypath.getSegmentVaryPathForRequest)(_types.FetchStrategy.Full, tree);
        const upserted = upsertSegmentEntry(now, map, segmentVaryPath, newEntry, // The concrete lookup path this BFCache upgrade applies to. (In
        // practice a Full request path is already fully concrete, so nothing
        // can shadow the new entry and the shadow check is a no-op.)
        tree.varyPath);
        if (upserted !== null && upserted.status === _cachemap.EntryStatus.Fulfilled) {
            return upserted;
        }
    }
    return null;
}
function pingBlockedTasks(entry) {
    const blockedTasks = entry.blockedTasks;
    if (blockedTasks !== null) {
        for (const task of blockedTasks){
            (0, _scheduler.pingPrefetchTask)(task);
        }
        entry.blockedTasks = null;
    }
}
function createMetadataRouteTree(metadataVaryPath) {
    // The Head is not actually part of the route tree, but other than that, it's
    // fetched and cached like a segment. Some functions expect a RouteTree
    // object, so rather than fork the logic in all those places, we use this
    // "fake" one.
    const metadata = {
        requestKey: _segmentvalueencoding.HEAD_REQUEST_KEY,
        segment: _segmentvalueencoding.HEAD_REQUEST_KEY,
        shellVaryPath: (0, _varypath.getShellSegmentVaryPath)(metadataVaryPath),
        refreshState: null,
        varyPath: metadataVaryPath,
        // The metadata isn't really a "page" (though it isn't really a "segment"
        // either) but for the purposes of how this field is used, it behaves like
        // one. If this logic ever gets more complex we can change this to an enum.
        isPage: true,
        slots: null,
        prefetchHints: 0
    };
    return metadata;
}
function fulfillRouteCacheEntry(now, entry, tree, metadataVaryPath, couldBeIntercepted, canonicalUrl, supportsPerSegmentPrefetching) {
    // Get the rendered search from the vary path
    const renderedSearch = (0, _varypath.getRenderedSearchFromVaryPath)(metadataVaryPath) ?? '';
    const fulfilledEntry = entry;
    fulfilledEntry.status = _cachemap.EntryStatus.Fulfilled;
    fulfilledEntry.tree = tree;
    fulfilledEntry.metadata = createMetadataRouteTree(metadataVaryPath);
    // Route structure is essentially static — it only changes on deploy.
    // Always use the static stale time.
    // NOTE: An exception is rewrites/redirects in middleware or proxy, which can
    // change routes dynamically. We have other strategies for handling those.
    //
    // If the route tree has stale inlining hints (e.g. the initial RSC payload
    // for a build-time static page, generated before collectPrefetchHints ran),
    // immediately expire the entry so it gets re-fetched with correct hints.
    // The segment data itself is still valid — only the route tree (which
    // contains the hint bits) needs to be re-fetched.
    if (tree.prefetchHints & _approutertypes.PrefetchHint.InliningHintsStale) {
        fulfilledEntry.staleAt = -1;
    } else {
        fulfilledEntry.staleAt = now + _navigatereducer.STATIC_STALETIME_MS;
    }
    fulfilledEntry.couldBeIntercepted = couldBeIntercepted;
    fulfilledEntry.canonicalUrl = canonicalUrl;
    fulfilledEntry.renderedSearch = renderedSearch;
    fulfilledEntry.supportsPerSegmentPrefetching = supportsPerSegmentPrefetching;
    fulfilledEntry.hasDynamicRewrite = false;
    pingBlockedTasks(entry);
    return fulfilledEntry;
}
function writeRouteIntoCache(now, pathname, search, nextUrl, tree, metadataVaryPath, couldBeIntercepted, canonicalUrl, supportsPerSegmentPrefetching) {
    const pendingEntry = createDetachedRouteCacheEntry();
    const fulfilledEntry = fulfillRouteCacheEntry(now, pendingEntry, tree, metadataVaryPath, couldBeIntercepted, canonicalUrl, supportsPerSegmentPrefetching);
    const varyPath = (0, _varypath.getFulfilledRouteVaryPath)(pathname, search, nextUrl, couldBeIntercepted);
    const isRevalidation = false;
    (0, _cachemap.setInCacheMap)(routeCacheMap, varyPath, fulfilledEntry, isRevalidation);
    return fulfilledEntry;
}
function markRouteEntryAsDynamicRewrite(entry) {
    entry.hasDynamicRewrite = true;
// Note: The caller is responsible for also calling invalidateRouteCacheEntries
// to invalidate other entries that may have been derived from this template
// before we knew it had a dynamic rewrite.
}
function fulfillSegmentCacheEntry(segmentCacheEntry, rsc, staleAt, isPartial, // Only static (per-segment PPR) responses can be ISR fallbacks; all other
// callers pass false. Always assigned (even when false) so that re-fulfilling
// a previously-fallback entry with a concrete response clears the flag and
// ends the retry loop.
isUpgradeableISRFallback, // The strategy tier describing the CONTENT this entry is fulfilled with —
// which comes from the response, not the tier the entry was requested at.
// Usually the two agree, but when a response's shell payload IS the full
// response (no shell/full split), shell-spawned entries are fulfilled with
// full-tier content and recorded as such (see the promotion in
// writeSegmentBundleResponse). Always assigned, replacing
// the spawn-time strategy set by upgradeToPendingSegment; the write walks'
// matching and keying decisions all happen against the spawn-time
// strategy, before fulfillment, so they are unaffected. See
// SegmentCacheEntryShared['fetchStrategy'].
fetchStrategy) {
    const fulfilledEntry = segmentCacheEntry;
    fulfilledEntry.status = _cachemap.EntryStatus.Fulfilled;
    fulfilledEntry.rsc = rsc;
    fulfilledEntry.staleAt = staleAt;
    fulfilledEntry.isPartial = isPartial;
    fulfilledEntry.isUpgradeableISRFallback = isUpgradeableISRFallback;
    fulfilledEntry.fetchStrategy = fetchStrategy;
    // Resolve any listeners that were waiting for this data.
    if (segmentCacheEntry.promise !== null) {
        segmentCacheEntry.promise.resolve(fulfilledEntry);
        // Free the promise for garbage collection.
        fulfilledEntry.promise = null;
    }
    pingBlockedTasks(segmentCacheEntry);
    return fulfilledEntry;
}
function rejectRouteCacheEntry(entry, staleAt) {
    const rejectedEntry = entry;
    rejectedEntry.status = _cachemap.EntryStatus.Rejected;
    rejectedEntry.staleAt = staleAt;
    pingBlockedTasks(entry);
}
function rejectSegmentCacheEntry(entry, staleAt) {
    const rejectedEntry = entry;
    rejectedEntry.status = _cachemap.EntryStatus.Rejected;
    rejectedEntry.staleAt = staleAt;
    if (entry.promise !== null) {
        // NOTE: We don't currently propagate the reason the prefetch was canceled
        // but we could by accepting a `reason` argument.
        entry.promise.resolve(null);
        entry.promise = null;
    }
    pingBlockedTasks(entry);
}
function convertRootTreePrefetchToRouteTree(rootTree, renderedPathname, renderedSearch, acc) {
    // Remove trailing and leading slashes
    const pathnameParts = (0, _cachekey.splitPathnameIntoParts)(renderedPathname);
    const index = 0;
    const rootSegment = _segmentvalueencoding.ROOT_SEGMENT_REQUEST_KEY;
    return convertTreePrefetchToRouteTree(rootTree.tree, rootSegment, null, _segmentvalueencoding.ROOT_SEGMENT_REQUEST_KEY, pathnameParts, index, renderedSearch, acc);
}
function convertTreePrefetchToRouteTree(prefetch, segment, partialVaryPath, requestKey, pathnameParts, pathnamePartsIndex, renderedSearch, acc) {
    // Converts the route tree sent by the server into the format used by the
    // cache. The cached version of the tree includes additional fields, such as a
    // cache key for each segment. Since this is frequently accessed, we compute
    // it once instead of on every access. This same cache key is also used to
    // request the segment from the server.
    let slots = null;
    let isPage;
    let varyPath;
    const prefetchSlots = prefetch.slots;
    if (prefetchSlots !== null) {
        isPage = false;
        varyPath = (0, _varypath.finalizeLayoutVaryPath)(requestKey, partialVaryPath);
        slots = new Map();
        for(let parallelRouteKey in prefetchSlots){
            const childPrefetch = prefetchSlots[parallelRouteKey];
            const childSegmentName = childPrefetch.name;
            const childParam = childPrefetch.param;
            let childDoesAppearInURL;
            let childSegment;
            let childPartialVaryPath;
            if (childParam !== null) {
                // This segment is parameterized. Get the param from the pathname.
                const childParamValue = (0, _routeparams.parseDynamicParamFromURLPart)(childParam.type, pathnameParts, pathnamePartsIndex);
                // Assign a cache key to the segment, based on the param value. In the
                // pre-Segment Cache implementation, the server computes this and sends
                // it in the body of the response. In the Segment Cache implementation,
                // the server sends an empty string and we fill it in here.
                // TODO: We're intentionally not adding the search param to page
                // segments here; it's tracked separately and added back during a read.
                // This would clearer if we waited to construct the segment until it's
                // read from the cache, since that's effectively what we're
                // doing anyway.
                const childParamKey = // The server omits this field from the prefetch response when
                // cacheComponents is enabled.
                childParam.key !== null ? childParam.key : (0, _routeparams.getCacheKeyForDynamicParam)(childParamValue, '');
                childPartialVaryPath = (0, _varypath.appendLayoutVaryPath)(partialVaryPath, childParamKey, childSegmentName, // The child's param is a root param iff the child segment is at or
                // above the root layout, which the server marks directly.
                (childPrefetch.prefetchHints & _approutertypes.PrefetchHint.IsRootLayoutOrAbove) !== 0);
                childSegment = [
                    childSegmentName,
                    childParamKey,
                    childParam.type,
                    childParam.siblings
                ];
                childDoesAppearInURL = true;
            } else {
                // This segment does not have a param. Inherit the partial vary path of
                // the parent.
                childPartialVaryPath = partialVaryPath;
                childSegment = childSegmentName;
                childDoesAppearInURL = (0, _routeparams.doesStaticSegmentAppearInURL)(childSegmentName);
            }
            // Only increment the index if the segment appears in the URL. If it's a
            // "virtual" segment, like a route group, it remains the same.
            const childPathnamePartsIndex = childDoesAppearInURL ? pathnamePartsIndex + 1 : pathnamePartsIndex;
            const childRequestKeyPart = (0, _segmentvalueencoding.createSegmentRequestKeyPart)(childSegment);
            const childRequestKey = (0, _segmentvalueencoding.appendSegmentRequestKeyPart)(requestKey, parallelRouteKey, childRequestKeyPart);
            slots.set(parallelRouteKey, convertTreePrefetchToRouteTree(childPrefetch, childSegment, childPartialVaryPath, childRequestKey, pathnameParts, childPathnamePartsIndex, renderedSearch, acc));
        }
    } else {
        if (requestKey.endsWith(_segment.PAGE_SEGMENT_KEY)) {
            // This is a page segment.
            isPage = true;
            varyPath = (0, _varypath.finalizePageVaryPath)(requestKey, renderedSearch, partialVaryPath);
            // The metadata "segment" is not part the route tree, but it has the same
            // conceptual params as a page segment. Write the vary path into the
            // accumulator object. If there are multiple parallel pages, we use the
            // first one. Which page we choose is arbitrary as long as it's
            // consistently the same one every time every time. See
            // finalizeMetadataVaryPath for more details.
            if (acc.metadataVaryPath === null) {
                acc.metadataVaryPath = (0, _varypath.finalizeMetadataVaryPath)(requestKey, renderedSearch, partialVaryPath);
            }
        } else {
            // This is a layout segment.
            isPage = false;
            varyPath = (0, _varypath.finalizeLayoutVaryPath)(requestKey, partialVaryPath);
        }
    }
    return {
        requestKey,
        segment,
        shellVaryPath: (0, _varypath.getShellSegmentVaryPath)(varyPath),
        refreshState: null,
        // TODO: Cheating the type system here a bit because TypeScript can't tell
        // that the type of isPage and varyPath are consistent. The fix would be to
        // create separate constructors and call the appropriate one from each of
        // the branches above. Just seems a bit overkill only for one field so I'll
        // leave it as-is for now. If isPage were wrong it would break the behavior
        // and we'd catch it quickly, anyway.
        varyPath: varyPath,
        isPage: isPage,
        slots,
        prefetchHints: prefetch.prefetchHints
    };
}
function convertRootFlightRouterStateToRouteTree(flightRouterState, renderedSearch, acc) {
    return convertFlightRouterStateToRouteTree(flightRouterState, _segmentvalueencoding.ROOT_SEGMENT_REQUEST_KEY, null, renderedSearch, acc);
}
function convertReusedFlightRouterStateToRouteTree(parentRouteTree, parallelRouteKey, flightRouterState, renderedSearch, acc) {
    // Create a RouteTree for a FlightRouterState that was reused from an older
    // route. This happens during a navigation when a parallel route slot does not
    // match the target route; we reuse whatever slot was already active.
    // Unlike a FlightRouterState, the RouteTree type contains backreferences to
    // the parent segments. Append the vary path to the parent's vary path.
    const parentPartialVaryPath = parentRouteTree.isPage ? (0, _varypath.getPartialPageVaryPath)(parentRouteTree.varyPath) : (0, _varypath.getPartialLayoutVaryPath)(parentRouteTree.varyPath);
    const segment = flightRouterState[0];
    // And the request key.
    const parentRequestKey = parentRouteTree.requestKey;
    const requestKeyPart = (0, _segmentvalueencoding.createSegmentRequestKeyPart)(segment);
    const requestKey = (0, _segmentvalueencoding.appendSegmentRequestKeyPart)(parentRequestKey, parallelRouteKey, requestKeyPart);
    return convertFlightRouterStateToRouteTree(flightRouterState, requestKey, parentPartialVaryPath, renderedSearch, acc);
}
function convertFlightRouterStateToRouteTree(flightRouterState, requestKey, parentPartialVaryPath, parentRenderedSearch, acc) {
    const originalSegment = flightRouterState[0];
    // This segment's param (if any) is a root param iff the segment is at or
    // above the root layout, which the server marks directly.
    const isRootParam = ((flightRouterState[4] ?? 0) & _approutertypes.PrefetchHint.IsRootLayoutOrAbove) !== 0;
    // If the FlightRouterState has a refresh state, then this segment is part of
    // an inactive parallel route. It has a different rendered search query than
    // the outer parent route. In order to construct the inactive route correctly,
    // we must restore the query that was originally used to render it.
    const compressedRefreshState = flightRouterState[2] ?? null;
    const refreshState = compressedRefreshState !== null ? {
        canonicalUrl: compressedRefreshState[0],
        renderedSearch: compressedRefreshState[1]
    } : null;
    const renderedSearch = refreshState !== null ? refreshState.renderedSearch : parentRenderedSearch;
    let segment;
    let partialVaryPath;
    let isPage;
    let varyPath;
    if (Array.isArray(originalSegment)) {
        isPage = false;
        const paramCacheKey = originalSegment[1];
        const paramName = originalSegment[0];
        partialVaryPath = (0, _varypath.appendLayoutVaryPath)(parentPartialVaryPath, paramCacheKey, paramName, isRootParam);
        varyPath = (0, _varypath.finalizeLayoutVaryPath)(requestKey, partialVaryPath);
        segment = originalSegment;
    } else {
        // This segment does not have a param. Inherit the partial vary path of
        // the parent.
        partialVaryPath = parentPartialVaryPath;
        if (requestKey.endsWith(_segment.PAGE_SEGMENT_KEY)) {
            // This is a page segment.
            isPage = true;
            // The navigation implementation expects the search params to be included
            // in the segment. However, in the case of a static response, the search
            // params are omitted. So the client needs to add them back in when reading
            // from the Segment Cache.
            //
            // For consistency, we'll do this for dynamic responses, too.
            //
            // TODO: We should move search params out of FlightRouterState and handle
            // them entirely on the client, similar to our plan for dynamic params.
            segment = _segment.PAGE_SEGMENT_KEY;
            varyPath = (0, _varypath.finalizePageVaryPath)(requestKey, renderedSearch, partialVaryPath);
            // The metadata "segment" is not part the route tree, but it has the same
            // conceptual params as a page segment. Write the vary path into the
            // accumulator object. If there are multiple parallel pages, we use the
            // first one. Which page we choose is arbitrary as long as it's
            // consistently the same one every time every time. See
            // finalizeMetadataVaryPath for more details.
            if (acc.metadataVaryPath === null) {
                acc.metadataVaryPath = (0, _varypath.finalizeMetadataVaryPath)(requestKey, renderedSearch, partialVaryPath);
            }
        } else {
            // This is a layout segment.
            isPage = false;
            segment = originalSegment;
            varyPath = (0, _varypath.finalizeLayoutVaryPath)(requestKey, partialVaryPath);
        }
    }
    let slots = null;
    const parallelRoutes = flightRouterState[1];
    for(let parallelRouteKey in parallelRoutes){
        const childRouterState = parallelRoutes[parallelRouteKey];
        const childSegment = childRouterState[0];
        // TODO: Eventually, the param values will not be included in the response
        // from the server. We'll instead fill them in on the client by parsing
        // the URL. This is where we'll do that.
        const childRequestKeyPart = (0, _segmentvalueencoding.createSegmentRequestKeyPart)(childSegment);
        const childRequestKey = (0, _segmentvalueencoding.appendSegmentRequestKeyPart)(requestKey, parallelRouteKey, childRequestKeyPart);
        const childTree = convertFlightRouterStateToRouteTree(childRouterState, childRequestKey, partialVaryPath, renderedSearch, acc);
        if (slots === null) {
            slots = new Map();
        }
        slots.set(parallelRouteKey, childTree);
    }
    return {
        requestKey,
        segment,
        shellVaryPath: (0, _varypath.getShellSegmentVaryPath)(varyPath),
        refreshState,
        // TODO: Cheating the type system here a bit because TypeScript can't tell
        // that the type of isPage and varyPath are consistent. The fix would be to
        // create separate constructors and call the appropriate one from each of
        // the branches above. Just seems a bit overkill only for one field so I'll
        // leave it as-is for now. If isPage were wrong it would break the behavior
        // and we'd catch it quickly, anyway.
        varyPath: varyPath,
        isPage: isPage,
        slots,
        prefetchHints: flightRouterState[4] ?? 0
    };
}
function convertRouteTreeToFlightRouterState(routeTree) {
    const parallelRoutes = {};
    const slots = routeTree.slots;
    if (slots !== null) {
        for (const [parallelRouteKey, childTree] of slots){
            parallelRoutes[parallelRouteKey] = convertRouteTreeToFlightRouterState(childTree);
        }
    }
    const flightRouterState = [
        routeTree.segment,
        parallelRoutes,
        null,
        null
    ];
    if (routeTree.prefetchHints !== 0) {
        flightRouterState[4] = routeTree.prefetchHints;
    }
    return flightRouterState;
}
async function fetchRouteOnCacheMiss(entry, key, // The spawning task's `PrefetchTask.segmentCacheMap`, for the legacy
// branch that writes segment data included in the tree response.
map) {
    // This function is allowed to use async/await because it contains the actual
    // fetch that gets issued on a cache miss. Notice it writes the result to the
    // cache entry directly, rather than return data that is then written by
    // the caller.
    const pathname = key.pathname;
    const search = key.search;
    const nextUrl = key.nextUrl;
    const segmentPath = '/_tree';
    const headers = {
        [_approuterheaders.RSC_HEADER]: '1',
        [_approuterheaders.NEXT_ROUTER_PREFETCH_HEADER]: '1',
        [_approuterheaders.NEXT_ROUTER_SEGMENT_PREFETCH_HEADER]: segmentPath
    };
    if (nextUrl !== null) {
        headers[_approuterheaders.NEXT_URL] = nextUrl;
    }
    try {
        const url = new URL(pathname + search, location.origin);
        let response;
        let urlAfterRedirects;
        if (isOutputExportMode) {
            // In output: "export" mode, we can't use headers to request a particular
            // segment. Instead, we encode the extra request information into the URL.
            // This is not part of the "public" interface of the app; it's an internal
            // Next.js implementation detail that the app developer should not need to
            // concern themselves with.
            //
            // For example, to request a segment:
            //
            //   Path passed to <Link>:   /path/to/page
            //   Path passed to fetch:    /path/to/page/__next-segments/_tree
            //
            //   (This is not the exact protocol, just an illustration.)
            //
            // Before we do that, though, we need to account for redirects. Even in
            // output: "export" mode, a proxy might redirect the page to a different
            // location, but we shouldn't assume or expect that they also redirect all
            // the segment files, too.
            //
            // To check whether the page is redirected, previously we perform a range
            // request of 64 bytes of the HTML document to check if the target page
            // is part of this app (by checking if build id matches). Only if the target
            // page is part of this app do we determine the final canonical URL.
            //
            // However, as mentioned in https://github.com/vercel/next.js/pull/85903,
            // some popular static hosting providers (like Cloudflare Pages or Render.com)
            // do not support range requests, in the worst case, the entire HTML instead
            // of 64 bytes could be returned, which is wasteful.
            //
            // So instead, we drops the check for build id here, and simply perform
            // a HEAD request to rejects 1xx/4xx/5xx responses, and then determine the
            // final URL after redirects.
            //
            // NOTE: We could embed the route tree into the HTML document, to avoid
            // a second request. We're not doing that currently because it would make
            // the HTML document larger and affect normal page loads.
            const headResponse = await (0, _fetch.fetch)(url, {
                method: 'HEAD'
            });
            if (headResponse.status < 200 || headResponse.status >= 400) {
                // The target page responded w/o a successful status code
                // Could be a WAF serving a 403, or a 5xx from a backend
                //
                // Note that we can't use headResponse.ok here, because
                // Response#ok returns `false` with 3xx responses.
                rejectRouteCacheEntry(entry, Date.now() + 10 * 1000);
                return null;
            }
            urlAfterRedirects = headResponse.redirected ? new URL(headResponse.url) : url;
            response = await fetchPrefetchResponse(addSegmentPathToUrlInOutputExportMode(urlAfterRedirects, segmentPath), headers);
        } else {
            // "Server" mode. We can use request headers instead of the pathname.
            // TODO: The eventual plan is to get rid of our custom request headers and
            // encode everything into the URL, using a similar strategy to the
            // "output: export" block above.
            response = await fetchPrefetchResponse(url, headers);
            urlAfterRedirects = response !== null && response.redirected ? new URL(response.url) : url;
        }
        if (!response || !response.ok || !response.body) {
            // Server responded with an error, or with a miss. We should still cache
            // the response, but we can try again after 10 seconds.
            rejectRouteCacheEntry(entry, Date.now() + 10 * 1000);
            return null;
        }
        // TODO: The canonical URL is the href without the origin. I think
        // historically the reason for this is because the initial canonical URL
        // gets passed as a prop to the top-level React component, which means it
        // needs to be computed during SSR. If it were to include the origin, it
        // would need to always be same as location.origin on the client, to prevent
        // a hydration mismatch. To sidestep this complexity, we omit the origin.
        //
        // However, since this is neither a native URL object nor a fully qualified
        // URL string, we need to be careful about how we use it. To prevent subtle
        // mistakes, we should create a special type for it, instead of just string.
        // Or, we should just use a (readonly) URL object instead. The type of the
        // prop that we pass to seed the initial state does not need to be the same
        // type as the state itself.
        const canonicalUrl = (0, _createhreffromurl.createHrefFromUrl)(urlAfterRedirects);
        // Check whether the response varies based on the Next-Url header.
        const varyHeader = response.headers.get('vary');
        const couldBeIntercepted = varyHeader !== null && varyHeader.includes(_approuterheaders.NEXT_URL);
        // TODO: The `closed` promise was originally used to track when a streaming
        // network connection closes, so the scheduler could limit concurrent
        // connections. Now that prefetch responses are buffered, `closed` is
        // resolved immediately after buffering — before the outer function even
        // returns. This mechanism is only still meaningful for dynamic (Full)
        // prefetches, which use incremental streaming. Consider removing the
        // `closed` plumbing for buffered prefetch paths.
        const closed = (0, _promisewithresolvers.createPromiseWithResolvers)();
        // This checks whether the response was served from the per-segment cache,
        // rather than the old prefetching flow. If it fails, it implies that PPR
        // is disabled on this route.
        const routeIsPPREnabled = response.headers.get(_approuterheaders.NEXT_DID_POSTPONE_HEADER) === '2' || // In output: "export" mode, we can't rely on response headers. But if we
        // receive a well-formed response, we can assume it's a static response,
        // because all data is static in this mode.
        isOutputExportMode;
        if (routeIsPPREnabled) {
            const { stream: prefetchStream, size: responseSize } = await createNonTaskyPrefetchResponseStream(response.body);
            closed.resolve();
            (0, _cachemap.setSizeInCacheMap)(entry, responseSize);
            const serverData = await (0, _fetchserverresponse.createFromNextReadableStream)(prefetchStream, headers, {
                allowPartialStream: true
            });
            if ((response.headers.get(_constants.NEXT_NAV_DEPLOYMENT_ID_HEADER) ?? serverData.buildId) !== (0, _navigationbuildid.getNavigationBuildId)()) {
                // The server build does not match the client. Treat as a 404. During
                // an actual navigation, the router will trigger an MPA navigation.
                // TODO: We should cache the fact that this is an MPA navigation.
                rejectRouteCacheEntry(entry, Date.now() + 10 * 1000);
                return null;
            }
            // Get the params that were used to render the target page. These may
            // be different from the params in the request URL, if the page
            // was rewritten.
            const renderedPathname = (0, _routeparams.getRenderedPathname)(response);
            const renderedSearch = (0, _routeparams.getRenderedSearch)(response);
            // Convert the server-sent data into the RouteTree format used by the
            // client cache.
            //
            // During this traversal, we accumulate additional data into this
            // "accumulator" object.
            const acc = {
                metadataVaryPath: null,
                treeDivergedFromBase: false
            };
            const routeTree = convertRootTreePrefetchToRouteTree(serverData, renderedPathname, renderedSearch, acc);
            const metadataVaryPath = acc.metadataVaryPath;
            if (metadataVaryPath === null) {
                rejectRouteCacheEntry(entry, Date.now() + 10 * 1000);
                return null;
            }
            (0, _optimisticroutes.discoverKnownRoute)(Date.now(), pathname, search, nextUrl, entry, routeTree, metadataVaryPath, couldBeIntercepted, canonicalUrl, routeIsPPREnabled, false // hasDynamicRewrite
            );
        } else {
            // PPR is not enabled for this route. The server responds with a
            // different format (FlightRouterState) that we need to convert.
            // TODO: We will unify the responses eventually. I'm keeping the types
            // separate for now because FlightRouterState has so many
            // overloaded concerns.
            const { stream: prefetchStream, size: responseSize } = await createNonTaskyPrefetchResponseStream(response.body);
            closed.resolve();
            (0, _cachemap.setSizeInCacheMap)(entry, responseSize);
            const serverData = await (0, _fetchserverresponse.createFromNextReadableStream)(prefetchStream, headers, {
                allowPartialStream: true
            });
            if ((response.headers.get(_constants.NEXT_NAV_DEPLOYMENT_ID_HEADER) ?? serverData.b) !== (0, _navigationbuildid.getNavigationBuildId)()) {
                // The server build does not match the client. Treat as a 404. During
                // an actual navigation, the router will trigger an MPA navigation.
                // TODO: We should cache the fact that this is an MPA navigation.
                rejectRouteCacheEntry(entry, Date.now() + 10 * 1000);
                return null;
            }
            // Read head vary params synchronously (unioning in the response-level
            // root params). Individual segments carry their own iterables in
            // CacheNodeSeedData; the root iterable is threaded down so each segment
            // unions it too.
            const headVaryParams = (0, _varyparamsdecoding.readVaryParams)(serverData.h, serverData.r);
            writeDynamicTreeResponseIntoCache(Date.now(), // The non-PPR response format is what we'd get if we prefetched these segments
            // using the LoadingBoundary fetch strategy, so mark their cache entries accordingly.
            _types.FetchStrategy.LoadingBoundary, response, serverData, entry, couldBeIntercepted, canonicalUrl, routeIsPPREnabled, headVaryParams, serverData.r ?? null, pathname, search, nextUrl, map);
        }
        if (!couldBeIntercepted) {
            // This route will never be intercepted. So we can use this entry for all
            // requests to this route, regardless of the Next-Url header. This works
            // because when reading the cache we always check for a valid
            // non-intercepted entry first.
            // Re-key the entry. The `set` implementation handles removing it from
            // its previous position in the cache. We don't need to do anything to
            // update the LRU, because the entry is already in it.
            // TODO: Treat this as an upsert — should check if an entry already
            // exists at the new keypath, and if so, whether we should keep that
            // one instead.
            const fulfilledVaryPath = (0, _varypath.getFulfilledRouteVaryPath)(pathname, search, nextUrl, couldBeIntercepted);
            const isRevalidation = false;
            (0, _cachemap.setInCacheMap)(routeCacheMap, fulfilledVaryPath, entry, isRevalidation);
        }
        // Return a promise that resolves when the network connection closes, so
        // the scheduler can track the number of concurrent network connections.
        return {
            value: null,
            closed: closed.promise
        };
    } catch (error) {
        // Either the connection itself failed, or something bad happened while
        // decoding the response. If we're offline, reject with staleAt=-1 so the
        // entry immediately expires and gets retried once the scheduler is
        // re-pinged after connectivity is restored.
        if (process.env.__NEXT_USE_OFFLINE) {
            const { checkOfflineError } = require('../offline');
            if (checkOfflineError(error)) {
                // Unlike navigations and server actions, prefetches don't await
                // waitForConnection — they just reject the cache entry with an
                // immediate expiration so it gets retried once the scheduler is
                // re-pinged after connectivity is restored.
                rejectRouteCacheEntry(entry, -1);
                return null;
            }
        }
        rejectRouteCacheEntry(entry, Date.now() + 10 * 1000);
        return null;
    }
}
function rejectRemainingSegmentsInBundle(entries, staleAt) {
    let node = entries;
    while(node !== null){
        if (node.entry !== null && node.entry.status === _cachemap.EntryStatus.Pending) {
            rejectSegmentCacheEntry(node.entry, staleAt);
        }
        node = node.parent;
    }
}
// When a static (per-segment PPR) prefetch receives an upgradeable fallback
// shell, the localized retry loop re-issues the same fetch after this delay to
// pick up the concrete version once the server's background regeneration
// finishes.
const FALLBACK_RETRY_DELAY_MS = 2000;
// Maximum number of fallback retries per task, to avoid looping indefinitely
// if the server keeps returning a fallback (e.g. misconfiguration).
const MAX_FALLBACK_RETRIES = 3;
async function fetchSegmentsOnCacheMiss(task, route, routeKey, tree, segments, segmentCount, // Which walk spawned the bundle's entries. The request on the wire is
// identical either way; this only decides which payload of the response
// fulfills the entries.
fetchStrategy) {
    // This function is allowed to use async/await because it contains the actual
    // fetch that gets issued on a cache miss. Notice it writes the result to the
    // cache entry directly, rather than return data that is then written by
    // the caller.
    //
    // Segment fetches are non-blocking so we don't need to ping the scheduler
    // on completion.
    let result;
    try {
        result = await fetchSegmentsOnCacheMissImpl(route, routeKey, tree);
    } catch (error) {
        // The connection failed, or the response couldn't be decoded. Reject the
        // pending entries so they don't stay Pending forever, and get retried once
        // the entry expires. If we're offline, expire immediately (-1) so the entry
        // is re-fetched once the scheduler is re-pinged on reconnect; otherwise
        // apply a 10s backoff. (Unlike navigations and server actions, prefetches
        // don't await `waitForConnection`.)
        let staleAt = Date.now() + 10 * 1000;
        if (process.env.__NEXT_USE_OFFLINE) {
            const { checkOfflineError } = require('../offline');
            if (checkOfflineError(error)) {
                staleAt = -1;
            }
        }
        rejectRemainingSegmentsInBundle(segments, staleAt);
        return null;
    }
    if (result === null) {
        // The response was fetched but isn't usable yet (server error/miss, empty
        // data, or a build-id mismatch — the server may be transiently unready).
        // Reject with a short backoff so the entries are retried soon.
        rejectRemainingSegmentsInBundle(segments, Date.now() + 10 * 1000);
        return null;
    }
    const { serverResponse, shellResponse, responseSize, closed } = result;
    const now = Date.now();
    writeSegmentBundleResponseVariants(task.segmentCacheMap, serverResponse, shellResponse, responseSize, segments, segmentCount, now, fetchStrategy);
    // If the server served an upgradeable fallback shell, drive a localized
    // retry loop to pick up the concrete version once the server's background
    // regeneration finishes. Only the first such response per task starts a loop
    // (`fallbackRetryStatus === Empty`); once it leaves Empty, no second loop is
    // started — sibling bundle responses that also got a fallback don't, and
    // neither does a re-hover.
    if (serverResponse.isUpgradeableISRFallback && task.fallbackRetryStatus === _cachemap.EntryStatus.Empty && !task.isCanceled) {
        task.fallbackRetryStatus = _cachemap.EntryStatus.Pending;
        // Fire-and-forget: the loop drives itself via timers and pings the task
        // on success.
        void retryUpgradeableFallbackPrefetch(task, route, routeKey, tree, segments, segmentCount, fetchStrategy);
    }
    return {
        value: null,
        closed
    };
}
/**
 * Issues a single segment-bundle prefetch request, validates it, and decodes
 * the response. Returns the decoded response (see the return type below)
 * on success, or `null` if the response was fetched but isn't usable yet
 * (server error/miss, empty data, or a build-id mismatch — the server may be
 * transiently unready, so it's worth retrying). THROWS if the connection failed
 * or the response couldn't be decoded; re-issuing the identical request won't
 * fix that, so callers should give up rather than retry.
 *
 * This deliberately does NOT touch the cache — it neither writes the decoded
 * segments nor rejects entries. The caller decides what to do with the result:
 * write it (`fetchSegmentsOnCacheMiss`) or ignore it and try again (the retry
 * loop). Calling this again with the same arguments reproduces the exact same
 * request.
 */ async function fetchSegmentsOnCacheMissImpl(route, routeKey, tree) {
    // Use the canonical URL to request the segment, not the original URL. These
    // are usually the same, but the canonical URL will be different if the route
    // tree response was redirected. To avoid an extra waterfall on every segment
    // request, we pass the redirected URL instead of the original one.
    const url = new URL(route.canonicalUrl, location.origin);
    const nextUrl = routeKey.nextUrl;
    const requestKey = tree.requestKey;
    const normalizedRequestKey = requestKey === _segmentvalueencoding.ROOT_SEGMENT_REQUEST_KEY ? // handling of these requests, we encode the root segment path as
    // `_index` instead of as an empty string. This should be treated as
    // an implementation detail and not as a stable part of the protocol.
    // It just needs to match the equivalent logic that happens when
    // prerendering the responses. It should not leak outside of Next.js.
    '/_index' : requestKey;
    const headers = {
        [_approuterheaders.RSC_HEADER]: '1',
        [_approuterheaders.NEXT_ROUTER_PREFETCH_HEADER]: '1',
        [_approuterheaders.NEXT_ROUTER_SEGMENT_PREFETCH_HEADER]: normalizedRequestKey
    };
    if (nextUrl !== null) {
        headers[_approuterheaders.NEXT_URL] = nextUrl;
    }
    const requestUrl = isOutputExportMode ? addSegmentPathToUrlInOutputExportMode(url, normalizedRequestKey) : url;
    const response = await fetchPrefetchResponse(requestUrl, headers);
    if (!response || !response.ok || // This checks whether the response was served from the per-segment cache,
    // rather than the old prefetching flow. If it fails, it implies that PPR
    // is disabled on this route. Theoretically this should never happen
    // because we only issue requests for segments once we've verified that
    // the route supports PPR.
    response.headers.get(_approuterheaders.NEXT_DID_POSTPONE_HEADER) !== '2' && // In output: "export" mode, we can't rely on response headers. But if
    // we receive a well-formed response, we can assume it's a static
    // response, because all data is static in this mode.
    !isOutputExportMode || !response.body) {
        // Server responded with an error or a miss — fetched but not usable.
        return null;
    }
    // See TODO in fetchRouteOnCacheMiss about removing `closed` for
    // buffered prefetch paths.
    const closed = (0, _promisewithresolvers.createPromiseWithResolvers)();
    const { stream: prefetchStream, size: responseSize, buffer } = await createNonTaskyPrefetchResponseStream(response.body);
    closed.resolve();
    // Parse the response. Always a SegmentPrefetchResponse with a build ID and a
    // data array. A connection drop or malformed stream throws here, which
    // propagates to the caller as a non-retryable failure.
    const serverResponse = await (0, _fetchserverresponse.createFromNextReadableStream)(prefetchStream, headers, {
        allowPartialStream: true
    });
    if (serverResponse.data.length === 0) {
        return null;
    }
    if ((response.headers.get(_constants.NEXT_NAV_DEPLOYMENT_ID_HEADER) ?? serverResponse.buildId) !== (0, _navigationbuildid.getNavigationBuildId)()) {
        // The server build does not match the client. Treat as a 404. During
        // an actual navigation, the router will trigger an MPA navigation.
        return null;
    }
    // Extract the shell payload, if the response carries a distinct one
    // (positive shell byte offset): decode the buffered bytes a SECOND time,
    // truncated at the boundary. The truncation is what produces the shell
    // variant: each segment's param-dependent rows land past the boundary and
    // decode as still-pending, which renders as the param fallback. It also
    // rewinds the response's signals — `needsRuntimeRequest` and `isPartial`
    // fulfillments past the boundary read as pending in this decode, so a
    // post-shell runtime-data access doesn't mark the shell variant itself as
    // needing a runtime request.
    // (The offset is never legitimately pending or 0 in this decode: the full
    // buffer is present, and the server only ever emits a positive offset or
    // null. Reading 0 — the default for an unfulfilled `a` — therefore means a
    // bug in Next.js itself, and is handled like an error: the response is
    // treated as carrying no shell, and the scheduler skips the affected
    // segments rather than falling back to a runtime request — see the
    // `shellResponse === null` handling in writeSegmentBundleResponseVariants.
    // Failing in that direction costs a shell prefetch but never leaks
    // post-shell content into shell positions.)
    const shellOffset = readFulfilledValue(serverResponse.a, 0);
    let shellResponse;
    if (shellOffset === null) {
        shellResponse = serverResponse;
    } else if (shellOffset === 0) {
        shellResponse = null;
    } else {
        try {
            shellResponse = await (0, _fetchserverresponse.decodeBufferedStage)(buffer.subarray(0, shellOffset), headers);
        } catch  {
            // The truncated prefix couldn't be decoded. Treat it as if no shell
            // exists; the full payload is still usable. (For a StaticShell-spawned
            // bundle this means the spawned entries are rejected — the scheduler
            // then skips them rather than issuing a runtime substitute; see the
            // no-shell branch in fetchSegmentsOnCacheMiss.)
            shellResponse = null;
        }
    }
    return {
        serverResponse,
        responseSize,
        shellResponse,
        closed: closed.promise
    };
}
/**
 * Writes every payload of a parsed segment-bundle response into the cache.
 * The bundle's entries are fulfilled by the payload matching the walk that
 * spawned them; the other payload, when distinct, is written with a detached
 * copy of the bundle. The full payload is written first so the shell write's
 * shadow eviction sees the fresh concrete entry.
 *
 * Shared by the initial fetch (`fetchSegmentsOnCacheMiss`) and the localized
 * fallback-retry loop. The retry's bundle entries are already settled, so
 * for that caller every write is a detached upsert and the rejection below
 * is a no-op (it only touches Pending entries).
 */ function writeSegmentBundleResponseVariants(// The map the bundle's entries live in (pinned when the request was
// spawned).
map, serverResponse, shellResponse, responseSize, segments, segmentCount, now, // Which walk spawned the bundle's entries; decides which payload fulfills
// them. See fetchSegmentsOnCacheMiss.
fetchStrategy) {
    if (fetchStrategy === _types.FetchStrategy.StaticShell) {
        if (shellResponse !== serverResponse) {
            writeSegmentBundleResponse(map, serverResponse, responseSize, detachEntriesFromSegmentBundle(segments), segmentCount, now, _types.FetchStrategy.PPR, _types.FetchStrategy.PPR);
        }
        if (shellResponse === null) {
            // No shell exists. Reject the spawned entries so the task isn't
            // stranded blocking on them. Note the scheduler does NOT fall back to
            // a runtime request for rejected segments — it skips them outright (see
            // the Rejected case in pingSegmentBundle in scheduler.ts), so these
            // segments get no shell prefetch and no runtime substitute until the
            // rejection's backoff expires.
            rejectRemainingSegmentsInBundle(segments, now + 10 * 1000);
        } else {
            writeSegmentBundleResponse(map, shellResponse, responseSize, segments, segmentCount, now, _types.FetchStrategy.StaticShell, // When the shell IS the full response (no shell/full split), the
            // entries this write fulfills carry full-tier content, so PPR is
            // the strategy that describes it. They're still keyed at the shell
            // vary path: that's the reusable slot, and it serves concrete
            // fallback reads correctly precisely because the shell and concrete
            // variants coincide.
            shellResponse === serverResponse ? _types.FetchStrategy.PPR : _types.FetchStrategy.StaticShell);
        }
    } else {
        writeSegmentBundleResponse(map, serverResponse, responseSize, segments, segmentCount, now, _types.FetchStrategy.PPR, _types.FetchStrategy.PPR);
        if (shellResponse !== null && shellResponse !== serverResponse) {
            writeSegmentBundleResponse(map, shellResponse, responseSize, detachEntriesFromSegmentBundle(segments), segmentCount, now, _types.FetchStrategy.StaticShell, _types.FetchStrategy.StaticShell);
        }
    }
}
/**
 * Writes one payload of a parsed segment-bundle response into the cache:
 * distributes the response size across the bundle, then walks the segments
 * list and the response's `data` array in parallel, fulfilling/upserting
 * each entry. Any segments the server didn't return are rejected so they
 * don't stay Pending forever.
 *
 * `fetchStrategy` says which of the response's payloads this call is
 * writing — StaticShell for the shell payload, PPR for the full payload —
 * which determines the vary paths the entries are keyed at.
 *
 * The walk fulfills any Pending entry in `segments`, so the caller must
 * pass the bundle only to the walk matching the entries' own strategy, and
 * a detached copy to the other. In particular, fulfilling a spawned
 * StaticShell entry with the concrete payload would leak param-dependent
 * content into shell positions: during a navigation, a pending entry can be
 * rendered as a promise that resolves to its eventual value.
 *
 * Shared by the initial fetch and the localized fallback-retry loop (which
 * re-issues the same request and upserts the upgraded result here).
 */ function writeSegmentBundleResponse(map, serverResponse, responseSize, segments, segmentCount, now, fetchStrategy, // The strategy tier that describes this payload's CONTENT, recorded on
// the entries it fulfills. Differs from `fetchStrategy` (which drives
// matching and keying) in one case: a StaticShell write whose payload IS
// the full response (no shell/full split) records PPR — see
// writeSegmentBundleResponseVariants.
payloadFetchStrategy) {
    // Distribute the response size evenly across all segments in the bundle.
    // (When a response produces two payload writes, each write distributes the
    // full response size — intentionally double-charging the LRU for one wire
    // response, since it produced two live entries per segment.)
    const averageSize = responseSize / segmentCount;
    let sizeNode = segments;
    while(sizeNode !== null){
        if (sizeNode.entry !== null) {
            (0, _cachemap.setSizeInCacheMap)(sizeNode.entry, averageSize);
        }
        sizeNode = sizeNode.parent;
    }
    const serverDataArray = serverResponse.data;
    // True if the server served an upgradeable fallback shell (page not yet
    // prerendered with concrete params, but the route can be upgraded). Applies
    // to the whole response and is recorded on each fulfilled entry.
    const responseIsUpgradeableISRFallback = serverResponse.isUpgradeableISRFallback;
    // Whether the render that produced this payload accessed runtime data
    // (page-global; combined with each segment's `isPartial` below to decide
    // the tier each entry records). Read from THIS decode's thenable status,
    // which scopes it to the payload being written — see
    // `SegmentPrefetchResponse['needsRuntimeRequest']` for the encoding.
    //
    // Reading it from the same decode that produced the entry's data is what
    // makes the answer rewindable: a truncated shell decode reads a post-shell
    // runtime access as pending, i.e. `false`, because the shell variant itself
    // doesn't need that data.
    //
    // It is load-bearing in one direction only. A false `true` costs a wasted
    // runtime request; a false `false` would record too high a tier and skip a
    // runtime request that had more content.
    const responseNeedsRuntimeRequest = readFulfilledValue(serverResponse.needsRuntimeRequest, false);
    let node = segments;
    let dataIndex = 0;
    while(node !== null && dataIndex < serverDataArray.length){
        const data = serverDataArray[dataIndex];
        // Null data means this segment has prefetching disabled
        // (prefetch: 'force-disabled' — Partial Prefetching segments have static
        // data, so the server emits a real slot for them). Skip it without
        // creating a cache entry.
        if (data === null || node.tree === null) {
            // The server's and the client's prefetch-disabled hints normally agree,
            // so there shouldn't be a spawned entry for a segment the server
            // skipped. But if they disagree, a Pending entry that a task blocked on
            // would otherwise never settle, stranding the task forever. Settle
            // it defensively.
            if (node.entry !== null && node.entry.status === _cachemap.EntryStatus.Pending) {
                rejectSegmentCacheEntry(node.entry, now + 10 * 1000);
            }
            node = node.parent;
            dataIndex++;
            continue;
        }
        // The segment's late-resolving metadata can be read synchronously
        // because the payload was fully buffered before it was decoded (and, for
        // a truncated shell decode, delivered as a single chunk).
        const entryStaleAt = readFulfilledStaleAt(now, data.staleTime);
        // Root params are emitted once at the top level of the response and
        // unioned into each segment's set here, same as for a route-level
        // response.
        const varyParams = (0, _varyparamsdecoding.readVaryParams)(data.varyParams, serverResponse.rootVaryParams);
        const isPartial = readFulfilledIsPartial(data.isPartial);
        // A runtime prefetch can only provide more content than this entry if the
        // render accessed runtime data AND this particular segment has holes — a
        // fully static segment gains nothing from a runtime request no matter
        // what the page accessed.
        const needsRuntimeRequest = responseNeedsRuntimeRequest && isPartial;
        // An entry records the tier of the content that actually satisfied it,
        // which spans both axes: shell-vs-concrete AND static-vs-runtime.
        //
        // When this payload fully satisfied the segment — no runtime request
        // needed — the content is as complete as a RUNTIME response of the same
        // variant would have been, so it records that runtime tier. That's what
        // lets the scheduler decide "would a runtime request return more?" by
        // comparing tiers alone, with no separate signal to consult.
        //
        // Otherwise the content is only as complete as the static tier it was
        // requested at, so a follow-up runtime request can still supersede it.
        const recordedFetchStrategy = !needsRuntimeRequest ? payloadFetchStrategy === _types.FetchStrategy.StaticShell ? _types.FetchStrategy.RuntimeShell : _types.FetchStrategy.PPRRuntime : fetchStrategy;
        // Determine the vary path to key the segment at. For the full payload,
        // re-key to a more generic path if the server tells us which params the
        // segment varies by.
        const payloadVaryPath = fetchStrategy === _types.FetchStrategy.StaticShell ? node.tree.shellVaryPath : process.env.__NEXT_VARY_PARAMS && varyParams !== null ? (0, _varypath.getFulfilledSegmentVaryPath)(node.tree.varyPath, varyParams) : (0, _varypath.getSegmentVaryPathForRequest)(_types.FetchStrategy.PPR, node.tree);
        const nodeEntry = node.entry;
        if (nodeEntry !== null && nodeEntry.status === _cachemap.EntryStatus.Pending) {
            // We own this entry — fulfill it directly.
            const fulfilledEntry = fulfillSegmentCacheEntry(nodeEntry, data.rsc, entryStaleAt, isPartial, responseIsUpgradeableISRFallback, recordedFetchStrategy);
            if (fetchStrategy === _types.FetchStrategy.StaticShell) {
                // Re-key at the shell vary path, mirroring the RuntimeShell re-key
                // in fulfillEntrySpawnedByRuntimePrefetch. Usually the entry already
                // lives there, but the scheduler can also upgrade a pre-existing
                // Empty entry at a more concrete path in place, so the re-key is
                // load-bearing. The shadow eviction keeps a stale settled entry at
                // a more specific path from hiding the shell entry (the just-written
                // full payload's entry is preferred and survives it). Routed through
                // the upsert rather than a bare set so the usual precedence rules
                // apply: a concurrent task's response (e.g. a RuntimeShell entry)
                // can land in the shell slot first, and this write must not
                // downgrade it. (In the common case the slot already holds this
                // very entry, which the upsert replaces in place.)
                if (process.env.__NEXT_VARY_PARAMS) {
                    upsertSegmentEntry(now, map, node.tree.shellVaryPath, fulfilledEntry, node.tree.varyPath);
                }
            } else {
                // Set the fulfilled entry into the canonical cache slot. Pass the
                // concrete lookup path — the most specific path a read for this
                // segment position would use — so that if the canonical path is more
                // generic (i.e. the server re-keyed the segment), any stale settled
                // entry at a more specific path (e.g. a partial shell entry) that
                // would shadow this one is evicted. See evictShadowingSegmentEntries.
                upsertSegmentEntry(now, map, payloadVaryPath, fulfilledEntry, node.tree.varyPath);
            }
        } else {
            // We don't own this entry. Create a detached entry and attempt to
            // upsert it into this payload's slot.
            const detachedEntry = createDetachedSegmentCacheEntry(now);
            const fulfilledEntry = fulfillSegmentCacheEntry(upgradeToPendingSegment(detachedEntry, fetchStrategy), data.rsc, entryStaleAt, isPartial, responseIsUpgradeableISRFallback, recordedFetchStrategy);
            upsertSegmentEntry(now, map, payloadVaryPath, fulfilledEntry, node.tree.varyPath);
        }
        node = node.parent;
        dataIndex++;
    }
    // If the server returned fewer segments than expected, reject any
    // remaining pending entries so they don't stay Pending forever.
    if (node !== null) {
        rejectRemainingSegmentsInBundle(node, now + 10 * 1000);
    }
}
/**
 * Clones a SegmentBundle chain with every `entry` removed, so a write walk
 * over it is pure detached upserts. Used for the payload that does NOT
 * match the bundle's spawned entries (see writeSegmentBundleResponse).
 */ function detachEntriesFromSegmentBundle(segments) {
    const head = {
        tree: segments.tree,
        entry: null,
        parent: null
    };
    let clonedTail = head;
    let node = segments.parent;
    while(node !== null){
        const clonedNode = {
            tree: node.tree,
            entry: null,
            parent: null
        };
        clonedTail.parent = clonedNode;
        clonedTail = clonedNode;
        node = node.parent;
    }
    return head;
}
// TODO: Consolidate the read* helpers below with the ones in
// vary-params-decoding — they all perform a version of the same synchronous
// read of a buffered decode's late-resolving values.
/**
 * Reads a segment's partialness from its `isPartial` promise. (Unlike the
 * values read via `readFulfilledValue` below, the fulfillment value here is
 * void — partialness is encoded as the ABSENCE of a fulfillment.) The server
 * fulfills it only for a fully-static segment and leaves it pending for a
 * partial one (see `SegmentPrefetch['isPartial']`), so partial == not
 * fulfilled. The read is synchronous because the response is fully buffered
 * before it's decoded, so a fulfillment is already visible on the thenable's
 * status — the same trick `readVaryParams` uses for the vary params iterables.
 */ function readFulfilledIsPartial(isPartial) {
    const thenable = isPartial;
    // Force Flight to unwrap a received-but-not-yet-settled row. A pending row,
    // or a truncated shell decode whose fulfillment landed past the boundary,
    // stays non-fulfilled — read as partial, which is correct either way.
    thenable.then(noop, noop);
    return thenable.status !== 'fulfilled';
}
/**
 * Reads a late-resolving value off a fully-buffered decode's thenable status,
 * using the same trick as above. Returns `valueIfUnresolved` for a row that
 * is pending or absent in this decode — e.g. one whose fulfillment landed
 * past a truncated shell decode's boundary. That's what scopes a response's
 * late-resolving signals to the payload being decoded.
 */ function readFulfilledValue(valueFromServer, valueIfUnresolved) {
    const thenable = valueFromServer;
    // Force Flight to unwrap a received-but-not-yet-settled row.
    thenable.then(noop, noop);
    if (thenable.status === 'fulfilled' && thenable.value !== undefined) {
        return thenable.value;
    }
    return valueIfUnresolved;
}
/**
 * Reads a stale-at time from the staleTime async iterable of a fully-buffered
 * response — segment bundles and stage decodes, which go through
 * `createNonTaskyPrefetchResponseStream`. Because the bytes are all present,
 * each yielded value is already visible on its chunk's thenable status (the
 * same trick `readVaryParams` uses), so this drains synchronously and takes
 * the last value (the final staleTime, as `resolveStaleAt` does for the
 * async case). A missing iterable, or a truncated shell decode whose value
 * landed past the boundary, reads as absent and falls back to the static
 * stale time.
 *
 * For the one response kind that isn't buffered when read — a dynamic `Full`
 * response (fetchStrategy.Full with Partial Prefetching disabled) — use
 * `resolveStaleAt` instead, since its values aren't materialized synchronously.
 */ function readFulfilledStaleAt(now, staleTime) {
    if (staleTime === undefined) {
        return now + _navigatereducer.STATIC_STALETIME_MS;
    }
    const iterator = staleTime[Symbol.asyncIterator]();
    let staleTimeSeconds;
    while(true){
        const chunk = iterator.next();
        chunk.then(noop, noop);
        if (chunk.status !== 'fulfilled' || chunk.value === undefined) {
            break;
        }
        if (chunk.value.done) {
            break;
        }
        staleTimeSeconds = chunk.value.value;
    }
    if (staleTimeSeconds === undefined || isNaN(staleTimeSeconds)) {
        return now + _navigatereducer.STATIC_STALETIME_MS;
    }
    return now + getStaleTimeMs(staleTimeSeconds);
}
const noop = ()=>{};
/**
 * The localized retry loop for an upgradeable fallback shell. Re-issues the
 * exact same segment-bundle request (via `fetchSegmentsOnCacheMissImpl`) up to
 * MAX_FALLBACK_RETRIES times, FALLBACK_RETRY_DELAY_MS apart, until the server
 * returns the concrete (upgraded) version. On success it upserts the upgraded
 * segments (so they aren't re-fetched) and pings the task, so the task's
 * *other* fallback segments get re-attempted. If every attempt is still a
 * fallback (or fails), it gives up.
 *
 * A loop runs at most once per task, ever (the caller gates on
 * `fallbackRetryStatus === Empty`, set to `Pending` before this runs and never
 * reset to `Empty`). The sleep timer is never `clearTimeout`-ed, so the awaited
 * sleep always settles; the loop simply checks `isCanceled` after waking and
 * bails if the task was canceled in the meantime. On success the status becomes
 * `Fulfilled`; on any non-success exit (exhausted retries, fetch error, or
 * cancel) it becomes `Rejected`.
 */ async function retryUpgradeableFallbackPrefetch(task, route, routeKey, tree, segments, segmentCount, // The strategy the initial fetch wrote its payloads with; the upgraded
// result is written through the same payload fork so the same cache slots
// (including the shell paths) are upgraded.
fetchStrategy) {
    for(let attempt = 0; attempt < MAX_FALLBACK_RETRIES; attempt++){
        await new Promise((resolve)=>setTimeout(resolve, FALLBACK_RETRY_DELAY_MS));
        if (task.isCanceled) {
            break;
        }
        let result;
        try {
            result = await fetchSegmentsOnCacheMissImpl(route, routeKey, tree);
        } catch  {
            break;
        }
        if (task.isCanceled) {
            break;
        }
        if (result === null) {
            continue;
        }
        if (result.serverResponse.isUpgradeableISRFallback) {
            continue;
        }
        // Success: the server returned the concrete (upgraded) version. Write it
        // back through the same payload fork as the initial fetch, so every slot
        // the initial fetch wrote — including the shell paths, even when the
        // upgraded response is fully static (shell === full) — is upgraded. The
        // bundle's entries were already settled by the initial fetch, so every
        // write is a detached upsert that replaces the fallback. Mark the loop
        // fulfilled and ping the task; its other fallback segments are now
        // allowed to revalidate.
        const { serverResponse, shellResponse, responseSize } = result;
        const now = Date.now();
        writeSegmentBundleResponseVariants(task.segmentCacheMap, serverResponse, shellResponse, responseSize, segments, segmentCount, now, fetchStrategy);
        task.fallbackRetryStatus = _cachemap.EntryStatus.Fulfilled;
        (0, _scheduler.pingPrefetchTask)(task);
        return;
    }
    // The loop finished without success (exhausted its retries, broke out on a
    // fetch error, or the task was canceled). It won't run again for this task.
    task.fallbackRetryStatus = _cachemap.EntryStatus.Rejected;
}
async function fetchSegmentPrefetchesUsingDynamicRequest(task, route, fetchStrategy, dynamicRequestTree, spawnedEntries) {
    const key = task.key;
    const url = new URL(route.canonicalUrl, location.origin);
    const nextUrl = key.nextUrl;
    if (spawnedEntries.size === 1 && spawnedEntries.has(route.metadata.requestKey)) {
        // The only thing pending is the head. Instruct the server to
        // skip over everything else.
        // TODO: Lift this logic into the caller. Or perhaps unify the
        // "request tree" and the spawnedEntries into the same type so they are
        // guaranteed to always been in sync.
        dynamicRequestTree = MetadataOnlyRequestTree;
    }
    const headers = {
        [_approuterheaders.RSC_HEADER]: '1',
        [_approuterheaders.NEXT_ROUTER_STATE_TREE_HEADER]: (0, _flightdatahelpers.prepareFlightRouterStateForRequest)(dynamicRequestTree)
    };
    if (nextUrl !== null) {
        headers[_approuterheaders.NEXT_URL] = nextUrl;
    }
    switch(fetchStrategy){
        case _types.FetchStrategy.Full:
            {
                break;
            }
        case _types.FetchStrategy.PPRRuntime:
            {
                headers[_approuterheaders.NEXT_ROUTER_PREFETCH_HEADER] = '2';
                break;
            }
        case _types.FetchStrategy.RuntimeShell:
            {
                headers[_approuterheaders.NEXT_ROUTER_PREFETCH_HEADER] = '3';
                break;
            }
        case _types.FetchStrategy.LoadingBoundary:
            {
                headers[_approuterheaders.NEXT_ROUTER_PREFETCH_HEADER] = '1';
                break;
            }
        default:
            {
                fetchStrategy;
            }
    }
    try {
        const response = await fetchPrefetchResponse(url, headers);
        if (!response || !response.ok || !response.body) {
            // Server responded with an error, or with a miss. We should still cache
            // the response, but we can try again after 10 seconds.
            rejectSegmentEntriesIfStillPending(spawnedEntries, Date.now() + 10 * 1000);
            return null;
        }
        const renderedSearch = (0, _routeparams.getRenderedSearch)(response);
        if (renderedSearch !== route.renderedSearch) {
            // The search params that were used to render the target page are
            // different from the search params in the request URL. This only happens
            // when there's a dynamic rewrite in between the tree prefetch and the
            // data prefetch.
            // TODO: For now, since this is an edge case, we reject the prefetch, but
            // the proper way to handle this is to evict the stale route tree entry
            // then fill the cache with the new response.
            rejectSegmentEntriesIfStillPending(spawnedEntries, Date.now() + 10 * 1000);
            return null;
        }
        // Track when the network connection closes. Only meaningful for Full
        // (dynamic) prefetches which use incremental streaming. For buffered
        // paths, this is resolved immediately — see TODO in fetchRouteOnCacheMiss.
        const closed = (0, _promisewithresolvers.createPromiseWithResolvers)();
        let fulfilledEntries = null;
        let prefetchStream;
        let bufferedResponseSize = null;
        if (fetchStrategy === _types.FetchStrategy.Full) {
            // Full prefetches are dynamic responses stored in the prefetch cache.
            // They don't carry vary params or other cache metadata, so there's no
            // need to buffer them. Use the incremental version to allow data to be
            // processed as it arrives.
            prefetchStream = createIncrementalPrefetchResponseStream(response.body, closed.resolve, function onResponseSizeUpdate(totalBytesReceivedSoFar) {
                // When processing a dynamic response, we don't know how large each
                // individual segment is, so approximate by assigning each segment
                // the average of the total response size.
                if (fulfilledEntries === null) {
                    // Haven't received enough data yet to know which segments
                    // were included.
                    return;
                }
                const averageSize = totalBytesReceivedSoFar / fulfilledEntries.length;
                for (const entry of fulfilledEntries){
                    (0, _cachemap.setSizeInCacheMap)(entry, averageSize);
                }
            });
        } else {
            const { stream, size } = await createNonTaskyPrefetchResponseStream(response.body);
            closed.resolve();
            prefetchStream = stream;
            bufferedResponseSize = size;
        }
        const [serverData, cacheData] = await Promise.all([
            (0, _fetchserverresponse.createFromNextReadableStream)(prefetchStream, headers, {
                allowPartialStream: true
            }),
            response.cacheData
        ]);
        const now = Date.now();
        const staleAt = await resolveStaleAt(now, serverData.s, response);
        const buildId = response.headers.get(_constants.NEXT_NAV_DEPLOYMENT_ID_HEADER) ?? serverData.b;
        // Check if a reusable App Shell can be extracted from the main response.
        let serverDataThatSatisfiesSpawnedEntries;
        // The shell and full response have independent stale times. Track the
        // staleAt that corresponds to whatever payload the spawned entries get
        // filled with below.
        let staleAtForSpawnedEntries = staleAt;
        if (cacheData === null) {
            // No shell can be extracted without cache metadata (only present when
            // Cached Navigations is enabled). For routes without a distinct App Shell
            // the extraction below is a no-op anyway (`resolveShellStageData` returns
            // null), so this just short-circuits that case.
            serverDataThatSatisfiesSpawnedEntries = serverData;
        } else {
            const shellStageData = await (0, _fetchserverresponse.resolveShellStageData)(cacheData, serverData, headers);
            if (shellStageData === null) {
                // No App Shell can be extracted. This usually means the entire response
                // _is_ the App Shell. The other possibility (for now, until the feature
                // is fully stabilized) is that App Shells are not yet enabled. Either
                // way, there's nothing extra for us to do: fulfill the pending entries
                // using the response from the server.
                serverDataThatSatisfiesSpawnedEntries = serverData;
            } else {
                // Successfully extracted an App Shell that is a subset of the main
                // response. Depending on the type of prefetch this is, we need to
                // decide whether to fulfill the pending entries with the shell or with
                // the entire response. In either scenario, we'll be inserting _both_
                // versions of the response into the cache; the extra logic is only
                // here so that we don't fulfill pending shell entries with something
                // that's more concrete than what they expect.
                // TODO: The only reason this matters is because during a navigation,
                // if a segment is still pending, we render a promise that resolves to
                // the eventual value of that segment. But that means we cannot
                // eventually resolve that segment to something more concrete than what
                // was already requested. Hence the extra logic here. A cleaner way to
                // model this, though, is whenever we render a promise that resolves to
                // the result of a pending entry, do one additional cache look-up right
                // after the promise resolves, to ensure we never get a mismatching
                // entry. Leaving this for a follow up.
                // shellStageData is a fully-buffered stage decode, so read staleTime
                // synchronously off the thenable status.
                const shellStaleAt = readFulfilledStaleAt(now, shellStageData.s);
                if (fetchStrategy === _types.FetchStrategy.RuntimeShell) {
                    // This is a Shell prefetch, so the pending entries must be fulfilled
                    // with the shell.
                    serverDataThatSatisfiesSpawnedEntries = shellStageData;
                    staleAtForSpawnedEntries = shellStaleAt;
                    // Separately, we'll also cache the entire response, by upserting it
                    // into the cache.
                    writePrerenderResponseIntoCache(now, _types.FetchStrategy.PPR, serverData.f, buildId, serverData.h, serverData.r ?? null, staleAt, dynamicRequestTree, renderedSearch, cacheData.isResponsePartial, task.segmentCacheMap);
                } else {
                    // This is _not_ a Shell prefetch, so the pending entries should be
                    // fulfilled with the entire response.
                    serverDataThatSatisfiesSpawnedEntries = serverData;
                    // Additionally, we might as well upsert the extracted Shell into the
                    // cache, too.
                    // `shellStageData` is only provided in cases where the shell is
                    // different from the main response. If they are equivalent, this
                    // branch is skipped. So it follows that any shell data reaches
                    // this path must be partial -- it does not represent the entire
                    // UI of the target page.
                    const isShellStagePartial = true;
                    writePrerenderResponseIntoCache(now, _types.FetchStrategy.RuntimeShell, shellStageData.f, buildId, shellStageData.h, shellStageData.r ?? null, shellStaleAt, dynamicRequestTree, renderedSearch, isShellStagePartial, task.segmentCacheMap);
                }
            }
        }
        // Read head vary params synchronously (unioning in the response-level root
        // params). Individual segments carry their own iterables in
        // CacheNodeSeedData; the root iterable is threaded down so each segment
        // unions it too.
        const rootVaryParamsIterable = serverDataThatSatisfiesSpawnedEntries.r ?? null;
        const headVaryParams = (0, _varyparamsdecoding.readVaryParams)(serverDataThatSatisfiesSpawnedEntries.h, rootVaryParamsIterable);
        // PPRRuntime and RuntimeShell prefetches are partial when the server
        // marks the response as '~' (Partial). RuntimeShell additionally omits
        // every dynamic suspense boundary below the App Shell, so its segments
        // are always partial regardless of what the server marker says.
        // Full/LoadingBoundary prefetches are always complete.
        const isResponsePartial = fetchStrategy === _types.FetchStrategy.RuntimeShell || fetchStrategy === _types.FetchStrategy.PPRRuntime && (cacheData?.isResponsePartial ?? false);
        const flightDatas = (0, _flightdatahelpers.normalizeFlightData)(serverDataThatSatisfiesSpawnedEntries.f);
        if (typeof flightDatas === 'string') {
            rejectSegmentEntriesIfStillPending(spawnedEntries, Date.now() + 10 * 1000);
            return null;
        }
        const navigationSeed = (0, _navigation.convertServerPatchToFullTree)(now, dynamicRequestTree, flightDatas, renderedSearch, // Not needed for prefetch responses; pass unknown to use the default.
        _bfcache.UnknownDynamicStaleTime);
        if (navigationSeed.treeDivergedFromBase && // A head-only request uses the MetadataOnlyRequestTree stub rather than
        // a tree derived from the route entry, so divergence from it carries
        // no signal.
        // TODO: This special case goes away once convertServerPatchToFullTree
        // diffs against the base RouteTree (route.tree) instead of the
        // request tree.
        dynamicRequestTree !== MetadataOnlyRequestTree) {
            // The server rendered a different route tree than the one we requested:
            // the URL has a rewrite that behaves dynamically, so the params baked
            // into the request are wrong and the server can never fulfill it. Mark
            // the route entry — which doubles as the stored prediction pattern, so
            // this also disables a bad prediction (see matchKnownRoute) that would
            // otherwise be re-derived on every retry — and invalidate entries that
            // were derived from it. This mirrors dispatchRetryDueToTreeMismatch on
            // the navigation path. It can't loop: the refetched route entry is
            // built from the server's response, so it only mismatches again if the
            // rewrite's behavior changes again.
            markRouteEntryAsDynamicRewrite(route);
            invalidateRouteCacheEntries(key.nextUrl, task.treeAtTimeOfPrefetch);
            // Reject with an immediate expiration instead of the usual backoff: the
            // invalidation above triggers a re-prefetch, which per the above does
            // not loop.
            // TODO: Consider also bounding retries with a counter on the task
            // object, so a prefetch that repeatedly fails to settle backs off
            // regardless of the reason.
            rejectSegmentEntriesIfStillPending(spawnedEntries, -1);
            return null;
        }
        // Aside from writing the data into the cache, this function also returns
        // the entries that were fulfilled, so we can streamingly update their sizes
        // in the LRU as more data comes in.
        fulfilledEntries = writeDynamicRenderResponseIntoCache(now, fetchStrategy, flightDatas, buildId, isResponsePartial, headVaryParams, rootVaryParamsIterable, staleAtForSpawnedEntries, navigationSeed, spawnedEntries, task.segmentCacheMap);
        // For buffered responses, update LRU sizes now that we know which
        // entries were fulfilled.
        if (bufferedResponseSize !== null && fulfilledEntries !== null && fulfilledEntries.length > 0) {
            const averageSize = bufferedResponseSize / fulfilledEntries.length;
            for (const entry of fulfilledEntries){
                (0, _cachemap.setSizeInCacheMap)(entry, averageSize);
            }
        }
        // Return a promise that resolves when the network connection closes, so
        // the scheduler can track the number of concurrent network connections.
        return {
            value: null,
            closed: closed.promise
        };
    } catch (error) {
        if (process.env.__NEXT_USE_OFFLINE) {
            const { checkOfflineError } = require('../offline');
            if (checkOfflineError(error)) {
                // Unlike navigations and server actions, prefetches don't await
                // waitForConnection — they just reject the cache entry with an
                // immediate expiration so it gets retried once the scheduler is
                // re-pinged after connectivity is restored.
                rejectSegmentEntriesIfStillPending(spawnedEntries, -1);
                return null;
            }
        }
        rejectSegmentEntriesIfStillPending(spawnedEntries, Date.now() + 10 * 1000);
        return null;
    }
}
function writeDynamicTreeResponseIntoCache(now, fetchStrategy, response, serverData, entry, couldBeIntercepted, canonicalUrl, routeIsPPREnabled, headVaryParams, rootVaryParamsIterable, originalPathname, originalSearch, nextUrl, // The spawning task's `PrefetchTask.segmentCacheMap`.
map) {
    const renderedSearch = (0, _routeparams.getRenderedSearch)(response);
    const normalizedFlightDataResult = (0, _flightdatahelpers.normalizeFlightData)(serverData.f);
    if (// A string result means navigating to this route will result in an
    // MPA navigation.
    typeof normalizedFlightDataResult === 'string' || normalizedFlightDataResult.length !== 1) {
        rejectRouteCacheEntry(entry, now + 10 * 1000);
        return;
    }
    const flightData = normalizedFlightDataResult[0];
    if (!flightData.isRootRender) {
        // Unexpected response format.
        rejectRouteCacheEntry(entry, now + 10 * 1000);
        return;
    }
    const flightRouterState = flightData.tree;
    // If the response was postponed, segments may contain dynamic holes.
    // The head has its own partiality flag (flightDataEntry.isHeadPartial)
    // which is handled separately in writeDynamicRenderResponseIntoCache.
    const isResponsePartial = response.headers.get(_approuterheaders.NEXT_DID_POSTPONE_HEADER) === '1';
    // Convert the server-sent data into the RouteTree format used by the
    // client cache.
    //
    // During this traversal, we accumulate additional data into this
    // "accumulator" object.
    const acc = {
        metadataVaryPath: null,
        treeDivergedFromBase: false
    };
    const routeTree = convertRootFlightRouterStateToRouteTree(flightRouterState, renderedSearch, acc);
    const metadataVaryPath = acc.metadataVaryPath;
    if (metadataVaryPath === null) {
        rejectRouteCacheEntry(entry, now + 10 * 1000);
        return;
    }
    (0, _optimisticroutes.discoverKnownRoute)(now, originalPathname, originalSearch, nextUrl, entry, routeTree, metadataVaryPath, couldBeIntercepted, canonicalUrl, routeIsPPREnabled, false // hasDynamicRewrite
    );
    // If the server sent segment data as part of the response, we should write
    // it into the cache to prevent a second, redundant prefetch request.
    // TODO: This is a leftover branch from before Client Segment Cache was
    // enabled everywhere. Tree prefetches should never include segment data.  We
    // can delete it. Leaving for a subsequent PR.
    const navigationSeed = (0, _navigation.convertServerPatchToFullTree)(now, flightRouterState, normalizedFlightDataResult, renderedSearch, _bfcache.UnknownDynamicStaleTime);
    const buildId = response.headers.get(_constants.NEXT_NAV_DEPLOYMENT_ID_HEADER) ?? serverData.b;
    writeDynamicRenderResponseIntoCache(now, fetchStrategy, normalizedFlightDataResult, buildId, isResponsePartial, headVaryParams, rootVaryParamsIterable, getStaleAtFromHeader(now, response), navigationSeed, null, map);
}
function rejectSegmentEntriesIfStillPending(entries, staleAt) {
    const fulfilledEntries = [];
    for (const entry of entries.values()){
        if (entry.status === _cachemap.EntryStatus.Pending) {
            rejectSegmentCacheEntry(entry, staleAt);
        } else if (entry.status === _cachemap.EntryStatus.Fulfilled) {
            fulfilledEntries.push(entry);
        }
    }
    return fulfilledEntries;
}
function writeDynamicRenderResponseIntoCache(now, fetchStrategy, flightDatas, buildId, isResponsePartial, headVaryParams, rootVaryParamsIterable, staleAt, navigationSeed, spawnedEntries, // The map the work that spawned this response's request is bound to: the
// spawning task's `PrefetchTask.segmentCacheMap` for prefetches, the
// navigation's map for navigation-side writes. Binding the write to the
// requesting work means a response that lands after a testing-lock scope
// boundary still writes into the map its entries live in.
map) {
    if (buildId && buildId !== (0, _navigationbuildid.getNavigationBuildId)()) {
        // The server build does not match the client. Treat as a 404. During
        // an actual navigation, the router will trigger an MPA navigation.
        if (spawnedEntries !== null) {
            rejectSegmentEntriesIfStillPending(spawnedEntries, now + 10 * 1000);
        }
        return null;
    }
    const routeTree = navigationSeed.routeTree;
    const metadataTree = navigationSeed.metadataVaryPath !== null ? createMetadataRouteTree(navigationSeed.metadataVaryPath) : null;
    for (const flightDataEntry of flightDatas){
        const seedData = flightDataEntry.seedData;
        if (seedData !== null) {
            // The data sent by the server represents only a subtree of the app. We
            // need to find the part of the task tree that matches the response.
            //
            // segmentPath represents the parent path of subtree. It's a repeating
            // pattern of parallel route key and segment:
            //
            //   [string, Segment, string, Segment, string, Segment, ...]
            const segmentPath = flightDataEntry.segmentPath;
            let tree = routeTree;
            for(let i = 0; i < segmentPath.length; i += 2){
                const parallelRouteKey = segmentPath[i];
                const childTree = tree?.slots?.get(parallelRouteKey);
                if (childTree !== undefined) {
                    tree = childTree;
                } else {
                    if (spawnedEntries !== null) {
                        rejectSegmentEntriesIfStillPending(spawnedEntries, now + 10 * 1000);
                    }
                    return null;
                }
            }
            writeSeedDataIntoCache(now, // A response write is bound to the map its entries live in (the
            // spawning task's `PrefetchTask.segmentCacheMap`).
            map, fetchStrategy, tree, staleAt, seedData, isResponsePartial, rootVaryParamsIterable, spawnedEntries);
        }
        const head = flightDataEntry.head;
        if (head !== null && metadataTree !== null) {
            // When Cache Components is enabled, the server's `isHeadPartial` flag
            // (isPossiblyPartialHead in app-render.tsx) is unreliable: it's computed
            // before the head is serialized, so it's conservatively `true` for every
            // statically-generated PPR page — even pages whose head is actually
            // complete — and it's `false` for runtime/dynamic responses whose head is
            // actually partial (e.g. a route with an async `generateMetadata`). So we
            // ignore it and derive the head's partiality from whether the response
            // itself was partial, exactly as we do for segments (see
            // `writeSeedDataIntoCache`). A non-partial response carries a complete
            // head; a partial (postponed) one does not.
            //
            // Without Cache Components, the server sends the correct isHeadPartial.
            const isHeadPartial = process.env.__NEXT_CACHE_COMPONENTS ? isResponsePartial : flightDataEntry.isHeadPartial;
            fulfillEntrySpawnedByRuntimePrefetch(now, // A response write is bound to the map its entries live in (the
            // spawning task's `PrefetchTask.segmentCacheMap`).
            map, fetchStrategy, head, isHeadPartial, staleAt, // For head entries, use the head-specific vary params passed as
            // parameter.
            headVaryParams, metadataTree, spawnedEntries);
        }
    }
    // Any entry that's still pending was intentionally not rendered by the
    // server, because it was inside the loading boundary. Mark them as rejected
    // so we know not to fetch them again.
    // TODO: If PPR is enabled on some routes but not others, then it's possible
    // that a different page is able to do a per-segment prefetch of one of the
    // segments we're marking as rejected here. We should mark on the segment
    // somehow that the reason for the rejection is because of a non-PPR prefetch.
    // That way a per-segment prefetch knows to disregard the rejection.
    if (spawnedEntries !== null) {
        const fulfilledEntries = rejectSegmentEntriesIfStillPending(spawnedEntries, now + 10 * 1000);
        return fulfilledEntries;
    }
    return null;
}
function writeSeedDataIntoCache(now, map, fetchStrategy, tree, staleAt, seedData, isResponsePartial, rootVaryParamsIterable, entriesOwnedByCurrentTask) {
    // This function is used to write the result of a runtime server request
    // (CacheNodeSeedData) into the prefetch cache.
    const rsc = seedData[0];
    const isPartial = rsc === null || isResponsePartial;
    // Each segment carries its own vary params iterable in the seed data, which
    // drains to the set of params the segment accessed during render. A null
    // iterable means tracking was not enabled (not a prerender). readVaryParams
    // unions in the response-level root params.
    const varyParams = (0, _varyparamsdecoding.readVaryParams)(seedData[4], rootVaryParamsIterable);
    fulfillEntrySpawnedByRuntimePrefetch(now, // A response write is bound to the map its entries live in (the spawning
    // task's `PrefetchTask.segmentCacheMap`).
    map, fetchStrategy, rsc, isPartial, staleAt, varyParams, tree, entriesOwnedByCurrentTask);
    // Recursively write the child data into the cache.
    const slots = tree.slots;
    if (slots !== null) {
        const seedDataChildren = seedData[1];
        for (const [parallelRouteKey, childTree] of slots){
            const childSeedData = seedDataChildren[parallelRouteKey];
            if (childSeedData !== null && childSeedData !== undefined) {
                writeSeedDataIntoCache(now, map, fetchStrategy, childTree, staleAt, childSeedData, isResponsePartial, rootVaryParamsIterable, entriesOwnedByCurrentTask);
            }
        }
    }
}
function fulfillEntrySpawnedByRuntimePrefetch(now, map, fetchStrategy, rsc, isPartial, staleAt, segmentVaryParams, tree, entriesOwnedByCurrentTask) {
    // Decide whether to re-key the entry under a more generic vary path based on
    // which params the segment actually depends on.
    //
    // Skip re-keying for Full prefetches: as of today, `varyParams` tracking only
    // works within the static stage portion of a response. A Full prefetch
    // response covers all stages, and we can't track params during the dynamic
    // stage without dead-locking the Flight stream, so the server-reported set is
    // incomplete and can't be trusted for the full response. Re-keying with an
    // untrustworthy set could replace concrete params with Fallback and let
    // unrelated URLs read each other's content from the cache.
    //
    // For RuntimeShell prefetches, always re-key to the precomputed shell vary
    // path. A shell entry is spawned at a concrete param path but is reusable
    // across all of them; tree.shellVaryPath (root-param values kept, every other
    // param replaced with Fallback) is exactly the path that shell reads look it
    // up under.
    let fulfilledVaryPath = null;
    if (process.env.__NEXT_VARY_PARAMS) {
        if (fetchStrategy === _types.FetchStrategy.RuntimeShell) {
            fulfilledVaryPath = tree.shellVaryPath;
        } else if (fetchStrategy !== _types.FetchStrategy.Full && segmentVaryParams !== null) {
            fulfilledVaryPath = (0, _varypath.getFulfilledSegmentVaryPath)(tree.varyPath, segmentVaryParams);
        }
    }
    // We should only write into cache entries that are owned by us. Or create
    // a new one and write into that. We must never write over an entry that was
    // created by a different task, because that causes data races.
    const ownedEntry = entriesOwnedByCurrentTask !== null ? entriesOwnedByCurrentTask.get(tree.requestKey) : undefined;
    if (ownedEntry !== undefined) {
        const fulfilledEntry = fulfillSegmentCacheEntry(ownedEntry, rsc, staleAt, isPartial, // Dynamic-request (Full/Runtime) responses are not ISR fallbacks.
        false, fetchStrategy);
        // Re-key the entry at its canonical path. When `varyParams` produced a
        // generalized path above, use that; otherwise fall back to the request's
        // own keying (this is load-bearing for entries spawned as revalidations:
        // without the re-key they'd stay in their Revalidation slot forever,
        // invisible to canonical reads, and the partial entry that prompted the
        // revalidation would keep serving navigations). Full responses are
        // excluded, matching the varyParams re-key: they're spawned as canonical
        // entries at their final path, and their vary tracking can't be trusted
        // for re-keying (see the fulfilledVaryPath derivation above).
        const canonicalVaryPath = fulfilledVaryPath !== null ? fulfilledVaryPath : fetchStrategy !== _types.FetchStrategy.Full ? (0, _varypath.getSegmentVaryPathForRequest)(fetchStrategy, tree) : null;
        if (canonicalVaryPath !== null) {
            const isRevalidation = false;
            (0, _cachemap.setInCacheMap)(map, canonicalVaryPath, fulfilledEntry, isRevalidation);
            // The re-key moved the entry to a more generic path (and, for a spawned
            // revalidation, vacated its Revalidation slot). A stale settled entry
            // at a more specific path — e.g. the partial entry that prompted the
            // revalidation — would shadow every read at the concrete lookup path,
            // causing the scheduler to keep re-reading the stale entry and respawn
            // the revalidation forever. Evict it so the fulfilled entry is
            // reachable. See evictShadowingSegmentEntries.
            evictShadowingSegmentEntries(now, map, tree.varyPath, fulfilledEntry);
        }
    } else {
        // There's no matching entry. Attempt to create a new one.
        let possiblyNewEntry = (0, _cachemap.getFromCacheMap)(now, getCurrentSegmentCacheVersion(), map, tree.varyPath, false, false);
        if (possiblyNewEntry === null) {
            possiblyNewEntry = insertEmptySegmentCacheEntry(now, map, fetchStrategy, tree);
        }
        if (possiblyNewEntry.status === _cachemap.EntryStatus.Empty) {
            // Confirmed this is a new entry. We can fulfill it.
            const newEntry = possiblyNewEntry;
            const fulfilledEntry = fulfillSegmentCacheEntry(upgradeToPendingSegment(newEntry, fetchStrategy), rsc, staleAt, isPartial, // Dynamic-request (Full/Runtime) responses are not ISR fallbacks.
            false, fetchStrategy);
            if (fulfilledVaryPath !== null) {
                const isRevalidation = false;
                (0, _cachemap.setInCacheMap)(map, fulfilledVaryPath, fulfilledEntry, isRevalidation);
                // Same as the owned-entry re-key above. Usually the entry really is
                // new — the read a moment ago returned nothing at the concrete lookup
                // path, so nothing can shadow it and this is a no-op — but this
                // branch also claims a pre-existing Empty entry, and re-keying that
                // away can expose a stale settled entry at an intermediate path.
                evictShadowingSegmentEntries(now, map, tree.varyPath, fulfilledEntry);
            }
        } else {
            // There was already an entry in the cache. But we may be able to
            // replace it with the new one from the server.
            const newEntry = fulfillSegmentCacheEntry(upgradeToPendingSegment(createDetachedSegmentCacheEntry(now), fetchStrategy), rsc, staleAt, isPartial, // Dynamic-request (Full/Runtime) responses are not ISR fallbacks.
            false, fetchStrategy);
            const varyPath = fulfilledVaryPath !== null ? fulfilledVaryPath : (0, _varypath.getSegmentVaryPathForRequest)(fetchStrategy, tree);
            // Pass the concrete lookup path so that if the entry was re-keyed to
            // a more generic path, any stale settled entry at a more specific path
            // that would shadow it is evicted (the upsert handles this; the other
            // branches above call evictShadowingSegmentEntries themselves).
            upsertSegmentEntry(now, map, varyPath, newEntry, tree.varyPath);
        }
    }
}
async function fetchPrefetchResponse(url, headers) {
    const fetchPriority = 'low';
    // When issuing a prefetch request, don't immediately decode the response; we
    // use the lower level `createFromResponse` API instead because we need to do
    // some extra processing of the response stream. See
    // `createNonTaskyPrefetchResponseStream` for more details.
    const shouldImmediatelyDecode = false;
    const response = await (0, _fetchserverresponse.createFetch)(url, headers, fetchPriority, shouldImmediatelyDecode);
    if (!response.ok) {
        return null;
    }
    // Check the content type
    if (isOutputExportMode) {
    // In output: "export" mode, we relaxed about the content type, since it's
    // not Next.js that's serving the response. If the status is OK, assume the
    // response is valid. If it's not a valid response, the Flight client won't
    // be able to decode it, and we'll treat it as a miss.
    } else {
        const contentType = response.headers.get('content-type');
        const isFlightResponse = contentType && contentType.startsWith(_approuterheaders.RSC_CONTENT_TYPE_HEADER);
        if (!isFlightResponse) {
            return null;
        }
    }
    return response;
}
async function createNonTaskyPrefetchResponseStream(body, byteLimit) {
    // Buffer the entire response before passing it to the Flight client. This
    // ensures that when Flight processes the stream, all model data is available
    // synchronously. This is important for readVaryParams, which synchronously
    // checks the thenable status — if data arrived in multiple network chunks,
    // the thenables might not yet be fulfilled.
    //
    // TODO: There are too many intermediate stream transformations in the
    // prefetch response pipeline (e.g. stripIsPartialByte, this function).
    // These could all be consolidated into a single transformation. Refactor
    // once the cached navigations experiment lands.
    //
    // Read the response from the network, optionally truncating at byteLimit.
    const reader = body.getReader();
    const chunks = [];
    let size = 0;
    while(true){
        const { done, value } = await reader.read();
        if (done) break;
        if (byteLimit !== undefined && size + value.byteLength >= byteLimit) {
            const remaining = byteLimit - size;
            if (remaining > 0) {
                chunks.push(value.byteLength > remaining ? value.subarray(0, remaining) : value);
                size += remaining;
            }
            reader.cancel();
            break;
        }
        chunks.push(value);
        size += value.byteLength;
    }
    // Concatenate into a single chunk so that Flight's processBinaryChunk
    // processes all rows synchronously in one call. Multiple chunks would not
    // be sufficient: even though reader.read() resolves as a microtask for
    // already-enqueued data, the `await` continuation from
    // createFromReadableStream can interleave between chunks. If the root
    // model row isn't the first row (e.g. outlined values come first), the
    // PromiseResolveThenableJob from `await` can cause the root to initialize
    // eagerly, scheduling the continuation before remaining chunks (including
    // promise value rows) are processed. A single chunk avoids this.
    let buffer;
    if (chunks.length === 1) {
        buffer = chunks[0];
    } else if (chunks.length > 1) {
        buffer = new Uint8Array(size);
        let offset = 0;
        for (const chunk of chunks){
            buffer.set(chunk, offset);
            offset += chunk.byteLength;
        }
    } else {
        buffer = new Uint8Array(0);
    }
    const stream = new ReadableStream({
        start (controller) {
            controller.enqueue(buffer);
            controller.close();
        }
    });
    return {
        stream,
        size,
        buffer
    };
}
/**
 * Creates a streaming (non-buffered) prefetch response stream for dynamic/Full
 * prefetches. These are essentially dynamic responses that get stored in the
 * prefetch cache — they don't carry vary params or other cache metadata that
 * requires synchronous thenable resolution, so there's no need to buffer them.
 * They should continue to stream so consumers can process data as it arrives.
 */ function createIncrementalPrefetchResponseStream(originalFlightStream, onStreamClose, onResponseSizeUpdate) {
    // While processing the original stream, we incrementally update the size
    // of the cache entry in the LRU.
    let totalByteLength = 0;
    const reader = originalFlightStream.getReader();
    return new ReadableStream({
        async pull (controller) {
            while(true){
                const { done, value } = await reader.read();
                if (!done) {
                    // Pass to the target stream and keep consuming the Flight response
                    // from the server.
                    controller.enqueue(value);
                    // Incrementally update the size of the cache entry in the LRU.
                    totalByteLength += value.byteLength;
                    onResponseSizeUpdate(totalByteLength);
                    continue;
                }
                controller.close();
                onStreamClose();
                return;
            }
        }
    });
}
function addSegmentPathToUrlInOutputExportMode(url, segmentPath) {
    if (isOutputExportMode) {
        // In output: "export" mode, we cannot use a header to encode the segment
        // path. Instead, we append it to the end of the pathname.
        const staticUrl = new URL(url);
        const routeDir = staticUrl.pathname.endsWith('/') ? staticUrl.pathname.slice(0, -1) : staticUrl.pathname;
        const staticExportFilename = (0, _segmentvalueencoding.convertSegmentPathToStaticExportFilename)(segmentPath);
        staticUrl.pathname = `${routeDir}/${staticExportFilename}`;
        return staticUrl;
    }
    return url;
}
function canNewFetchStrategyProvideMoreContent(currentStrategy, newStrategy) {
    return currentStrategy < newStrategy;
}
function getStaleAtFromHeader(now, response) {
    const staleTimeSeconds = parseInt(response.headers.get(_approuterheaders.NEXT_ROUTER_STALE_TIME_HEADER) ?? '', 10);
    const staleTimeMs = !isNaN(staleTimeSeconds) ? getStaleTimeMs(staleTimeSeconds) : _navigatereducer.STATIC_STALETIME_MS;
    return now + staleTimeMs;
}
async function resolveStaleAt(now, staleTimeIterable, response) {
    if (staleTimeIterable !== undefined) {
        // Iterate the async iterable and take the last yielded value. The server
        // yields updated staleTime values during the render; the last one is the
        // final staleTime.
        let staleTimeSeconds;
        for await (const value of staleTimeIterable){
            staleTimeSeconds = value;
        }
        if (staleTimeSeconds !== undefined) {
            const staleTimeMs = isNaN(staleTimeSeconds) ? _navigatereducer.STATIC_STALETIME_MS : getStaleTimeMs(staleTimeSeconds);
            return now + staleTimeMs;
        }
    }
    if (response !== undefined) {
        return getStaleAtFromHeader(now, response);
    }
    return now + _navigatereducer.STATIC_STALETIME_MS;
}
function writePrerenderResponseIntoCache(now, fetchStrategy, flightData, buildId, headVaryParamsIterable, rootVaryParamsIterable, staleAt, baseTree, renderedSearch, isResponsePartial, // The map the work that spawned this response's request is bound to. See
// writeDynamicRenderResponseIntoCache.
map) {
    // Root params are emitted once at the top level; readVaryParams unions them
    // into the head, and they're threaded down to each segment below.
    const headVaryParams = (0, _varyparamsdecoding.readVaryParams)(headVaryParamsIterable, rootVaryParamsIterable);
    const flightDatas = (0, _flightdatahelpers.normalizeFlightData)(flightData);
    if (typeof flightDatas === 'string') {
        return;
    }
    const navigationSeed = (0, _navigation.convertServerPatchToFullTree)(now, baseTree, flightDatas, renderedSearch, _bfcache.UnknownDynamicStaleTime);
    writeDynamicRenderResponseIntoCache(now, fetchStrategy, flightDatas, buildId, isResponsePartial, headVaryParams, rootVaryParamsIterable, staleAt, navigationSeed, null, map);
}
async function processRuntimePrefetchStream(now, runtimePrefetchStream, baseTree, renderedSearch) {
    const { stream, isPartial } = await stripIsPartialByte(runtimePrefetchStream);
    const serverData = await (0, _fetchserverresponse.createFromNextReadableStream)(stream, undefined, {
        allowPartialStream: true
    });
    // Root params are emitted once at the top level; readVaryParams unions them
    // into the head, and we return the iterable so the caller can union it into
    // each segment too.
    const rootVaryParamsIterable = serverData.r ?? null;
    const headVaryParams = (0, _varyparamsdecoding.readVaryParams)(serverData.h, rootVaryParamsIterable);
    const staleAt = await resolveStaleAt(now, serverData.s);
    const flightDatas = (0, _flightdatahelpers.normalizeFlightData)(serverData.f);
    if (typeof flightDatas === 'string') {
        return null;
    }
    const navigationSeed = (0, _navigation.convertServerPatchToFullTree)(now, baseTree, flightDatas, renderedSearch, _bfcache.UnknownDynamicStaleTime);
    return {
        flightDatas,
        navigationSeed,
        buildId: serverData.b,
        isResponsePartial: isPartial,
        headVaryParams,
        rootVaryParamsIterable,
        staleAt
    };
}
async function stripIsPartialByte(stream) {
    // When there is no recognized marker byte, the fallback depends on whether
    // Cached Navigations is enabled. When enabled, dynamic navigation responses
    // don't have a marker but may contain dynamic holes, so they are treated as
    // partial. When disabled, unmarked responses are treated as non-partial.
    const defaultIsPartial = !!process.env.__NEXT_EXPERIMENTAL_CACHED_NAVIGATIONS;
    const reader = stream.getReader();
    const { done, value } = await reader.read();
    if (done || !value || value.byteLength === 0) {
        return {
            stream: new ReadableStream({
                start: (c)=>c.close()
            }),
            isPartial: defaultIsPartial
        };
    }
    const firstByte = value[0];
    const hasMarker = firstByte === 0x23 || firstByte === 0x7e;
    const isPartial = hasMarker ? firstByte === 0x7e : defaultIsPartial;
    const remainder = hasMarker ? value.byteLength > 1 ? value.subarray(1) : null : value;
    return {
        isPartial,
        stream: new ReadableStream({
            start (controller) {
                if (remainder) {
                    controller.enqueue(remainder);
                }
            },
            async pull (controller) {
                const result = await reader.read();
                if (result.done) {
                    controller.close();
                } else {
                    controller.enqueue(result.value);
                }
            }
        })
    };
}

if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') {
  Object.defineProperty(exports.default, '__esModule', { value: true });
  Object.assign(exports.default, exports);
  module.exports = exports.default;
}

//# sourceMappingURL=cache.js.map