65 lines
1.6 KiB
TypeScript
65 lines
1.6 KiB
TypeScript
export type SafeStreamWriterOptions = {
|
|
beforeWrite?: () => void;
|
|
onBrokenPipe?: (err: NodeJS.ErrnoException, stream: NodeJS.WriteStream) => void;
|
|
};
|
|
|
|
export type SafeStreamWriter = {
|
|
write: (stream: NodeJS.WriteStream, text: string) => boolean;
|
|
writeLine: (stream: NodeJS.WriteStream, text: string) => boolean;
|
|
reset: () => void;
|
|
isClosed: () => boolean;
|
|
};
|
|
|
|
function isBrokenPipeError(err: unknown): err is NodeJS.ErrnoException {
|
|
const code = (err as NodeJS.ErrnoException)?.code;
|
|
return code === "EPIPE" || code === "EIO";
|
|
}
|
|
|
|
export function createSafeStreamWriter(options: SafeStreamWriterOptions = {}): SafeStreamWriter {
|
|
let closed = false;
|
|
let notified = false;
|
|
|
|
const noteBrokenPipe = (err: NodeJS.ErrnoException, stream: NodeJS.WriteStream) => {
|
|
if (notified) return;
|
|
notified = true;
|
|
options.onBrokenPipe?.(err, stream);
|
|
};
|
|
|
|
const handleError = (err: unknown, stream: NodeJS.WriteStream): boolean => {
|
|
if (!isBrokenPipeError(err)) {
|
|
throw err;
|
|
}
|
|
closed = true;
|
|
noteBrokenPipe(err, stream);
|
|
return false;
|
|
};
|
|
|
|
const write = (stream: NodeJS.WriteStream, text: string): boolean => {
|
|
if (closed) return false;
|
|
try {
|
|
options.beforeWrite?.();
|
|
} catch (err) {
|
|
return handleError(err, process.stderr);
|
|
}
|
|
try {
|
|
stream.write(text);
|
|
return !closed;
|
|
} catch (err) {
|
|
return handleError(err, stream);
|
|
}
|
|
};
|
|
|
|
const writeLine = (stream: NodeJS.WriteStream, text: string): boolean =>
|
|
write(stream, `${text}\n`);
|
|
|
|
return {
|
|
write,
|
|
writeLine,
|
|
reset: () => {
|
|
closed = false;
|
|
notified = false;
|
|
},
|
|
isClosed: () => closed,
|
|
};
|
|
}
|