gijl's picture
download
raw
2.95 kB
import {
SSE_DATA_PREFIX,
SSE_DONE_MARKER,
SSE_LINE_SEPARATOR,
SSE_RECORD_SEPARATOR
} from '$lib/constants';
/**
* Minimal SSE-with-JSON stream iterator.
*
* Yields one event per `\n\n`-separated record. Each event payload is the
* decoded `data:` field after JSON-parsing. A `[DONE]` sentinel terminates
* the stream early. Malformed records - any record whose `data:` payload
* fails `JSON.parse` - are skipped silently: usually a transient mid-stream
* fault that the caller should not have to special-case, and the noise of
* logging every occurrence on long-running streams outweighs the diagnostic
* value.
*
* Less ambitious than ChatService.handleStreamResponse (no resume, no byte
* offset tracking) - suitable for one-shot streams like `/tools?stream=true`
* where the consumer just reads chunks until done.
*/
export interface SseJsonEvent<T = unknown> {
data: T;
}
/**
* Splits a raw SSE byte buffer into complete records on the blank-line
* boundary, returning the leftover partial record separately. Shared by the
* record-based consumers (parseSseJsonStream, models.service).
*/
export function splitSseRecords(buffer: string): { records: string[]; rest: string } {
const parts = buffer.split(SSE_RECORD_SEPARATOR);
return { records: parts.slice(0, -1), rest: parts[parts.length - 1] ?? '' };
}
/**
* Extracts the joined `data:` payload from one SSE record (the data lines
* concatenated with a newline), or an empty string when the record carries
* no data lines. Used by models.service to parse status envelopes.
*/
export function extractSseDataPayload(record: string): string {
return record
.split(SSE_LINE_SEPARATOR)
.filter((line) => line.startsWith(SSE_DATA_PREFIX))
.map((line) => line.slice(SSE_DATA_PREFIX.length).trim())
.join(SSE_LINE_SEPARATOR);
}
export async function* parseSseJsonStream<T = unknown>(
response: Response,
signal?: AbortSignal
): AsyncGenerator<SseJsonEvent<T>> {
const reader = response.body?.getReader();
if (!reader) return;
const decoder = new TextDecoder();
let buffer = '';
try {
while (true) {
if (signal?.aborted) return;
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const { records, rest } = splitSseRecords(buffer);
buffer = rest;
for (const record of records) {
if (!record) continue;
for (const line of record.split(SSE_LINE_SEPARATOR)) {
if (!line.startsWith(SSE_DATA_PREFIX)) continue;
const payload = line.slice(SSE_DATA_PREFIX.length).trim();
if (payload === SSE_DONE_MARKER) return;
if (!payload) continue;
try {
yield { data: JSON.parse(payload) as T };
} catch {
// Skip silently per the function contract above.
}
}
}
}
} finally {
try {
reader.releaseLock();
} catch (error) {
console.error('[sse] failed to release reader lock:', error);
}
}
}

Xet Storage Details

Size:
2.95 kB
·
Xet hash:
73bce982a6fd9364aa1bb441d4035fa16acf2cecdc677778165517f0dd4ef64e

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.