@tanstack/start-server-core
Version:
Modern and scalable routing for React applications
153 lines (152 loc) • 4.47 kB
JavaScript
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