source dump of claude code
at main 32 lines 1.4 kB view raw
1import { jsonStringify } from '../utils/slowOperations.js' 2 3// JSON.stringify emits U+2028/U+2029 raw (valid per ECMA-404). When the 4// output is a single NDJSON line, any receiver that uses JavaScript 5// line-terminator semantics (ECMA-262 §11.3 — \n \r U+2028 U+2029) to 6// split the stream will cut the JSON mid-string. ProcessTransport now 7// silently skips non-JSON lines rather than crashing (gh-28405), but 8// the truncated fragment is still lost — the message is silently dropped. 9// 10// The \uXXXX form is equivalent JSON (parses to the same string) but 11// can never be mistaken for a line terminator by ANY receiver. This is 12// what ES2019's "Subsume JSON" proposal and Node's util.inspect do. 13// 14// Single regex with alternation: the callback's one dispatch per match 15// is cheaper than two full-string scans. 16const JS_LINE_TERMINATORS = /\u2028|\u2029/g 17 18function escapeJsLineTerminators(json: string): string { 19 return json.replace(JS_LINE_TERMINATORS, c => 20 c === '\u2028' ? '\\u2028' : '\\u2029', 21 ) 22} 23 24/** 25 * JSON.stringify for one-message-per-line transports. Escapes U+2028 26 * LINE SEPARATOR and U+2029 PARAGRAPH SEPARATOR so the serialized output 27 * cannot be broken by a line-splitting receiver. Output is still valid 28 * JSON and parses to the same value. 29 */ 30export function ndjsonSafeStringify(value: unknown): string { 31 return escapeJsLineTerminators(jsonStringify(value)) 32}