"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
Object.defineProperty(exports, "cache", {
    enumerable: true,
    get: function() {
        return cache;
    }
});
const _serveredge = require("react-server-dom-webpack/server.edge");
const _clientedge = require("react-server-dom-webpack/client.edge");
const _workasyncstorageexternal = require("../app-render/work-async-storage.external");
const _workunitasyncstorageexternal = require("../app-render/work-unit-async-storage.external");
const _cleanasyncsnapshotexternal = require("../app-render/clean-async-snapshot.external");
const _dynamicrenderingutils = require("../dynamic-rendering-utils");
const _cachescopeexternal = require("../async-storage/cache-scope.external");
const _encryptionutils = require("../app-render/encryption-utils");
const isEdgeRuntime = process.env.NEXT_RUNTIME === 'edge';
// If the expire time is less than .
const DYNAMIC_EXPIRE = 300;
const cacheHandlerMap = new Map();
// TODO: Move default implementation to be injectable.
const defaultCacheStorage = new Map();
cacheHandlerMap.set('default', {
    async get (cacheKey) {
        // TODO: Implement proper caching.
        const promiseOfEntry = defaultCacheStorage.get(cacheKey);
        if (promiseOfEntry !== undefined) {
            const entry = await promiseOfEntry;
            if (performance.timeOrigin + performance.now() > entry.timestamp + entry.revalidate * 1000) {
                // In memory caches should expire after revalidate time because it is unlikely that
                // a new entry will be able to be used before it is dropped from the cache.
                return undefined;
            }
            const [returnStream, newSaved] = entry.value.tee();
            entry.value = newSaved;
            return {
                value: returnStream,
                timestamp: entry.timestamp,
                revalidate: entry.revalidate,
                expire: entry.expire,
                stale: entry.stale,
                tags: entry.tags
            };
        }
        return undefined;
    },
    async set (cacheKey, promise) {
        // TODO: Implement proper caching.
        defaultCacheStorage.set(cacheKey, promise);
        await promise;
    }
});
function generateCacheEntry(workStore, outerWorkUnitStore, cacheScope, clientReferenceManifest, encodedArguments, fn) {
    // We need to run this inside a clean AsyncLocalStorage snapshot so that the cache
    // generation cannot read anything from the context we're currently executing which
    // might include request specific things like cookies() inside a React.cache().
    // Note: It is important that we await at least once before this because it lets us
    // pop out of any stack specific contexts as well - aka "Sync" Local Storage.
    return (0, _cleanasyncsnapshotexternal.runInCleanSnapshot)(generateCacheEntryWithRestoredWorkStore, workStore, outerWorkUnitStore, cacheScope, clientReferenceManifest, encodedArguments, fn);
}
function generateCacheEntryWithRestoredWorkStore(workStore, outerWorkUnitStore, cacheScope, clientReferenceManifest, encodedArguments, fn) {
    // Since we cleared the AsyncLocalStorage we need to restore the workStore.
    // Note: We explicitly don't restore the RequestStore nor the PrerenderStore.
    // We don't want any request specific information leaking an we don't want to create a
    // bloated fake request mock for every cache call. So any feature that currently lives
    // in RequestStore but should be available to Caches need to move to WorkStore.
    // PrerenderStore is not needed inside the cache scope because the outer most one will
    // be the one to report its result to the outer Prerender.
    if (cacheScope) {
        return _cachescopeexternal.cacheScopeAsyncLocalStorage.run(cacheScope, ()=>_workasyncstorageexternal.workAsyncStorage.run(workStore, generateCacheEntryWithCacheContext, workStore, outerWorkUnitStore, clientReferenceManifest, encodedArguments, fn));
    }
    return _workasyncstorageexternal.workAsyncStorage.run(workStore, generateCacheEntryWithCacheContext, workStore, outerWorkUnitStore, clientReferenceManifest, encodedArguments, fn);
}
function generateCacheEntryWithCacheContext(workStore, outerWorkUnitStore, clientReferenceManifest, encodedArguments, fn) {
    if (!workStore.cacheLifeProfiles) {
        throw new Error('cacheLifeProfiles should always be provided. This is a bug in Next.js.');
    }
    const defaultCacheLife = workStore.cacheLifeProfiles['default'];
    if (!defaultCacheLife || defaultCacheLife.revalidate == null || defaultCacheLife.expire == null || defaultCacheLife.stale == null) {
        throw new Error('A default cacheLife profile must always be provided. This is a bug in Next.js.');
    }
    // Initialize the Store for this Cache entry.
    const cacheStore = {
        type: 'cache',
        phase: 'render',
        implicitTags: outerWorkUnitStore === undefined || outerWorkUnitStore.type === 'unstable-cache' ? [] : outerWorkUnitStore.implicitTags,
        revalidate: defaultCacheLife.revalidate,
        expire: defaultCacheLife.expire,
        stale: defaultCacheLife.stale,
        explicitRevalidate: undefined,
        explicitExpire: undefined,
        explicitStale: undefined,
        tags: null
    };
    return _workunitasyncstorageexternal.workUnitAsyncStorage.run(cacheStore, generateCacheEntryImpl, outerWorkUnitStore, cacheStore, clientReferenceManifest, encodedArguments, fn);
}
function propagateCacheLifeAndTags(workUnitStore, entry) {
    if (workUnitStore && (workUnitStore.type === 'cache' || workUnitStore.type === 'prerender' || workUnitStore.type === 'prerender-ppr' || workUnitStore.type === 'prerender-legacy')) {
        // Propagate tags and revalidate upwards
        const outerTags = workUnitStore.tags ?? (workUnitStore.tags = []);
        const entryTags = entry.tags;
        for(let i = 0; i < entryTags.length; i++){
            const tag = entryTags[i];
            if (!outerTags.includes(tag)) {
                outerTags.push(tag);
            }
        }
        if (workUnitStore.stale > entry.stale) {
            workUnitStore.stale = entry.stale;
        }
        if (workUnitStore.revalidate > entry.revalidate) {
            workUnitStore.revalidate = entry.revalidate;
        }
        if (workUnitStore.expire > entry.expire) {
            workUnitStore.expire = entry.expire;
        }
    }
}
async function collectResult(savedStream, outerWorkUnitStore, innerCacheStore, startTime, errors) {
    // We create a buffered stream that collects all chunks until the end to
    // ensure that RSC has finished rendering and therefore we have collected
    // all tags. In the future the RSC API might allow for the equivalent of
    // the allReady Promise that exists on SSR streams.
    //
    // If something errored or rejected anywhere in the render, we close
    // the stream as errored. This lets a CacheHandler choose to save the
    // partial result up until that point for future hits for a while to avoid
    // unnecessary retries or not to retry. We use the end of the stream for
    // this to avoid another complicated side-channel. A receiver has to consider
    // that the stream might also error for other reasons anyway such as losing
    // connection.
    const buffer = [];
    const reader = savedStream.getReader();
    for(let entry; !(entry = await reader.read()).done;){
        buffer.push(entry.value);
    }
    let idx = 0;
    const bufferStream = new ReadableStream({
        pull (controller) {
            if (idx < buffer.length) {
                controller.enqueue(buffer[idx++]);
            } else if (errors.length > 0) {
                // TODO: Should we use AggregateError here?
                controller.error(errors[0]);
            } else {
                controller.close();
            }
        }
    });
    const collectedTags = innerCacheStore.tags;
    // If cacheLife() was used to set an explicit revalidate time we use that.
    // Otherwise, we use the lowest of all inner fetch()/unstable_cache() or nested "use cache".
    // If they're lower than our default.
    const collectedRevalidate = innerCacheStore.explicitRevalidate !== undefined ? innerCacheStore.explicitRevalidate : innerCacheStore.revalidate;
    const collectedExpire = innerCacheStore.explicitExpire !== undefined ? innerCacheStore.explicitExpire : innerCacheStore.expire;
    const collectedStale = innerCacheStore.explicitStale !== undefined ? innerCacheStore.explicitStale : innerCacheStore.stale;
    const entry = {
        value: bufferStream,
        timestamp: startTime,
        revalidate: collectedRevalidate,
        expire: collectedExpire,
        stale: collectedStale,
        tags: collectedTags === null ? [] : collectedTags
    };
    // Propagate tags/revalidate to the parent context.
    propagateCacheLifeAndTags(outerWorkUnitStore, entry);
    const cacheSignal = outerWorkUnitStore && outerWorkUnitStore.type === 'prerender' ? outerWorkUnitStore.cacheSignal : null;
    if (cacheSignal) {
        cacheSignal.endRead();
    }
    return entry;
}
async function generateCacheEntryImpl(outerWorkUnitStore, innerCacheStore, clientReferenceManifest, encodedArguments, fn) {
    const temporaryReferences = (0, _serveredge.createTemporaryReferenceSet)();
    const [, , args] = await (0, _serveredge.decodeReply)(encodedArguments, (0, _encryptionutils.getServerModuleMap)(), {
        temporaryReferences
    });
    // Track the timestamp when we started copmuting the result.
    const startTime = performance.timeOrigin + performance.now();
    // Invoke the inner function to load a new result.
    const result = fn.apply(null, args);
    let errors = [];
    const stream = (0, _serveredge.renderToReadableStream)(result, clientReferenceManifest.clientModules, {
        environmentName: 'Cache',
        temporaryReferences,
        onError (error) {
            // Report the error.
            console.error(error);
            errors.push(error);
        }
    });
    const [returnStream, savedStream] = stream.tee();
    const promiseOfCacheEntry = collectResult(savedStream, outerWorkUnitStore, innerCacheStore, startTime, errors);
    // Return the stream as we're creating it. This means that if it ends up
    // erroring we cannot return a stale-while-error version but it allows
    // streaming back the result earlier.
    return [
        returnStream,
        promiseOfCacheEntry
    ];
}
async function clonePendingCacheEntry(pendingCacheEntry) {
    const entry = await pendingCacheEntry;
    const [streamA, streamB] = entry.value.tee();
    entry.value = streamA;
    const clonedEntry = {
        value: streamB,
        timestamp: entry.timestamp,
        revalidate: entry.revalidate,
        expire: entry.expire,
        stale: entry.stale,
        tags: entry.tags
    };
    return [
        entry,
        clonedEntry
    ];
}
async function getNthCacheEntry(split, i) {
    return (await split)[i];
}
function cache(kind, id, fn) {
    if (!process.env.__NEXT_DYNAMIC_IO) {
        throw new Error('"use cache" is only available with the experimental.dynamicIO config.');
    }
    const cacheHandler = cacheHandlerMap.get(kind);
    if (cacheHandler === undefined) {
        throw new Error('Unknown cache handler: ' + kind);
    }
    const name = fn.name;
    const cachedFn = {
        [name]: async function(...args) {
            const workStore = _workasyncstorageexternal.workAsyncStorage.getStore();
            if (workStore === undefined) {
                throw new Error('"use cache" cannot be used outside of App Router. Expected a WorkStore.');
            }
            const workUnitStore = _workunitasyncstorageexternal.workUnitAsyncStorage.getStore();
            // Get the clientReferenceManifest while we're still in the outer Context.
            // In case getClientReferenceManifestSingleton is implemented using AsyncLocalStorage.
            const clientReferenceManifest = (0, _encryptionutils.getClientReferenceManifestSingleton)();
            // Because the Action ID is not yet unique per implementation of that Action we can't
            // safely reuse the results across builds yet. In the meantime we add the buildId to the
            // arguments as a seed to ensure they're not reused. Remove this once Action IDs hash
            // the implementation.
            const buildId = workStore.buildId;
            const temporaryReferences = (0, _clientedge.createTemporaryReferenceSet)();
            const encodedArguments = await (0, _clientedge.encodeReply)([
                buildId,
                id,
                args
            ], {
                temporaryReferences
            });
            const serializedCacheKey = typeof encodedArguments === 'string' ? // Convert it to an ArrayBuffer if it wants to.
            encodedArguments : // key may generate a UCS-2 string. Passing this to another service needs to be
            // aware that the key might not be compatible.
            String.fromCodePoint(...new Uint8Array(await new Response(encodedArguments).arrayBuffer()));
            let stream = undefined;
            const cacheScope = _cachescopeexternal.cacheScopeAsyncLocalStorage.getStore();
            if (cacheScope) {
                const cachedEntry = cacheScope.cache.get(serializedCacheKey);
                if (cachedEntry !== undefined) {
                    const existingEntry = await cachedEntry;
                    propagateCacheLifeAndTags(workUnitStore, existingEntry);
                    if (workUnitStore !== undefined && workUnitStore.type === 'prerender' && existingEntry !== undefined && (existingEntry.revalidate === 0 || existingEntry.expire < DYNAMIC_EXPIRE)) {
                        // In a Dynamic I/O prerender, if the cache entry has revalidate: 0 or if the
                        // expire time is under 5 minutes, then we consider this cache entry dynamic
                        // as it's not worth generating static pages for such data. It's better to leave
                        // a PPR hole that can be filled in dynamically with a potentially cached entry.
                        return (0, _dynamicrenderingutils.makeHangingPromise)(workUnitStore.renderSignal, 'dynamic "use cache"');
                    }
                    const [streamA, streamB] = existingEntry.value.tee();
                    existingEntry.value = streamB;
                    stream = streamA;
                }
            }
            if (stream === undefined) {
                const cacheSignal = workUnitStore && workUnitStore.type === 'prerender' ? workUnitStore.cacheSignal : null;
                if (cacheSignal) {
                    // Either the cache handler or the generation can be using I/O at this point.
                    // We need to track when they start and when they complete.
                    cacheSignal.beginRead();
                }
                const implicitTags = workUnitStore === undefined || workUnitStore.type === 'unstable-cache' ? [] : workUnitStore.implicitTags;
                const entry = await cacheHandler.get(serializedCacheKey, implicitTags);
                const currentTime = performance.timeOrigin + performance.now();
                if (workUnitStore !== undefined && workUnitStore.type === 'prerender' && entry !== undefined && (entry.revalidate === 0 || entry.expire < DYNAMIC_EXPIRE)) {
                    // In a Dynamic I/O prerender, if the cache entry has revalidate: 0 or if the
                    // expire time is under 5 minutes, then we consider this cache entry dynamic
                    // as it's not worth generating static pages for such data. It's better to leave
                    // a PPR hole that can be filled in dynamically with a potentially cached entry.
                    if (cacheSignal) {
                        cacheSignal.endRead();
                    }
                    return (0, _dynamicrenderingutils.makeHangingPromise)(workUnitStore.renderSignal, 'dynamic "use cache"');
                } else if (entry === undefined || currentTime > entry.timestamp + entry.expire * 1000 || workStore.isStaticGeneration && currentTime > entry.timestamp + entry.revalidate * 1000) {
                    // Miss. Generate a new result.
                    // If the cache entry is stale and we're prerendering, we don't want to use the
                    // stale entry since it would unnecessarily need to shorten the lifetime of the
                    // prerender. We're not time constrained here so we can re-generated it now.
                    // We need to run this inside a clean AsyncLocalStorage snapshot so that the cache
                    // generation cannot read anything from the context we're currently executing which
                    // might include request specific things like cookies() inside a React.cache().
                    // Note: It is important that we await at least once before this because it lets us
                    // pop out of any stack specific contexts as well - aka "Sync" Local Storage.
                    const [newStream, pendingCacheEntry] = await generateCacheEntry(workStore, workUnitStore, cacheScope, clientReferenceManifest, encodedArguments, fn);
                    let savedCacheEntry;
                    if (cacheScope) {
                        // Create a clone that goes into the cache scope memory cache.
                        const split = clonePendingCacheEntry(pendingCacheEntry);
                        savedCacheEntry = getNthCacheEntry(split, 0);
                        cacheScope.cache.set(serializedCacheKey, getNthCacheEntry(split, 1));
                    } else {
                        savedCacheEntry = pendingCacheEntry;
                    }
                    const promise = cacheHandler.set(serializedCacheKey, savedCacheEntry);
                    if (!workStore.pendingRevalidateWrites) {
                        workStore.pendingRevalidateWrites = [];
                    }
                    workStore.pendingRevalidateWrites.push(promise);
                    stream = newStream;
                } else {
                    propagateCacheLifeAndTags(workUnitStore, entry);
                    if (cacheSignal) {
                        // If we're not regenerating we need to signal that we've finished
                        // putting the entry into the cache scope at this point. Otherwise we do
                        // that inside generateCacheEntry.
                        cacheSignal.endRead();
                    }
                    if (currentTime > entry.timestamp + entry.revalidate * 1000) {
                        // If this is stale, and we're not in a prerender (i.e. this is dynamic render),
                        // then we should warm up the cache with a fresh revalidated entry.
                        const [ignoredStream, pendingCacheEntry] = await generateCacheEntry(workStore, undefined, cacheScope, clientReferenceManifest, encodedArguments, fn);
                        const promise = cacheHandler.set(serializedCacheKey, pendingCacheEntry);
                        if (!workStore.pendingRevalidateWrites) {
                            workStore.pendingRevalidateWrites = [];
                        }
                        workStore.pendingRevalidateWrites.push(promise);
                        await ignoredStream.cancel();
                    }
                    stream = entry.value;
                }
            }
            // Logs are replayed even if it's a hit - to ensure we see them on the client eventually.
            // If we didn't then the client wouldn't see the logs if it was seeded from a prewarm that
            // never made it to the client. However, this also means that you see logs even when the
            // cached function isn't actually re-executed. We should instead ensure prewarms always
            // make it to the client. Another issue is that this will cause double logging in the
            // server terminal. Once while generating the cache entry and once when replaying it on
            // the server, which is required to pick it up for replaying again on the client.
            const replayConsoleLogs = true;
            const ssrManifest = {
                // moduleLoading must be null because we don't want to trigger preloads of ClientReferences
                // to be added to the consumer. Instead, we'll wait for any ClientReference to be emitted
                // which themselves will handle the preloading.
                moduleLoading: null,
                moduleMap: isEdgeRuntime ? clientReferenceManifest.edgeRscModuleMapping : clientReferenceManifest.rscModuleMapping
            };
            return (0, _clientedge.createFromReadableStream)(stream, {
                ssrManifest,
                temporaryReferences,
                replayConsoleLogs,
                environmentName: 'Cache'
            });
        }
    }[name];
    return cachedFn;
}

//# sourceMappingURL=use-cache-wrapper.js.map