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