UNPKG

@tanstack/start-server-core

Version:

Modern and scalable routing for React applications

153 lines (152 loc) 4.47 kB
import { FRAME_HEADER_SIZE, FrameType, TSS_CONTENT_TYPE_FRAMED_VERSIONED } from "@tanstack/start-client-core"; //#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); /** * Encodes a single frame with header and payload. */ function encodeFrame(type, streamId, payload) { 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 a JSON frame (type 0, streamId 0). */ function encodeJSONFrame(json) { return encodeFrame(FrameType.JSON, 0, textEncoder.encode(json)); } /** * Encodes a raw stream chunk frame. */ function encodeChunkFrame(streamId, chunk) { return encodeFrame(FrameType.CHUNK, streamId, chunk); } /** * Encodes a raw stream end frame. */ function encodeEndFrame(streamId) { return encodeFrame(FrameType.END, streamId, EMPTY_PAYLOAD); } /** * Encodes a raw stream error frame. */ function encodeErrorFrame(streamId, error) { const message = error instanceof Error ? error.message : String(error ?? "Unknown error"); return encodeFrame(FrameType.ERROR, streamId, textEncoder.encode(message)); } /** * Creates a multiplexed ReadableStream from JSON stream and raw streams. * * The JSON stream emits NDJSON lines (from seroval's toCrossJSONStream). * Raw streams are pumped concurrently, interleaved with JSON frames. * * @param jsonStream Stream of JSON strings (each string is one NDJSON line) * @param rawStreams Map of stream IDs to raw binary streams */ function createMultiplexedStream(jsonStream, rawStreams) { let activePumps = 1 + rawStreams.size; let controllerRef = null; let cancelled = false; const cancelReaders = []; const safeEnqueue = (chunk) => { if (cancelled || !controllerRef) return; try { controllerRef.enqueue(chunk); } catch {} }; const safeError = (err) => { if (cancelled || !controllerRef) return; try { controllerRef.error(err); } catch {} }; const safeClose = () => { if (cancelled || !controllerRef) return; try { controllerRef.close(); } catch {} }; const checkComplete = () => { activePumps--; if (activePumps === 0) safeClose(); }; return new ReadableStream({ start(controller) { controllerRef = controller; cancelReaders.length = 0; const pumpJSON = async () => { const reader = jsonStream.getReader(); cancelReaders.push(() => { reader.cancel().catch(() => {}); }); try { while (true) { const { done, value } = await reader.read(); if (cancelled) break; if (done) break; safeEnqueue(encodeJSONFrame(value)); } } catch (error) { safeError(error); } finally { reader.releaseLock(); checkComplete(); } }; const pumpRawStream = async (streamId, stream) => { const reader = stream.getReader(); cancelReaders.push(() => { reader.cancel().catch(() => {}); }); try { while (true) { const { done, value } = await reader.read(); if (cancelled) break; if (done) { safeEnqueue(encodeEndFrame(streamId)); break; } safeEnqueue(encodeChunkFrame(streamId, value)); } } catch (error) { safeEnqueue(encodeErrorFrame(streamId, error)); } finally { reader.releaseLock(); checkComplete(); } }; pumpJSON(); for (const [streamId, stream] of rawStreams) pumpRawStream(streamId, stream); }, cancel() { cancelled = true; controllerRef = null; for (const cancelReader of cancelReaders) cancelReader(); cancelReaders.length = 0; } }); } //#endregion export { TSS_CONTENT_TYPE_FRAMED_VERSIONED, createMultiplexedStream }; //# sourceMappingURL=frame-protocol.js.map