import { BufferedBlob, BufferedFile, BufferedRequest, BufferedResponse, } from "./buffered" import { isPlainObject } from "./lib/utils" /** * Walks a value tree and replaces anything with an async body (`Request`, * `Response`, `Blob`, `File`) with a sync-buffered snapshot, so the result can * be handed to a synchronous encoder. * * Recurses through plain objects, arrays, `Map`s, and `Set`s. Leaves * primitives, typed arrays, and sync class instances (`Headers`, `URL`, etc.) * alone — those either have no async surface or msgpackr handles them natively * via registered extensions at pack time. * * Dedupes by reference within a single call so that a tree sharing the same * `Request`/`Response`/`Blob`/`File` in multiple places doesn't trigger "body * already used". * * Does not detect cycles. Automation outputs are expected to be tree-shaped. * * @param value - Value tree to prepare for synchronous encoding. */ export async function bufferAsyncValues(value: unknown): Promise { return walk(value, new WeakMap()) } /** * Walks a value while reusing in-flight work for repeated object references. * * @param value - Value at the current traversal position. * @param cache - Buffered results keyed by source object identity. */ async function walk( value: unknown, cache: WeakMap>, ): Promise { if (value === null || typeof value !== "object") return value const cached = cache.get(value) if (cached) return cached const task = walkFresh(value, cache) cache.set(value, task) return task } /** * Buffers an object that does not yet have a cached traversal task. * * @param value - Object to inspect and buffer. * @param cache - Buffered results keyed by source object identity. */ async function walkFresh( value: object, cache: WeakMap>, ): Promise { if (value instanceof Request) return BufferedRequest.from(value) if (value instanceof Response) return BufferedResponse.from(value) // File extends Blob — must be checked first so we don't lose name/lastModified. if (value instanceof File) return BufferedFile.from(value) if (value instanceof Blob) return BufferedBlob.from(value) if (Array.isArray(value)) { return Promise.all(value.map((v) => walk(v, cache))) } if (value instanceof Map) { return new Map( await Promise.all( [...value.entries()].map( async ([k, v]) => [await walk(k, cache), await walk(v, cache)] as const, ), ), ) } if (value instanceof Set) { return new Set(await Promise.all([...value].map((v) => walk(v, cache)))) } if (isPlainObject(value)) { return Object.fromEntries( await Promise.all( Object.entries(value).map( async ([k, v]) => [k, await walk(v, cache)] as const, ), ), ) } return value }