feat(backend): LF-only JSONL line splitter
Implement attachJsonlReader to split streams on \n only, handling trailing \r, and reassembling lines split across chunk boundaries. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,17 @@
|
|||||||
|
import { StringDecoder } from "node:string_decoder";
|
||||||
|
import type { Readable } from "node:stream";
|
||||||
|
|
||||||
|
export function attachJsonlReader(stream: Readable, onLine: (line: string) => void): () => void {
|
||||||
|
const decoder = new StringDecoder("utf8");
|
||||||
|
let buffer = "";
|
||||||
|
const emit = (line: string) => onLine(line.endsWith("\r") ? line.slice(0, -1) : line);
|
||||||
|
const onData = (chunk: Buffer | string) => {
|
||||||
|
buffer += typeof chunk === "string" ? chunk : decoder.write(chunk);
|
||||||
|
for (let nl; (nl = buffer.indexOf("\n")) !== -1; ) {
|
||||||
|
emit(buffer.slice(0, nl)); buffer = buffer.slice(nl + 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const onEnd = () => { buffer += decoder.end(); if (buffer) { emit(buffer); buffer = ""; } };
|
||||||
|
stream.on("data", onData); stream.on("end", onEnd);
|
||||||
|
return () => { stream.off("data", onData); stream.off("end", onEnd); };
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { test, expect } from "vitest";
|
||||||
|
import { Readable } from "node:stream";
|
||||||
|
import { attachJsonlReader } from "../src/rpc/line-splitter.js";
|
||||||
|
|
||||||
|
test("riassembla righe spezzate tra chunk, split solo su \\n", async () => {
|
||||||
|
const lines: string[] = [];
|
||||||
|
const s = new Readable({ read() {} });
|
||||||
|
attachJsonlReader(s, (l) => lines.push(l));
|
||||||
|
s.push('{"a":1}\n{"b":'); s.push('2}\r\n{"u":" in stringa"}\n'); s.push(null);
|
||||||
|
await new Promise((r) => s.on("end", r));
|
||||||
|
expect(lines).toEqual(['{"a":1}', '{"b":2}', '{"u":" in stringa"}']);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user