@logtape/file 1.1.0-dev.323 → 1.1.0-dev.332
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/deno.json +1 -1
- package/dist/streamfilesink.cjs +15 -3
- package/dist/streamfilesink.d.cts +1 -1
- package/dist/streamfilesink.d.cts.map +1 -1
- package/dist/streamfilesink.d.ts +1 -1
- package/dist/streamfilesink.d.ts.map +1 -1
- package/dist/streamfilesink.js +15 -3
- package/dist/streamfilesink.js.map +1 -1
- package/package.json +2 -2
- package/src/streamfilesink.test.ts +21 -21
- package/src/streamfilesink.ts +26 -6
package/deno.json
CHANGED
package/dist/streamfilesink.cjs
CHANGED
|
@@ -71,11 +71,23 @@ function getStreamFileSink(path, options = {}) {
|
|
|
71
71
|
if (disposed) return;
|
|
72
72
|
passThrough.write(formatter(record));
|
|
73
73
|
};
|
|
74
|
-
sink[Symbol.
|
|
74
|
+
sink[Symbol.asyncDispose] = async () => {
|
|
75
75
|
if (disposed) return;
|
|
76
76
|
disposed = true;
|
|
77
|
-
passThrough.
|
|
78
|
-
|
|
77
|
+
if (passThrough.writableNeedDrain) await new Promise((resolve) => {
|
|
78
|
+
passThrough.once("drain", resolve);
|
|
79
|
+
});
|
|
80
|
+
await new Promise((resolve) => {
|
|
81
|
+
passThrough.once("finish", resolve);
|
|
82
|
+
passThrough.end();
|
|
83
|
+
});
|
|
84
|
+
await new Promise((resolve) => {
|
|
85
|
+
if (writeStream.closed || writeStream.destroyed) {
|
|
86
|
+
resolve();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
writeStream.once("close", resolve);
|
|
90
|
+
});
|
|
79
91
|
};
|
|
80
92
|
return sink;
|
|
81
93
|
}
|
|
@@ -88,7 +88,7 @@ interface StreamFileSinkOptions {
|
|
|
88
88
|
*
|
|
89
89
|
* @since 1.0.0
|
|
90
90
|
*/
|
|
91
|
-
declare function getStreamFileSink(path: string, options?: StreamFileSinkOptions): Sink &
|
|
91
|
+
declare function getStreamFileSink(path: string, options?: StreamFileSinkOptions): Sink & AsyncDisposable;
|
|
92
92
|
//# sourceMappingURL=streamfilesink.d.ts.map
|
|
93
93
|
//#endregion
|
|
94
94
|
export { StreamFileSinkOptions, getStreamFileSink };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streamfilesink.d.cts","names":[],"sources":["../src/streamfilesink.ts"],"sourcesContent":[],"mappings":";;;;;;AAkBA;AA+EA;;;;;
|
|
1
|
+
{"version":3,"file":"streamfilesink.d.cts","names":[],"sources":["../src/streamfilesink.ts"],"sourcesContent":[],"mappings":";;;;;;AAkBA;AA+EA;;;;;AAGyB;UAlFR,qBAAA;;;;;;;;;;;;;;;;;;;;;;uBAuBM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwDP,iBAAA,yBAEL,wBACR,OAAO"}
|
package/dist/streamfilesink.d.ts
CHANGED
|
@@ -88,7 +88,7 @@ interface StreamFileSinkOptions {
|
|
|
88
88
|
*
|
|
89
89
|
* @since 1.0.0
|
|
90
90
|
*/
|
|
91
|
-
declare function getStreamFileSink(path: string, options?: StreamFileSinkOptions): Sink &
|
|
91
|
+
declare function getStreamFileSink(path: string, options?: StreamFileSinkOptions): Sink & AsyncDisposable;
|
|
92
92
|
//# sourceMappingURL=streamfilesink.d.ts.map
|
|
93
93
|
//#endregion
|
|
94
94
|
export { StreamFileSinkOptions, getStreamFileSink };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streamfilesink.d.ts","names":[],"sources":["../src/streamfilesink.ts"],"sourcesContent":[],"mappings":";;;;;;AAkBA;AA+EA;;;;;
|
|
1
|
+
{"version":3,"file":"streamfilesink.d.ts","names":[],"sources":["../src/streamfilesink.ts"],"sourcesContent":[],"mappings":";;;;;;AAkBA;AA+EA;;;;;AAGyB;UAlFR,qBAAA;;;;;;;;;;;;;;;;;;;;;;uBAuBM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwDP,iBAAA,yBAEL,wBACR,OAAO"}
|
package/dist/streamfilesink.js
CHANGED
|
@@ -70,11 +70,23 @@ function getStreamFileSink(path, options = {}) {
|
|
|
70
70
|
if (disposed) return;
|
|
71
71
|
passThrough.write(formatter(record));
|
|
72
72
|
};
|
|
73
|
-
sink[Symbol.
|
|
73
|
+
sink[Symbol.asyncDispose] = async () => {
|
|
74
74
|
if (disposed) return;
|
|
75
75
|
disposed = true;
|
|
76
|
-
passThrough.
|
|
77
|
-
|
|
76
|
+
if (passThrough.writableNeedDrain) await new Promise((resolve) => {
|
|
77
|
+
passThrough.once("drain", resolve);
|
|
78
|
+
});
|
|
79
|
+
await new Promise((resolve) => {
|
|
80
|
+
passThrough.once("finish", resolve);
|
|
81
|
+
passThrough.end();
|
|
82
|
+
});
|
|
83
|
+
await new Promise((resolve) => {
|
|
84
|
+
if (writeStream.closed || writeStream.destroyed) {
|
|
85
|
+
resolve();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
writeStream.once("close", resolve);
|
|
89
|
+
});
|
|
78
90
|
};
|
|
79
91
|
return sink;
|
|
80
92
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streamfilesink.js","names":["path: string","options: StreamFileSinkOptions","sink: Sink &
|
|
1
|
+
{"version":3,"file":"streamfilesink.js","names":["path: string","options: StreamFileSinkOptions","sink: Sink & AsyncDisposable","record: LogRecord"],"sources":["../src/streamfilesink.ts"],"sourcesContent":["import {\n defaultTextFormatter,\n type LogRecord,\n type Sink,\n type TextFormatter,\n} from \"@logtape/logtape\";\nimport { createWriteStream } from \"node:fs\";\nimport { PassThrough } from \"node:stream\";\n\n/**\n * Options for the {@link getStreamFileSink} function.\n *\n * This interface configures the high-performance stream-based file sink that\n * uses Node.js PassThrough streams for optimal I/O performance with automatic\n * backpressure management.\n *\n * @since 1.0.0\n */\nexport interface StreamFileSinkOptions {\n /**\n * High water mark for the PassThrough stream buffer in bytes.\n *\n * This controls the internal buffer size of the PassThrough stream.\n * Higher values can improve performance for high-volume logging but use\n * more memory. Lower values reduce memory usage but may impact performance.\n *\n * @default 16384\n * @since 1.0.0\n */\n readonly highWaterMark?: number;\n\n /**\n * A custom formatter for log records.\n *\n * If not specified, the default text formatter will be used, which formats\n * records in the standard LogTape format with timestamp, level, category,\n * and message.\n *\n * @default defaultTextFormatter\n * @since 1.0.0\n */\n readonly formatter?: TextFormatter;\n}\n\n/**\n * Create a high-performance stream-based file sink that writes log records to a file.\n *\n * This sink uses Node.js PassThrough streams piped to WriteStreams for optimal\n * I/O performance. It leverages the Node.js stream infrastructure to provide\n * automatic backpressure management, efficient buffering, and asynchronous writes\n * without blocking the main thread.\n *\n * ## Performance Characteristics\n *\n * - **High Performance**: Optimized for high-volume logging scenarios\n * - **Non-blocking**: Uses asynchronous I/O that doesn't block the main thread\n * - **Memory Efficient**: Automatic backpressure prevents memory buildup\n * - **Stream-based**: Leverages Node.js native stream optimizations\n *\n * ## When to Use\n *\n * Use this sink when you need:\n * - High-performance file logging for production applications\n * - Non-blocking I/O behavior for real-time applications\n * - Automatic backpressure handling for high-volume scenarios\n * - Simple file output without complex buffering configuration\n *\n * For more control over buffering behavior, consider using {@link getFileSink}\n * instead, which provides options for buffer size, flush intervals, and\n * non-blocking modes.\n *\n * ## Example\n *\n * ```typescript\n * import { configure } from \"@logtape/logtape\";\n * import { getStreamFileSink } from \"@logtape/file\";\n *\n * await configure({\n * sinks: {\n * file: getStreamFileSink(\"app.log\", {\n * highWaterMark: 32768 // 32KB buffer for high-volume logging\n * })\n * },\n * loggers: [\n * { category: [\"myapp\"], sinks: [\"file\"] }\n * ]\n * });\n * ```\n *\n * @param path The path to the file to write logs to. The file will be created\n * if it doesn't exist, or appended to if it does exist.\n * @param options Configuration options for the stream-based sink.\n * @returns A sink that writes formatted log records to the specified file.\n * The returned sink implements `Disposable` for proper resource cleanup.\n *\n * @since 1.0.0\n */\nexport function getStreamFileSink(\n path: string,\n options: StreamFileSinkOptions = {},\n): Sink & AsyncDisposable {\n const highWaterMark = options.highWaterMark ?? 16384;\n const formatter = options.formatter ?? defaultTextFormatter;\n\n // Create PassThrough stream for optimal performance\n const passThrough = new PassThrough({\n highWaterMark,\n objectMode: false,\n });\n\n // Create WriteStream immediately (not lazy)\n const writeStream = createWriteStream(path, { flags: \"a\" });\n\n // Pipe PassThrough to WriteStream for automatic backpressure handling\n passThrough.pipe(writeStream);\n\n let disposed = false;\n\n // Stream-based sink function for high performance\n const sink: Sink & AsyncDisposable = (record: LogRecord) => {\n if (disposed) return;\n\n // Direct write to PassThrough stream\n passThrough.write(formatter(record));\n };\n\n // Asynchronous disposal with sequential stream closure\n sink[Symbol.asyncDispose] = async () => {\n if (disposed) return;\n disposed = true;\n\n // Wait for PassThrough to drain if needed\n if (passThrough.writableNeedDrain) {\n await new Promise<void>((resolve) => {\n passThrough.once(\"drain\", resolve);\n });\n }\n\n // End the PassThrough stream first and wait for it to finish\n await new Promise<void>((resolve) => {\n passThrough.once(\"finish\", resolve);\n passThrough.end();\n });\n\n // Wait for WriteStream to finish and close (piped streams auto-close)\n await new Promise<void>((resolve) => {\n if (writeStream.closed || writeStream.destroyed) {\n resolve();\n return;\n }\n writeStream.once(\"close\", resolve);\n });\n };\n\n return sink;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiGA,SAAgB,kBACdA,MACAC,UAAiC,CAAE,GACX;CACxB,MAAM,gBAAgB,QAAQ,iBAAiB;CAC/C,MAAM,YAAY,QAAQ,aAAa;CAGvC,MAAM,cAAc,IAAI,YAAY;EAClC;EACA,YAAY;CACb;CAGD,MAAM,cAAc,kBAAkB,MAAM,EAAE,OAAO,IAAK,EAAC;AAG3D,aAAY,KAAK,YAAY;CAE7B,IAAI,WAAW;CAGf,MAAMC,OAA+B,CAACC,WAAsB;AAC1D,MAAI,SAAU;AAGd,cAAY,MAAM,UAAU,OAAO,CAAC;CACrC;AAGD,MAAK,OAAO,gBAAgB,YAAY;AACtC,MAAI,SAAU;AACd,aAAW;AAGX,MAAI,YAAY,kBACd,OAAM,IAAI,QAAc,CAAC,YAAY;AACnC,eAAY,KAAK,SAAS,QAAQ;EACnC;AAIH,QAAM,IAAI,QAAc,CAAC,YAAY;AACnC,eAAY,KAAK,UAAU,QAAQ;AACnC,eAAY,KAAK;EAClB;AAGD,QAAM,IAAI,QAAc,CAAC,YAAY;AACnC,OAAI,YAAY,UAAU,YAAY,WAAW;AAC/C,aAAS;AACT;GACD;AACD,eAAY,KAAK,SAAS,QAAQ;EACnC;CACF;AAED,QAAO;AACR"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logtape/file",
|
|
3
|
-
"version": "1.1.0-dev.
|
|
3
|
+
"version": "1.1.0-dev.332+cfc70069",
|
|
4
4
|
"description": "File sink and rotating file sink for LogTape",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"logging",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
},
|
|
58
58
|
"sideEffects": false,
|
|
59
59
|
"peerDependencies": {
|
|
60
|
-
"@logtape/logtape": "1.1.0-dev.
|
|
60
|
+
"@logtape/logtape": "1.1.0-dev.332+cfc70069"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@alinea/suite": "^0.6.3",
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { getStreamFileSink } from "./streamfilesink.ts";
|
|
2
2
|
import { suite } from "@alinea/suite";
|
|
3
|
-
import type { LogRecord
|
|
3
|
+
import type { LogRecord } from "@logtape/logtape";
|
|
4
4
|
import { assert } from "@std/assert/assert";
|
|
5
5
|
import { assertEquals } from "@std/assert/equals";
|
|
6
6
|
import { delay } from "@std/async/delay";
|
|
7
7
|
import { join } from "@std/path/join";
|
|
8
8
|
import fs from "node:fs";
|
|
9
|
-
import {
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
10
|
import {
|
|
11
11
|
debug,
|
|
12
12
|
error,
|
|
@@ -23,7 +23,7 @@ function makeTempFileSync(): string {
|
|
|
23
23
|
|
|
24
24
|
test("getStreamFileSink() basic functionality", async () => {
|
|
25
25
|
const path = makeTempFileSync();
|
|
26
|
-
const sink
|
|
26
|
+
const sink = getStreamFileSink(path);
|
|
27
27
|
|
|
28
28
|
sink(debug);
|
|
29
29
|
sink(info);
|
|
@@ -31,7 +31,7 @@ test("getStreamFileSink() basic functionality", async () => {
|
|
|
31
31
|
sink(error);
|
|
32
32
|
sink(fatal);
|
|
33
33
|
|
|
34
|
-
sink[Symbol.
|
|
34
|
+
await sink[Symbol.asyncDispose]();
|
|
35
35
|
|
|
36
36
|
// Allow stream to fully flush
|
|
37
37
|
await delay(50);
|
|
@@ -53,7 +53,7 @@ test("getStreamFileSink() with custom highWaterMark", async () => {
|
|
|
53
53
|
|
|
54
54
|
sink(debug);
|
|
55
55
|
sink(info);
|
|
56
|
-
sink[Symbol.
|
|
56
|
+
await sink[Symbol.asyncDispose]();
|
|
57
57
|
|
|
58
58
|
await delay(50);
|
|
59
59
|
|
|
@@ -73,7 +73,7 @@ test("getStreamFileSink() with custom formatter", async () => {
|
|
|
73
73
|
|
|
74
74
|
sink(debug);
|
|
75
75
|
sink(info);
|
|
76
|
-
sink[Symbol.
|
|
76
|
+
await sink[Symbol.asyncDispose]();
|
|
77
77
|
|
|
78
78
|
await delay(50);
|
|
79
79
|
|
|
@@ -93,7 +93,7 @@ test("getStreamFileSink() appends to existing file", async () => {
|
|
|
93
93
|
|
|
94
94
|
const sink = getStreamFileSink(path);
|
|
95
95
|
sink(debug);
|
|
96
|
-
sink[Symbol.
|
|
96
|
+
await sink[Symbol.asyncDispose]();
|
|
97
97
|
|
|
98
98
|
await delay(50);
|
|
99
99
|
|
|
@@ -115,7 +115,7 @@ test("getStreamFileSink() high-volume logging", async () => {
|
|
|
115
115
|
sink(record);
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
sink[Symbol.
|
|
118
|
+
await sink[Symbol.asyncDispose]();
|
|
119
119
|
await delay(100); // Allow streams to finish
|
|
120
120
|
|
|
121
121
|
const content = fs.readFileSync(path, { encoding: "utf-8" });
|
|
@@ -132,7 +132,7 @@ test("getStreamFileSink() disposal stops writing", async () => {
|
|
|
132
132
|
const sink = getStreamFileSink(path);
|
|
133
133
|
|
|
134
134
|
sink(debug);
|
|
135
|
-
sink[Symbol.
|
|
135
|
+
await sink[Symbol.asyncDispose]();
|
|
136
136
|
|
|
137
137
|
// Writing after disposal should be ignored
|
|
138
138
|
sink(info);
|
|
@@ -153,8 +153,8 @@ test("getStreamFileSink() double disposal", async () => {
|
|
|
153
153
|
const sink = getStreamFileSink(path);
|
|
154
154
|
|
|
155
155
|
sink(debug);
|
|
156
|
-
sink[Symbol.
|
|
157
|
-
sink[Symbol.
|
|
156
|
+
await sink[Symbol.asyncDispose]();
|
|
157
|
+
await sink[Symbol.asyncDispose](); // Should not throw
|
|
158
158
|
|
|
159
159
|
await delay(50);
|
|
160
160
|
|
|
@@ -169,7 +169,7 @@ test("getStreamFileSink() handles rapid disposal", async () => {
|
|
|
169
169
|
|
|
170
170
|
sink(debug);
|
|
171
171
|
// Dispose immediately without waiting
|
|
172
|
-
sink[Symbol.
|
|
172
|
+
await sink[Symbol.asyncDispose]();
|
|
173
173
|
|
|
174
174
|
await delay(50);
|
|
175
175
|
|
|
@@ -199,7 +199,7 @@ test("getStreamFileSink() concurrent writes", async () => {
|
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
await Promise.all(promises);
|
|
202
|
-
sink[Symbol.
|
|
202
|
+
await sink[Symbol.asyncDispose]();
|
|
203
203
|
await delay(100);
|
|
204
204
|
|
|
205
205
|
const content = fs.readFileSync(path, { encoding: "utf-8" });
|
|
@@ -222,7 +222,7 @@ test("getStreamFileSink() with empty records", async () => {
|
|
|
222
222
|
};
|
|
223
223
|
|
|
224
224
|
sink(emptyRecord);
|
|
225
|
-
sink[Symbol.
|
|
225
|
+
await sink[Symbol.asyncDispose]();
|
|
226
226
|
|
|
227
227
|
await delay(50);
|
|
228
228
|
|
|
@@ -243,7 +243,7 @@ test("getStreamFileSink() with large messages", async () => {
|
|
|
243
243
|
};
|
|
244
244
|
|
|
245
245
|
sink(largeRecord);
|
|
246
|
-
sink[Symbol.
|
|
246
|
+
await sink[Symbol.asyncDispose]();
|
|
247
247
|
|
|
248
248
|
await delay(100); // Give more time for large write
|
|
249
249
|
|
|
@@ -270,8 +270,8 @@ test("getStreamFileSink() memory efficiency", async () => {
|
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
-
|
|
274
|
-
await
|
|
273
|
+
// Use async disposal to ensure all streams are properly flushed
|
|
274
|
+
await sink[Symbol.asyncDispose]();
|
|
275
275
|
|
|
276
276
|
const content = fs.readFileSync(path, { encoding: "utf-8" });
|
|
277
277
|
const lines = content.split("\n").filter((line) => line.length > 0);
|
|
@@ -289,7 +289,7 @@ test("getStreamFileSink() creates new file when it doesn't exist", async () => {
|
|
|
289
289
|
|
|
290
290
|
const sink = getStreamFileSink(path);
|
|
291
291
|
sink(debug);
|
|
292
|
-
sink[Symbol.
|
|
292
|
+
await sink[Symbol.asyncDispose]();
|
|
293
293
|
|
|
294
294
|
await delay(50);
|
|
295
295
|
|
|
@@ -308,8 +308,8 @@ test("getStreamFileSink() multiple instances on same file", async () => {
|
|
|
308
308
|
sink1(debug);
|
|
309
309
|
sink2(info);
|
|
310
310
|
|
|
311
|
-
sink1[Symbol.
|
|
312
|
-
sink2[Symbol.
|
|
311
|
+
await sink1[Symbol.asyncDispose]();
|
|
312
|
+
await sink2[Symbol.asyncDispose]();
|
|
313
313
|
|
|
314
314
|
await delay(100);
|
|
315
315
|
|
|
@@ -323,7 +323,7 @@ test("getStreamFileSink() stream error handling", async () => {
|
|
|
323
323
|
const sink = getStreamFileSink(path);
|
|
324
324
|
|
|
325
325
|
sink(debug);
|
|
326
|
-
sink[Symbol.
|
|
326
|
+
await sink[Symbol.asyncDispose]();
|
|
327
327
|
await delay(50);
|
|
328
328
|
|
|
329
329
|
// Delete the file after disposal
|
package/src/streamfilesink.ts
CHANGED
|
@@ -98,7 +98,7 @@ export interface StreamFileSinkOptions {
|
|
|
98
98
|
export function getStreamFileSink(
|
|
99
99
|
path: string,
|
|
100
100
|
options: StreamFileSinkOptions = {},
|
|
101
|
-
): Sink &
|
|
101
|
+
): Sink & AsyncDisposable {
|
|
102
102
|
const highWaterMark = options.highWaterMark ?? 16384;
|
|
103
103
|
const formatter = options.formatter ?? defaultTextFormatter;
|
|
104
104
|
|
|
@@ -117,19 +117,39 @@ export function getStreamFileSink(
|
|
|
117
117
|
let disposed = false;
|
|
118
118
|
|
|
119
119
|
// Stream-based sink function for high performance
|
|
120
|
-
const sink: Sink &
|
|
120
|
+
const sink: Sink & AsyncDisposable = (record: LogRecord) => {
|
|
121
121
|
if (disposed) return;
|
|
122
122
|
|
|
123
123
|
// Direct write to PassThrough stream
|
|
124
124
|
passThrough.write(formatter(record));
|
|
125
125
|
};
|
|
126
126
|
|
|
127
|
-
//
|
|
128
|
-
sink[Symbol.
|
|
127
|
+
// Asynchronous disposal with sequential stream closure
|
|
128
|
+
sink[Symbol.asyncDispose] = async () => {
|
|
129
129
|
if (disposed) return;
|
|
130
130
|
disposed = true;
|
|
131
|
-
|
|
132
|
-
|
|
131
|
+
|
|
132
|
+
// Wait for PassThrough to drain if needed
|
|
133
|
+
if (passThrough.writableNeedDrain) {
|
|
134
|
+
await new Promise<void>((resolve) => {
|
|
135
|
+
passThrough.once("drain", resolve);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// End the PassThrough stream first and wait for it to finish
|
|
140
|
+
await new Promise<void>((resolve) => {
|
|
141
|
+
passThrough.once("finish", resolve);
|
|
142
|
+
passThrough.end();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Wait for WriteStream to finish and close (piped streams auto-close)
|
|
146
|
+
await new Promise<void>((resolve) => {
|
|
147
|
+
if (writeStream.closed || writeStream.destroyed) {
|
|
148
|
+
resolve();
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
writeStream.once("close", resolve);
|
|
152
|
+
});
|
|
133
153
|
};
|
|
134
154
|
|
|
135
155
|
return sink;
|