// src/utils.ts var HOLE = -1; var NAN = -2; var NEGATIVE_INFINITY = -3; var NEGATIVE_ZERO = -4; var NULL = -5; var POSITIVE_INFINITY = -6; var UNDEFINED = -7; var TYPE_BIGINT = "B"; var TYPE_DATE = "D"; var TYPE_ERROR = "E"; var TYPE_MAP = "M"; var TYPE_NULL_OBJECT = "N"; var TYPE_PROMISE = "P"; var TYPE_REGEXP = "R"; var TYPE_SET = "S"; var TYPE_SYMBOL = "Y"; var TYPE_URL = "U"; var TYPE_PREVIOUS_RESOLVED = "Z"; var Deferred = class { promise; resolve; reject; constructor() { this.promise = new Promise((resolve, reject) => { this.resolve = resolve; this.reject = reject; }); } }; function createLineSplittingTransform() { const decoder = new TextDecoder(); let leftover = ""; return new TransformStream({ transform(chunk, controller) { const str = decoder.decode(chunk, { stream: true }); const parts = (leftover + str).split("\n"); leftover = parts.pop() || ""; for (const part of parts) { controller.enqueue(part); } }, flush(controller) { if (leftover) { controller.enqueue(leftover); } } }); } // src/flatten.ts function flatten(input) { const { indices } = this; const existing = indices.get(input); if (existing) return [existing]; if (input === void 0) return UNDEFINED; if (input === null) return NULL; if (Number.isNaN(input)) return NAN; if (input === Number.POSITIVE_INFINITY) return POSITIVE_INFINITY; if (input === Number.NEGATIVE_INFINITY) return NEGATIVE_INFINITY; if (input === 0 && 1 / input < 0) return NEGATIVE_ZERO; const index = this.index++; indices.set(input, index); stringify.call(this, input, index); return index; } function stringify(input, index) { const { deferred, plugins, postPlugins } = this; const str = this.stringified; const stack = [[input, index]]; while (stack.length > 0) { const [input2, index2] = stack.pop(); const partsForObj = (obj) => Object.keys(obj).map((k) => `"_${flatten.call(this, k)}":${flatten.call(this, obj[k])}`).join(","); let error = null; switch (typeof input2) { case "boolean": case "number": case "string": str[index2] = JSON.stringify(input2); break; case "bigint": str[index2] = `["${TYPE_BIGINT}","${input2}"]`; break; case "symbol": { const keyFor = Symbol.keyFor(input2); if (!keyFor) { error = new Error( "Cannot encode symbol unless created with Symbol.for()" ); } else { str[index2] = `["${TYPE_SYMBOL}",${JSON.stringify(keyFor)}]`; } break; } case "object": { if (!input2) { str[index2] = `${NULL}`; break; } const isArray = Array.isArray(input2); let pluginHandled = false; if (!isArray && plugins) { for (const plugin of plugins) { const pluginResult = plugin(input2); if (Array.isArray(pluginResult)) { pluginHandled = true; const [pluginIdentifier, ...rest] = pluginResult; str[index2] = `[${JSON.stringify(pluginIdentifier)}`; if (rest.length > 0) { str[index2] += `,${rest.map((v) => flatten.call(this, v)).join(",")}`; } str[index2] += "]"; break; } } } if (!pluginHandled) { let result = isArray ? "[" : "{"; if (isArray) { for (let i = 0; i < input2.length; i++) result += (i ? "," : "") + (i in input2 ? flatten.call(this, input2[i]) : HOLE); str[index2] = `${result}]`; } else if (input2 instanceof Date) { str[index2] = `["${TYPE_DATE}",${input2.getTime()}]`; } else if (input2 instanceof URL) { str[index2] = `["${TYPE_URL}",${JSON.stringify(input2.href)}]`; } else if (input2 instanceof RegExp) { str[index2] = `["${TYPE_REGEXP}",${JSON.stringify( input2.source )},${JSON.stringify(input2.flags)}]`; } else if (input2 instanceof Set) { if (input2.size > 0) { str[index2] = `["${TYPE_SET}",${[...input2].map((val) => flatten.call(this, val)).join(",")}]`; } else { str[index2] = `["${TYPE_SET}"]`; } } else if (input2 instanceof Map) { if (input2.size > 0) { str[index2] = `["${TYPE_MAP}",${[...input2].flatMap(([k, v]) => [ flatten.call(this, k), flatten.call(this, v) ]).join(",")}]`; } else { str[index2] = `["${TYPE_MAP}"]`; } } else if (input2 instanceof Promise) { str[index2] = `["${TYPE_PROMISE}",${index2}]`; deferred[index2] = input2; } else if (input2 instanceof Error) { str[index2] = `["${TYPE_ERROR}",${JSON.stringify(input2.message)}`; if (input2.name !== "Error") { str[index2] += `,${JSON.stringify(input2.name)}`; } str[index2] += "]"; } else if (Object.getPrototypeOf(input2) === null) { str[index2] = `["${TYPE_NULL_OBJECT}",{${partsForObj(input2)}}]`; } else if (isPlainObject(input2)) { str[index2] = `{${partsForObj(input2)}}`; } else { error = new Error("Cannot encode object with prototype"); } } break; } default: { const isArray = Array.isArray(input2); let pluginHandled = false; if (!isArray && plugins) { for (const plugin of plugins) { const pluginResult = plugin(input2); if (Array.isArray(pluginResult)) { pluginHandled = true; const [pluginIdentifier, ...rest] = pluginResult; str[index2] = `[${JSON.stringify(pluginIdentifier)}`; if (rest.length > 0) { str[index2] += `,${rest.map((v) => flatten.call(this, v)).join(",")}`; } str[index2] += "]"; break; } } } if (!pluginHandled) { error = new Error("Cannot encode function or unexpected type"); } } } if (error) { let pluginHandled = false; if (postPlugins) { for (const plugin of postPlugins) { const pluginResult = plugin(input2); if (Array.isArray(pluginResult)) { pluginHandled = true; const [pluginIdentifier, ...rest] = pluginResult; str[index2] = `[${JSON.stringify(pluginIdentifier)}`; if (rest.length > 0) { str[index2] += `,${rest.map((v) => flatten.call(this, v)).join(",")}`; } str[index2] += "]"; break; } } } if (!pluginHandled) { throw error; } } } } var objectProtoNames = Object.getOwnPropertyNames(Object.prototype).sort().join("\0"); function isPlainObject(thing) { const proto = Object.getPrototypeOf(thing); return proto === Object.prototype || proto === null || Object.getOwnPropertyNames(proto).sort().join("\0") === objectProtoNames; } // src/unflatten.ts var globalObj = typeof window !== "undefined" ? window : typeof globalThis !== "undefined" ? globalThis : void 0; function unflatten(parsed) { const { hydrated, values } = this; if (typeof parsed === "number") return hydrate.call(this, parsed); if (!Array.isArray(parsed) || !parsed.length) throw new SyntaxError(); const startIndex = values.length; for (const value of parsed) { values.push(value); } hydrated.length = values.length; return hydrate.call(this, startIndex); } function hydrate(index) { const { hydrated, values, deferred, plugins } = this; let result; const stack = [ [ index, (v) => { result = v; } ] ]; let postRun = []; while (stack.length > 0) { const [index2, set] = stack.pop(); switch (index2) { case UNDEFINED: set(void 0); continue; case NULL: set(null); continue; case NAN: set(NaN); continue; case POSITIVE_INFINITY: set(Infinity); continue; case NEGATIVE_INFINITY: set(-Infinity); continue; case NEGATIVE_ZERO: set(-0); continue; } if (hydrated[index2]) { set(hydrated[index2]); continue; } const value = values[index2]; if (!value || typeof value !== "object") { hydrated[index2] = value; set(value); continue; } if (Array.isArray(value)) { if (typeof value[0] === "string") { const [type, b, c] = value; switch (type) { case TYPE_DATE: set(hydrated[index2] = new Date(b)); continue; case TYPE_URL: set(hydrated[index2] = new URL(b)); continue; case TYPE_BIGINT: set(hydrated[index2] = BigInt(b)); continue; case TYPE_REGEXP: set(hydrated[index2] = new RegExp(b, c)); continue; case TYPE_SYMBOL: set(hydrated[index2] = Symbol.for(b)); continue; case TYPE_SET: const newSet = /* @__PURE__ */ new Set(); hydrated[index2] = newSet; for (let i = 1; i < value.length; i++) stack.push([ value[i], (v) => { newSet.add(v); } ]); set(newSet); continue; case TYPE_MAP: const map = /* @__PURE__ */ new Map(); hydrated[index2] = map; for (let i = 1; i < value.length; i += 2) { const r = []; stack.push([ value[i + 1], (v) => { r[1] = v; } ]); stack.push([ value[i], (k) => { r[0] = k; } ]); postRun.push(() => { map.set(r[0], r[1]); }); } set(map); continue; case TYPE_NULL_OBJECT: const obj = /* @__PURE__ */ Object.create(null); hydrated[index2] = obj; for (const key of Object.keys(b).reverse()) { const r = []; stack.push([ b[key], (v) => { r[1] = v; } ]); stack.push([ Number(key.slice(1)), (k) => { r[0] = k; } ]); postRun.push(() => { obj[r[0]] = r[1]; }); } set(obj); continue; case TYPE_PROMISE: if (hydrated[b]) { set(hydrated[index2] = hydrated[b]); } else { const d = new Deferred(); deferred[b] = d; set(hydrated[index2] = d.promise); } continue; case TYPE_ERROR: const [, message, errorType] = value; let error = errorType && globalObj && globalObj[errorType] ? new globalObj[errorType](message) : new Error(message); hydrated[index2] = error; set(error); continue; case TYPE_PREVIOUS_RESOLVED: set(hydrated[index2] = hydrated[b]); continue; default: if (Array.isArray(plugins)) { const r = []; const vals = value.slice(1); for (let i = 0; i < vals.length; i++) { const v = vals[i]; stack.push([ v, (v2) => { r[i] = v2; } ]); } postRun.push(() => { for (const plugin of plugins) { const result2 = plugin(value[0], ...r); if (result2) { set(hydrated[index2] = result2.value); return; } } throw new SyntaxError(); }); continue; } throw new SyntaxError(); } } else { const array = []; hydrated[index2] = array; for (let i = 0; i < value.length; i++) { const n = value[i]; if (n !== HOLE) { stack.push([ n, (v) => { array[i] = v; } ]); } } set(array); continue; } } else { const object = {}; hydrated[index2] = object; for (const key of Object.keys(value).reverse()) { const r = []; stack.push([ value[key], (v) => { r[1] = v; } ]); stack.push([ Number(key.slice(1)), (k) => { r[0] = k; } ]); postRun.push(() => { object[r[0]] = r[1]; }); } set(object); continue; } } while (postRun.length > 0) { postRun.pop()(); } return result; } // src/turbo-stream.ts async function decode(readable, options) { const { plugins } = options ?? {}; const done = new Deferred(); const reader = readable.pipeThrough(createLineSplittingTransform()).getReader(); const decoder = { values: [], hydrated: [], deferred: {}, plugins }; const decoded = await decodeInitial.call(decoder, reader); let donePromise = done.promise; if (decoded.done) { done.resolve(); } else { donePromise = decodeDeferred.call(decoder, reader).then(done.resolve).catch((reason) => { for (const deferred of Object.values(decoder.deferred)) { deferred.reject(reason); } done.reject(reason); }); } return { done: donePromise.then(() => reader.closed), value: decoded.value }; } async function decodeInitial(reader) { const read = await reader.read(); if (!read.value) { throw new SyntaxError(); } let line; try { line = JSON.parse(read.value); } catch (reason) { throw new SyntaxError(); } return { done: read.done, value: unflatten.call(this, line) }; } async function decodeDeferred(reader) { let read = await reader.read(); while (!read.done) { if (!read.value) continue; const line = read.value; switch (line[0]) { case TYPE_PROMISE: { const colonIndex = line.indexOf(":"); const deferredId = Number(line.slice(1, colonIndex)); const deferred = this.deferred[deferredId]; if (!deferred) { throw new Error(`Deferred ID ${deferredId} not found in stream`); } const lineData = line.slice(colonIndex + 1); let jsonLine; try { jsonLine = JSON.parse(lineData); } catch (reason) { throw new SyntaxError(); } const value = unflatten.call(this, jsonLine); deferred.resolve(value); break; } case TYPE_ERROR: { const colonIndex = line.indexOf(":"); const deferredId = Number(line.slice(1, colonIndex)); const deferred = this.deferred[deferredId]; if (!deferred) { throw new Error(`Deferred ID ${deferredId} not found in stream`); } const lineData = line.slice(colonIndex + 1); let jsonLine; try { jsonLine = JSON.parse(lineData); } catch (reason) { throw new SyntaxError(); } const value = unflatten.call(this, jsonLine); deferred.reject(value); break; } default: throw new SyntaxError(); } read = await reader.read(); } } function encode(input, options) { const { plugins, postPlugins, signal } = options ?? {}; const encoder = { deferred: {}, index: 0, indices: /* @__PURE__ */ new Map(), stringified: [], plugins, postPlugins, signal }; const textEncoder = new TextEncoder(); let lastSentIndex = 0; const readable = new ReadableStream({ async start(controller) { const id = flatten.call(encoder, input); if (Array.isArray(id)) { throw new Error("This should never happen"); } if (id < 0) { controller.enqueue(textEncoder.encode(`${id} `)); } else { controller.enqueue( textEncoder.encode(`[${encoder.stringified.join(",")}] `) ); lastSentIndex = encoder.stringified.length - 1; } const seenPromises = /* @__PURE__ */ new WeakSet(); while (Object.keys(encoder.deferred).length > 0) { for (const [deferredId, deferred] of Object.entries(encoder.deferred)) { if (seenPromises.has(deferred)) continue; seenPromises.add( encoder.deferred[Number(deferredId)] = raceSignal( deferred, encoder.signal ).then( (resolved) => { const id2 = flatten.call(encoder, resolved); if (Array.isArray(id2)) { controller.enqueue( textEncoder.encode( `${TYPE_PROMISE}${deferredId}:[["${TYPE_PREVIOUS_RESOLVED}",${id2[0]}]] ` ) ); encoder.index++; lastSentIndex++; } else if (id2 < 0) { controller.enqueue( textEncoder.encode(`${TYPE_PROMISE}${deferredId}:${id2} `) ); } else { const values = encoder.stringified.slice(lastSentIndex + 1).join(","); controller.enqueue( textEncoder.encode( `${TYPE_PROMISE}${deferredId}:[${values}] ` ) ); lastSentIndex = encoder.stringified.length - 1; } }, (reason) => { if (!reason || typeof reason !== "object" || !(reason instanceof Error)) { reason = new Error("An unknown error occurred"); } const id2 = flatten.call(encoder, reason); if (Array.isArray(id2)) { controller.enqueue( textEncoder.encode( `${TYPE_ERROR}${deferredId}:[["${TYPE_PREVIOUS_RESOLVED}",${id2[0]}]] ` ) ); encoder.index++; lastSentIndex++; } else if (id2 < 0) { controller.enqueue( textEncoder.encode(`${TYPE_ERROR}${deferredId}:${id2} `) ); } else { const values = encoder.stringified.slice(lastSentIndex + 1).join(","); controller.enqueue( textEncoder.encode( `${TYPE_ERROR}${deferredId}:[${values}] ` ) ); lastSentIndex = encoder.stringified.length - 1; } } ).finally(() => { delete encoder.deferred[Number(deferredId)]; }) ); } await Promise.race(Object.values(encoder.deferred)); } await Promise.all(Object.values(encoder.deferred)); controller.close(); } }); return readable; } function raceSignal(promise, signal) { if (!signal) return promise; if (signal.aborted) return Promise.reject(signal.reason || new Error("Signal was aborted.")); const abort = new Promise((resolve, reject) => { signal.addEventListener("abort", (event) => { reject(signal.reason || new Error("Signal was aborted.")); }); promise.then(resolve).catch(reject); }); abort.catch(() => { }); return Promise.race([abort, promise]); } export { decode, encode };