UNPKG

univ-conv

Version:

The universal binary and text converter

217 lines 7.37 kB
import { Duplex, PassThrough, Readable } from "stream"; import { AbstractConverter, _ } from "./AbstractConverter"; import { deleteStartLength, getStartEnd, } from "./core"; import { EMPTY_BUFFER, fileURLToReadable, handleReadable, hasReadable, isReadable, } from "./Environment"; import { getTextHelper } from "./StringUtil"; export class PartialReadable extends Readable { constructor(src, start, end = Number.MAX_SAFE_INTEGER) { super(); this.src = src; this.start = start; this.end = end; src.once("readable", () => this.setup()); } _read() { } setup() { let iStart = 0; const onData = (value) => { const u8 = value; const size = u8.byteLength; const iEnd = iStart + size; const u8End = (iEnd < this.end ? iEnd : this.end) - iStart; let chunk; if (iStart <= this.start && this.start < iEnd) { chunk = u8.subarray(this.start - iStart, u8End); } else if (this.start < iStart && iStart < this.end) { chunk = u8.subarray(0, u8End); } if (chunk) { this.push(chunk); } iStart += size; }; const src = this.src; src.once("error", (e) => { this.destroy(e); src.off("data", onData); }); src.once("end", () => { this.push(null); src.off("data", onData); }); src.on("data", onData); } } class ReadableOfReadableStream extends Readable { constructor(stream, start, end = Number.MAX_SAFE_INTEGER) { super(); this.stream = stream; this.start = start; this.end = end; this.setup(); } async setup() { const reader = this.stream.getReader(); try { const converter = _()._of("uint8array"); let iStart = 0; let done; do { const res = await reader.read(); const value = res.value; done = res.done; if (value) { const u8 = await converter.from(value); const size = u8.byteLength; const iEnd = iStart + size; const u8End = (iEnd < this.end ? iEnd : this.end) - iStart; let chunk; if (iStart <= this.start && this.start < iEnd) { chunk = u8.subarray(this.start - iStart, u8End); } else if (this.start < iStart && iStart < this.end) { chunk = u8.subarray(0, u8End); } if (chunk) { this.push(chunk); } iStart += size; } } while (!done && iStart < this.end); this.push(null); } catch (e) { this.destroy(e); } finally { reader.releaseLock(); this.stream.cancel().catch((e) => console.debug(e)); } } } export class ReadableConverter extends AbstractConverter { constructor() { super(...arguments); this.type = "readable"; } empty() { return new Readable({ read() { this.push(EMPTY_BUFFER); this.push(null); }, }); } is(input) { return isReadable(input); } async _from(input, options) { if (typeof input === "string" && options.inputStringType === "url") { if (input.startsWith("http:") || input.startsWith("https:")) { const resp = await fetch(input); if (hasReadable) { input = resp.body; } else { input = resp.body; } } else if (input.startsWith("file:") && fileURLToReadable) { input = await fileURLToReadable(input); } } if (this.is(input)) { const { start, end } = getStartEnd(options); return new PartialReadable(input, start, end); } if (_().is("readablestream", input, options)) { const { start, end } = getStartEnd(options); return new ReadableOfReadableStream(input, start, end); } const buffer = await _().convert("uint8array", input, options); const duplex = new Duplex(); duplex.push(buffer); duplex.push(null); return duplex; } _getStartEnd(_input, options) { return Promise.resolve(getStartEnd(options)); } _isEmpty(input) { return !input.readable; } _merge(readables) { const end = readables.length; if (!readables || end === 0) { return Promise.resolve(this.empty()); } if (end === 1) { return Promise.resolve(readables[0]); } const pt = new PassThrough(); const process = (i) => { if (i < end) { const readable = readables[i]; readable.once("error", (e) => { readable.unpipe(); pt.destroy(e); for (let j = i; j < end; j++) { const r = readables[j]; r.destroy(); } }); readable.once("end", () => process(++i)); readable.pipe(pt, { end: false }); } else { pt.end(); } }; process(0); return Promise.resolve(pt); } _size() { throw new Error("Cannot get size of Readable"); } async _toArrayBuffer(input, options) { const u8 = await this._toUint8Array(input, options); return u8.buffer.slice(u8.byteOffset, u8.byteOffset + u8.byteLength); } async _toBase64(input, options) { const buffer = await this._toUint8Array(input, options); return await _() ._of("uint8array") .toBase64(buffer, deleteStartLength(options)); } async _toText(input, options) { const u8 = await this._toUint8Array(input, options); const textHelper = await getTextHelper(); return await textHelper.bufferToText(u8, options); } async _toUint8Array(input, options) { const { start, end } = await this._getStartEnd(input, options); const bufferSize = options.bufferSize; let index = 0; const converter = _()._of("uint8array"); const chunks = []; await handleReadable(input, async (chunk) => { const buffer = await converter.from(chunk, { bufferSize }); const size = buffer.byteLength; let e = index + size; if (end != null && end < e) e = end; if (index < start && start < e) { chunks.push(buffer.subarray(start, e)); } else if (start <= index) { chunks.push(buffer); } index += size; return end == null || index < end; }); return Buffer.concat(chunks); } } //# sourceMappingURL=ReadableConverter.js.map