UNPKG

@tanstack/start-server-core

Version:

Modern and scalable routing for React applications

203 lines (202 loc) 6.93 kB
import { FRAME_HEADER_SIZE, FRAME_TYPE_CHUNK, FRAME_TYPE_END, FRAME_TYPE_ERROR, FRAME_TYPE_JSON, MAX_FRAME_PAYLOAD_SIZE } from "@tanstack/start-client-core/client-rpc"; //#region src/frame-protocol.ts /** * Binary frame protocol for multiplexing JSON and raw streams over HTTP. * * Frame format: [type:1][streamId:4][length:4][payload:length] * - type: 1 byte - frame type (JSON, CHUNK, END, ERROR) * - streamId: 4 bytes big-endian uint32 - stream identifier * - length: 4 bytes big-endian uint32 - payload length * - payload: variable length bytes */ /** Cached TextEncoder for frame encoding */ var textEncoder = new TextEncoder(); /** Shared empty payload for END frames - avoids allocation per call */ var EMPTY_PAYLOAD = new Uint8Array(0); var MAX_ERROR_MESSAGE_CODE_UNITS = 4096; /** * Encodes a single frame with header and payload. */ function encodeFrame(type, streamId, payload) { if (payload.byteLength > MAX_FRAME_PAYLOAD_SIZE) throw new RangeError(`Frame payload exceeds ${MAX_FRAME_PAYLOAD_SIZE} bytes`); const frame = new Uint8Array(FRAME_HEADER_SIZE + payload.length); frame[0] = type; frame[1] = streamId >>> 24 & 255; frame[2] = streamId >>> 16 & 255; frame[3] = streamId >>> 8 & 255; frame[4] = streamId & 255; frame[5] = payload.length >>> 24 & 255; frame[6] = payload.length >>> 16 & 255; frame[7] = payload.length >>> 8 & 255; frame[8] = payload.length & 255; frame.set(payload, FRAME_HEADER_SIZE); return frame; } /** Encodes an error message payload, truncated to a bounded length. */ function encodeErrorPayload(error) { const originalMessage = error instanceof Error ? error.message : String(error ?? "Unknown error"); const message = originalMessage.length > MAX_ERROR_MESSAGE_CODE_UNITS ? `${originalMessage.slice(0, MAX_ERROR_MESSAGE_CODE_UNITS)}…` : originalMessage; return textEncoder.encode(message); } /** * Creates a multiplexed ReadableStream from serialized response records. * * A record's JSON frame is admitted before any raw stream referenced by that * record starts. Raw streams from admitted records are pumped concurrently. * The caller bounds the stream count before records reach this function. */ function createMultiplexedStream(recordStream, options = {}) { let controller; let stopped = false; let activePumps = 0; let wakeDemand; let admission; const readers = /* @__PURE__ */ new Set(); const pendingRawStreams = /* @__PURE__ */ new Set(); const abortOutput = () => errorOutput(options.signal?.reason); const wakeAdmission = () => { const wake = wakeDemand; wakeDemand = void 0; wake?.(); }; const cancelReader = (reader, reason) => { reader.cancel(reason).catch(() => {}); }; const cancelStream = (stream, reason) => { stream.cancel(reason).catch(() => {}); }; const stop = (reason) => { if (stopped) return false; stopped = [reason]; options.signal?.removeEventListener("abort", abortOutput); wakeAdmission(); for (const reader of readers) cancelReader(reader, reason); for (const stream of pendingRawStreams) cancelStream(stream, reason); pendingRawStreams.clear(); return true; }; const errorOutput = (error) => { if (!stop(error)) return; try { controller.error(error); } catch {} }; const waitForDemand = async () => { while (!stopped && (controller.desiredSize ?? 0) <= 0) await new Promise((resolve) => { wakeDemand = resolve; }); return !stopped; }; const admitFrame = (type, streamId, payload) => { if (stopped) return false; if (!admission && (controller.desiredSize ?? 0) > 0) { controller.enqueue(encodeFrame(type, streamId, payload)); return true; } const runAdmission = async () => { if (!await waitForDemand()) return false; controller.enqueue(encodeFrame(type, streamId, payload)); return true; }; const result = admission ? admission.then(runAdmission) : runAdmission(); const clearAdmission = () => { if (admission === tail) admission = void 0; }; const tail = result.then(clearAdmission, clearAdmission); admission = tail; return result; }; const maybeClose = () => { if (activePumps !== 0 || !stop()) return; try { controller.close(); } catch {} }; const startPump = (pump) => { activePumps++; pump().then(() => { activePumps--; maybeClose(); }, (error) => { activePumps--; errorOutput(error); }); }; async function pumpRawStream(streamId, stream) { const reader = stream.getReader(); readers.add(reader); try { while (!stopped) { const { done, value } = await reader.read(); if (stopped) return; if (done) { const frameAdmission = admitFrame(FRAME_TYPE_END, streamId, EMPTY_PAYLOAD); if (frameAdmission !== true) await frameAdmission; return; } if (!(value instanceof Uint8Array)) throw new TypeError("RawStream chunks must be Uint8Array"); let offset = 0; do { const frameAdmission = admitFrame(FRAME_TYPE_CHUNK, streamId, value.byteLength <= MAX_FRAME_PAYLOAD_SIZE ? value : value.subarray(offset, offset + MAX_FRAME_PAYLOAD_SIZE)); if (frameAdmission !== true && (frameAdmission === false || !await frameAdmission)) return; offset += MAX_FRAME_PAYLOAD_SIZE; } while (offset < value.byteLength); } } catch (error) { if (!stopped) { const frameAdmission = admitFrame(FRAME_TYPE_ERROR, streamId, encodeErrorPayload(error)); if (frameAdmission !== true) await frameAdmission; } } finally { readers.delete(reader); reader.releaseLock(); } } async function pumpRecords() { const reader = recordStream.getReader(); readers.add(reader); try { while (!stopped) { const { done, value } = await reader.read(); if (stopped) { if (!done) for (const registration of value.rawStreams) cancelStream(registration.stream, stopped[0]); return; } if (done) return; for (const registration of value.rawStreams) pendingRawStreams.add(registration.stream); const frameAdmission = admitFrame(FRAME_TYPE_JSON, 0, value.json); if (frameAdmission !== true && (frameAdmission === false || !await frameAdmission)) return; for (const registration of value.rawStreams) { pendingRawStreams.delete(registration.stream); startPump(pumpRawStream.bind(void 0, registration.id, registration.stream)); } } } catch (error) { if (!stopped) errorOutput(error); } finally { readers.delete(reader); reader.releaseLock(); } } return new ReadableStream({ start(ctrl) { controller = ctrl; if (options.signal?.aborted) { cancelStream(recordStream, options.signal.reason); errorOutput(options.signal.reason); return; } options.signal?.addEventListener("abort", abortOutput, { once: true }); startPump(pumpRecords); }, pull() { wakeAdmission(); }, cancel(reason) { if (stop(reason)) options.onCancel?.(reason); } }); } //#endregion export { createMultiplexedStream }; //# sourceMappingURL=frame-protocol.js.map