@logtape/file 1.0.0-dev.236 → 1.0.0-dev.237

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/filesink.base.ts CHANGED
@@ -31,6 +31,16 @@ export type FileSinkOptions = StreamSinkOptions & {
31
31
  * @since 0.12.0
32
32
  */
33
33
  flushInterval?: number;
34
+
35
+ /**
36
+ * Enable non-blocking mode with background flushing.
37
+ * When enabled, flush operations are performed asynchronously to prevent
38
+ * blocking the main thread during file I/O operations.
39
+ *
40
+ * @default `false`
41
+ * @since 1.0.0
42
+ */
43
+ nonBlocking?: boolean;
34
44
  };
35
45
 
36
46
  /**
@@ -64,6 +74,25 @@ export interface FileSinkDriver<TFile> {
64
74
  closeSync(fd: TFile): void;
65
75
  }
66
76
 
77
+ /**
78
+ * A platform-specific async file sink driver.
79
+ * @typeParam TFile The type of the file descriptor.
80
+ * @since 1.0.0
81
+ */
82
+ export interface AsyncFileSinkDriver<TFile> extends FileSinkDriver<TFile> {
83
+ /**
84
+ * Asynchronously flush the file to ensure that all data is written to the disk.
85
+ * @param fd The file descriptor.
86
+ */
87
+ flush(fd: TFile): Promise<void>;
88
+
89
+ /**
90
+ * Asynchronously close the file.
91
+ * @param fd The file descriptor.
92
+ */
93
+ close(fd: TFile): Promise<void>;
94
+ }
95
+
67
96
  /**
68
97
  * Get a platform-independent file sink.
69
98
  *
@@ -71,12 +100,23 @@ export interface FileSinkDriver<TFile> {
71
100
  * @param path A path to the file to write to.
72
101
  * @param options The options for the sink and the file driver.
73
102
  * @returns A sink that writes to the file. The sink is also a disposable
74
- * object that closes the file when disposed.
103
+ * object that closes the file when disposed. If `nonBlocking` is enabled,
104
+ * returns a sink that also implements {@link AsyncDisposable}.
75
105
  */
76
106
  export function getBaseFileSink<TFile>(
77
107
  path: string,
78
108
  options: FileSinkOptions & FileSinkDriver<TFile>,
79
- ): Sink & Disposable {
109
+ ): Sink & Disposable;
110
+ export function getBaseFileSink<TFile>(
111
+ path: string,
112
+ options: FileSinkOptions & AsyncFileSinkDriver<TFile>,
113
+ ): Sink & AsyncDisposable;
114
+ export function getBaseFileSink<TFile>(
115
+ path: string,
116
+ options:
117
+ & FileSinkOptions
118
+ & (FileSinkDriver<TFile> | AsyncFileSinkDriver<TFile>),
119
+ ): Sink & (Disposable | AsyncDisposable) {
80
120
  const formatter = options.formatter ?? defaultTextFormatter;
81
121
  const encoder = options.encoder ?? new TextEncoder();
82
122
  const bufferSize = options.bufferSize ?? 1024 * 8; // Default buffer size of 8192 chars
@@ -85,18 +125,79 @@ export function getBaseFileSink<TFile>(
85
125
  let buffer: string = "";
86
126
  let lastFlushTimestamp: number = Date.now();
87
127
 
88
- function flushBuffer(): void {
89
- if (fd == null) return;
90
- if (buffer.length > 0) {
91
- options.writeSync(fd, encoder.encode(buffer));
92
- buffer = "";
93
- options.flushSync(fd);
128
+ if (!options.nonBlocking) {
129
+ // Blocking mode implementation
130
+ // deno-lint-ignore no-inner-declarations
131
+ function flushBuffer(): void {
132
+ if (fd == null) return;
133
+ if (buffer.length > 0) {
134
+ options.writeSync(fd, encoder.encode(buffer));
135
+ buffer = "";
136
+ options.flushSync(fd);
137
+ lastFlushTimestamp = Date.now();
138
+ }
139
+ }
140
+
141
+ const sink: Sink & Disposable = (record: LogRecord) => {
142
+ if (fd == null) fd = options.openSync(path);
143
+ buffer += formatter(record);
144
+
145
+ const shouldFlushBySize = buffer.length >= bufferSize;
146
+ const shouldFlushByTime = flushInterval > 0 &&
147
+ (record.timestamp - lastFlushTimestamp) >= flushInterval;
148
+
149
+ if (shouldFlushBySize || shouldFlushByTime) {
150
+ flushBuffer();
151
+ }
152
+ };
153
+ sink[Symbol.dispose] = () => {
154
+ if (fd !== null) {
155
+ flushBuffer();
156
+ options.closeSync(fd);
157
+ }
158
+ };
159
+ return sink;
160
+ }
161
+
162
+ // Non-blocking mode implementation
163
+ const asyncOptions = options as AsyncFileSinkDriver<TFile>;
164
+ let disposed = false;
165
+ let activeFlush: Promise<void> | null = null;
166
+ let flushTimer: ReturnType<typeof setInterval> | null = null;
167
+
168
+ async function flushBuffer(): Promise<void> {
169
+ if (fd == null || buffer.length === 0) return;
170
+
171
+ const data = buffer;
172
+ buffer = "";
173
+ try {
174
+ asyncOptions.writeSync(fd, encoder.encode(data));
175
+ await asyncOptions.flush(fd);
94
176
  lastFlushTimestamp = Date.now();
177
+ } catch {
178
+ // Silently ignore errors in non-blocking mode
95
179
  }
96
180
  }
97
181
 
98
- const sink: Sink & Disposable = (record: LogRecord) => {
99
- if (fd == null) fd = options.openSync(path);
182
+ function scheduleFlush(): void {
183
+ if (activeFlush || disposed) return;
184
+
185
+ activeFlush = flushBuffer().finally(() => {
186
+ activeFlush = null;
187
+ });
188
+ }
189
+
190
+ function startFlushTimer(): void {
191
+ if (flushTimer !== null || disposed) return;
192
+
193
+ flushTimer = setInterval(() => {
194
+ scheduleFlush();
195
+ }, flushInterval);
196
+ }
197
+
198
+ const nonBlockingSink: Sink & AsyncDisposable = (record: LogRecord) => {
199
+ if (disposed) return;
200
+ if (fd == null) fd = asyncOptions.openSync(path);
100
201
  buffer += formatter(record);
101
202
 
102
203
  const shouldFlushBySize = buffer.length >= bufferSize;
@@ -104,16 +205,29 @@ export function getBaseFileSink<TFile>(
104
205
  (record.timestamp - lastFlushTimestamp) >= flushInterval;
105
206
 
106
207
  if (shouldFlushBySize || shouldFlushByTime) {
107
- flushBuffer();
208
+ scheduleFlush();
209
+ } else if (flushTimer === null && flushInterval > 0) {
210
+ startFlushTimer();
108
211
  }
109
212
  };
110
- sink[Symbol.dispose] = () => {
213
+
214
+ nonBlockingSink[Symbol.asyncDispose] = async () => {
215
+ disposed = true;
216
+ if (flushTimer !== null) {
217
+ clearInterval(flushTimer);
218
+ flushTimer = null;
219
+ }
220
+ await flushBuffer();
111
221
  if (fd !== null) {
112
- flushBuffer();
113
- options.closeSync(fd);
222
+ try {
223
+ await asyncOptions.close(fd);
224
+ } catch {
225
+ // Writer might already be closed or errored
226
+ }
114
227
  }
115
228
  };
116
- return sink;
229
+
230
+ return nonBlockingSink;
117
231
  }
118
232
 
119
233
  /**
@@ -150,6 +264,27 @@ export interface RotatingFileSinkDriver<TFile> extends FileSinkDriver<TFile> {
150
264
  renameSync(oldPath: string, newPath: string): void;
151
265
  }
152
266
 
267
+ /**
268
+ * A platform-specific async rotating file sink driver.
269
+ * @since 1.0.0
270
+ */
271
+ export interface AsyncRotatingFileSinkDriver<TFile>
272
+ extends AsyncFileSinkDriver<TFile> {
273
+ /**
274
+ * Get the size of the file.
275
+ * @param path A path to the file.
276
+ * @returns The `size` of the file in bytes, in an object.
277
+ */
278
+ statSync(path: string): { size: number };
279
+
280
+ /**
281
+ * Rename a file.
282
+ * @param oldPath A path to the file to rename.
283
+ * @param newPath A path to be renamed to.
284
+ */
285
+ renameSync(oldPath: string, newPath: string): void;
286
+ }
287
+
153
288
  /**
154
289
  * Get a platform-independent rotating file sink.
155
290
  *
@@ -161,12 +296,23 @@ export interface RotatingFileSinkDriver<TFile> extends FileSinkDriver<TFile> {
161
296
  * @param path A path to the file to write to.
162
297
  * @param options The options for the sink and the file driver.
163
298
  * @returns A sink that writes to the file. The sink is also a disposable
164
- * object that closes the file when disposed.
299
+ * object that closes the file when disposed. If `nonBlocking` is enabled,
300
+ * returns a sink that also implements {@link AsyncDisposable}.
165
301
  */
166
302
  export function getBaseRotatingFileSink<TFile>(
167
303
  path: string,
168
304
  options: RotatingFileSinkOptions & RotatingFileSinkDriver<TFile>,
169
- ): Sink & Disposable {
305
+ ): Sink & Disposable;
306
+ export function getBaseRotatingFileSink<TFile>(
307
+ path: string,
308
+ options: RotatingFileSinkOptions & AsyncRotatingFileSinkDriver<TFile>,
309
+ ): Sink & AsyncDisposable;
310
+ export function getBaseRotatingFileSink<TFile>(
311
+ path: string,
312
+ options:
313
+ & RotatingFileSinkOptions
314
+ & (RotatingFileSinkDriver<TFile> | AsyncRotatingFileSinkDriver<TFile>),
315
+ ): Sink & (Disposable | AsyncDisposable) {
170
316
  const formatter = options.formatter ?? defaultTextFormatter;
171
317
  const encoder = options.encoder ?? new TextEncoder();
172
318
  const maxSize = options.maxSize ?? 1024 * 1024;
@@ -182,6 +328,7 @@ export function getBaseRotatingFileSink<TFile>(
182
328
  }
183
329
  let fd = options.openSync(path);
184
330
  let lastFlushTimestamp: number = Date.now();
331
+ let buffer: string = "";
185
332
 
186
333
  function shouldRollover(bytes: Uint8Array): boolean {
187
334
  return offset + bytes.length > maxSize;
@@ -202,20 +349,80 @@ export function getBaseRotatingFileSink<TFile>(
202
349
  fd = options.openSync(path);
203
350
  }
204
351
 
205
- function flushBuffer(): void {
206
- if (buffer.length > 0) {
207
- const bytes = encoder.encode(buffer);
208
- buffer = "";
352
+ if (!options.nonBlocking) {
353
+ // Blocking mode implementation
354
+ // deno-lint-ignore no-inner-declarations
355
+ function flushBuffer(): void {
356
+ if (buffer.length > 0) {
357
+ const bytes = encoder.encode(buffer);
358
+ buffer = "";
359
+ if (shouldRollover(bytes)) performRollover();
360
+ options.writeSync(fd, bytes);
361
+ options.flushSync(fd);
362
+ offset += bytes.length;
363
+ lastFlushTimestamp = Date.now();
364
+ }
365
+ }
366
+
367
+ const sink: Sink & Disposable = (record: LogRecord) => {
368
+ buffer += formatter(record);
369
+
370
+ const shouldFlushBySize = buffer.length >= bufferSize;
371
+ const shouldFlushByTime = flushInterval > 0 &&
372
+ (record.timestamp - lastFlushTimestamp) >= flushInterval;
373
+
374
+ if (shouldFlushBySize || shouldFlushByTime) {
375
+ flushBuffer();
376
+ }
377
+ };
378
+ sink[Symbol.dispose] = () => {
379
+ flushBuffer();
380
+ options.closeSync(fd);
381
+ };
382
+ return sink;
383
+ }
384
+
385
+ // Non-blocking mode implementation
386
+ const asyncOptions = options as AsyncRotatingFileSinkDriver<TFile>;
387
+ let disposed = false;
388
+ let activeFlush: Promise<void> | null = null;
389
+ let flushTimer: ReturnType<typeof setInterval> | null = null;
390
+
391
+ async function flushBuffer(): Promise<void> {
392
+ if (buffer.length === 0) return;
393
+
394
+ const data = buffer;
395
+ buffer = "";
396
+ try {
397
+ const bytes = encoder.encode(data);
209
398
  if (shouldRollover(bytes)) performRollover();
210
- options.writeSync(fd, bytes);
211
- options.flushSync(fd);
399
+ asyncOptions.writeSync(fd, bytes);
400
+ await asyncOptions.flush(fd);
212
401
  offset += bytes.length;
213
402
  lastFlushTimestamp = Date.now();
403
+ } catch {
404
+ // Silently ignore errors in non-blocking mode
214
405
  }
215
406
  }
216
407
 
217
- let buffer: string = "";
218
- const sink: Sink & Disposable = (record: LogRecord) => {
408
+ function scheduleFlush(): void {
409
+ if (activeFlush || disposed) return;
410
+
411
+ activeFlush = flushBuffer().finally(() => {
412
+ activeFlush = null;
413
+ });
414
+ }
415
+
416
+ function startFlushTimer(): void {
417
+ if (flushTimer !== null || disposed) return;
418
+
419
+ flushTimer = setInterval(() => {
420
+ scheduleFlush();
421
+ }, flushInterval);
422
+ }
423
+
424
+ const nonBlockingSink: Sink & AsyncDisposable = (record: LogRecord) => {
425
+ if (disposed) return;
219
426
  buffer += formatter(record);
220
427
 
221
428
  const shouldFlushBySize = buffer.length >= bufferSize;
@@ -223,12 +430,25 @@ export function getBaseRotatingFileSink<TFile>(
223
430
  (record.timestamp - lastFlushTimestamp) >= flushInterval;
224
431
 
225
432
  if (shouldFlushBySize || shouldFlushByTime) {
226
- flushBuffer();
433
+ scheduleFlush();
434
+ } else if (flushTimer === null && flushInterval > 0) {
435
+ startFlushTimer();
227
436
  }
228
437
  };
229
- sink[Symbol.dispose] = () => {
230
- flushBuffer();
231
- options.closeSync(fd);
438
+
439
+ nonBlockingSink[Symbol.asyncDispose] = async () => {
440
+ disposed = true;
441
+ if (flushTimer !== null) {
442
+ clearInterval(flushTimer);
443
+ flushTimer = null;
444
+ }
445
+ await flushBuffer();
446
+ try {
447
+ await asyncOptions.close(fd);
448
+ } catch {
449
+ // Writer might already be closed or errored
450
+ }
232
451
  };
233
- return sink;
452
+
453
+ return nonBlockingSink;
234
454
  }
package/filesink.deno.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { Sink } from "@logtape/logtape";
2
2
  import {
3
+ type AsyncRotatingFileSinkDriver,
3
4
  type FileSinkOptions,
4
5
  getBaseFileSink,
5
6
  getBaseRotatingFileSink,
@@ -27,6 +28,20 @@ export const denoDriver: RotatingFileSinkDriver<Deno.FsFile> = {
27
28
  renameSync: globalThis?.Deno.renameSync,
28
29
  };
29
30
 
31
+ /**
32
+ * A Deno-specific async file sink driver.
33
+ * @since 1.0.0
34
+ */
35
+ export const denoAsyncDriver: AsyncRotatingFileSinkDriver<Deno.FsFile> = {
36
+ ...denoDriver,
37
+ async flush(fd) {
38
+ await fd.sync();
39
+ },
40
+ close(fd) {
41
+ return Promise.resolve(fd.close());
42
+ },
43
+ };
44
+
30
45
  /**
31
46
  * Get a file sink.
32
47
  *
@@ -35,12 +50,24 @@ export const denoDriver: RotatingFileSinkDriver<Deno.FsFile> = {
35
50
  * @param path A path to the file to write to.
36
51
  * @param options The options for the sink.
37
52
  * @returns A sink that writes to the file. The sink is also a disposable
38
- * object that closes the file when disposed.
53
+ * object that closes the file when disposed. If `nonBlocking` is enabled,
54
+ * returns a sink that also implements {@link AsyncDisposable}.
39
55
  */
56
+ export function getFileSink(
57
+ path: string,
58
+ options?: FileSinkOptions,
59
+ ): Sink & Disposable;
60
+ export function getFileSink(
61
+ path: string,
62
+ options: FileSinkOptions & { nonBlocking: true },
63
+ ): Sink & AsyncDisposable;
40
64
  export function getFileSink(
41
65
  path: string,
42
66
  options: FileSinkOptions = {},
43
- ): Sink & Disposable {
67
+ ): Sink & (Disposable | AsyncDisposable) {
68
+ if (options.nonBlocking) {
69
+ return getBaseFileSink(path, { ...options, ...denoAsyncDriver });
70
+ }
44
71
  return getBaseFileSink(path, { ...options, ...denoDriver });
45
72
  }
46
73
 
@@ -57,12 +84,24 @@ export function getFileSink(
57
84
  * @param path A path to the file to write to.
58
85
  * @param options The options for the sink and the file driver.
59
86
  * @returns A sink that writes to the file. The sink is also a disposable
60
- * object that closes the file when disposed.
87
+ * object that closes the file when disposed. If `nonBlocking` is enabled,
88
+ * returns a sink that also implements {@link AsyncDisposable}.
61
89
  */
90
+ export function getRotatingFileSink(
91
+ path: string,
92
+ options?: RotatingFileSinkOptions,
93
+ ): Sink & Disposable;
94
+ export function getRotatingFileSink(
95
+ path: string,
96
+ options: RotatingFileSinkOptions & { nonBlocking: true },
97
+ ): Sink & AsyncDisposable;
62
98
  export function getRotatingFileSink(
63
99
  path: string,
64
100
  options: RotatingFileSinkOptions = {},
65
- ): Sink & Disposable {
101
+ ): Sink & (Disposable | AsyncDisposable) {
102
+ if (options.nonBlocking) {
103
+ return getBaseRotatingFileSink(path, { ...options, ...denoAsyncDriver });
104
+ }
66
105
  return getBaseRotatingFileSink(path, { ...options, ...denoDriver });
67
106
  }
68
107
 
package/filesink.jsr.ts CHANGED
@@ -4,7 +4,10 @@ import type {
4
4
  RotatingFileSinkOptions,
5
5
  } from "./filesink.base.ts";
6
6
 
7
- const filesink: Omit<typeof import("./filesink.deno.ts"), "denoDriver"> =
7
+ const filesink: Omit<
8
+ typeof import("./filesink.deno.ts"),
9
+ "denoDriver" | "denoAsyncDriver"
10
+ > =
8
11
  // dnt-shim-ignore
9
12
  await ("Deno" in globalThis
10
13
  ? import("./filesink.deno.ts")
@@ -18,13 +21,24 @@ const filesink: Omit<typeof import("./filesink.deno.ts"), "denoDriver"> =
18
21
  * @param path A path to the file to write to.
19
22
  * @param options The options for the sink.
20
23
  * @returns A sink that writes to the file. The sink is also a disposable
21
- * object that closes the file when disposed.
24
+ * object that closes the file when disposed. If `nonBlocking` is enabled,
25
+ * returns a sink that also implements {@link AsyncDisposable}.
22
26
  */
27
+ export function getFileSink(
28
+ path: string,
29
+ options?: FileSinkOptions,
30
+ ): Sink & Disposable;
31
+ export function getFileSink(
32
+ path: string,
33
+ options: FileSinkOptions & { nonBlocking: true },
34
+ ): Sink & AsyncDisposable;
23
35
  export function getFileSink(
24
36
  path: string,
25
37
  options: FileSinkOptions = {},
26
- ): Sink & Disposable {
27
- return filesink.getFileSink(path, options);
38
+ ): Sink & (Disposable | AsyncDisposable) {
39
+ return filesink.getFileSink(path, options) as
40
+ & Sink
41
+ & (Disposable | AsyncDisposable);
28
42
  }
29
43
 
30
44
  /**
@@ -40,13 +54,24 @@ export function getFileSink(
40
54
  * @param path A path to the file to write to.
41
55
  * @param options The options for the sink and the file driver.
42
56
  * @returns A sink that writes to the file. The sink is also a disposable
43
- * object that closes the file when disposed.
57
+ * object that closes the file when disposed. If `nonBlocking` is enabled,
58
+ * returns a sink that also implements {@link AsyncDisposable}.
44
59
  */
60
+ export function getRotatingFileSink(
61
+ path: string,
62
+ options?: RotatingFileSinkOptions,
63
+ ): Sink & Disposable;
64
+ export function getRotatingFileSink(
65
+ path: string,
66
+ options: RotatingFileSinkOptions & { nonBlocking: true },
67
+ ): Sink & AsyncDisposable;
45
68
  export function getRotatingFileSink(
46
69
  path: string,
47
70
  options: RotatingFileSinkOptions = {},
48
- ): Sink & Disposable {
49
- return filesink.getRotatingFileSink(path, options);
71
+ ): Sink & (Disposable | AsyncDisposable) {
72
+ return filesink.getRotatingFileSink(path, options) as
73
+ & Sink
74
+ & (Disposable | AsyncDisposable);
50
75
  }
51
76
 
52
77
  // cSpell: ignore filesink
package/filesink.node.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import type { Sink } from "@logtape/logtape";
2
2
  import fs from "node:fs";
3
+ import { promisify } from "node:util";
3
4
  import {
5
+ type AsyncRotatingFileSinkDriver,
4
6
  type FileSinkOptions,
5
7
  getBaseFileSink,
6
8
  getBaseRotatingFileSink,
@@ -22,6 +24,16 @@ export const nodeDriver: RotatingFileSinkDriver<number | void> = {
22
24
  renameSync: fs.renameSync,
23
25
  };
24
26
 
27
+ /**
28
+ * A Node.js-specific async file sink driver.
29
+ * @since 1.0.0
30
+ */
31
+ export const nodeAsyncDriver: AsyncRotatingFileSinkDriver<number | void> = {
32
+ ...nodeDriver,
33
+ flush: promisify(fs.fsync),
34
+ close: promisify(fs.close),
35
+ };
36
+
25
37
  /**
26
38
  * Get a file sink.
27
39
  *
@@ -30,12 +42,24 @@ export const nodeDriver: RotatingFileSinkDriver<number | void> = {
30
42
  * @param path A path to the file to write to.
31
43
  * @param options The options for the sink.
32
44
  * @returns A sink that writes to the file. The sink is also a disposable
33
- * object that closes the file when disposed.
45
+ * object that closes the file when disposed. If `nonBlocking` is enabled,
46
+ * returns a sink that also implements {@link AsyncDisposable}.
34
47
  */
48
+ export function getFileSink(
49
+ path: string,
50
+ options?: FileSinkOptions,
51
+ ): Sink & Disposable;
52
+ export function getFileSink(
53
+ path: string,
54
+ options: FileSinkOptions & { nonBlocking: true },
55
+ ): Sink & AsyncDisposable;
35
56
  export function getFileSink(
36
57
  path: string,
37
58
  options: FileSinkOptions = {},
38
- ): Sink & Disposable {
59
+ ): Sink & (Disposable | AsyncDisposable) {
60
+ if (options.nonBlocking) {
61
+ return getBaseFileSink(path, { ...options, ...nodeAsyncDriver });
62
+ }
39
63
  return getBaseFileSink(path, { ...options, ...nodeDriver });
40
64
  }
41
65
 
@@ -52,12 +76,24 @@ export function getFileSink(
52
76
  * @param path A path to the file to write to.
53
77
  * @param options The options for the sink and the file driver.
54
78
  * @returns A sink that writes to the file. The sink is also a disposable
55
- * object that closes the file when disposed.
79
+ * object that closes the file when disposed. If `nonBlocking` is enabled,
80
+ * returns a sink that also implements {@link AsyncDisposable}.
56
81
  */
82
+ export function getRotatingFileSink(
83
+ path: string,
84
+ options?: RotatingFileSinkOptions,
85
+ ): Sink & Disposable;
86
+ export function getRotatingFileSink(
87
+ path: string,
88
+ options: RotatingFileSinkOptions & { nonBlocking: true },
89
+ ): Sink & AsyncDisposable;
57
90
  export function getRotatingFileSink(
58
91
  path: string,
59
92
  options: RotatingFileSinkOptions = {},
60
- ): Sink & Disposable {
93
+ ): Sink & (Disposable | AsyncDisposable) {
94
+ if (options.nonBlocking) {
95
+ return getBaseRotatingFileSink(path, { ...options, ...nodeAsyncDriver });
96
+ }
61
97
  return getBaseRotatingFileSink(path, { ...options, ...nodeDriver });
62
98
  }
63
99