MCPcopy
hub / github.com/promptfoo/promptfoo / JsonlFileWriter

Class JsonlFileWriter

src/util/exportToFile/writeToFile.ts:3–110  ·  view source on GitHub ↗

Source from the content-addressed store, hash-verified

1import { createWriteStream, type WriteStream } from 'fs';
2
3export class JsonlFileWriter {
4 private readonly flags: 'a' | 'w';
5 private writeStream: WriteStream | undefined;
6 // The first async stream error (e.g. an fd failure that surfaces between writes). Captured
7 // by a persistent listener so it can be re-thrown from the next write()/close() instead of
8 // escaping as an unhandled 'error' event that would crash the process.
9 private streamError: Error | undefined;
10
11 constructor(
12 private filePath: string,
13 { append = false }: { append?: boolean } = {},
14 ) {
15 // Open lazily on the first write so we never truncate an existing file for an eval that
16 // produces no rows — e.g. one that throws during setup before writing anything. The
17 // first write still truncates (flags 'w') so a reused path holds only the current run.
18 this.flags = append ? 'a' : 'w';
19 }
20
21 private getStream(): WriteStream {
22 if (!this.writeStream) {
23 const stream = createWriteStream(this.filePath, { flags: this.flags });
24 // Keep a persistent listener for the stream's lifetime so an error emitted while no
25 // write is in flight is recorded rather than thrown. write()/close() surface it as a
26 // rejected promise with the output path. The listener also stays attached after
27 // close() settles so a stray late error is absorbed instead of crashing the process.
28 stream.on('error', (error: Error) => {
29 if (!this.streamError) {
30 this.streamError = error;
31 }
32 });
33 this.writeStream = stream;
34 }
35 return this.writeStream;
36 }
37
38 // Attach the output path to a stream error so callers (e.g. the evaluator aggregating
39 // writer failures) can attribute it to a specific file. Shared by write() and close() so
40 // both rejection paths report the path consistently; the original error is kept as `cause`.
41 private wrapStreamError(action: 'write' | 'close', error: Error): Error {
42 return new Error(`Failed to ${action} JSONL output ${this.filePath}: ${error.message}`, {
43 cause: error,
44 });
45 }
46
47 async write(data: unknown): Promise<void> {
48 if (this.streamError) {
49 throw this.wrapStreamError('write', this.streamError);
50 }
51 const jsonLine = JSON.stringify(data) + '\n';
52 const stream = this.getStream();
53
54 return new Promise<void>((resolve, reject) => {
55 stream.write(jsonLine, (error) => {
56 if (error) {
57 reject(this.wrapStreamError('write', error));
58 } else {
59 resolve();
60 }

Callers

nothing calls this directly

Calls

no outgoing calls

Tested by

no test coverage detected

Used in the wild real call sites across dependent graphs

searching dependent graphs…