UNPKG

turbo-stream

Version:

A streaming data transport format that aims to support built-in features such as Promises, Dates, RegExps, Maps, Sets and more.

325 lines (324 loc) 9.95 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TurboFile = exports.TurboBlob = exports.DeferredReadableStream = exports.DeferredAsyncIterable = exports.Deferred = exports.WaitGroup = exports.SUPPORTS_FILE = exports.STR_URL = exports.STR_UNDEFINED = exports.STR_UINT_8_ARRAY_CLAMPED = exports.STR_UINT_8_ARRAY = exports.STR_UINT_32_ARRAY = exports.STR_UINT_16_ARRAY = exports.STR_TRUE = exports.STR_SYMBOL = exports.STR_SUCCESS = exports.STR_SET = exports.STR_REGEXP = exports.STR_REFERENCE_SYMBOL = exports.STR_REDACTED = exports.STR_READABLE_STREAM = exports.STR_PROMISE = exports.STR_PLUGIN = exports.STR_NULL = exports.STR_NEGATIVE_ZERO = exports.STR_NEGATIVE_INFINITY = exports.STR_NaN = exports.STR_MAP = exports.STR_INT_8_ARRAY = exports.STR_INT_32_ARRAY = exports.STR_INT_16_ARRAY = exports.STR_INFINITY = exports.STR_FORM_DATA = exports.STR_FLOAT_64_ARRAY = exports.STR_FLOAT_32_ARRAY = exports.STR_FILE = exports.STR_FALSE = exports.STR_FAILURE = exports.STR_ERROR = exports.STR_DATE = exports.STR_DATA_VIEW = exports.STR_BLOB = exports.STR_BIGINT = exports.STR_BIG_UINT_64_ARRAY = exports.STR_BIG_INT_64_ARRAY = exports.STR_ASYNC_ITERABLE = exports.STR_ARRAY_BUFFER = void 0; exports.STR_ARRAY_BUFFER = "A"; exports.STR_ASYNC_ITERABLE = "*"; exports.STR_BIG_INT_64_ARRAY = "J"; exports.STR_BIG_UINT_64_ARRAY = "j"; exports.STR_BIGINT = "b"; exports.STR_BLOB = "K"; exports.STR_DATA_VIEW = "V"; exports.STR_DATE = "D"; exports.STR_ERROR = "E"; exports.STR_FAILURE = "!"; exports.STR_FALSE = "false"; exports.STR_FILE = "k"; exports.STR_FLOAT_32_ARRAY = "H"; exports.STR_FLOAT_64_ARRAY = "h"; exports.STR_FORM_DATA = "F"; exports.STR_INFINITY = "I"; exports.STR_INT_16_ARRAY = "L"; exports.STR_INT_32_ARRAY = "G"; exports.STR_INT_8_ARRAY = "O"; exports.STR_MAP = "M"; exports.STR_NaN = "NaN"; exports.STR_NEGATIVE_INFINITY = "i"; exports.STR_NEGATIVE_ZERO = "z"; exports.STR_NULL = "null"; exports.STR_PLUGIN = "P"; exports.STR_PROMISE = "$"; exports.STR_READABLE_STREAM = "R"; exports.STR_REDACTED = "<redacted>"; exports.STR_REFERENCE_SYMBOL = "@"; exports.STR_REGEXP = "r"; exports.STR_SET = "S"; exports.STR_SUCCESS = ":"; exports.STR_SYMBOL = "s"; exports.STR_TRUE = "true"; exports.STR_UINT_16_ARRAY = "l"; exports.STR_UINT_32_ARRAY = "g"; exports.STR_UINT_8_ARRAY = "o"; exports.STR_UINT_8_ARRAY_CLAMPED = "C"; exports.STR_UNDEFINED = "u"; exports.STR_URL = "U"; let SUPPORTS_FILE = true; exports.SUPPORTS_FILE = SUPPORTS_FILE; try { new File([], ""); } catch { exports.SUPPORTS_FILE = SUPPORTS_FILE = false; } class WaitGroup { p = 0; #q = []; #waitQueue(resolve) { if (this.p === 0) { resolve(); } else { this.#q.push(resolve); } } add() { this.p++; } done() { if (--this.p === 0) { let r; while ((r = this.#q.shift()) !== undefined) { r(); } } } wait() { return new Promise(this.#waitQueue.bind(this)); } } exports.WaitGroup = WaitGroup; class Deferred { promise; resolve; reject; constructor() { this.promise = new Promise((resolve, reject) => { this.resolve = resolve; this.reject = reject; }); } } exports.Deferred = Deferred; class DeferredAsyncIterable { iterable; #deferred = new Deferred(); #next = this.#deferred; constructor() { this.iterable = async function* () { let next = this.#deferred; while (true) { const res = await next.promise; if (res.done) { return; } yield res.value; next = res.next; } }.bind(this)(); } resolve() { this.#next.resolve({ done: true }); } reject(error) { // We reject before there is a chance to consume the error, so we need to catch it // to avoid an unhandled rejection. this.#next.promise.catch(() => { }); this.#next.reject(error); } yield(value) { const deferred = new Deferred(); this.#next.resolve({ done: false, value, next: deferred, }); this.#next = deferred; } } exports.DeferredAsyncIterable = DeferredAsyncIterable; class DeferredReadableStream extends DeferredAsyncIterable { readable = new ReadableStream({ start: async (controller) => { try { for await (const value of this.iterable) { controller.enqueue(value); } controller.close(); } catch (error) { controller.error(error); } }, }); } exports.DeferredReadableStream = DeferredReadableStream; class TurboBlob extends Blob { promise; #size; #type; #slice = {}; constructor(from, start, end, contentType) { super(); if (typeof from !== "undefined") { this.promise = from.promise; let nextStart = from.#slice.start ?? 0; if (typeof start !== "undefined") { nextStart += start; } this.#slice.start = nextStart; let nextEnd = from.#slice.end; if (typeof end !== "undefined") { nextEnd = (from.#slice.start ?? 0) + end; } this.#slice.end = nextEnd; this.#type = contentType ?? from?.type; this.#size = (nextEnd ?? from.size) - nextStart; } } get size() { if (typeof this.#size === "undefined") { throw new Error("Size is not set"); } return this.#size; } set size(value) { this.#size = value; } get type() { if (typeof this.#type === "undefined") { throw new Error("Type is not set"); } return this.#type; } set type(value) { this.#type = value; } async arrayBuffer() { if (!this.promise) { throw new Error("Promise is not set"); } const buffer = await this.promise; if (this.#slice) { return buffer.slice(this.#slice.start, this.#slice.end); } return buffer; } bytes() { return this.arrayBuffer().then((buffer) => new Uint8Array(buffer)); } slice(start, end, contentType) { return new TurboBlob(this, start, end, contentType); } stream() { return new ReadableStream({ start: async (controller) => { try { controller.enqueue(await this.bytes()); controller.close(); } catch (err) { controller.error(err); } }, }); } text() { return this.bytes().then((bytes) => { return new TextDecoder().decode(bytes); }); } } exports.TurboBlob = TurboBlob; const FileBaseClass = SUPPORTS_FILE ? File : Blob; class TurboFile extends FileBaseClass { promise; #size; #type; #name; #lastModified; #slice = {}; constructor(from, start, end, contentType) { if (SUPPORTS_FILE) { super([], ""); } else { super([]); } if (typeof from !== "undefined") { this.promise = from.promise; let nextStart = from.#slice.start ?? 0; if (typeof start !== "undefined") { nextStart += start; } this.#slice.start = nextStart; let nextEnd = from.#slice.end; if (typeof end !== "undefined") { nextEnd = (from.#slice.start ?? 0) + end; } this.#slice.end = nextEnd; this.#type = contentType ?? from?.type; this.#name = from.name; this.#lastModified = from.lastModified; } } get name() { if (typeof this.#name === "undefined") { throw new Error("Name is not set"); } return this.#name; } set name(value) { this.#name = value; } get lastModified() { if (typeof this.#lastModified === "undefined") { throw new Error("Last modified is not set"); } return this.#lastModified; } set lastModified(value) { this.#lastModified = value; } get size() { if (typeof this.#size === "undefined") { throw new Error("Size is not set"); } return this.#size; } set size(value) { this.#size = value; } get type() { if (typeof this.#type === "undefined") { throw new Error("Type is not set"); } return this.#type; } set type(value) { this.#type = value; } async arrayBuffer() { if (!this.promise) { throw new Error("Promise is not set"); } const buffer = await this.promise; if (this.#slice) { return buffer.slice(this.#slice.start, this.#slice.end); } return buffer; } bytes() { return this.arrayBuffer().then((buffer) => new Uint8Array(buffer)); } slice(start, end, contentType) { return new TurboFile(this, start, end, contentType); } stream() { return new ReadableStream({ start: async (controller) => { try { controller.enqueue(await this.bytes()); controller.close(); } catch (err) { controller.error(err); } }, }); } text() { return this.bytes().then((bytes) => { return new TextDecoder().decode(bytes); }); } } exports.TurboFile = TurboFile;