JSON Lines (.jsonl) to format tekstowy gdzie:
- Każda linia = jeden kompletny JSON object
- Linie oddzielone znakiem
\n(newline) - BEZ prefix "data: " (to jest SSE format)
- Prosty do parsowania w streaming
data: {"type":"text-delta","delta":"Hello"}\n
\n
data: {"type":"text-delta","delta":" world"}\n
\n
- Wymaga prefix
data: - Podwójny newline
\n\nmiędzy eventami - Bardziej złożony parsing
{"type":"text-delta","delta":"Hello","timestamp":1234567890}\n
{"type":"text-delta","delta":" world","timestamp":1234567891}\n
- Czysty JSON
- Pojedynczy newline
\n - Prostszy i szybszy parsing
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
const sendEvent = (data: any) => {
// Dodaj metadane
const eventData = {
...data,
timestamp: Date.now(),
requestId: requestId || "unknown",
};
// JSON Lines: JSON + newline
const line = JSON.stringify(eventData) + '\n';
controller.enqueue(encoder.encode(line));
console.log(`[STREAM] Sent: ${data.type}`);
};
// Wysyłanie eventów
sendEvent({ type: "text-delta", delta: "Hello" });
sendEvent({ type: "text-delta", delta: " world" });
}
});const reader = response.body?.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
// Dekoduj chunk
buffer += decoder.decode(value, { stream: true });
// Split na linie
const lines = buffer.split('\n');
buffer = lines.pop() || ''; // Zachowaj niekompletną linię
// Parsuj każdą kompletną linię
for (const line of lines) {
if (!line.trim()) continue; // Skip pustych linii
try {
// Czysty JSON parse - BEZ slice(6)!
const data = JSON.parse(line);
// Natychmiastowe renderowanie z flushSync
if (data.type === "text-delta") {
flushSync(() => {
setMessages(prev => /* ... */);
});
}
} catch (e) {
if (!(e instanceof SyntaxError)) throw e;
}
}
}{"type":"text-delta","delta":"tekst","timestamp":1234567890}{"type":"tool-call-start","toolCallId":"call_123","timestamp":1234567890}
{"type":"tool-name-delta","toolCallId":"call_123","toolName":"computer","timestamp":1234567891}
{"type":"tool-argument-delta","toolCallId":"call_123","delta":"{\"action\":","timestamp":1234567892}
{"type":"tool-input-available","toolCallId":"call_123","input":{"action":"screenshot"},"timestamp":1234567893}
{"type":"tool-output-available","toolCallId":"call_123","output":"Done","timestamp":1234567894}{"type":"screenshot-update","screenshot":"base64...","timestamp":1234567895}{"type":"finish","timestamp":1234567896}- Prostota - brak SSE boilerplate
- Szybkość - mniej parsowania, bezpośredni JSON.parse()
- Debugging - łatwe czytanie w network tab
- Kompatybilność - działa z każdym fetch() API
- Real-time - natychmiastowe przetwarzanie każdej linii
- Używaj
JSON.parse(line)bezpośrednio - Split na
\n - Zachowuj niekompletne linie w bufferze
- Używaj
flushSync()dla każdej aktualizacji
Nie używaj(to dla SSE)line.slice(6)Nie sprawdzaj(to dla SSE)line.startsWith('data: ')Nie dziel na(to dla SSE)\n\n- Nie buforuj kompletnych linii przed parsowaniem
- Otwórz request do
/api/chat - W Response → Preview powinieneś widzieć:
{"type":"text-delta","delta":"H",...}
{"type":"text-delta","delta":"e",...}
{"type":"text-delta","delta":"l",...}
[STREAM] Sent: text-delta at 2025-10-19T22:00:00.000Z
[STREAM] Sent: text-delta at 2025-10-19T22:00:00.001Z
Jeśli masz stary kod z SSE:
// ❌ STARE (SSE)
if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(6));
}
// ✅ NOWE (JSON Lines)
if (line.trim()) {
const data = JSON.parse(line);
}- JSON Lines spec
- MDN: ReadableStream
- React: flushSync