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.

645 lines (644 loc) 25 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.decode = void 0; const shared_js_1 = require("./shared.js"); let MODE_UNKNOWN = 0; let MODE_NUMBER = 1; let MODE_STRING = 2; let MODE_ASYNC = 3; let SUB_MODE_UNKNOWN = 0; let SUB_MODE_BIGINT = 1; let SUB_MODE_DATE = 2; let SUB_MODE_URL = 3; let SUB_MODE_SYMBOL = 4; let SUB_MODE_REFERENCE = 5; let SUB_MODE_OBJECT_KEY = 6; let SUB_MODE_PROMISE_ID = 7; let SUB_MODE_ASYNC_ITERABLE_ID = 8; let SUB_MODE_READABLE_STREAM_ID = 9; let SUB_MODE_ASYNC_STATUS = 10; let SUB_MODE_ARRAY_BUFFER = 11; let SUB_MODE_INT_8_ARRAY = 12; let SUB_MODE_UINT_8_ARRAY = 13; let SUB_MODE_UINT_8_ARRAY_CLAMPED = 14; let SUB_MODE_INT_16_ARRAY = 15; let SUB_MODE_UINT_16_ARRAY = 16; let SUB_MODE_INT_32_ARRAY = 17; let SUB_MODE_UINT_32_ARRAY = 18; let SUB_MODE_FLOAT_32_ARRAY = 19; let SUB_MODE_FLOAT_64_ARRAY = 20; let SUB_MODE_BIG_INT_64_ARRAY = 21; let SUB_MODE_BIG_UINT_64_ARRAY = 22; let SUB_MODE_DATA_VIEW = 23; let ARRAY_TYPE_SET = 0; let ARRAY_TYPE_MAP = 1; let ARRAY_TYPE_REGEXP = 2; let ARRAY_TYPE_FORM_DATA = 3; let ARRAY_TYPE_PLUGIN = 4; let RELEASE_TYPE_VALUE = 0; let RELEASE_TYPE_OBJECT = 1; let RELEASE_TYPE_ARRAY = 2; let RELEASE_TYPE_PROMISE = 3; async function decode(stream, { plugins = [] } = {}) { let root = new shared_js_1.Deferred(); let references = new Map(); let deferredValues = new Map(); let stack = []; let mode = MODE_UNKNOWN; let subMode = SUB_MODE_UNKNOWN; let buffer = ""; let shouldSkip = 0; let lastChar; let numSlashes = 0; let hasSlashes = false; let releaseValue = (value, type) => { if (type === RELEASE_TYPE_OBJECT) { if (typeof value !== "object" || value === null) { throw new Error("Expected object"); } } else if (type === RELEASE_TYPE_ARRAY) { if (!Array.isArray(value)) { throw new Error("Expected array"); } } if (Array.isArray(value) && typeof value.__type === "number") { switch (value.__type) { case ARRAY_TYPE_MAP: for (let [key, val] of value) { value.__ref.set(key, val); } value = value.__ref; break; case ARRAY_TYPE_SET: for (let val of value) { value.__ref.add(val); } value = value.__ref; break; case ARRAY_TYPE_REGEXP: value = new RegExp(value[0], value[1]); references.set(value.__id, value); break; case ARRAY_TYPE_FORM_DATA: { let formData = new FormData(); for (let [key, val] of value) { formData.append(key, val); } value = formData; references.set(value.__id, value); break; } case ARRAY_TYPE_PLUGIN: { let pluginHandled = false; let pluginsLength = plugins.length; for (let i = 0; i < pluginsLength; i++) { let result = plugins[i](...value); if (typeof result === "object" && result !== null) { value = result.value; pluginHandled = true; break; } } if (!pluginHandled) { // TODO: Should this throw? Should we have a way to recover from errors in the options? value = undefined; } break; } } } if (stack.length === 0) { if (root === null) { throw new Error("Unexpected root value"); } if (root !== null) { root.resolve(value); root = null; return; } } let parent = stack[stack.length - 1]; if (Array.isArray(parent)) { parent.push(value); } else if (typeof parent === "string") { stack.pop(); stack[stack.length - 1][parent] = value; } else if (typeof parent === "boolean") { stack.pop(); let deferred = deferredValues.get(stack.pop()); if (!deferred) { throw new Error("Invalid stack state"); } if (deferred instanceof shared_js_1.Deferred) { if (parent) { deferred.resolve(value); } else { deferred.reject(value); } } else { if (parent) { deferred.yield(value); } else { deferred.reject(value); } } } else { throw new Error("Invalid stack state"); } }; let step = (chunk) => { let length = chunk.length; let charCode; let start = shouldSkip; shouldSkip = 0; let i = start; for (; i < length; i++) { charCode = chunk.charCodeAt(i); if (mode === MODE_UNKNOWN) { if (charCode === 44) { // , mode = MODE_UNKNOWN; subMode = Array.isArray(stack[stack.length - 1]) ? SUB_MODE_UNKNOWN : SUB_MODE_OBJECT_KEY; } else if (charCode === 10) { // \n if (subMode === SUB_MODE_ASYNC_STATUS) { let id = stack.pop(); if (typeof id !== "number") { throw new Error("Invalid stack state"); } let deferred = deferredValues.get(id); deferred.resolve(); deferredValues.delete(id); } mode = MODE_ASYNC; subMode = MODE_UNKNOWN; buffer = ""; } else if (charCode === 123) { // { let newObj = {}; stack.push(newObj); references.set(references.size, newObj); subMode = SUB_MODE_OBJECT_KEY; } else if (charCode === 125) { // } releaseValue(stack.pop(), 1); } else if (charCode === 91) { // [ let newArr = []; stack.push(newArr); references.set(references.size, newArr); } else if (charCode === 83) { // S let newArr = []; newArr.__type = ARRAY_TYPE_SET; newArr.__ref = new Set(); stack.push(newArr); references.set(references.size, newArr.__ref); i++; } else if (charCode === 77) { // M let newArr = []; newArr.__type = ARRAY_TYPE_MAP; newArr.__ref = new Map(); stack.push(newArr); references.set(references.size, newArr.__ref); i++; } else if (charCode === 114) { // r let newArr = []; newArr.__type = ARRAY_TYPE_REGEXP; newArr.__id = references.size; stack.push(newArr); references.set(newArr.__id, newArr); i++; } else if (charCode === 80) { // P let newArr = []; newArr.__type = ARRAY_TYPE_PLUGIN; newArr.__id = references.size; stack.push(newArr); references.set(newArr.__id, newArr); i++; } else if (charCode === 93) { // ] releaseValue(stack.pop(), 2); } else if (charCode === 64) { // @ subMode = SUB_MODE_REFERENCE; } else if (charCode === 68) { // D subMode = SUB_MODE_DATE; } else if (charCode === 85) { // U subMode = SUB_MODE_URL; } else if (charCode === 115) { // s subMode = SUB_MODE_SYMBOL; } else if (charCode === 34) { // " mode = MODE_STRING; buffer = ""; lastChar = undefined; numSlashes = 0; hasSlashes = false; } else if (charCode === 36) { // $ subMode = SUB_MODE_PROMISE_ID; } else if (charCode === 42) { // * subMode = SUB_MODE_ASYNC_ITERABLE_ID; } else if (charCode === 82) { // R subMode = SUB_MODE_READABLE_STREAM_ID; } else if (charCode === 58) { // : if (subMode !== SUB_MODE_ASYNC_STATUS) { throw new SyntaxError("Unexpected character: ':'"); } stack.push(true); } else if (charCode === 33) { // ! if (subMode !== SUB_MODE_ASYNC_STATUS) { throw new SyntaxError("Unexpected character: '!'"); } stack.push(false); } else if (charCode === 117) { // u releaseValue(undefined, 0); subMode = SUB_MODE_UNKNOWN; } else if (charCode === 110) { // n i += 3; releaseValue(null, 0); subMode = SUB_MODE_UNKNOWN; } else if (charCode === 116) { // t i += 3; releaseValue(true, 0); subMode = SUB_MODE_UNKNOWN; } else if (charCode === 102) { // f i += 4; releaseValue(false, 0); subMode = SUB_MODE_UNKNOWN; } else if (charCode === 78) { // N i += 2; releaseValue(Number.NaN, 0); subMode = SUB_MODE_UNKNOWN; } else if (charCode === 73) { // I releaseValue(Number.POSITIVE_INFINITY, 0); subMode = SUB_MODE_UNKNOWN; } else if (charCode === 105) { // i releaseValue(Number.NEGATIVE_INFINITY, 0); subMode = SUB_MODE_UNKNOWN; } else if (charCode === 122) { // z releaseValue(-0, 0); subMode = SUB_MODE_UNKNOWN; } else if (charCode === 98) { // b subMode = SUB_MODE_BIGINT; } else if (charCode === 45 || // - charCode === 46 || // . (charCode >= 48 && charCode <= 57) // 0-9 ) { mode = MODE_NUMBER; buffer = chunk[i]; } else if (charCode === 69) { // E let newObj = new Error(); stack.push(newObj); references.set(references.size, newObj); subMode = SUB_MODE_OBJECT_KEY; i++; } else if (charCode === 70) { // F let newArr = []; newArr.__type = ARRAY_TYPE_FORM_DATA; newArr.__id = references.size; stack.push(newArr); references.set(newArr.__id, newArr); i++; } else if (charCode === 75) { // K let newObj = new shared_js_1.TurboBlob(); stack.push(newObj); references.set(references.size, newObj); subMode = SUB_MODE_OBJECT_KEY; i++; } else if (charCode === 107) { // k let newObj = new shared_js_1.TurboFile(); stack.push(newObj); references.set(references.size, newObj); subMode = SUB_MODE_OBJECT_KEY; i++; } else if (charCode === 65) { // A subMode = SUB_MODE_ARRAY_BUFFER; } else if (charCode === 79) { // O subMode = SUB_MODE_INT_8_ARRAY; } else if (charCode === 111) { // o subMode = SUB_MODE_UINT_8_ARRAY; } else if (charCode === 67) { // C subMode = SUB_MODE_UINT_8_ARRAY_CLAMPED; } else if (charCode === 76) { // L subMode = SUB_MODE_INT_16_ARRAY; } else if (charCode === 108) { // l subMode = SUB_MODE_UINT_16_ARRAY; } else if (charCode === 71) { // G subMode = SUB_MODE_INT_32_ARRAY; } else if (charCode === 103) { // g subMode = SUB_MODE_UINT_32_ARRAY; } else if (charCode === 72) { // H subMode = SUB_MODE_FLOAT_32_ARRAY; } else if (charCode === 104) { // h subMode = SUB_MODE_FLOAT_64_ARRAY; } else if (charCode === 74) { // J subMode = SUB_MODE_BIG_INT_64_ARRAY; } else if (charCode === 106) { // j subMode = SUB_MODE_BIG_UINT_64_ARRAY; } else if (charCode === 86) { // V subMode = SUB_MODE_DATA_VIEW; } else { throw new SyntaxError(`Unexpected character: '${chunk[i]}'`); } } else if (mode === MODE_NUMBER || mode === MODE_ASYNC) { if (charCode === 45 || // - charCode === 46 || // . (charCode >= 48 && charCode <= 57) // 0-9 ) { buffer += chunk[i]; } else { if (mode === MODE_ASYNC) { stack.push(Number(buffer)); mode = MODE_UNKNOWN; subMode = SUB_MODE_ASYNC_STATUS; i--; continue; } if (subMode === SUB_MODE_PROMISE_ID) { let id = Number(buffer); let existing = deferredValues.get(id); if (existing) { releaseValue(existing.promise, 0); } else { let deferred = new shared_js_1.Deferred(); deferredValues.set(id, deferred); releaseValue(deferred.promise, 0); } } else if (subMode === SUB_MODE_ASYNC_ITERABLE_ID) { let id = Number(buffer); let existing = deferredValues.get(id); if (existing) { releaseValue(existing.iterable, 0); } else { let deferred = new shared_js_1.DeferredAsyncIterable(); deferredValues.set(id, deferred); releaseValue(deferred.iterable, 0); } } else if (subMode === SUB_MODE_READABLE_STREAM_ID) { let id = Number(buffer); let existing = deferredValues.get(id); if (existing) { releaseValue(existing.readable, 0); } else { let deferred = new shared_js_1.DeferredReadableStream(); deferredValues.set(id, deferred); releaseValue(deferred.readable, 0); } } else { releaseValue(subMode === SUB_MODE_BIGINT ? BigInt(buffer) : subMode === SUB_MODE_REFERENCE ? references.get(Number(buffer)) : Number(buffer), 0); } buffer = ""; mode = MODE_UNKNOWN; subMode = SUB_MODE_UNKNOWN; i--; } } else if (mode === MODE_STRING) { let stringEnd = false; for (; i < length; i++) { charCode = chunk.charCodeAt(i); if (charCode !== 34 || (lastChar === 92 && numSlashes % 2 === 1)) { buffer += chunk[i]; lastChar = charCode; if (lastChar === 92) { numSlashes++; hasSlashes = true; } else { numSlashes = 0; } } else { stringEnd = true; break; } } if (stringEnd) { let value = hasSlashes ? JSON.parse(`"${buffer}"`) : buffer; if (subMode === SUB_MODE_OBJECT_KEY) { stack.push(value); i++; } else { if (subMode === SUB_MODE_DATE) { value = new Date(value); references.set(references.size, value); } else if (subMode === SUB_MODE_SYMBOL) { value = Symbol.for(value); } else if (subMode === SUB_MODE_URL) { value = new URL(value); references.set(references.size, value); } else if (subMode === SUB_MODE_ARRAY_BUFFER) { value = decodeTypedArray(value).buffer; references.set(references.size, value); } else if (subMode === SUB_MODE_INT_8_ARRAY) { value = new Int8Array(decodeTypedArray(value).buffer); references.set(references.size, value); } else if (subMode === SUB_MODE_UINT_8_ARRAY) { value = decodeTypedArray(value); references.set(references.size, value); } else if (subMode === SUB_MODE_UINT_8_ARRAY_CLAMPED) { value = new Uint8ClampedArray(decodeTypedArray(value).buffer); references.set(references.size, value); } else if (subMode === SUB_MODE_INT_16_ARRAY) { value = new Int16Array(decodeTypedArray(value).buffer); references.set(references.size, value); } else if (subMode === SUB_MODE_UINT_16_ARRAY) { value = new Uint16Array(decodeTypedArray(value).buffer); references.set(references.size, value); } else if (subMode === SUB_MODE_INT_32_ARRAY) { value = new Int32Array(decodeTypedArray(value).buffer); references.set(references.size, value); } else if (subMode === SUB_MODE_UINT_32_ARRAY) { value = new Uint32Array(decodeTypedArray(value).buffer); references.set(references.size, value); } else if (subMode === SUB_MODE_FLOAT_32_ARRAY) { value = new Float32Array(decodeTypedArray(value).buffer); references.set(references.size, value); } else if (subMode === SUB_MODE_FLOAT_64_ARRAY) { value = new Float64Array(decodeTypedArray(value).buffer); references.set(references.size, value); } else if (subMode === SUB_MODE_BIG_INT_64_ARRAY) { value = new BigInt64Array(decodeTypedArray(value).buffer); references.set(references.size, value); } else if (subMode === SUB_MODE_BIG_UINT_64_ARRAY) { value = new BigUint64Array(decodeTypedArray(value).buffer); references.set(references.size, value); } else if (subMode === SUB_MODE_DATA_VIEW) { value = decodeTypedArray(value); value = new DataView(value.buffer, value.byteOffset, value.byteLength); references.set(references.size, value); } releaseValue(value, 0); } mode = MODE_UNKNOWN; subMode = SUB_MODE_UNKNOWN; } else { i--; } } } if (i > length) { shouldSkip = i - length; } }; let reader = stream.getReader(); (async () => { let read; while (!(read = await reader.read()).done) { step(read.value); } })() .catch((error) => { if (root) { root.reject(error); root = null; } for (let deferred of deferredValues.values()) { deferred.reject(error); } }) .finally(() => { reader.releaseLock(); if (root) { root.reject(new Error("Stream ended before root value was parsed")); root = null; } for (let deferred of deferredValues.values()) { deferred.reject(new Error("Stream ended before promise was resolved")); } }); return root.promise; } exports.decode = decode; function decodeTypedArray(base64) { const decodedStr = atob(base64); const uint8Array = new Uint8Array(decodedStr.length); for (let i = 0; i < decodedStr.length; i++) { uint8Array[i] = decodedStr.charCodeAt(i); } return uint8Array; }