"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
Object.defineProperty(exports, "getCachedImageResponseBody", {
    enumerable: true,
    get: function() {
        return getCachedImageResponseBody;
    }
});
const _nodestream = require("node:stream");
const _nodecrypto = require("node:crypto");
const _invarianterror = require("../../shared/lib/invariant-error");
const _workasyncstorageexternal = require("../app-render/work-async-storage.external");
const _workunitasyncstorageexternal = require("../app-render/work-unit-async-storage.external");
const _dynamicrendering = require("../app-render/dynamic-rendering");
const _dynamicrenderingutils = require("../dynamic-rendering-utils");
const _manifestssingleton = require("../app-render/manifests-singleton");
const _static = require("react-server-dom-webpack/static");
const _client = require("react-server-dom-webpack/client");
function importOgModule() {
    // Cache Components is Node-only (rejected for the edge runtime at compile
    // time), so we always load the Node build. Loading it dynamically keeps the
    // heavy `@vercel/og` renderer (satori + WASM) off the module-load path, so
    // it's pulled in only when an image is actually rendered.
    return import('next/dist/compiled/@vercel/og/index.node.js');
}
function getCachedImageResponseBody(args) {
    return new ReadableStream({
        async start (controller) {
            const arrayBuffer = await getCachedImageResponseArrayBuffer(args);
            if (arrayBuffer.byteLength > 0) {
                controller.enqueue(new Uint8Array(arrayBuffer));
            }
            controller.close();
        }
    });
}
async function getCachedImageResponseArrayBuffer(args) {
    const workUnitStore = _workunitasyncstorageexternal.workUnitAsyncStorage.getStore();
    switch(workUnitStore == null ? void 0 : workUnitStore.type){
        case 'prerender':
            break;
        case undefined:
        case 'request':
        case 'cache':
        case 'private-cache':
        case 'unstable-cache':
        case 'prerender-runtime':
        case 'prerender-client':
        case 'validation-client':
        case 'prerender-ppr':
        case 'prerender-legacy':
        case 'generate-static-params':
            return renderImageResponseArrayBuffer(args);
        default:
            return workUnitStore;
    }
    const { cacheSignal, resumeDataCache, renderSignal } = workUnitStore;
    if (!resumeDataCache) {
        return renderImageResponseArrayBuffer(args);
    }
    const workStore = _workasyncstorageexternal.workAsyncStorage.getStore();
    if (!workStore) {
        throw Object.defineProperty(new _invarianterror.InvariantError('Expected a work store while caching an `ImageResponse` during prerendering.'), "__NEXT_ERROR_CODE", {
            value: "E1382",
            enumerable: false,
            configurable: true
        });
    }
    const [element, options] = args;
    // `createHangingInputAbortSignal` aborts once the prerender's cache-sourced
    // input is ready, so anything the serialization below is still awaiting past
    // that point can be treated as dynamic (non-cache) input. In the prospective
    // pass it aborts when `cacheSignal.inputReady()` resolves (no cache reads
    // in progress); in the final pass the caches are already filled, so it just
    // aborts on the next tick.
    const hangingInputAbortSignal = (0, _dynamicrendering.createHangingInputAbortSignal)(workUnitStore);
    // We open the cache read lazily, once we know the serialization completed and
    // we're about to render and store the image. Opening it before serializing
    // would keep `cacheSignal.inputReady()` from resolving and thus prevent the
    // abort signal from ever firing, deadlocking the prospective prerender.
    let readState = 'ready';
    function beginReadOnce() {
        if (readState === 'ready') {
            readState = 'pending';
            cacheSignal == null ? void 0 : cacheSignal.beginRead();
        }
    }
    function endReadIfStarted() {
        if (readState === 'pending') {
            cacheSignal == null ? void 0 : cacheSignal.endRead();
        }
        readState = 'done';
    }
    // We serialize the element tree with `prerenderToNodeStream` rather than
    // `renderToPipeableStream`. It's the right fit for prerendering, and it
    // schedules work deferred for size (`deferTask`) on microtasks, so a fully
    // static tree finishes flushing before the abort signal fires; a tree still
    // pending at abort time is then genuinely waiting on dynamic input rather
    // than just deferred.
    //
    // `renderToPipeableStream` would schedule that deferred work on
    // `setImmediate` instead, which isn't necessarily a deal-breaker: the
    // sequential-task scheme page rendering uses (`runInSequentialTasks`) drains
    // pending immediates at each task boundary, so deferred work still runs in
    // time. But route handler prerendering doesn't use that scheme, so here the
    // deferred immediates would race the abort.
    //
    // The prerender halts silently on abort, leaving unfulfilled references in
    // place rather than reporting through `onError`. So to tell a halt (the tree
    // needed dynamic input) apart from a normal completion, we record whether the
    // abort fired before the serialization finished. `abort()` runs this listener
    // synchronously, well before we read `resultIsPartial` below.
    let prerenderCompleted = false;
    let resultIsPartial = false;
    let serializationError;
    hangingInputAbortSignal.addEventListener('abort', ()=>{
        if (!prerenderCompleted) {
            resultIsPartial = true;
        }
    }, {
        once: true
    });
    const { clientModules, rscModuleMapping } = (0, _manifestssingleton.getClientReferenceManifest)();
    try {
        // We serialize only the `element`. It's the part that needs Flight, to run
        // its async Server Components once and to surface any dynamic input. The
        // `options` are already-resolved plain data; they're folded into the cache
        // key directly and passed to satori as-is below.
        const { prelude } = await (0, _static.prerenderToNodeStream)(element, clientModules, {
            signal: hangingInputAbortSignal,
            filterStackFrame: undefined,
            onError (error) {
                // A halt (our deliberate abort) emits nothing, so this is only called
                // for genuine serialization errors. We surface the first one.
                if (serializationError === undefined && !resultIsPartial) {
                    serializationError = error;
                }
            }
        });
        prerenderCompleted = true;
        if (serializationError !== undefined) {
            throw serializationError;
        }
        if (resultIsPartial) {
            // The element tree needed dynamic input (e.g. `cookies()` or an uncached
            // `fetch`), so the image can't be produced statically. Return a hanging
            // promise: the body never resolves, and the final prerender's macrotask
            // budget then classifies the route as dynamic.
            // Whatever dynamic input made the element partial already classified
            // itself when it created its own hanging promise (cookies() creates a
            // runtime hanging promise, an uncached fetch creates a dynamic one,
            // ...), so this wrapper adds no new information and can use the
            // non-recording dynamic variant.
            return (0, _dynamicrenderingutils.makeDynamicHangingPromise)(renderSignal, workStore.route, 'dynamic `ImageResponse`');
        }
        // The serialization finished before any dynamic input was needed, so we
        // will render and cache the image. Hold the cache read now, before the
        // stream is buffered and deserialized below, so that the prospective
        // prerender's `cacheReady()` waits for the image to be stored.
        beginReadOnce();
        const chunks = [];
        for await (const chunk of prelude){
            chunks.push(chunk);
        }
        const elementBuffer = Buffer.concat(chunks);
        // Derive a stable cache key from the serialized element plus the options.
        // We hash rather than reuse the raw serialized bytes so the key stays
        // compact even for large inputs (e.g. embedded fonts), and we fold the
        // options in by content so two images that differ only in their options
        // (size, fonts, ...) don't collide. The options are hashed directly here,
        // never serialized through Flight, which would both bloat the key and apply
        // `Buffer.prototype .toJSON` to font data.
        const hash = (0, _nodecrypto.createHash)('sha256');
        hash.update(elementBuffer);
        updateHashWithOptions(hash, options);
        const cacheKey = hash.digest('base64');
        const cached = resumeDataCache.imageResponses.get(cacheKey);
        if (cached) {
            return await cached;
        }
        // Deserialize the element and hand it to satori. Because the user's
        // components already ran during serialization, satori only walks resolved
        // host elements and never re-runs them, confining user-space I/O to the
        // in-store serialization above.
        const deserializedElement = await (0, _client.createFromNodeStream)(_nodestream.Readable.from([
            elementBuffer
        ]), {
            // We don't want to trigger preloads of client references here.
            moduleLoading: null,
            moduleMap: rscModuleMapping,
            serverModuleMap: (0, _manifestssingleton.getServerModuleMap)()
        }, {
            findSourceMapURL: undefined
        });
        // The Flight client hands back the output of an async Server Component as
        // a `React.lazy` (sync components and plain host elements are inlined).
        // satori can't unwrap lazies, so we resolve them into plain elements first.
        // We only reach here once the serialization completed, so every lazy is
        // already resolved and `_init` returns synchronously.
        const resolvedElement = resolveFlightLazies(deserializedElement);
        // Pair the resolved element with the original, in-memory `options`, which
        // never went through Flight. This keeps the font `Buffer` intact: had it
        // been serialized, Flight would apply the `toJSON` method that Node's
        // `Buffer` carries, turning it into a `{ type: 'Buffer', data: [...] }`
        // object that satori's font parser rejects (it needs an `ArrayBuffer` or a
        // typed array).
        const resolvedArgs = [
            resolvedElement,
            options
        ];
        // Render satori outside the prerender work-unit store. It does uncached
        // `fetch` calls (e.g. loading a font), and inside a Cache Components
        // prerender an uncached `fetch` outside a cache scope becomes a hanging
        // promise. Those are framework fetches, not user I/O, so we let them
        // resolve normally with no store.
        const arrayBufferPromise = _workunitasyncstorageexternal.workUnitAsyncStorage.exit(()=>renderImageResponseArrayBuffer(resolvedArgs));
        if (resumeDataCache.mutable) {
            resumeDataCache.imageResponses.set(cacheKey, arrayBufferPromise);
        }
        return await arrayBufferPromise;
    } finally{
        endReadIfStarted();
    }
}
/**
 * Updates a hash with a stable encoding of the `ImageResponse` options so they
 * can participate in the cache key without being serialized through Flight.
 * Binary values (font `Buffer`s, `ArrayBuffer`s, typed arrays) are hashed by
 * their raw bytes; objects are walked in sorted-key order.
 *
 * `ImageResponse` options are plain data: numbers, strings, booleans, nested
 * plain objects/arrays, and binary font data. Exotic objects such as `Map` or
 * `Date` keep their state outside their enumerable own keys, so the key walk
 * below would hash them incorrectly. Options never contain these, but we warn
 * if one ever shows up so a mis-keyed cache can be reported.
 *
 * The encoding is self-delimiting: every node starts with a type tag, and
 * variable-length parts (byte runs, primitives, keys) are length-prefixed,
 * while arrays and objects are count-prefixed. This makes it injective, so no
 * concatenation of values can be mistaken for a differently shaped input.
 */ function updateHashWithOptions(hash, value) {
    if (value === undefined) {
        hash.update('u');
        return;
    }
    if (value === null) {
        hash.update('n');
        return;
    }
    const type = typeof value;
    if (type !== 'object') {
        // Tag with the primitive type so e.g. the number `1` and the string `'1'`
        // don't hash the same.
        updateHashWithBytes(hash, 'p', Buffer.from(`${type}:${String(value)}`));
        return;
    }
    if (value instanceof ArrayBuffer) {
        updateHashWithBytes(hash, 'a', new Uint8Array(value));
        return;
    }
    if (ArrayBuffer.isView(value)) {
        updateHashWithBytes(hash, 'v', new Uint8Array(value.buffer, value.byteOffset, value.byteLength));
        return;
    }
    if (Array.isArray(value)) {
        hash.update(`[${value.length},`);
        for (const item of value){
            updateHashWithOptions(hash, item);
        }
        return;
    }
    // The key walk below captures a plain object faithfully, but an exotic object
    // keeps its state elsewhere (a `Map`'s/`Set`'s entries, a `Date`'s time), so
    // two different values would hash the same and could return the wrong cached
    // image. This shouldn't happen for `ImageResponse` options, so we warn rather
    // than fail, then hash best-effort, so it can be reported. Not gated on
    // `NODE_ENV`: this runs during the production `next build` prerender, where
    // the warning is most useful.
    const prototype = Object.getPrototypeOf(value);
    if (prototype !== Object.prototype && prototype !== null) {
        var _value_constructor;
        const typeName = ((_value_constructor = value.constructor) == null ? void 0 : _value_constructor.name) ?? 'object';
        console.warn(`Cannot reliably include an \`ImageResponse\` option of type ` + `\`${typeName}\` in the cache key, so different images may collide and ` + `return an incorrect cached result. Please report this to the Next.js ` + `team.`);
    }
    const keys = Object.keys(value).sort();
    hash.update(`{${keys.length},`);
    for (const key of keys){
        updateHashWithBytes(hash, 'k', Buffer.from(key));
        updateHashWithOptions(hash, value[key]);
    }
}
/**
 * Hashes a length-prefixed, tagged byte run: `<tag><byteLength>:<bytes>`. The
 * length prefix keeps the run self-delimiting so it can't blend into adjacent
 * nodes.
 */ function updateHashWithBytes(hash, tag, bytes) {
    hash.update(`${tag}${bytes.byteLength}:`);
    hash.update(bytes);
}
async function renderImageResponseArrayBuffer(args) {
    const OGImageResponse = (await importOgModule()).ImageResponse;
    const imageResponse = new OGImageResponse(...args);
    if (!imageResponse.body) {
        return new ArrayBuffer(0);
    }
    return imageResponse.arrayBuffer();
}
const REACT_LAZY_TYPE = Symbol.for('react.lazy');
/**
 * Recursively replaces the `React.lazy` references that Flight emits for
 * resolved async Server Components with the elements they resolve to, so that
 * satori (which doesn't understand lazy nodes) can walk the tree. This must
 * only be called on a fully resolved (completed) Flight result, where each
 * lazy's `_init` returns synchronously rather than suspending.
 */ function resolveFlightLazies(node) {
    if (node === null || typeof node !== 'object') {
        return node;
    }
    if (node.$$typeof === REACT_LAZY_TYPE) {
        const lazy = node;
        return resolveFlightLazies(lazy._init(lazy._payload));
    }
    if (Array.isArray(node)) {
        return node.map(resolveFlightLazies);
    }
    const element = node;
    if (element.props && 'children' in element.props) {
        return {
            ...element,
            props: {
                ...element.props,
                children: resolveFlightLazies(element.props.children)
            }
        };
    }
    return node;
}

//# sourceMappingURL=cache-image-response.js.map