Streaming Overview
Real-time delivery in MisarBlog — Server-Sent Events (SSE) streams and WebSocket channels.
MisarBlog delivers real-time data through two mechanisms:
- Server-Sent Events (SSE) — an HTTP response whose body is a
text/event-stream, where JSON events arrive incrementally over a single request. Used for AI research and newsletter send progress. - WebSocket — a persistent, bidirectional connection for user notifications and per-article live updates.
SSE endpoints
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/api/v1/ai/research | POST | Session (first-party editor) | Stream an AI research run for the Research & Write panel |
/api/newsletter/issues/:id/send-stream | GET | API key (mbk_…) | Track a newsletter issue's send progress |
These are the only text/event-stream endpoints. Other AI routes (the editor's inline generate and chat helpers) stream over the Vercel AI SDK data protocol, not SSE, and are first-party editor internals.
Event format
Each SSE endpoint emits newline-delimited events. Every event is a single line beginning with data: , followed by a JSON payload, and events are separated by a blank line (standard SSE framing):
data: {"type":"content","delta":"…partial text…"}
data: {"type":"done","result":{ … }}
data: [DONE]- Each
data:line carries one JSON object. The exact shape is per-endpoint — see each page for its event schema. data: [DONE]is the terminator. Stop reading once you receive it.- Blank lines separate events.
Reading an SSE stream
fetch + ReadableStream works in browsers and Node 18+ and lets you buffer partial lines across chunks:
async function readStream(url: string, init: RequestInit) {
const res = await fetch(url, init);
if (!res.ok) throw new Error(`Stream error: ${res.status}`);
const reader = res.body!.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() ?? "";
for (const line of lines) {
if (!line.startsWith("data: ")) continue;
const payload = line.slice(6).trim();
if (payload === "[DONE]") return;
const event = JSON.parse(payload);
// handle event per the endpoint's schema
}
}
}Error handling
Endpoints deliver errors as a final data event before the terminator, rather than changing the HTTP status mid-stream:
data: {"error":"…"}
data: [DONE]Check for an error (or, for AI research, an event with type: "error") field on each parsed event.