Skip to content

Commit 52a6c05

Browse files
author
Sahar Shemesh
committed
refactor: improve ReadBuffer internal structure & logic
1 parent 157bad0 commit 52a6c05

File tree

1 file changed

+21
-32
lines changed

1 file changed

+21
-32
lines changed

src/shared/stdio.ts

Lines changed: 21 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,54 +4,43 @@ import { JSONRPCMessage, JSONRPCMessageSchema } from '../types.js';
44
* Buffers a continuous stdio stream into discrete JSON-RPC messages.
55
*/
66
export class ReadBuffer {
7-
private _buffer?: Buffer;
7+
private _validLines: string[] = [];
8+
private _lastIncompleteLine: string = '';
89

910
append(chunk: Buffer): void {
10-
this._buffer = filterNonJsonLines(this._buffer ? Buffer.concat([this._buffer, chunk]) : chunk);
11+
this._processChunk(chunk);
1112
}
1213

1314
readMessage(): JSONRPCMessage | null {
14-
if (!this._buffer) {
15+
if (this._validLines.length === 0) {
1516
return null;
1617
}
17-
18-
const index = this._buffer.indexOf('\n');
19-
if (index === -1) {
20-
return null;
21-
}
22-
23-
const line = this._buffer.toString('utf8', 0, index).replace(/\r$/, '');
24-
this._buffer = this._buffer.subarray(index + 1);
18+
const line = this._validLines.shift()!;
2519
return deserializeMessage(line);
2620
}
2721

2822
clear(): void {
29-
this._buffer = undefined;
23+
this._validLines = [];
24+
this._lastIncompleteLine = '';
25+
}
26+
27+
private _processChunk(newChunk: Buffer): void {
28+
// Combine any previously incomplete line with the new chunk
29+
const combinedText = this._lastIncompleteLine + newChunk.toString('utf8');
30+
const newLines = combinedText.split('\n');
31+
32+
// The last element may be incomplete, so store it for the next chunk
33+
this._lastIncompleteLine = newLines.pop() ?? '';
34+
const completedLines = newLines.filter(looksLikeJson);
35+
this._validLines.push(...completedLines);
3036
}
3137
}
3238

3339
/**
34-
* Filters out any lines that are not valid JSON objects from the given buffer.
35-
* Retains the last line in case it is incomplete.
36-
* @param buffer The buffer to filter.
37-
* @returns A new buffer containing only valid JSON object lines and the last line.
40+
* Checks if a line looks like a JSON object.
41+
* @param line The line to check.
42+
* @returns True if the line looks like a JSON object, false otherwise.
3843
*/
39-
function filterNonJsonLines(buffer: Buffer): Buffer {
40-
const text = buffer.toString('utf8');
41-
const lines = text.split('\n');
42-
43-
// Pop the last line - it may be incomplete (no trailing newline yet)
44-
const incompleteLine = lines.pop() ?? '';
45-
46-
// Filter complete lines to only keep those that look like JSON objects
47-
const validLines = lines.filter(looksLikeJson);
48-
49-
// Reconstruct: valid JSON lines + incomplete line
50-
const filteredText = validLines.length > 0 ? validLines.join('\n') + '\n' + incompleteLine : incompleteLine;
51-
52-
return Buffer.from(filteredText, 'utf8');
53-
}
54-
5544
function looksLikeJson(line: string): boolean {
5645
const trimmed = line.trim();
5746
return trimmed.startsWith('{') && trimmed.endsWith('}');

0 commit comments

Comments
 (0)