/** Reusable Server-Sent Events parser for browser and test environments. */ export class SseParseError extends Error { constructor(message, details = {}) { super(message); this.name = "SseParseError"; Object.assign(this, details); } } function parseEvent(block, {onComment, onRetry} = {}) { if (!block) return null; let event = "message"; const data = []; let id; let retry; for (const line of block.split(/\r?\n/)) { if (line.startsWith(":")) { onComment?.(line.slice(1).trimStart()); continue; } const separator = line.indexOf(":"); const field = separator < 0 ? line : line.slice(0, separator); const value = separator < 0 ? "" : line.slice(separator + 1).replace(/^ /, ""); if (field === "event") event = value; else if (field === "data") data.push(value); else if (field === "id" && !value.includes("\u0000")) id = value; else if (field === "retry" && /^\d+$/.test(value)) { retry = Number(value); onRetry?.(retry); } } if (!data.length) return null; return {event, data: data.join("\n"), ...(id !== undefined ? {id} : {}), ...(retry !== undefined ? {retry} : {})}; } export function createSseParser(options = {}) { let buffer = ""; return { push(text) { buffer += text; const blocks = buffer.split(/\r?\n\r?\n/); buffer = blocks.pop() || ""; return blocks.map(block => parseEvent(block, options)).filter(Boolean); }, finish() { const event = parseEvent(buffer, options); buffer = ""; return event ? [event] : []; } }; } export function parseSseData(event) { try { return JSON.parse(event.data); } catch (error) { throw new SseParseError("Malformed SSE JSON payload.", {cause: error, event}); } }