101 lines
2.5 KiB
TypeScript
101 lines
2.5 KiB
TypeScript
type WriteFn = (content: string) => void
|
|
|
|
export type BufferedWriter = {
|
|
write: (content: string) => void
|
|
flush: () => void
|
|
dispose: () => void
|
|
}
|
|
|
|
export function createBufferedWriter({
|
|
writeFn,
|
|
flushIntervalMs = 1000,
|
|
maxBufferSize = 100,
|
|
maxBufferBytes = Infinity,
|
|
immediateMode = false,
|
|
}: {
|
|
writeFn: WriteFn
|
|
flushIntervalMs?: number
|
|
maxBufferSize?: number
|
|
maxBufferBytes?: number
|
|
immediateMode?: boolean
|
|
}): BufferedWriter {
|
|
let buffer: string[] = []
|
|
let bufferBytes = 0
|
|
let flushTimer: NodeJS.Timeout | null = null
|
|
// Batch detached by overflow that hasn't been written yet. Tracked so
|
|
// flush()/dispose() can drain it synchronously if the process exits
|
|
// before the setImmediate fires.
|
|
let pendingOverflow: string[] | null = null
|
|
|
|
function clearTimer(): void {
|
|
if (flushTimer) {
|
|
clearTimeout(flushTimer)
|
|
flushTimer = null
|
|
}
|
|
}
|
|
|
|
function flush(): void {
|
|
if (pendingOverflow) {
|
|
writeFn(pendingOverflow.join(''))
|
|
pendingOverflow = null
|
|
}
|
|
if (buffer.length === 0) return
|
|
writeFn(buffer.join(''))
|
|
buffer = []
|
|
bufferBytes = 0
|
|
clearTimer()
|
|
}
|
|
|
|
function scheduleFlush(): void {
|
|
if (!flushTimer) {
|
|
flushTimer = setTimeout(flush, flushIntervalMs)
|
|
}
|
|
}
|
|
|
|
// Detach the buffer synchronously so the caller never waits on writeFn.
|
|
// writeFn may block (e.g. errorLogSink.ts appendFileSync) — if overflow fires
|
|
// mid-render or mid-keystroke, deferring the write keeps the current tick
|
|
// short. Timer-based flushes already run outside user code paths so they
|
|
// stay synchronous.
|
|
function flushDeferred(): void {
|
|
if (pendingOverflow) {
|
|
// A previous overflow write is still queued. Coalesce into it to
|
|
// preserve ordering — writes land in a single setImmediate-ordered batch.
|
|
pendingOverflow.push(...buffer)
|
|
buffer = []
|
|
bufferBytes = 0
|
|
clearTimer()
|
|
return
|
|
}
|
|
const detached = buffer
|
|
buffer = []
|
|
bufferBytes = 0
|
|
clearTimer()
|
|
pendingOverflow = detached
|
|
setImmediate(() => {
|
|
const toWrite = pendingOverflow
|
|
pendingOverflow = null
|
|
if (toWrite) writeFn(toWrite.join(''))
|
|
})
|
|
}
|
|
|
|
return {
|
|
write(content: string): void {
|
|
if (immediateMode) {
|
|
writeFn(content)
|
|
return
|
|
}
|
|
buffer.push(content)
|
|
bufferBytes += content.length
|
|
scheduleFlush()
|
|
if (buffer.length >= maxBufferSize || bufferBytes >= maxBufferBytes) {
|
|
flushDeferred()
|
|
}
|
|
},
|
|
flush,
|
|
dispose(): void {
|
|
flush()
|
|
},
|
|
}
|
|
}
|