import {concatenateArrayBuffers} from '../binary-utils/array-buffer-utils'; // GENERAL UTILITIES /** * Iterates over an {@link AsyncIterable} or {@link Iterable}, invoking `visitor` for each yielded * value without rewinding the iterator when exiting early. This enables the caller to continue * iterating in another loop after `visitor` signals cancellation. */ export async function forEach( iterable: AsyncIterable | Iterable | AsyncIterator, visitor: (value: TValue) => any ) { const iterator = toAsyncIterator(iterable); // eslint-disable-next-line while (true) { const {done, value} = await iterator.next(); if (done) { if (iterator.return) { iterator.return(); } return; } const cancel = visitor(value); if (cancel) { return; } } } /** * Concatenates all binary chunks yielded by an async or sync iterator. * Supports `ArrayBuffer`, typed array views, and `ArrayBufferLike` sources (e.g. `SharedArrayBuffer`). * This allows atomic parsers to operate on iterator inputs by materializing them into a single buffer. */ export async function concatenateArrayBuffersAsync( asyncIterator: | AsyncIterable | Iterable ): Promise { const arrayBuffers: ArrayBuffer[] = []; for await (const chunk of asyncIterator) { arrayBuffers.push(copyToArrayBuffer(chunk)); } return concatenateArrayBuffers(...arrayBuffers); } export async function concatenateStringsAsync( asyncIterator: AsyncIterable | Iterable ): Promise { const strings: string[] = []; for await (const chunk of asyncIterator) { strings.push(chunk); } return strings.join(''); } /** * Normalizes binary chunk iterators to yield `ArrayBuffer` instances. * Accepts `ArrayBuffer`, `ArrayBufferView`, and `ArrayBufferLike` sources * (e.g. `SharedArrayBuffer`) and returns a copied `ArrayBuffer` for each chunk. */ export async function* toArrayBufferIterator( asyncIterator: | AsyncIterable | Iterable ): AsyncIterable { for await (const chunk of asyncIterator) { yield copyToArrayBuffer(chunk); } } function copyToArrayBuffer(chunk: ArrayBufferLike | ArrayBufferView | ArrayBuffer): ArrayBuffer { if (chunk instanceof ArrayBuffer) { return chunk; } if (ArrayBuffer.isView(chunk)) { const {buffer, byteOffset, byteLength} = chunk; return copyFromBuffer(buffer, byteOffset, byteLength); } return copyFromBuffer(chunk as ArrayBufferLike); } function copyFromBuffer( buffer: ArrayBufferLike, byteOffset = 0, byteLength = buffer.byteLength - byteOffset ): ArrayBuffer { const view = new Uint8Array(buffer, byteOffset, byteLength); const copy = new Uint8Array(view.length); copy.set(view); return copy.buffer; } function toAsyncIterator( iterable: AsyncIterable | Iterable | AsyncIterator ): AsyncIterator { if (typeof iterable[Symbol.asyncIterator] === 'function') { return iterable[Symbol.asyncIterator](); } if (typeof iterable[Symbol.iterator] === 'function') { const iterator = iterable[Symbol.iterator](); return iteratorToAsyncIterator(iterator); } return iterable as AsyncIterator; } function iteratorToAsyncIterator(iterator: Iterator): AsyncIterator { return { next(value?: any) { return Promise.resolve(iterator.next(value)); }, return(value?: any) { if (typeof iterator.return === 'function') { return Promise.resolve(iterator.return(value)); } return Promise.resolve({done: true, value}); }, throw(error?: any) { if (typeof iterator.throw === 'function') { return Promise.resolve(iterator.throw(error)); } return Promise.reject(error); } }; }