@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/deno.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logtape/file",
3
- "version": "1.0.0-dev.236+0c0f47bf",
3
+ "version": "1.0.0-dev.237+0615301b",
4
4
  "license": "MIT",
5
5
  "exports": "./mod.ts",
6
6
  "imports": {
@@ -2,15 +2,6 @@ const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
2
2
  const __logtape_logtape = require_rolldown_runtime.__toESM(require("@logtape/logtape"));
3
3
 
4
4
  //#region filesink.base.ts
5
- /**
6
- * Get a platform-independent file sink.
7
- *
8
- * @typeParam TFile The type of the file descriptor.
9
- * @param path A path to the file to write to.
10
- * @param options The options for the sink and the file driver.
11
- * @returns A sink that writes to the file. The sink is also a disposable
12
- * object that closes the file when disposed.
13
- */
14
5
  function getBaseFileSink(path, options) {
15
6
  const formatter = options.formatter ?? __logtape_logtape.defaultTextFormatter;
16
7
  const encoder = options.encoder ?? new TextEncoder();
@@ -19,43 +10,79 @@ function getBaseFileSink(path, options) {
19
10
  let fd = options.lazy ? null : options.openSync(path);
20
11
  let buffer = "";
21
12
  let lastFlushTimestamp = Date.now();
22
- function flushBuffer() {
23
- if (fd == null) return;
24
- if (buffer.length > 0) {
25
- options.writeSync(fd, encoder.encode(buffer));
26
- buffer = "";
27
- options.flushSync(fd);
28
- lastFlushTimestamp = Date.now();
13
+ if (!options.nonBlocking) {
14
+ function flushBuffer$1() {
15
+ if (fd == null) return;
16
+ if (buffer.length > 0) {
17
+ options.writeSync(fd, encoder.encode(buffer));
18
+ buffer = "";
19
+ options.flushSync(fd);
20
+ lastFlushTimestamp = Date.now();
21
+ }
29
22
  }
23
+ const sink = (record) => {
24
+ if (fd == null) fd = options.openSync(path);
25
+ buffer += formatter(record);
26
+ const shouldFlushBySize = buffer.length >= bufferSize;
27
+ const shouldFlushByTime = flushInterval > 0 && record.timestamp - lastFlushTimestamp >= flushInterval;
28
+ if (shouldFlushBySize || shouldFlushByTime) flushBuffer$1();
29
+ };
30
+ sink[Symbol.dispose] = () => {
31
+ if (fd !== null) {
32
+ flushBuffer$1();
33
+ options.closeSync(fd);
34
+ }
35
+ };
36
+ return sink;
37
+ }
38
+ const asyncOptions = options;
39
+ let disposed = false;
40
+ let activeFlush = null;
41
+ let flushTimer = null;
42
+ async function flushBuffer() {
43
+ if (fd == null || buffer.length === 0) return;
44
+ const data = buffer;
45
+ buffer = "";
46
+ try {
47
+ asyncOptions.writeSync(fd, encoder.encode(data));
48
+ await asyncOptions.flush(fd);
49
+ lastFlushTimestamp = Date.now();
50
+ } catch {}
51
+ }
52
+ function scheduleFlush() {
53
+ if (activeFlush || disposed) return;
54
+ activeFlush = flushBuffer().finally(() => {
55
+ activeFlush = null;
56
+ });
30
57
  }
31
- const sink = (record) => {
32
- if (fd == null) fd = options.openSync(path);
58
+ function startFlushTimer() {
59
+ if (flushTimer !== null || disposed) return;
60
+ flushTimer = setInterval(() => {
61
+ scheduleFlush();
62
+ }, flushInterval);
63
+ }
64
+ const nonBlockingSink = (record) => {
65
+ if (disposed) return;
66
+ if (fd == null) fd = asyncOptions.openSync(path);
33
67
  buffer += formatter(record);
34
68
  const shouldFlushBySize = buffer.length >= bufferSize;
35
69
  const shouldFlushByTime = flushInterval > 0 && record.timestamp - lastFlushTimestamp >= flushInterval;
36
- if (shouldFlushBySize || shouldFlushByTime) flushBuffer();
70
+ if (shouldFlushBySize || shouldFlushByTime) scheduleFlush();
71
+ else if (flushTimer === null && flushInterval > 0) startFlushTimer();
37
72
  };
38
- sink[Symbol.dispose] = () => {
39
- if (fd !== null) {
40
- flushBuffer();
41
- options.closeSync(fd);
73
+ nonBlockingSink[Symbol.asyncDispose] = async () => {
74
+ disposed = true;
75
+ if (flushTimer !== null) {
76
+ clearInterval(flushTimer);
77
+ flushTimer = null;
42
78
  }
79
+ await flushBuffer();
80
+ if (fd !== null) try {
81
+ await asyncOptions.close(fd);
82
+ } catch {}
43
83
  };
44
- return sink;
84
+ return nonBlockingSink;
45
85
  }
46
- /**
47
- * Get a platform-independent rotating file sink.
48
- *
49
- * This sink writes log records to a file, and rotates the file when it reaches
50
- * the `maxSize`. The rotated files are named with the original file name
51
- * followed by a dot and a number, starting from 1. The number is incremented
52
- * for each rotation, and the maximum number of files to keep is `maxFiles`.
53
- *
54
- * @param path A path to the file to write to.
55
- * @param options The options for the sink and the file driver.
56
- * @returns A sink that writes to the file. The sink is also a disposable
57
- * object that closes the file when disposed.
58
- */
59
86
  function getBaseRotatingFileSink(path, options) {
60
87
  const formatter = options.formatter ?? __logtape_logtape.defaultTextFormatter;
61
88
  const encoder = options.encoder ?? new TextEncoder();
@@ -70,6 +97,7 @@ function getBaseRotatingFileSink(path, options) {
70
97
  } catch {}
71
98
  let fd = options.openSync(path);
72
99
  let lastFlushTimestamp = Date.now();
100
+ let buffer = "";
73
101
  function shouldRollover(bytes) {
74
102
  return offset + bytes.length > maxSize;
75
103
  }
@@ -86,29 +114,79 @@ function getBaseRotatingFileSink(path, options) {
86
114
  offset = 0;
87
115
  fd = options.openSync(path);
88
116
  }
89
- function flushBuffer() {
90
- if (buffer.length > 0) {
91
- const bytes = encoder.encode(buffer);
92
- buffer = "";
117
+ if (!options.nonBlocking) {
118
+ function flushBuffer$1() {
119
+ if (buffer.length > 0) {
120
+ const bytes = encoder.encode(buffer);
121
+ buffer = "";
122
+ if (shouldRollover(bytes)) performRollover();
123
+ options.writeSync(fd, bytes);
124
+ options.flushSync(fd);
125
+ offset += bytes.length;
126
+ lastFlushTimestamp = Date.now();
127
+ }
128
+ }
129
+ const sink = (record) => {
130
+ buffer += formatter(record);
131
+ const shouldFlushBySize = buffer.length >= bufferSize;
132
+ const shouldFlushByTime = flushInterval > 0 && record.timestamp - lastFlushTimestamp >= flushInterval;
133
+ if (shouldFlushBySize || shouldFlushByTime) flushBuffer$1();
134
+ };
135
+ sink[Symbol.dispose] = () => {
136
+ flushBuffer$1();
137
+ options.closeSync(fd);
138
+ };
139
+ return sink;
140
+ }
141
+ const asyncOptions = options;
142
+ let disposed = false;
143
+ let activeFlush = null;
144
+ let flushTimer = null;
145
+ async function flushBuffer() {
146
+ if (buffer.length === 0) return;
147
+ const data = buffer;
148
+ buffer = "";
149
+ try {
150
+ const bytes = encoder.encode(data);
93
151
  if (shouldRollover(bytes)) performRollover();
94
- options.writeSync(fd, bytes);
95
- options.flushSync(fd);
152
+ asyncOptions.writeSync(fd, bytes);
153
+ await asyncOptions.flush(fd);
96
154
  offset += bytes.length;
97
155
  lastFlushTimestamp = Date.now();
98
- }
156
+ } catch {}
99
157
  }
100
- let buffer = "";
101
- const sink = (record) => {
158
+ function scheduleFlush() {
159
+ if (activeFlush || disposed) return;
160
+ activeFlush = flushBuffer().finally(() => {
161
+ activeFlush = null;
162
+ });
163
+ }
164
+ function startFlushTimer() {
165
+ if (flushTimer !== null || disposed) return;
166
+ flushTimer = setInterval(() => {
167
+ scheduleFlush();
168
+ }, flushInterval);
169
+ }
170
+ const nonBlockingSink = (record) => {
171
+ if (disposed) return;
102
172
  buffer += formatter(record);
103
173
  const shouldFlushBySize = buffer.length >= bufferSize;
104
174
  const shouldFlushByTime = flushInterval > 0 && record.timestamp - lastFlushTimestamp >= flushInterval;
105
- if (shouldFlushBySize || shouldFlushByTime) flushBuffer();
175
+ if (shouldFlushBySize || shouldFlushByTime) scheduleFlush();
176
+ else if (flushTimer === null && flushInterval > 0) startFlushTimer();
106
177
  };
107
- sink[Symbol.dispose] = () => {
108
- flushBuffer();
109
- options.closeSync(fd);
178
+ nonBlockingSink[Symbol.asyncDispose] = async () => {
179
+ disposed = true;
180
+ if (flushTimer !== null) {
181
+ clearInterval(flushTimer);
182
+ flushTimer = null;
183
+ }
184
+ await flushBuffer();
185
+ try {
186
+ await asyncOptions.close(fd);
187
+ } catch {}
110
188
  };
111
- return sink;
189
+ return nonBlockingSink;
112
190
  }
113
191
 
114
192
  //#endregion
@@ -26,6 +26,15 @@ type FileSinkOptions = StreamSinkOptions & {
26
26
  * @since 0.12.0
27
27
  */
28
28
  flushInterval?: number;
29
+ /**
30
+ * Enable non-blocking mode with background flushing.
31
+ * When enabled, flush operations are performed asynchronously to prevent
32
+ * blocking the main thread during file I/O operations.
33
+ *
34
+ * @default `false`
35
+ * @since 1.0.0
36
+ */
37
+ nonBlocking?: boolean;
29
38
  };
30
39
  /**
31
40
  * A platform-specific file sink driver.
@@ -54,6 +63,23 @@ interface FileSinkDriver<TFile> {
54
63
  */
55
64
  closeSync(fd: TFile): void;
56
65
  }
66
+ /**
67
+ * A platform-specific async file sink driver.
68
+ * @typeParam TFile The type of the file descriptor.
69
+ * @since 1.0.0
70
+ */
71
+ interface AsyncFileSinkDriver<TFile> extends FileSinkDriver<TFile> {
72
+ /**
73
+ * Asynchronously flush the file to ensure that all data is written to the disk.
74
+ * @param fd The file descriptor.
75
+ */
76
+ flush(fd: TFile): Promise<void>;
77
+ /**
78
+ * Asynchronously close the file.
79
+ * @param fd The file descriptor.
80
+ */
81
+ close(fd: TFile): Promise<void>;
82
+ }
57
83
  /**
58
84
  * Get a platform-independent file sink.
59
85
  *
@@ -61,7 +87,8 @@ interface FileSinkDriver<TFile> {
61
87
  * @param path A path to the file to write to.
62
88
  * @param options The options for the sink and the file driver.
63
89
  * @returns A sink that writes to the file. The sink is also a disposable
64
- * object that closes the file when disposed.
90
+ * object that closes the file when disposed. If `nonBlocking` is enabled,
91
+ * returns a sink that also implements {@link AsyncDisposable}.
65
92
  */
66
93
 
67
94
  /**
@@ -96,6 +123,26 @@ interface RotatingFileSinkDriver<TFile> extends FileSinkDriver<TFile> {
96
123
  */
97
124
  renameSync(oldPath: string, newPath: string): void;
98
125
  }
126
+ /**
127
+ * A platform-specific async rotating file sink driver.
128
+ * @since 1.0.0
129
+ */
130
+ interface AsyncRotatingFileSinkDriver<TFile> extends AsyncFileSinkDriver<TFile> {
131
+ /**
132
+ * Get the size of the file.
133
+ * @param path A path to the file.
134
+ * @returns The `size` of the file in bytes, in an object.
135
+ */
136
+ statSync(path: string): {
137
+ size: number;
138
+ };
139
+ /**
140
+ * Rename a file.
141
+ * @param oldPath A path to the file to rename.
142
+ * @param newPath A path to be renamed to.
143
+ */
144
+ renameSync(oldPath: string, newPath: string): void;
145
+ }
99
146
  /**
100
147
  * Get a platform-independent rotating file sink.
101
148
  *
@@ -107,8 +154,9 @@ interface RotatingFileSinkDriver<TFile> extends FileSinkDriver<TFile> {
107
154
  * @param path A path to the file to write to.
108
155
  * @param options The options for the sink and the file driver.
109
156
  * @returns A sink that writes to the file. The sink is also a disposable
110
- * object that closes the file when disposed.
157
+ * object that closes the file when disposed. If `nonBlocking` is enabled,
158
+ * returns a sink that also implements {@link AsyncDisposable}.
111
159
  */
112
160
  //#endregion
113
- export { FileSinkDriver, FileSinkOptions, RotatingFileSinkDriver, RotatingFileSinkOptions };
161
+ export { AsyncRotatingFileSinkDriver, FileSinkDriver, FileSinkOptions, RotatingFileSinkDriver, RotatingFileSinkOptions };
114
162
  //# sourceMappingURL=filesink.base.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"filesink.base.d.cts","names":[],"sources":["../filesink.base.ts"],"sourcesContent":[],"mappings":";;;;;;AAUA;AA6BiB,KA7BL,eAAA,GAAkB,iBA6BC,GAAA;EAAA;;;EAYV,IAAS,CAAA,EAAA,OAAA;EAAU;;AAYnB;AA0DrB;;;;EAAqD,UAAA,CAAA,EAAA,MAAA;EAepC;;;;AAAoD;;;;;;;;;UAjGpD;;;;;0BAKS;;;;;;gBAOV,cAAc;;;;;gBAMd;;;;;gBAMA;;;;;;;;;;;;;;;UA0DC,uBAAA,SAAgC,KAAK;;;;;;;;;;;;;UAerC,sCAAsC,eAAe"}
1
+ {"version":3,"file":"filesink.base.d.cts","names":[],"sources":["../filesink.base.ts"],"sourcesContent":[],"mappings":";;;;;;AAUA;AAuCiB,KAvCL,eAAA,GAAkB,iBAuCC,GAAA;EAAA;;;EAYV,IAAS,CAAA,EAAA,OAAA;EAAU;;AAYnB;AAQrB;;;;EAKiB,UAAG,CAAA,EAAA,MAAA;EAAO;;;AALuC;AA0JlE;;;EAAqE,aAApB,CAAA,EAAA,MAAA;EAAI;AAerD;;;;AAAqE;AAoBrE;;EAA4C,WACd,CAAA,EAAA,OAAA;CAAK;AAAN;;;;UA9NZ;;;;;0BAKS;;;;;;gBAOV,cAAc;;;;;gBAMd;;;;;gBAMA;;;;;;;UAQC,mCAAmC,eAAe;;;;;YAKvD,QAAQ;;;;;YAMR,QAAQ;;;;;;;;;;;;;;;;UA+IH,uBAAA,SAAgC,KAAK;;;;;;;;;;;;;UAerC,sCAAsC,eAAe;;;;;;;;;;;;;;;;;;;;UAoBrD,2CACP,oBAAoB"}
@@ -26,6 +26,15 @@ type FileSinkOptions = StreamSinkOptions & {
26
26
  * @since 0.12.0
27
27
  */
28
28
  flushInterval?: number;
29
+ /**
30
+ * Enable non-blocking mode with background flushing.
31
+ * When enabled, flush operations are performed asynchronously to prevent
32
+ * blocking the main thread during file I/O operations.
33
+ *
34
+ * @default `false`
35
+ * @since 1.0.0
36
+ */
37
+ nonBlocking?: boolean;
29
38
  };
30
39
  /**
31
40
  * A platform-specific file sink driver.
@@ -54,6 +63,23 @@ interface FileSinkDriver<TFile> {
54
63
  */
55
64
  closeSync(fd: TFile): void;
56
65
  }
66
+ /**
67
+ * A platform-specific async file sink driver.
68
+ * @typeParam TFile The type of the file descriptor.
69
+ * @since 1.0.0
70
+ */
71
+ interface AsyncFileSinkDriver<TFile> extends FileSinkDriver<TFile> {
72
+ /**
73
+ * Asynchronously flush the file to ensure that all data is written to the disk.
74
+ * @param fd The file descriptor.
75
+ */
76
+ flush(fd: TFile): Promise<void>;
77
+ /**
78
+ * Asynchronously close the file.
79
+ * @param fd The file descriptor.
80
+ */
81
+ close(fd: TFile): Promise<void>;
82
+ }
57
83
  /**
58
84
  * Get a platform-independent file sink.
59
85
  *
@@ -61,7 +87,8 @@ interface FileSinkDriver<TFile> {
61
87
  * @param path A path to the file to write to.
62
88
  * @param options The options for the sink and the file driver.
63
89
  * @returns A sink that writes to the file. The sink is also a disposable
64
- * object that closes the file when disposed.
90
+ * object that closes the file when disposed. If `nonBlocking` is enabled,
91
+ * returns a sink that also implements {@link AsyncDisposable}.
65
92
  */
66
93
 
67
94
  /**
@@ -96,6 +123,26 @@ interface RotatingFileSinkDriver<TFile> extends FileSinkDriver<TFile> {
96
123
  */
97
124
  renameSync(oldPath: string, newPath: string): void;
98
125
  }
126
+ /**
127
+ * A platform-specific async rotating file sink driver.
128
+ * @since 1.0.0
129
+ */
130
+ interface AsyncRotatingFileSinkDriver<TFile> extends AsyncFileSinkDriver<TFile> {
131
+ /**
132
+ * Get the size of the file.
133
+ * @param path A path to the file.
134
+ * @returns The `size` of the file in bytes, in an object.
135
+ */
136
+ statSync(path: string): {
137
+ size: number;
138
+ };
139
+ /**
140
+ * Rename a file.
141
+ * @param oldPath A path to the file to rename.
142
+ * @param newPath A path to be renamed to.
143
+ */
144
+ renameSync(oldPath: string, newPath: string): void;
145
+ }
99
146
  /**
100
147
  * Get a platform-independent rotating file sink.
101
148
  *
@@ -107,8 +154,9 @@ interface RotatingFileSinkDriver<TFile> extends FileSinkDriver<TFile> {
107
154
  * @param path A path to the file to write to.
108
155
  * @param options The options for the sink and the file driver.
109
156
  * @returns A sink that writes to the file. The sink is also a disposable
110
- * object that closes the file when disposed.
157
+ * object that closes the file when disposed. If `nonBlocking` is enabled,
158
+ * returns a sink that also implements {@link AsyncDisposable}.
111
159
  */
112
160
  //#endregion
113
- export { FileSinkDriver, FileSinkOptions, RotatingFileSinkDriver, RotatingFileSinkOptions };
161
+ export { AsyncRotatingFileSinkDriver, FileSinkDriver, FileSinkOptions, RotatingFileSinkDriver, RotatingFileSinkOptions };
114
162
  //# sourceMappingURL=filesink.base.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"filesink.base.d.ts","names":[],"sources":["../filesink.base.ts"],"sourcesContent":[],"mappings":";;;;;;AAUA;AA6BiB,KA7BL,eAAA,GAAkB,iBA6BC,GAAA;EAAA;;;EAYV,IAAS,CAAA,EAAA,OAAA;EAAU;;AAYnB;AA0DrB;;;;EAAqD,UAAA,CAAA,EAAA,MAAA;EAepC;;;;AAAoD;;;;;;;;;UAjGpD;;;;;0BAKS;;;;;;gBAOV,cAAc;;;;;gBAMd;;;;;gBAMA;;;;;;;;;;;;;;;UA0DC,uBAAA,SAAgC,KAAK;;;;;;;;;;;;;UAerC,sCAAsC,eAAe"}
1
+ {"version":3,"file":"filesink.base.d.ts","names":[],"sources":["../filesink.base.ts"],"sourcesContent":[],"mappings":";;;;;;AAUA;AAuCiB,KAvCL,eAAA,GAAkB,iBAuCC,GAAA;EAAA;;;EAYV,IAAS,CAAA,EAAA,OAAA;EAAU;;AAYnB;AAQrB;;;;EAKiB,UAAG,CAAA,EAAA,MAAA;EAAO;;;AALuC;AA0JlE;;;EAAqE,aAApB,CAAA,EAAA,MAAA;EAAI;AAerD;;;;AAAqE;AAoBrE;;EAA4C,WACd,CAAA,EAAA,OAAA;CAAK;AAAN;;;;UA9NZ;;;;;0BAKS;;;;;;gBAOV,cAAc;;;;;gBAMd;;;;;gBAMA;;;;;;;UAQC,mCAAmC,eAAe;;;;;YAKvD,QAAQ;;;;;YAMR,QAAQ;;;;;;;;;;;;;;;;UA+IH,uBAAA,SAAgC,KAAK;;;;;;;;;;;;;UAerC,sCAAsC,eAAe;;;;;;;;;;;;;;;;;;;;UAoBrD,2CACP,oBAAoB"}
@@ -1,15 +1,6 @@
1
1
  import { defaultTextFormatter } from "@logtape/logtape";
2
2
 
3
3
  //#region filesink.base.ts
4
- /**
5
- * Get a platform-independent file sink.
6
- *
7
- * @typeParam TFile The type of the file descriptor.
8
- * @param path A path to the file to write to.
9
- * @param options The options for the sink and the file driver.
10
- * @returns A sink that writes to the file. The sink is also a disposable
11
- * object that closes the file when disposed.
12
- */
13
4
  function getBaseFileSink(path, options) {
14
5
  const formatter = options.formatter ?? defaultTextFormatter;
15
6
  const encoder = options.encoder ?? new TextEncoder();
@@ -18,43 +9,79 @@ function getBaseFileSink(path, options) {
18
9
  let fd = options.lazy ? null : options.openSync(path);
19
10
  let buffer = "";
20
11
  let lastFlushTimestamp = Date.now();
21
- function flushBuffer() {
22
- if (fd == null) return;
23
- if (buffer.length > 0) {
24
- options.writeSync(fd, encoder.encode(buffer));
25
- buffer = "";
26
- options.flushSync(fd);
27
- lastFlushTimestamp = Date.now();
12
+ if (!options.nonBlocking) {
13
+ function flushBuffer$1() {
14
+ if (fd == null) return;
15
+ if (buffer.length > 0) {
16
+ options.writeSync(fd, encoder.encode(buffer));
17
+ buffer = "";
18
+ options.flushSync(fd);
19
+ lastFlushTimestamp = Date.now();
20
+ }
28
21
  }
22
+ const sink = (record) => {
23
+ if (fd == null) fd = options.openSync(path);
24
+ buffer += formatter(record);
25
+ const shouldFlushBySize = buffer.length >= bufferSize;
26
+ const shouldFlushByTime = flushInterval > 0 && record.timestamp - lastFlushTimestamp >= flushInterval;
27
+ if (shouldFlushBySize || shouldFlushByTime) flushBuffer$1();
28
+ };
29
+ sink[Symbol.dispose] = () => {
30
+ if (fd !== null) {
31
+ flushBuffer$1();
32
+ options.closeSync(fd);
33
+ }
34
+ };
35
+ return sink;
36
+ }
37
+ const asyncOptions = options;
38
+ let disposed = false;
39
+ let activeFlush = null;
40
+ let flushTimer = null;
41
+ async function flushBuffer() {
42
+ if (fd == null || buffer.length === 0) return;
43
+ const data = buffer;
44
+ buffer = "";
45
+ try {
46
+ asyncOptions.writeSync(fd, encoder.encode(data));
47
+ await asyncOptions.flush(fd);
48
+ lastFlushTimestamp = Date.now();
49
+ } catch {}
50
+ }
51
+ function scheduleFlush() {
52
+ if (activeFlush || disposed) return;
53
+ activeFlush = flushBuffer().finally(() => {
54
+ activeFlush = null;
55
+ });
29
56
  }
30
- const sink = (record) => {
31
- if (fd == null) fd = options.openSync(path);
57
+ function startFlushTimer() {
58
+ if (flushTimer !== null || disposed) return;
59
+ flushTimer = setInterval(() => {
60
+ scheduleFlush();
61
+ }, flushInterval);
62
+ }
63
+ const nonBlockingSink = (record) => {
64
+ if (disposed) return;
65
+ if (fd == null) fd = asyncOptions.openSync(path);
32
66
  buffer += formatter(record);
33
67
  const shouldFlushBySize = buffer.length >= bufferSize;
34
68
  const shouldFlushByTime = flushInterval > 0 && record.timestamp - lastFlushTimestamp >= flushInterval;
35
- if (shouldFlushBySize || shouldFlushByTime) flushBuffer();
69
+ if (shouldFlushBySize || shouldFlushByTime) scheduleFlush();
70
+ else if (flushTimer === null && flushInterval > 0) startFlushTimer();
36
71
  };
37
- sink[Symbol.dispose] = () => {
38
- if (fd !== null) {
39
- flushBuffer();
40
- options.closeSync(fd);
72
+ nonBlockingSink[Symbol.asyncDispose] = async () => {
73
+ disposed = true;
74
+ if (flushTimer !== null) {
75
+ clearInterval(flushTimer);
76
+ flushTimer = null;
41
77
  }
78
+ await flushBuffer();
79
+ if (fd !== null) try {
80
+ await asyncOptions.close(fd);
81
+ } catch {}
42
82
  };
43
- return sink;
83
+ return nonBlockingSink;
44
84
  }
45
- /**
46
- * Get a platform-independent rotating file sink.
47
- *
48
- * This sink writes log records to a file, and rotates the file when it reaches
49
- * the `maxSize`. The rotated files are named with the original file name
50
- * followed by a dot and a number, starting from 1. The number is incremented
51
- * for each rotation, and the maximum number of files to keep is `maxFiles`.
52
- *
53
- * @param path A path to the file to write to.
54
- * @param options The options for the sink and the file driver.
55
- * @returns A sink that writes to the file. The sink is also a disposable
56
- * object that closes the file when disposed.
57
- */
58
85
  function getBaseRotatingFileSink(path, options) {
59
86
  const formatter = options.formatter ?? defaultTextFormatter;
60
87
  const encoder = options.encoder ?? new TextEncoder();
@@ -69,6 +96,7 @@ function getBaseRotatingFileSink(path, options) {
69
96
  } catch {}
70
97
  let fd = options.openSync(path);
71
98
  let lastFlushTimestamp = Date.now();
99
+ let buffer = "";
72
100
  function shouldRollover(bytes) {
73
101
  return offset + bytes.length > maxSize;
74
102
  }
@@ -85,29 +113,79 @@ function getBaseRotatingFileSink(path, options) {
85
113
  offset = 0;
86
114
  fd = options.openSync(path);
87
115
  }
88
- function flushBuffer() {
89
- if (buffer.length > 0) {
90
- const bytes = encoder.encode(buffer);
91
- buffer = "";
116
+ if (!options.nonBlocking) {
117
+ function flushBuffer$1() {
118
+ if (buffer.length > 0) {
119
+ const bytes = encoder.encode(buffer);
120
+ buffer = "";
121
+ if (shouldRollover(bytes)) performRollover();
122
+ options.writeSync(fd, bytes);
123
+ options.flushSync(fd);
124
+ offset += bytes.length;
125
+ lastFlushTimestamp = Date.now();
126
+ }
127
+ }
128
+ const sink = (record) => {
129
+ buffer += formatter(record);
130
+ const shouldFlushBySize = buffer.length >= bufferSize;
131
+ const shouldFlushByTime = flushInterval > 0 && record.timestamp - lastFlushTimestamp >= flushInterval;
132
+ if (shouldFlushBySize || shouldFlushByTime) flushBuffer$1();
133
+ };
134
+ sink[Symbol.dispose] = () => {
135
+ flushBuffer$1();
136
+ options.closeSync(fd);
137
+ };
138
+ return sink;
139
+ }
140
+ const asyncOptions = options;
141
+ let disposed = false;
142
+ let activeFlush = null;
143
+ let flushTimer = null;
144
+ async function flushBuffer() {
145
+ if (buffer.length === 0) return;
146
+ const data = buffer;
147
+ buffer = "";
148
+ try {
149
+ const bytes = encoder.encode(data);
92
150
  if (shouldRollover(bytes)) performRollover();
93
- options.writeSync(fd, bytes);
94
- options.flushSync(fd);
151
+ asyncOptions.writeSync(fd, bytes);
152
+ await asyncOptions.flush(fd);
95
153
  offset += bytes.length;
96
154
  lastFlushTimestamp = Date.now();
97
- }
155
+ } catch {}
98
156
  }
99
- let buffer = "";
100
- const sink = (record) => {
157
+ function scheduleFlush() {
158
+ if (activeFlush || disposed) return;
159
+ activeFlush = flushBuffer().finally(() => {
160
+ activeFlush = null;
161
+ });
162
+ }
163
+ function startFlushTimer() {
164
+ if (flushTimer !== null || disposed) return;
165
+ flushTimer = setInterval(() => {
166
+ scheduleFlush();
167
+ }, flushInterval);
168
+ }
169
+ const nonBlockingSink = (record) => {
170
+ if (disposed) return;
101
171
  buffer += formatter(record);
102
172
  const shouldFlushBySize = buffer.length >= bufferSize;
103
173
  const shouldFlushByTime = flushInterval > 0 && record.timestamp - lastFlushTimestamp >= flushInterval;
104
- if (shouldFlushBySize || shouldFlushByTime) flushBuffer();
174
+ if (shouldFlushBySize || shouldFlushByTime) scheduleFlush();
175
+ else if (flushTimer === null && flushInterval > 0) startFlushTimer();
105
176
  };
106
- sink[Symbol.dispose] = () => {
107
- flushBuffer();
108
- options.closeSync(fd);
177
+ nonBlockingSink[Symbol.asyncDispose] = async () => {
178
+ disposed = true;
179
+ if (flushTimer !== null) {
180
+ clearInterval(flushTimer);
181
+ flushTimer = null;
182
+ }
183
+ await flushBuffer();
184
+ try {
185
+ await asyncOptions.close(fd);
186
+ } catch {}
109
187
  };
110
- return sink;
188
+ return nonBlockingSink;
111
189
  }
112
190
 
113
191
  //#endregion