@logtape/file 2.2.0-dev.676 → 2.2.0-dev.682

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.
@@ -2,6 +2,9 @@ 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 src/filesink.base.ts
5
+ function isMetaLoggerRecord(record) {
6
+ return record.category.length === 2 && record.category[0] === "logtape" && record.category[1] === "meta";
7
+ }
5
8
  /**
6
9
  * Adaptive flush strategy that dynamically adjusts buffer thresholds
7
10
  * based on recent flush patterns for optimal performance.
@@ -13,8 +16,10 @@ var AdaptiveFlushStrategy = class {
13
16
  avgFlushInterval;
14
17
  maxHistorySize = 10;
15
18
  baseThreshold;
19
+ baseInterval;
16
20
  constructor(baseThreshold, baseInterval) {
17
21
  this.baseThreshold = baseThreshold;
22
+ this.baseInterval = baseInterval;
18
23
  this.avgFlushSize = baseThreshold;
19
24
  this.avgFlushInterval = baseInterval;
20
25
  }
@@ -53,7 +58,8 @@ var AdaptiveFlushStrategy = class {
53
58
  return Math.max(Math.min(4096, this.baseThreshold / 2), Math.min(64 * 1024, this.baseThreshold * adaptiveFactor));
54
59
  }
55
60
  calculateAdaptiveInterval() {
56
- if (this.avgFlushInterval <= 0) return 0;
61
+ if (this.baseInterval <= 0) return 0;
62
+ if (this.avgFlushInterval <= 0) return this.baseInterval;
57
63
  if (this.recentFlushTimes.length < 3) return this.avgFlushInterval;
58
64
  const variance = this.calculateVariance(this.recentFlushTimes);
59
65
  const stabilityFactor = Math.min(2, Math.max(.5, 1e3 / variance));
@@ -203,18 +209,22 @@ function getBaseFileSink(path, options) {
203
209
  }
204
210
  const sink = (record) => {
205
211
  if (fd == null) fd = options.openSync(path);
206
- if (byteBuffer.isEmpty() && bufferSize === 8192) {
207
- const formattedRecord$1 = formatter(record);
208
- const encodedRecord$1 = encoder.encode(formattedRecord$1);
209
- if (encodedRecord$1.length < 200) {
210
- options.writeSync(fd, encodedRecord$1);
212
+ let formattedRecord;
213
+ let encodedRecord;
214
+ if (byteBuffer.isEmpty()) {
215
+ formattedRecord = formatter(record);
216
+ encodedRecord = encoder.encode(formattedRecord);
217
+ if (encodedRecord.length < 200) {
218
+ options.writeSync(fd, encodedRecord);
211
219
  options.flushSync(fd);
212
- lastFlushTimestamp = Date.now();
220
+ const currentTime = Date.now();
221
+ adaptiveStrategy.recordFlush(encodedRecord.length, currentTime - lastFlushTimestamp);
222
+ lastFlushTimestamp = currentTime;
213
223
  return;
214
224
  }
215
225
  }
216
- const formattedRecord = formatter(record);
217
- const encodedRecord = encoder.encode(formattedRecord);
226
+ formattedRecord ??= formatter(record);
227
+ encodedRecord ??= encoder.encode(formattedRecord);
218
228
  byteBuffer.append(encodedRecord);
219
229
  if (bufferSize <= 0) flushBuffer$1();
220
230
  else {
@@ -236,24 +246,53 @@ function getBaseFileSink(path, options) {
236
246
  let disposed = false;
237
247
  let activeFlush = null;
238
248
  let flushTimer = null;
249
+ let bufferedNonMetaRecord = false;
250
+ function reportFlushError(error) {
251
+ try {
252
+ (0, __logtape_logtape.getLogger)(["logtape", "meta"]).warn("Non-blocking file sink flush failed for {path}: {error}", {
253
+ error,
254
+ path
255
+ });
256
+ } catch {}
257
+ }
239
258
  async function flushBuffer() {
240
259
  if (fd == null || byteBuffer.isEmpty()) return;
241
260
  const flushSize = byteBuffer.size();
242
261
  const currentTime = Date.now();
243
262
  const timeSinceLastFlush = currentTime - lastFlushTimestamp;
244
263
  const chunks = byteBuffer.flush();
264
+ const suppressErrorReport = !bufferedNonMetaRecord;
265
+ bufferedNonMetaRecord = false;
245
266
  try {
246
267
  if (asyncOptions.writeMany && chunks.length > 1) await asyncOptions.writeMany(fd, chunks);
247
268
  else for (const chunk of chunks) asyncOptions.writeSync(fd, chunk);
248
269
  await asyncOptions.flush(fd);
249
270
  adaptiveStrategy.recordFlush(flushSize, timeSinceLastFlush);
250
271
  lastFlushTimestamp = currentTime;
251
- } catch {}
272
+ } catch (error) {
273
+ if (!suppressErrorReport) reportFlushError(error);
274
+ }
252
275
  }
253
276
  function scheduleFlush() {
254
277
  if (activeFlush || disposed) return;
255
278
  activeFlush = flushBuffer().finally(() => {
256
279
  activeFlush = null;
280
+ if (!disposed && !byteBuffer.isEmpty()) scheduleFlush();
281
+ });
282
+ }
283
+ function scheduleDirectFlush(flushSize, suppressErrorReport) {
284
+ if (activeFlush || disposed || fd == null) return;
285
+ const flushFd = fd;
286
+ const startedAt = Date.now();
287
+ const timeSinceLastFlush = startedAt - lastFlushTimestamp;
288
+ activeFlush = Promise.resolve().then(() => asyncOptions.flush(flushFd)).then(() => {
289
+ adaptiveStrategy.recordFlush(flushSize, timeSinceLastFlush);
290
+ lastFlushTimestamp = Date.now();
291
+ }).catch((error) => {
292
+ if (!suppressErrorReport) reportFlushError(error);
293
+ }).finally(() => {
294
+ activeFlush = null;
295
+ if (!disposed && !byteBuffer.isEmpty()) scheduleFlush();
257
296
  });
258
297
  }
259
298
  function startFlushTimer() {
@@ -265,19 +304,21 @@ function getBaseFileSink(path, options) {
265
304
  const nonBlockingSink = (record) => {
266
305
  if (disposed) return;
267
306
  if (fd == null) fd = asyncOptions.openSync(path);
268
- if (byteBuffer.isEmpty() && !activeFlush && bufferSize === 8192) {
269
- const formattedRecord$1 = formatter(record);
270
- const encodedRecord$1 = encoder.encode(formattedRecord$1);
271
- if (encodedRecord$1.length < 200) {
272
- asyncOptions.writeSync(fd, encodedRecord$1);
273
- scheduleFlush();
274
- lastFlushTimestamp = Date.now();
307
+ let formattedRecord;
308
+ let encodedRecord;
309
+ if (byteBuffer.isEmpty() && !activeFlush) {
310
+ formattedRecord = formatter(record);
311
+ encodedRecord = encoder.encode(formattedRecord);
312
+ if (encodedRecord.length < 200) {
313
+ asyncOptions.writeSync(fd, encodedRecord);
314
+ scheduleDirectFlush(encodedRecord.length, isMetaLoggerRecord(record));
275
315
  return;
276
316
  }
277
317
  }
278
- const formattedRecord = formatter(record);
279
- const encodedRecord = encoder.encode(formattedRecord);
318
+ formattedRecord ??= formatter(record);
319
+ encodedRecord ??= encoder.encode(formattedRecord);
280
320
  byteBuffer.append(encodedRecord);
321
+ bufferedNonMetaRecord ||= !isMetaLoggerRecord(record);
281
322
  if (bufferSize <= 0) scheduleFlush();
282
323
  else {
283
324
  const timeSinceLastFlush = record.timestamp - lastFlushTimestamp;
@@ -292,6 +333,7 @@ function getBaseFileSink(path, options) {
292
333
  clearInterval(flushTimer);
293
334
  flushTimer = null;
294
335
  }
336
+ if (activeFlush !== null) await activeFlush;
295
337
  await flushBuffer();
296
338
  if (fd !== null) try {
297
339
  await asyncOptions.close(fd);
@@ -300,6 +342,11 @@ function getBaseFileSink(path, options) {
300
342
  };
301
343
  return nonBlockingSink;
302
344
  }
345
+ function isFileNotFoundError(error) {
346
+ if (!(error instanceof Error)) return false;
347
+ const errorWithCode = error;
348
+ return errorWithCode.code === "ENOENT" || error.name === "NotFound";
349
+ }
303
350
  function getBaseRotatingFileSink(path, options) {
304
351
  const formatter = options.formatter ?? __logtape_logtape.defaultTextFormatter;
305
352
  const encoder = options.encoder ?? new TextEncoder();
@@ -307,6 +354,7 @@ function getBaseRotatingFileSink(path, options) {
307
354
  const maxFiles = options.maxFiles ?? 5;
308
355
  const bufferSize = options.bufferSize ?? 1024 * 8;
309
356
  const flushInterval = options.flushInterval ?? 5e3;
357
+ if (maxFiles <= 0 && options.unlinkSync == null) throw new TypeError("maxFiles <= 0 requires unlinkSync support.");
310
358
  let offset = 0;
311
359
  try {
312
360
  const stat = options.statSync(path);
@@ -319,6 +367,27 @@ function getBaseRotatingFileSink(path, options) {
319
367
  return offset + bytes.length > maxSize;
320
368
  }
321
369
  function performRollover() {
370
+ if (maxFiles <= 0) {
371
+ const unlinkSync = options.unlinkSync;
372
+ if (unlinkSync == null) return;
373
+ options.closeSync(fd);
374
+ try {
375
+ unlinkSync(path);
376
+ } catch (error) {
377
+ if (!isFileNotFoundError(error)) {
378
+ try {
379
+ offset = options.statSync(path).size;
380
+ } catch {
381
+ offset = 0;
382
+ }
383
+ fd = options.openSync(path);
384
+ throw error;
385
+ }
386
+ }
387
+ offset = 0;
388
+ fd = options.openSync(path);
389
+ return;
390
+ }
322
391
  options.closeSync(fd);
323
392
  for (let i = maxFiles - 1; i > 0; i--) {
324
393
  const oldPath = `${path}.${i}`;
@@ -359,10 +428,21 @@ function getBaseRotatingFileSink(path, options) {
359
428
  let disposed = false;
360
429
  let activeFlush = null;
361
430
  let flushTimer = null;
431
+ let bufferedNonMetaRecord = false;
432
+ function reportFlushError(error) {
433
+ try {
434
+ (0, __logtape_logtape.getLogger)(["logtape", "meta"]).warn("Non-blocking rotating file sink flush failed for {path}: {error}", {
435
+ error,
436
+ path
437
+ });
438
+ } catch {}
439
+ }
362
440
  async function flushBuffer() {
363
441
  if (buffer.length === 0) return;
364
442
  const data = buffer;
365
443
  buffer = "";
444
+ const suppressErrorReport = !bufferedNonMetaRecord;
445
+ bufferedNonMetaRecord = false;
366
446
  try {
367
447
  const bytes = encoder.encode(data);
368
448
  if (shouldRollover(bytes)) performRollover();
@@ -370,7 +450,9 @@ function getBaseRotatingFileSink(path, options) {
370
450
  await asyncOptions.flush(fd);
371
451
  offset += bytes.length;
372
452
  lastFlushTimestamp = Date.now();
373
- } catch {}
453
+ } catch (error) {
454
+ if (!suppressErrorReport) reportFlushError(error);
455
+ }
374
456
  }
375
457
  function scheduleFlush() {
376
458
  if (activeFlush || disposed) return;
@@ -387,6 +469,7 @@ function getBaseRotatingFileSink(path, options) {
387
469
  const nonBlockingSink = (record) => {
388
470
  if (disposed) return;
389
471
  buffer += formatter(record);
472
+ bufferedNonMetaRecord ||= !isMetaLoggerRecord(record);
390
473
  const shouldFlushBySize = buffer.length >= bufferSize;
391
474
  const shouldFlushByTime = flushInterval > 0 && record.timestamp - lastFlushTimestamp >= flushInterval;
392
475
  if (shouldFlushBySize || shouldFlushByTime) scheduleFlush();
@@ -398,6 +481,7 @@ function getBaseRotatingFileSink(path, options) {
398
481
  clearInterval(flushTimer);
399
482
  flushTimer = null;
400
483
  }
484
+ if (activeFlush !== null) await activeFlush;
401
485
  await flushBuffer();
402
486
  try {
403
487
  await asyncOptions.close(fd);
@@ -136,6 +136,11 @@ interface RotatingFileSinkDriver<TFile> extends FileSinkDriver<TFile> {
136
136
  * @param newPath A path to be renamed to.
137
137
  */
138
138
  renameSync(oldPath: string, newPath: string): void;
139
+ /**
140
+ * Delete a file.
141
+ * @param path A path to the file to delete.
142
+ */
143
+ unlinkSync?(path: string): void;
139
144
  }
140
145
  /**
141
146
  * A platform-specific async rotating file sink driver.
@@ -156,6 +161,11 @@ interface AsyncRotatingFileSinkDriver<TFile> extends AsyncFileSinkDriver<TFile>
156
161
  * @param newPath A path to be renamed to.
157
162
  */
158
163
  renameSync(oldPath: string, newPath: string): void;
164
+ /**
165
+ * Delete a file.
166
+ * @param path A path to the file to delete.
167
+ */
168
+ unlinkSync?(path: string): void;
159
169
  }
160
170
  /**
161
171
  * Get a platform-independent rotating file sink.
@@ -1 +1 @@
1
- {"version":3,"file":"filesink.base.d.cts","names":[],"sources":["../src/filesink.base.ts"],"sourcesContent":[],"mappings":";;;;;;AAqQA;AAuCiB,UAvCA,eAAA,SAAwB,iBAuCV,CAAA;EAAA;;;EAYV,IAAS,CAAA,EAAA,OAAA;EAAU;;;;AAoBnB;AAQrB;;EAAoC,UAA+B,CAAA,EAAA,MAAA;EAAK;;;;;;;EAmB7C,aAnByB,CAAA,EAAA,MAAA;EAAc;AAgQlE;;;;AAAqD;AAerD;;EAAuC,WAA+B,CAAA,EAAA,OAAA;;AAAD;AAoBrE;;;AACU,UA5UO,cA4UP,CAAA,KAAA,CAAA,CAAA;EAAmB;;;;0BAvUH;;;;;;gBAOV,cAAc;;;;;;;qBAQT,eAAe;;;;;gBAMpB;;;;;gBAMA;;;;;;;UAQC,mCAAmC,eAAe;;;;;;;iBAOlD,eAAe,eAAe;;;;;YAMnC,QAAQ;;;;;YAMR,QAAQ;;;;;;;;;;;;;;;;UA6OH,uBAAA,SAAgC,KAAK;;;;;;;;;;;;;UAerC,sCAAsC,eAAe;;;;;;;;;;;;;;;;;;;;UAoBrD,2CACP,oBAAoB"}
1
+ {"version":3,"file":"filesink.base.d.cts","names":[],"sources":["../src/filesink.base.ts"],"sourcesContent":[],"mappings":";;;;;;AAgRA;AAuCiB,UAvCA,eAAA,SAAwB,iBAuCV,CAAA;EAAA;;;EAYV,IAAS,CAAA,EAAA,OAAA;EAAU;;;;AAoBnB;AAQrB;;EAAoC,UAA+B,CAAA,EAAA,MAAA;EAAK;;;;;;;EAmB7C,aAnByB,CAAA,EAAA,MAAA;EAAc;AAiTlE;;;;AAAqD;AAerD;;EAAuC,WAA+B,CAAA,EAAA,OAAA;;AAAD;AA0BrE;;;AACU,UAnYO,cAmYP,CAAA,KAAA,CAAA,CAAA;EAAmB;;;;0BA9XH;;;;;;gBAOV,cAAc;;;;;;;qBAQT,eAAe;;;;;gBAMpB;;;;;gBAMA;;;;;;;UAQC,mCAAmC,eAAe;;;;;;;iBAOlD,eAAe,eAAe;;;;;YAMnC,QAAQ;;;;;YAMR,QAAQ;;;;;;;;;;;;;;;;UA8RH,uBAAA,SAAgC,KAAK;;;;;;;;;;;;;UAerC,sCAAsC,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;UA0BrD,2CACP,oBAAoB"}
@@ -136,6 +136,11 @@ interface RotatingFileSinkDriver<TFile> extends FileSinkDriver<TFile> {
136
136
  * @param newPath A path to be renamed to.
137
137
  */
138
138
  renameSync(oldPath: string, newPath: string): void;
139
+ /**
140
+ * Delete a file.
141
+ * @param path A path to the file to delete.
142
+ */
143
+ unlinkSync?(path: string): void;
139
144
  }
140
145
  /**
141
146
  * A platform-specific async rotating file sink driver.
@@ -156,6 +161,11 @@ interface AsyncRotatingFileSinkDriver<TFile> extends AsyncFileSinkDriver<TFile>
156
161
  * @param newPath A path to be renamed to.
157
162
  */
158
163
  renameSync(oldPath: string, newPath: string): void;
164
+ /**
165
+ * Delete a file.
166
+ * @param path A path to the file to delete.
167
+ */
168
+ unlinkSync?(path: string): void;
159
169
  }
160
170
  /**
161
171
  * Get a platform-independent rotating file sink.
@@ -1 +1 @@
1
- {"version":3,"file":"filesink.base.d.ts","names":[],"sources":["../src/filesink.base.ts"],"sourcesContent":[],"mappings":";;;;;;AAqQA;AAuCiB,UAvCA,eAAA,SAAwB,iBAuCV,CAAA;EAAA;;;EAYV,IAAS,CAAA,EAAA,OAAA;EAAU;;;;AAoBnB;AAQrB;;EAAoC,UAA+B,CAAA,EAAA,MAAA;EAAK;;;;;;;EAmB7C,aAnByB,CAAA,EAAA,MAAA;EAAc;AAgQlE;;;;AAAqD;AAerD;;EAAuC,WAA+B,CAAA,EAAA,OAAA;;AAAD;AAoBrE;;;AACU,UA5UO,cA4UP,CAAA,KAAA,CAAA,CAAA;EAAmB;;;;0BAvUH;;;;;;gBAOV,cAAc;;;;;;;qBAQT,eAAe;;;;;gBAMpB;;;;;gBAMA;;;;;;;UAQC,mCAAmC,eAAe;;;;;;;iBAOlD,eAAe,eAAe;;;;;YAMnC,QAAQ;;;;;YAMR,QAAQ;;;;;;;;;;;;;;;;UA6OH,uBAAA,SAAgC,KAAK;;;;;;;;;;;;;UAerC,sCAAsC,eAAe;;;;;;;;;;;;;;;;;;;;UAoBrD,2CACP,oBAAoB"}
1
+ {"version":3,"file":"filesink.base.d.ts","names":[],"sources":["../src/filesink.base.ts"],"sourcesContent":[],"mappings":";;;;;;AAgRA;AAuCiB,UAvCA,eAAA,SAAwB,iBAuCV,CAAA;EAAA;;;EAYV,IAAS,CAAA,EAAA,OAAA;EAAU;;;;AAoBnB;AAQrB;;EAAoC,UAA+B,CAAA,EAAA,MAAA;EAAK;;;;;;;EAmB7C,aAnByB,CAAA,EAAA,MAAA;EAAc;AAiTlE;;;;AAAqD;AAerD;;EAAuC,WAA+B,CAAA,EAAA,OAAA;;AAAD;AA0BrE;;;AACU,UAnYO,cAmYP,CAAA,KAAA,CAAA,CAAA;EAAmB;;;;0BA9XH;;;;;;gBAOV,cAAc;;;;;;;qBAQT,eAAe;;;;;gBAMpB;;;;;gBAMA;;;;;;;UAQC,mCAAmC,eAAe;;;;;;;iBAOlD,eAAe,eAAe;;;;;YAMnC,QAAQ;;;;;YAMR,QAAQ;;;;;;;;;;;;;;;;UA8RH,uBAAA,SAAgC,KAAK;;;;;;;;;;;;;UAerC,sCAAsC,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;UA0BrD,2CACP,oBAAoB"}
@@ -1,6 +1,9 @@
1
- import { defaultTextFormatter } from "@logtape/logtape";
1
+ import { defaultTextFormatter, getLogger } from "@logtape/logtape";
2
2
 
3
3
  //#region src/filesink.base.ts
4
+ function isMetaLoggerRecord(record) {
5
+ return record.category.length === 2 && record.category[0] === "logtape" && record.category[1] === "meta";
6
+ }
4
7
  /**
5
8
  * Adaptive flush strategy that dynamically adjusts buffer thresholds
6
9
  * based on recent flush patterns for optimal performance.
@@ -12,8 +15,10 @@ var AdaptiveFlushStrategy = class {
12
15
  avgFlushInterval;
13
16
  maxHistorySize = 10;
14
17
  baseThreshold;
18
+ baseInterval;
15
19
  constructor(baseThreshold, baseInterval) {
16
20
  this.baseThreshold = baseThreshold;
21
+ this.baseInterval = baseInterval;
17
22
  this.avgFlushSize = baseThreshold;
18
23
  this.avgFlushInterval = baseInterval;
19
24
  }
@@ -52,7 +57,8 @@ var AdaptiveFlushStrategy = class {
52
57
  return Math.max(Math.min(4096, this.baseThreshold / 2), Math.min(64 * 1024, this.baseThreshold * adaptiveFactor));
53
58
  }
54
59
  calculateAdaptiveInterval() {
55
- if (this.avgFlushInterval <= 0) return 0;
60
+ if (this.baseInterval <= 0) return 0;
61
+ if (this.avgFlushInterval <= 0) return this.baseInterval;
56
62
  if (this.recentFlushTimes.length < 3) return this.avgFlushInterval;
57
63
  const variance = this.calculateVariance(this.recentFlushTimes);
58
64
  const stabilityFactor = Math.min(2, Math.max(.5, 1e3 / variance));
@@ -202,18 +208,22 @@ function getBaseFileSink(path, options) {
202
208
  }
203
209
  const sink = (record) => {
204
210
  if (fd == null) fd = options.openSync(path);
205
- if (byteBuffer.isEmpty() && bufferSize === 8192) {
206
- const formattedRecord$1 = formatter(record);
207
- const encodedRecord$1 = encoder.encode(formattedRecord$1);
208
- if (encodedRecord$1.length < 200) {
209
- options.writeSync(fd, encodedRecord$1);
211
+ let formattedRecord;
212
+ let encodedRecord;
213
+ if (byteBuffer.isEmpty()) {
214
+ formattedRecord = formatter(record);
215
+ encodedRecord = encoder.encode(formattedRecord);
216
+ if (encodedRecord.length < 200) {
217
+ options.writeSync(fd, encodedRecord);
210
218
  options.flushSync(fd);
211
- lastFlushTimestamp = Date.now();
219
+ const currentTime = Date.now();
220
+ adaptiveStrategy.recordFlush(encodedRecord.length, currentTime - lastFlushTimestamp);
221
+ lastFlushTimestamp = currentTime;
212
222
  return;
213
223
  }
214
224
  }
215
- const formattedRecord = formatter(record);
216
- const encodedRecord = encoder.encode(formattedRecord);
225
+ formattedRecord ??= formatter(record);
226
+ encodedRecord ??= encoder.encode(formattedRecord);
217
227
  byteBuffer.append(encodedRecord);
218
228
  if (bufferSize <= 0) flushBuffer$1();
219
229
  else {
@@ -235,24 +245,53 @@ function getBaseFileSink(path, options) {
235
245
  let disposed = false;
236
246
  let activeFlush = null;
237
247
  let flushTimer = null;
248
+ let bufferedNonMetaRecord = false;
249
+ function reportFlushError(error) {
250
+ try {
251
+ getLogger(["logtape", "meta"]).warn("Non-blocking file sink flush failed for {path}: {error}", {
252
+ error,
253
+ path
254
+ });
255
+ } catch {}
256
+ }
238
257
  async function flushBuffer() {
239
258
  if (fd == null || byteBuffer.isEmpty()) return;
240
259
  const flushSize = byteBuffer.size();
241
260
  const currentTime = Date.now();
242
261
  const timeSinceLastFlush = currentTime - lastFlushTimestamp;
243
262
  const chunks = byteBuffer.flush();
263
+ const suppressErrorReport = !bufferedNonMetaRecord;
264
+ bufferedNonMetaRecord = false;
244
265
  try {
245
266
  if (asyncOptions.writeMany && chunks.length > 1) await asyncOptions.writeMany(fd, chunks);
246
267
  else for (const chunk of chunks) asyncOptions.writeSync(fd, chunk);
247
268
  await asyncOptions.flush(fd);
248
269
  adaptiveStrategy.recordFlush(flushSize, timeSinceLastFlush);
249
270
  lastFlushTimestamp = currentTime;
250
- } catch {}
271
+ } catch (error) {
272
+ if (!suppressErrorReport) reportFlushError(error);
273
+ }
251
274
  }
252
275
  function scheduleFlush() {
253
276
  if (activeFlush || disposed) return;
254
277
  activeFlush = flushBuffer().finally(() => {
255
278
  activeFlush = null;
279
+ if (!disposed && !byteBuffer.isEmpty()) scheduleFlush();
280
+ });
281
+ }
282
+ function scheduleDirectFlush(flushSize, suppressErrorReport) {
283
+ if (activeFlush || disposed || fd == null) return;
284
+ const flushFd = fd;
285
+ const startedAt = Date.now();
286
+ const timeSinceLastFlush = startedAt - lastFlushTimestamp;
287
+ activeFlush = Promise.resolve().then(() => asyncOptions.flush(flushFd)).then(() => {
288
+ adaptiveStrategy.recordFlush(flushSize, timeSinceLastFlush);
289
+ lastFlushTimestamp = Date.now();
290
+ }).catch((error) => {
291
+ if (!suppressErrorReport) reportFlushError(error);
292
+ }).finally(() => {
293
+ activeFlush = null;
294
+ if (!disposed && !byteBuffer.isEmpty()) scheduleFlush();
256
295
  });
257
296
  }
258
297
  function startFlushTimer() {
@@ -264,19 +303,21 @@ function getBaseFileSink(path, options) {
264
303
  const nonBlockingSink = (record) => {
265
304
  if (disposed) return;
266
305
  if (fd == null) fd = asyncOptions.openSync(path);
267
- if (byteBuffer.isEmpty() && !activeFlush && bufferSize === 8192) {
268
- const formattedRecord$1 = formatter(record);
269
- const encodedRecord$1 = encoder.encode(formattedRecord$1);
270
- if (encodedRecord$1.length < 200) {
271
- asyncOptions.writeSync(fd, encodedRecord$1);
272
- scheduleFlush();
273
- lastFlushTimestamp = Date.now();
306
+ let formattedRecord;
307
+ let encodedRecord;
308
+ if (byteBuffer.isEmpty() && !activeFlush) {
309
+ formattedRecord = formatter(record);
310
+ encodedRecord = encoder.encode(formattedRecord);
311
+ if (encodedRecord.length < 200) {
312
+ asyncOptions.writeSync(fd, encodedRecord);
313
+ scheduleDirectFlush(encodedRecord.length, isMetaLoggerRecord(record));
274
314
  return;
275
315
  }
276
316
  }
277
- const formattedRecord = formatter(record);
278
- const encodedRecord = encoder.encode(formattedRecord);
317
+ formattedRecord ??= formatter(record);
318
+ encodedRecord ??= encoder.encode(formattedRecord);
279
319
  byteBuffer.append(encodedRecord);
320
+ bufferedNonMetaRecord ||= !isMetaLoggerRecord(record);
280
321
  if (bufferSize <= 0) scheduleFlush();
281
322
  else {
282
323
  const timeSinceLastFlush = record.timestamp - lastFlushTimestamp;
@@ -291,6 +332,7 @@ function getBaseFileSink(path, options) {
291
332
  clearInterval(flushTimer);
292
333
  flushTimer = null;
293
334
  }
335
+ if (activeFlush !== null) await activeFlush;
294
336
  await flushBuffer();
295
337
  if (fd !== null) try {
296
338
  await asyncOptions.close(fd);
@@ -299,6 +341,11 @@ function getBaseFileSink(path, options) {
299
341
  };
300
342
  return nonBlockingSink;
301
343
  }
344
+ function isFileNotFoundError(error) {
345
+ if (!(error instanceof Error)) return false;
346
+ const errorWithCode = error;
347
+ return errorWithCode.code === "ENOENT" || error.name === "NotFound";
348
+ }
302
349
  function getBaseRotatingFileSink(path, options) {
303
350
  const formatter = options.formatter ?? defaultTextFormatter;
304
351
  const encoder = options.encoder ?? new TextEncoder();
@@ -306,6 +353,7 @@ function getBaseRotatingFileSink(path, options) {
306
353
  const maxFiles = options.maxFiles ?? 5;
307
354
  const bufferSize = options.bufferSize ?? 1024 * 8;
308
355
  const flushInterval = options.flushInterval ?? 5e3;
356
+ if (maxFiles <= 0 && options.unlinkSync == null) throw new TypeError("maxFiles <= 0 requires unlinkSync support.");
309
357
  let offset = 0;
310
358
  try {
311
359
  const stat = options.statSync(path);
@@ -318,6 +366,27 @@ function getBaseRotatingFileSink(path, options) {
318
366
  return offset + bytes.length > maxSize;
319
367
  }
320
368
  function performRollover() {
369
+ if (maxFiles <= 0) {
370
+ const unlinkSync = options.unlinkSync;
371
+ if (unlinkSync == null) return;
372
+ options.closeSync(fd);
373
+ try {
374
+ unlinkSync(path);
375
+ } catch (error) {
376
+ if (!isFileNotFoundError(error)) {
377
+ try {
378
+ offset = options.statSync(path).size;
379
+ } catch {
380
+ offset = 0;
381
+ }
382
+ fd = options.openSync(path);
383
+ throw error;
384
+ }
385
+ }
386
+ offset = 0;
387
+ fd = options.openSync(path);
388
+ return;
389
+ }
321
390
  options.closeSync(fd);
322
391
  for (let i = maxFiles - 1; i > 0; i--) {
323
392
  const oldPath = `${path}.${i}`;
@@ -358,10 +427,21 @@ function getBaseRotatingFileSink(path, options) {
358
427
  let disposed = false;
359
428
  let activeFlush = null;
360
429
  let flushTimer = null;
430
+ let bufferedNonMetaRecord = false;
431
+ function reportFlushError(error) {
432
+ try {
433
+ getLogger(["logtape", "meta"]).warn("Non-blocking rotating file sink flush failed for {path}: {error}", {
434
+ error,
435
+ path
436
+ });
437
+ } catch {}
438
+ }
361
439
  async function flushBuffer() {
362
440
  if (buffer.length === 0) return;
363
441
  const data = buffer;
364
442
  buffer = "";
443
+ const suppressErrorReport = !bufferedNonMetaRecord;
444
+ bufferedNonMetaRecord = false;
365
445
  try {
366
446
  const bytes = encoder.encode(data);
367
447
  if (shouldRollover(bytes)) performRollover();
@@ -369,7 +449,9 @@ function getBaseRotatingFileSink(path, options) {
369
449
  await asyncOptions.flush(fd);
370
450
  offset += bytes.length;
371
451
  lastFlushTimestamp = Date.now();
372
- } catch {}
452
+ } catch (error) {
453
+ if (!suppressErrorReport) reportFlushError(error);
454
+ }
373
455
  }
374
456
  function scheduleFlush() {
375
457
  if (activeFlush || disposed) return;
@@ -386,6 +468,7 @@ function getBaseRotatingFileSink(path, options) {
386
468
  const nonBlockingSink = (record) => {
387
469
  if (disposed) return;
388
470
  buffer += formatter(record);
471
+ bufferedNonMetaRecord ||= !isMetaLoggerRecord(record);
389
472
  const shouldFlushBySize = buffer.length >= bufferSize;
390
473
  const shouldFlushByTime = flushInterval > 0 && record.timestamp - lastFlushTimestamp >= flushInterval;
391
474
  if (shouldFlushBySize || shouldFlushByTime) scheduleFlush();
@@ -397,6 +480,7 @@ function getBaseRotatingFileSink(path, options) {
397
480
  clearInterval(flushTimer);
398
481
  flushTimer = null;
399
482
  }
483
+ if (activeFlush !== null) await activeFlush;
400
484
  await flushBuffer();
401
485
  try {
402
486
  await asyncOptions.close(fd);
@@ -1 +1 @@
1
- {"version":3,"file":"filesink.base.js","names":["baseThreshold: number","baseInterval: number","size: number","timeSinceLastFlush: number","currentSize: number","values: number[]","buffer: Uint8Array","bufferPool: BufferPool","data: Uint8Array","path: string","options:\n & FileSinkOptions\n & (FileSinkDriver<TFile> | AsyncFileSinkDriver<TFile>)","lastFlushTimestamp: number","flushBuffer","sink: Sink & Disposable","record: LogRecord","formattedRecord","encodedRecord","activeFlush: Promise<void> | null","flushTimer: ReturnType<typeof setInterval> | null","nonBlockingSink: Sink & AsyncDisposable","options:\n & RotatingFileSinkOptions\n & (RotatingFileSinkDriver<TFile> | AsyncRotatingFileSinkDriver<TFile>)","offset: number","buffer: string","bytes: Uint8Array"],"sources":["../src/filesink.base.ts"],"sourcesContent":["import {\n defaultTextFormatter,\n type LogRecord,\n type Sink,\n type StreamSinkOptions,\n} from \"@logtape/logtape\";\n\n/**\n * Adaptive flush strategy that dynamically adjusts buffer thresholds\n * based on recent flush patterns for optimal performance.\n */\nclass AdaptiveFlushStrategy {\n private recentFlushSizes: number[] = [];\n private recentFlushTimes: number[] = [];\n private avgFlushSize: number;\n private avgFlushInterval: number;\n private readonly maxHistorySize = 10;\n private readonly baseThreshold: number;\n\n constructor(baseThreshold: number, baseInterval: number) {\n this.baseThreshold = baseThreshold;\n this.avgFlushSize = baseThreshold;\n this.avgFlushInterval = baseInterval;\n }\n\n /**\n * Record a flush event for pattern analysis.\n * @param size The size of data flushed in bytes.\n * @param timeSinceLastFlush Time since last flush in milliseconds.\n */\n recordFlush(size: number, timeSinceLastFlush: number): void {\n this.recentFlushSizes.push(size);\n this.recentFlushTimes.push(timeSinceLastFlush);\n\n // Keep only recent history\n if (this.recentFlushSizes.length > this.maxHistorySize) {\n this.recentFlushSizes.shift();\n this.recentFlushTimes.shift();\n }\n\n // Update averages\n this.updateAverages();\n }\n\n /**\n * Determine if buffer should be flushed based on adaptive strategy.\n * @param currentSize Current buffer size in bytes.\n * @param timeSinceLastFlush Time since last flush in milliseconds.\n * @returns True if buffer should be flushed.\n */\n shouldFlush(currentSize: number, timeSinceLastFlush: number): boolean {\n const adaptiveThreshold = this.calculateAdaptiveThreshold();\n const adaptiveInterval = this.calculateAdaptiveInterval();\n\n return currentSize >= adaptiveThreshold ||\n (adaptiveInterval > 0 && timeSinceLastFlush >= adaptiveInterval);\n }\n\n private updateAverages(): void {\n if (this.recentFlushSizes.length === 0) return;\n\n this.avgFlushSize =\n this.recentFlushSizes.reduce((sum, size) => sum + size, 0) /\n this.recentFlushSizes.length;\n\n this.avgFlushInterval =\n this.recentFlushTimes.reduce((sum, time) => sum + time, 0) /\n this.recentFlushTimes.length;\n }\n\n private calculateAdaptiveThreshold(): number {\n // Adjust threshold based on recent patterns\n // Higher average flush sizes suggest larger batches are beneficial\n const adaptiveFactor = Math.min(\n 2.0,\n Math.max(0.5, this.avgFlushSize / this.baseThreshold),\n );\n\n return Math.max(\n Math.min(4096, this.baseThreshold / 2),\n Math.min(64 * 1024, this.baseThreshold * adaptiveFactor),\n );\n }\n\n private calculateAdaptiveInterval(): number {\n // If base interval is 0, time-based flushing is disabled\n if (this.avgFlushInterval <= 0) return 0;\n\n // Adjust interval based on recent flush frequency\n // More frequent flushes suggest lower latency is preferred\n if (this.recentFlushTimes.length < 3) return this.avgFlushInterval;\n\n const variance = this.calculateVariance(this.recentFlushTimes);\n const stabilityFactor = Math.min(2.0, Math.max(0.5, 1000 / variance));\n\n return Math.max(\n 1000,\n Math.min(10000, this.avgFlushInterval * stabilityFactor),\n );\n }\n\n private calculateVariance(values: number[]): number {\n if (values.length < 2) return 1000; // Default variance\n\n const mean = values.reduce((sum, val) => sum + val, 0) / values.length;\n const squaredDiffs = values.map((val) => Math.pow(val - mean, 2));\n return squaredDiffs.reduce((sum, diff) => sum + diff, 0) / values.length;\n }\n}\n\n/**\n * Memory pool for reusing Uint8Array buffers to minimize GC pressure.\n * Maintains a pool of pre-allocated buffers for efficient reuse.\n */\nclass BufferPool {\n private pool: Uint8Array[] = [];\n private readonly maxPoolSize = 50; // Keep a reasonable pool size\n private readonly maxBufferSize = 64 * 1024; // Don't pool very large buffers\n\n /**\n * Acquire a buffer from the pool or create a new one.\n * @param size The minimum size needed for the buffer.\n * @returns A Uint8Array that can be used for encoding.\n */\n acquire(size: number): Uint8Array {\n // Don't pool very large buffers to avoid memory waste\n if (size > this.maxBufferSize) {\n return new Uint8Array(size);\n }\n\n // Try to find a suitable buffer from the pool\n for (let i = this.pool.length - 1; i >= 0; i--) {\n const buffer = this.pool[i];\n if (buffer.length >= size) {\n // Remove from pool and return\n this.pool.splice(i, 1);\n return buffer.subarray(0, size);\n }\n }\n\n // No suitable buffer found, create a new one\n // Create slightly larger buffer to improve reuse chances\n const actualSize = Math.max(size, 1024); // Minimum 1KB\n return new Uint8Array(actualSize);\n }\n\n /**\n * Return a buffer to the pool for future reuse.\n * @param buffer The buffer to return to the pool.\n */\n release(buffer: Uint8Array): void {\n // Don't pool if we're at capacity or buffer is too large\n if (\n this.pool.length >= this.maxPoolSize || buffer.length > this.maxBufferSize\n ) {\n return;\n }\n\n // Don't pool very small buffers as they're cheap to allocate\n if (buffer.length < 256) {\n return;\n }\n\n // Add to pool for reuse\n this.pool.push(buffer);\n }\n\n /**\n * Clear the pool to free memory. Useful for cleanup.\n */\n clear(): void {\n this.pool.length = 0;\n }\n\n /**\n * Get current pool statistics for monitoring.\n * @returns Object with pool size and buffer count.\n */\n getStats(): { poolSize: number; totalBuffers: number } {\n return {\n poolSize: this.pool.reduce((sum, buf) => sum + buf.length, 0),\n totalBuffers: this.pool.length,\n };\n }\n}\n\n/**\n * High-performance byte buffer for batching log records.\n * Eliminates string concatenation overhead by storing pre-encoded bytes.\n * Uses memory pooling to reduce GC pressure.\n */\nclass ByteRingBuffer {\n private buffers: Uint8Array[] = [];\n private totalSize: number = 0;\n private bufferPool: BufferPool;\n\n constructor(bufferPool: BufferPool) {\n this.bufferPool = bufferPool;\n }\n\n /**\n * Append pre-encoded log record bytes to the buffer.\n * @param data The encoded log record as bytes.\n */\n append(data: Uint8Array): void {\n this.buffers.push(data);\n this.totalSize += data.length;\n }\n\n /**\n * Get the current total size of buffered data in bytes.\n * @returns The total size in bytes.\n */\n size(): number {\n return this.totalSize;\n }\n\n /**\n * Get the number of buffered records.\n * @returns The number of records in the buffer.\n */\n count(): number {\n return this.buffers.length;\n }\n\n /**\n * Flush all buffered data and return it as an array of byte arrays.\n * This clears the internal buffer and returns used buffers to the pool.\n * @returns Array of buffered byte arrays ready for writev() operations.\n */\n flush(): Uint8Array[] {\n const result = [...this.buffers];\n this.clear();\n return result;\n }\n\n /**\n * Clear the buffer without returning data.\n * Returns buffers to the pool for reuse.\n */\n clear(): void {\n // Return buffers to pool for reuse\n for (const buffer of this.buffers) {\n this.bufferPool.release(buffer);\n }\n this.buffers.length = 0;\n this.totalSize = 0;\n }\n\n /**\n * Check if the buffer is empty.\n * @returns True if the buffer contains no data.\n */\n isEmpty(): boolean {\n return this.buffers.length === 0;\n }\n}\n\n/**\n * Options for the {@link getBaseFileSink} function.\n */\nexport interface FileSinkOptions extends StreamSinkOptions {\n /**\n * If `true`, the file is not opened until the first write. Defaults to `false`.\n */\n lazy?: boolean;\n\n /**\n * The size of the buffer to use when writing to the file. If not specified,\n * a default buffer size will be used. If it is less or equal to 0,\n * the file will be written directly without buffering.\n * @default 8192\n * @since 0.12.0\n */\n bufferSize?: number;\n\n /**\n * The maximum time interval in milliseconds between flushes. If this time\n * passes since the last flush, the buffer will be flushed regardless of size.\n * This helps prevent log loss during unexpected process termination.\n * @default 5000\n * @since 0.12.0\n */\n flushInterval?: number;\n\n /**\n * Enable non-blocking mode with background flushing.\n * When enabled, flush operations are performed asynchronously to prevent\n * blocking the main thread during file I/O operations.\n *\n * @default `false`\n * @since 1.0.0\n */\n nonBlocking?: boolean;\n}\n\n/**\n * A platform-specific file sink driver.\n * @template TFile The type of the file descriptor.\n */\nexport interface FileSinkDriver<TFile> {\n /**\n * Open a file for appending and return a file descriptor.\n * @param path A path to the file to open.\n */\n openSync(path: string): TFile;\n\n /**\n * Write a chunk of data to the file.\n * @param fd The file descriptor.\n * @param chunk The data to write.\n */\n writeSync(fd: TFile, chunk: Uint8Array): void;\n\n /**\n * Write multiple chunks of data to the file in a single operation.\n * This is optional - if not implemented, falls back to multiple writeSync calls.\n * @param fd The file descriptor.\n * @param chunks Array of data chunks to write.\n */\n writeManySync?(fd: TFile, chunks: Uint8Array[]): void;\n\n /**\n * Flush the file to ensure that all data is written to the disk.\n * @param fd The file descriptor.\n */\n flushSync(fd: TFile): void;\n\n /**\n * Close the file.\n * @param fd The file descriptor.\n */\n closeSync(fd: TFile): void;\n}\n\n/**\n * A platform-specific async file sink driver.\n * @template TFile The type of the file descriptor.\n * @since 1.0.0\n */\nexport interface AsyncFileSinkDriver<TFile> extends FileSinkDriver<TFile> {\n /**\n * Asynchronously write multiple chunks of data to the file in a single operation.\n * This is optional - if not implemented, falls back to multiple writeSync calls.\n * @param fd The file descriptor.\n * @param chunks Array of data chunks to write.\n */\n writeMany?(fd: TFile, chunks: Uint8Array[]): Promise<void>;\n\n /**\n * Asynchronously flush the file to ensure that all data is written to the disk.\n * @param fd The file descriptor.\n */\n flush(fd: TFile): Promise<void>;\n\n /**\n * Asynchronously close the file.\n * @param fd The file descriptor.\n */\n close(fd: TFile): Promise<void>;\n}\n\n/**\n * Get a platform-independent file sink.\n *\n * @template TFile The type of the file descriptor.\n * @param path A path to the file to write to.\n * @param options The options for the sink and the file driver.\n * @returns A sink that writes to the file. The sink is also a disposable\n * object that closes the file when disposed. If `nonBlocking` is enabled,\n * returns a sink that also implements {@link AsyncDisposable}.\n */\nexport function getBaseFileSink<TFile>(\n path: string,\n options: FileSinkOptions & FileSinkDriver<TFile>,\n): Sink & Disposable;\nexport function getBaseFileSink<TFile>(\n path: string,\n options: FileSinkOptions & AsyncFileSinkDriver<TFile>,\n): Sink & AsyncDisposable;\nexport function getBaseFileSink<TFile>(\n path: string,\n options:\n & FileSinkOptions\n & (FileSinkDriver<TFile> | AsyncFileSinkDriver<TFile>),\n): Sink & (Disposable | AsyncDisposable) {\n const formatter = options.formatter ?? defaultTextFormatter;\n const encoder = options.encoder ?? new TextEncoder();\n const bufferSize = options.bufferSize ?? 1024 * 8; // Default buffer size of 8192 bytes\n const flushInterval = options.flushInterval ?? 5000; // Default flush interval of 5 seconds\n let fd = options.lazy ? null : options.openSync(path);\n\n // Initialize memory pool and buffer systems\n const bufferPool = new BufferPool();\n const byteBuffer = new ByteRingBuffer(bufferPool);\n const adaptiveStrategy = new AdaptiveFlushStrategy(bufferSize, flushInterval);\n let lastFlushTimestamp: number = Date.now();\n\n if (!options.nonBlocking) {\n // Blocking mode implementation\n // deno-lint-ignore no-inner-declarations\n function flushBuffer(): void {\n if (fd == null || byteBuffer.isEmpty()) return;\n\n const flushSize = byteBuffer.size();\n const currentTime = Date.now();\n const timeSinceLastFlush = currentTime - lastFlushTimestamp;\n\n const chunks = byteBuffer.flush();\n if (options.writeManySync && chunks.length > 1) {\n // Use batch write if available\n options.writeManySync(fd, chunks);\n } else {\n // Fallback to individual writes\n for (const chunk of chunks) {\n options.writeSync(fd, chunk);\n }\n }\n options.flushSync(fd);\n\n // Record flush for adaptive strategy\n adaptiveStrategy.recordFlush(flushSize, timeSinceLastFlush);\n lastFlushTimestamp = currentTime;\n }\n\n const sink: Sink & Disposable = (record: LogRecord) => {\n if (fd == null) fd = options.openSync(path);\n\n // ULTRA FAST PATH: Direct write when buffer is empty and using default buffer settings\n if (byteBuffer.isEmpty() && bufferSize === 8192) {\n // Inline everything for maximum speed - avoid all function calls\n const formattedRecord = formatter(record);\n const encodedRecord = encoder.encode(formattedRecord);\n\n // Only use fast path for typical log sizes to avoid breaking edge cases\n if (encodedRecord.length < 200) {\n // Write directly for small logs - no complex buffering logic\n options.writeSync(fd, encodedRecord);\n options.flushSync(fd);\n lastFlushTimestamp = Date.now();\n return;\n }\n }\n\n // STANDARD PATH: Complex logic for edge cases\n const formattedRecord = formatter(record);\n const encodedRecord = encoder.encode(formattedRecord);\n byteBuffer.append(encodedRecord);\n\n // Check for immediate flush conditions\n if (bufferSize <= 0) {\n // No buffering - flush immediately\n flushBuffer();\n } else {\n // Use adaptive strategy for intelligent flushing\n const timeSinceLastFlush = record.timestamp - lastFlushTimestamp;\n const shouldFlush = adaptiveStrategy.shouldFlush(\n byteBuffer.size(),\n timeSinceLastFlush,\n );\n\n if (shouldFlush) {\n flushBuffer();\n }\n }\n };\n sink[Symbol.dispose] = () => {\n if (fd !== null) {\n flushBuffer();\n options.closeSync(fd);\n }\n // Clean up buffer pool\n bufferPool.clear();\n };\n return sink;\n }\n\n // Non-blocking mode implementation\n const asyncOptions = options as AsyncFileSinkDriver<TFile>;\n let disposed = false;\n let activeFlush: Promise<void> | null = null;\n let flushTimer: ReturnType<typeof setInterval> | null = null;\n\n async function flushBuffer(): Promise<void> {\n if (fd == null || byteBuffer.isEmpty()) return;\n\n const flushSize = byteBuffer.size();\n const currentTime = Date.now();\n const timeSinceLastFlush = currentTime - lastFlushTimestamp;\n\n const chunks = byteBuffer.flush();\n try {\n if (asyncOptions.writeMany && chunks.length > 1) {\n // Use async batch write if available\n await asyncOptions.writeMany(fd, chunks);\n } else {\n // Fallback to individual writes\n for (const chunk of chunks) {\n asyncOptions.writeSync(fd, chunk);\n }\n }\n await asyncOptions.flush(fd);\n\n // Record flush for adaptive strategy\n adaptiveStrategy.recordFlush(flushSize, timeSinceLastFlush);\n lastFlushTimestamp = currentTime;\n } catch {\n // Silently ignore errors in non-blocking mode\n }\n }\n\n function scheduleFlush(): void {\n if (activeFlush || disposed) return;\n\n activeFlush = flushBuffer().finally(() => {\n activeFlush = null;\n });\n }\n\n function startFlushTimer(): void {\n if (flushTimer !== null || disposed) return;\n\n flushTimer = setInterval(() => {\n scheduleFlush();\n }, flushInterval);\n }\n\n const nonBlockingSink: Sink & AsyncDisposable = (record: LogRecord) => {\n if (disposed) return;\n if (fd == null) fd = asyncOptions.openSync(path);\n\n // ULTRA FAST PATH: Direct write when buffer is empty and using default buffer settings\n if (byteBuffer.isEmpty() && !activeFlush && bufferSize === 8192) {\n // Inline everything for maximum speed - avoid all function calls\n const formattedRecord = formatter(record);\n const encodedRecord = encoder.encode(formattedRecord);\n\n // Only use fast path for typical log sizes to avoid breaking edge cases\n if (encodedRecord.length < 200) {\n // Write directly for small logs - no complex buffering logic\n asyncOptions.writeSync(fd, encodedRecord);\n scheduleFlush(); // Async flush\n lastFlushTimestamp = Date.now();\n return;\n }\n }\n\n // STANDARD PATH: Complex logic for edge cases\n const formattedRecord = formatter(record);\n const encodedRecord = encoder.encode(formattedRecord);\n byteBuffer.append(encodedRecord);\n\n // Check for immediate flush conditions\n if (bufferSize <= 0) {\n // No buffering - flush immediately\n scheduleFlush();\n } else {\n // Use adaptive strategy for intelligent flushing\n const timeSinceLastFlush = record.timestamp - lastFlushTimestamp;\n const shouldFlush = adaptiveStrategy.shouldFlush(\n byteBuffer.size(),\n timeSinceLastFlush,\n );\n\n if (shouldFlush) {\n scheduleFlush();\n } else if (flushTimer === null && flushInterval > 0) {\n startFlushTimer();\n }\n }\n };\n\n nonBlockingSink[Symbol.asyncDispose] = async () => {\n disposed = true;\n if (flushTimer !== null) {\n clearInterval(flushTimer);\n flushTimer = null;\n }\n await flushBuffer();\n if (fd !== null) {\n try {\n await asyncOptions.close(fd);\n } catch {\n // Writer might already be closed or errored\n }\n }\n // Clean up buffer pool\n bufferPool.clear();\n };\n\n return nonBlockingSink;\n}\n\n/**\n * Options for the {@link getBaseRotatingFileSink} function.\n */\nexport interface RotatingFileSinkOptions extends Omit<FileSinkOptions, \"lazy\"> {\n /**\n * The maximum bytes of the file before it is rotated. 1 MiB by default.\n */\n maxSize?: number;\n\n /**\n * The maximum number of files to keep. 5 by default.\n */\n maxFiles?: number;\n}\n\n/**\n * A platform-specific rotating file sink driver.\n */\nexport interface RotatingFileSinkDriver<TFile> extends FileSinkDriver<TFile> {\n /**\n * Get the size of the file.\n * @param path A path to the file.\n * @returns The `size` of the file in bytes, in an object.\n */\n statSync(path: string): { size: number };\n\n /**\n * Rename a file.\n * @param oldPath A path to the file to rename.\n * @param newPath A path to be renamed to.\n */\n renameSync(oldPath: string, newPath: string): void;\n}\n\n/**\n * A platform-specific async rotating file sink driver.\n * @since 1.0.0\n */\nexport interface AsyncRotatingFileSinkDriver<TFile>\n extends AsyncFileSinkDriver<TFile> {\n /**\n * Get the size of the file.\n * @param path A path to the file.\n * @returns The `size` of the file in bytes, in an object.\n */\n statSync(path: string): { size: number };\n\n /**\n * Rename a file.\n * @param oldPath A path to the file to rename.\n * @param newPath A path to be renamed to.\n */\n renameSync(oldPath: string, newPath: string): void;\n}\n\n/**\n * Get a platform-independent rotating file sink.\n *\n * This sink writes log records to a file, and rotates the file when it reaches\n * the `maxSize`. The rotated files are named with the original file name\n * followed by a dot and a number, starting from 1. The number is incremented\n * for each rotation, and the maximum number of files to keep is `maxFiles`.\n *\n * @param path A path to the file to write to.\n * @param options The options for the sink and the file driver.\n * @returns A sink that writes to the file. The sink is also a disposable\n * object that closes the file when disposed. If `nonBlocking` is enabled,\n * returns a sink that also implements {@link AsyncDisposable}.\n */\nexport function getBaseRotatingFileSink<TFile>(\n path: string,\n options: RotatingFileSinkOptions & RotatingFileSinkDriver<TFile>,\n): Sink & Disposable;\nexport function getBaseRotatingFileSink<TFile>(\n path: string,\n options: RotatingFileSinkOptions & AsyncRotatingFileSinkDriver<TFile>,\n): Sink & AsyncDisposable;\nexport function getBaseRotatingFileSink<TFile>(\n path: string,\n options:\n & RotatingFileSinkOptions\n & (RotatingFileSinkDriver<TFile> | AsyncRotatingFileSinkDriver<TFile>),\n): Sink & (Disposable | AsyncDisposable) {\n const formatter = options.formatter ?? defaultTextFormatter;\n const encoder = options.encoder ?? new TextEncoder();\n const maxSize = options.maxSize ?? 1024 * 1024;\n const maxFiles = options.maxFiles ?? 5;\n const bufferSize = options.bufferSize ?? 1024 * 8; // Default buffer size of 8192 chars\n const flushInterval = options.flushInterval ?? 5000; // Default flush interval of 5 seconds\n let offset: number = 0;\n try {\n const stat = options.statSync(path);\n offset = stat.size;\n } catch {\n // Continue as the offset is already 0.\n }\n let fd = options.openSync(path);\n let lastFlushTimestamp: number = Date.now();\n let buffer: string = \"\";\n\n function shouldRollover(bytes: Uint8Array): boolean {\n return offset + bytes.length > maxSize;\n }\n function performRollover(): void {\n options.closeSync(fd);\n for (let i = maxFiles - 1; i > 0; i--) {\n const oldPath = `${path}.${i}`;\n const newPath = `${path}.${i + 1}`;\n try {\n options.renameSync(oldPath, newPath);\n } catch (_) {\n // Continue if the file does not exist.\n }\n }\n options.renameSync(path, `${path}.1`);\n offset = 0;\n fd = options.openSync(path);\n }\n\n if (!options.nonBlocking) {\n // Blocking mode implementation\n // deno-lint-ignore no-inner-declarations\n function flushBuffer(): void {\n if (buffer.length > 0) {\n const bytes = encoder.encode(buffer);\n buffer = \"\";\n if (shouldRollover(bytes)) performRollover();\n options.writeSync(fd, bytes);\n options.flushSync(fd);\n offset += bytes.length;\n lastFlushTimestamp = Date.now();\n }\n }\n\n const sink: Sink & Disposable = (record: LogRecord) => {\n buffer += formatter(record);\n\n const shouldFlushBySize = buffer.length >= bufferSize;\n const shouldFlushByTime = flushInterval > 0 &&\n (record.timestamp - lastFlushTimestamp) >= flushInterval;\n\n if (shouldFlushBySize || shouldFlushByTime) {\n flushBuffer();\n }\n };\n sink[Symbol.dispose] = () => {\n flushBuffer();\n options.closeSync(fd);\n };\n return sink;\n }\n\n // Non-blocking mode implementation\n const asyncOptions = options as AsyncRotatingFileSinkDriver<TFile>;\n let disposed = false;\n let activeFlush: Promise<void> | null = null;\n let flushTimer: ReturnType<typeof setInterval> | null = null;\n\n async function flushBuffer(): Promise<void> {\n if (buffer.length === 0) return;\n\n const data = buffer;\n buffer = \"\";\n try {\n const bytes = encoder.encode(data);\n if (shouldRollover(bytes)) performRollover();\n asyncOptions.writeSync(fd, bytes);\n await asyncOptions.flush(fd);\n offset += bytes.length;\n lastFlushTimestamp = Date.now();\n } catch {\n // Silently ignore errors in non-blocking mode\n }\n }\n\n function scheduleFlush(): void {\n if (activeFlush || disposed) return;\n\n activeFlush = flushBuffer().finally(() => {\n activeFlush = null;\n });\n }\n\n function startFlushTimer(): void {\n if (flushTimer !== null || disposed) return;\n\n flushTimer = setInterval(() => {\n scheduleFlush();\n }, flushInterval);\n }\n\n const nonBlockingSink: Sink & AsyncDisposable = (record: LogRecord) => {\n if (disposed) return;\n buffer += formatter(record);\n\n const shouldFlushBySize = buffer.length >= bufferSize;\n const shouldFlushByTime = flushInterval > 0 &&\n (record.timestamp - lastFlushTimestamp) >= flushInterval;\n\n if (shouldFlushBySize || shouldFlushByTime) {\n scheduleFlush();\n } else if (flushTimer === null && flushInterval > 0) {\n startFlushTimer();\n }\n };\n\n nonBlockingSink[Symbol.asyncDispose] = async () => {\n disposed = true;\n if (flushTimer !== null) {\n clearInterval(flushTimer);\n flushTimer = null;\n }\n await flushBuffer();\n try {\n await asyncOptions.close(fd);\n } catch {\n // Writer might already be closed or errored\n }\n };\n\n return nonBlockingSink;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"filesink.base.js","names":["record: LogRecord","baseThreshold: number","baseInterval: number","size: number","timeSinceLastFlush: number","currentSize: number","values: number[]","buffer: Uint8Array","bufferPool: BufferPool","data: Uint8Array","path: string","options:\n & FileSinkOptions\n & (FileSinkDriver<TFile> | AsyncFileSinkDriver<TFile>)","lastFlushTimestamp: number","flushBuffer","sink: Sink & Disposable","formattedRecord: string | undefined","encodedRecord: Uint8Array | undefined","activeFlush: Promise<void> | null","flushTimer: ReturnType<typeof setInterval> | null","error: unknown","flushSize: number","suppressErrorReport: boolean","nonBlockingSink: Sink & AsyncDisposable","options:\n & RotatingFileSinkOptions\n & (RotatingFileSinkDriver<TFile> | AsyncRotatingFileSinkDriver<TFile>)","offset: number","buffer: string","bytes: Uint8Array"],"sources":["../src/filesink.base.ts"],"sourcesContent":["import {\n defaultTextFormatter,\n getLogger,\n type LogRecord,\n type Sink,\n type StreamSinkOptions,\n} from \"@logtape/logtape\";\n\nfunction isMetaLoggerRecord(record: LogRecord): boolean {\n return record.category.length === 2 &&\n record.category[0] === \"logtape\" &&\n record.category[1] === \"meta\";\n}\n\n/**\n * Adaptive flush strategy that dynamically adjusts buffer thresholds\n * based on recent flush patterns for optimal performance.\n */\nclass AdaptiveFlushStrategy {\n private recentFlushSizes: number[] = [];\n private recentFlushTimes: number[] = [];\n private avgFlushSize: number;\n private avgFlushInterval: number;\n private readonly maxHistorySize = 10;\n private readonly baseThreshold: number;\n private readonly baseInterval: number;\n\n constructor(baseThreshold: number, baseInterval: number) {\n this.baseThreshold = baseThreshold;\n this.baseInterval = baseInterval;\n this.avgFlushSize = baseThreshold;\n this.avgFlushInterval = baseInterval;\n }\n\n /**\n * Record a flush event for pattern analysis.\n * @param size The size of data flushed in bytes.\n * @param timeSinceLastFlush Time since last flush in milliseconds.\n */\n recordFlush(size: number, timeSinceLastFlush: number): void {\n this.recentFlushSizes.push(size);\n this.recentFlushTimes.push(timeSinceLastFlush);\n\n // Keep only recent history\n if (this.recentFlushSizes.length > this.maxHistorySize) {\n this.recentFlushSizes.shift();\n this.recentFlushTimes.shift();\n }\n\n // Update averages\n this.updateAverages();\n }\n\n /**\n * Determine if buffer should be flushed based on adaptive strategy.\n * @param currentSize Current buffer size in bytes.\n * @param timeSinceLastFlush Time since last flush in milliseconds.\n * @returns True if buffer should be flushed.\n */\n shouldFlush(currentSize: number, timeSinceLastFlush: number): boolean {\n const adaptiveThreshold = this.calculateAdaptiveThreshold();\n const adaptiveInterval = this.calculateAdaptiveInterval();\n\n return currentSize >= adaptiveThreshold ||\n (adaptiveInterval > 0 && timeSinceLastFlush >= adaptiveInterval);\n }\n\n private updateAverages(): void {\n if (this.recentFlushSizes.length === 0) return;\n\n this.avgFlushSize =\n this.recentFlushSizes.reduce((sum, size) => sum + size, 0) /\n this.recentFlushSizes.length;\n\n this.avgFlushInterval =\n this.recentFlushTimes.reduce((sum, time) => sum + time, 0) /\n this.recentFlushTimes.length;\n }\n\n private calculateAdaptiveThreshold(): number {\n // Adjust threshold based on recent patterns\n // Higher average flush sizes suggest larger batches are beneficial\n const adaptiveFactor = Math.min(\n 2.0,\n Math.max(0.5, this.avgFlushSize / this.baseThreshold),\n );\n\n return Math.max(\n Math.min(4096, this.baseThreshold / 2),\n Math.min(64 * 1024, this.baseThreshold * adaptiveFactor),\n );\n }\n\n private calculateAdaptiveInterval(): number {\n // If base interval is 0, time-based flushing is disabled\n if (this.baseInterval <= 0) return 0;\n\n if (this.avgFlushInterval <= 0) return this.baseInterval;\n\n // Adjust interval based on recent flush frequency\n // More frequent flushes suggest lower latency is preferred\n if (this.recentFlushTimes.length < 3) return this.avgFlushInterval;\n\n const variance = this.calculateVariance(this.recentFlushTimes);\n const stabilityFactor = Math.min(2.0, Math.max(0.5, 1000 / variance));\n\n return Math.max(\n 1000,\n Math.min(10000, this.avgFlushInterval * stabilityFactor),\n );\n }\n\n private calculateVariance(values: number[]): number {\n if (values.length < 2) return 1000; // Default variance\n\n const mean = values.reduce((sum, val) => sum + val, 0) / values.length;\n const squaredDiffs = values.map((val) => Math.pow(val - mean, 2));\n return squaredDiffs.reduce((sum, diff) => sum + diff, 0) / values.length;\n }\n}\n\n/**\n * Memory pool for reusing Uint8Array buffers to minimize GC pressure.\n * Maintains a pool of pre-allocated buffers for efficient reuse.\n */\nclass BufferPool {\n private pool: Uint8Array[] = [];\n private readonly maxPoolSize = 50; // Keep a reasonable pool size\n private readonly maxBufferSize = 64 * 1024; // Don't pool very large buffers\n\n /**\n * Acquire a buffer from the pool or create a new one.\n * @param size The minimum size needed for the buffer.\n * @returns A Uint8Array that can be used for encoding.\n */\n acquire(size: number): Uint8Array {\n // Don't pool very large buffers to avoid memory waste\n if (size > this.maxBufferSize) {\n return new Uint8Array(size);\n }\n\n // Try to find a suitable buffer from the pool\n for (let i = this.pool.length - 1; i >= 0; i--) {\n const buffer = this.pool[i];\n if (buffer.length >= size) {\n // Remove from pool and return\n this.pool.splice(i, 1);\n return buffer.subarray(0, size);\n }\n }\n\n // No suitable buffer found, create a new one\n // Create slightly larger buffer to improve reuse chances\n const actualSize = Math.max(size, 1024); // Minimum 1KB\n return new Uint8Array(actualSize);\n }\n\n /**\n * Return a buffer to the pool for future reuse.\n * @param buffer The buffer to return to the pool.\n */\n release(buffer: Uint8Array): void {\n // Don't pool if we're at capacity or buffer is too large\n if (\n this.pool.length >= this.maxPoolSize || buffer.length > this.maxBufferSize\n ) {\n return;\n }\n\n // Don't pool very small buffers as they're cheap to allocate\n if (buffer.length < 256) {\n return;\n }\n\n // Add to pool for reuse\n this.pool.push(buffer);\n }\n\n /**\n * Clear the pool to free memory. Useful for cleanup.\n */\n clear(): void {\n this.pool.length = 0;\n }\n\n /**\n * Get current pool statistics for monitoring.\n * @returns Object with pool size and buffer count.\n */\n getStats(): { poolSize: number; totalBuffers: number } {\n return {\n poolSize: this.pool.reduce((sum, buf) => sum + buf.length, 0),\n totalBuffers: this.pool.length,\n };\n }\n}\n\n/**\n * High-performance byte buffer for batching log records.\n * Eliminates string concatenation overhead by storing pre-encoded bytes.\n * Uses memory pooling to reduce GC pressure.\n */\nclass ByteRingBuffer {\n private buffers: Uint8Array[] = [];\n private totalSize: number = 0;\n private bufferPool: BufferPool;\n\n constructor(bufferPool: BufferPool) {\n this.bufferPool = bufferPool;\n }\n\n /**\n * Append pre-encoded log record bytes to the buffer.\n * @param data The encoded log record as bytes.\n */\n append(data: Uint8Array): void {\n this.buffers.push(data);\n this.totalSize += data.length;\n }\n\n /**\n * Get the current total size of buffered data in bytes.\n * @returns The total size in bytes.\n */\n size(): number {\n return this.totalSize;\n }\n\n /**\n * Get the number of buffered records.\n * @returns The number of records in the buffer.\n */\n count(): number {\n return this.buffers.length;\n }\n\n /**\n * Flush all buffered data and return it as an array of byte arrays.\n * This clears the internal buffer and returns used buffers to the pool.\n * @returns Array of buffered byte arrays ready for writev() operations.\n */\n flush(): Uint8Array[] {\n const result = [...this.buffers];\n this.clear();\n return result;\n }\n\n /**\n * Clear the buffer without returning data.\n * Returns buffers to the pool for reuse.\n */\n clear(): void {\n // Return buffers to pool for reuse\n for (const buffer of this.buffers) {\n this.bufferPool.release(buffer);\n }\n this.buffers.length = 0;\n this.totalSize = 0;\n }\n\n /**\n * Check if the buffer is empty.\n * @returns True if the buffer contains no data.\n */\n isEmpty(): boolean {\n return this.buffers.length === 0;\n }\n}\n\n/**\n * Options for the {@link getBaseFileSink} function.\n */\nexport interface FileSinkOptions extends StreamSinkOptions {\n /**\n * If `true`, the file is not opened until the first write. Defaults to `false`.\n */\n lazy?: boolean;\n\n /**\n * The size of the buffer to use when writing to the file. If not specified,\n * a default buffer size will be used. If it is less or equal to 0,\n * the file will be written directly without buffering.\n * @default 8192\n * @since 0.12.0\n */\n bufferSize?: number;\n\n /**\n * The maximum time interval in milliseconds between flushes. If this time\n * passes since the last flush, the buffer will be flushed regardless of size.\n * This helps prevent log loss during unexpected process termination.\n * @default 5000\n * @since 0.12.0\n */\n flushInterval?: number;\n\n /**\n * Enable non-blocking mode with background flushing.\n * When enabled, flush operations are performed asynchronously to prevent\n * blocking the main thread during file I/O operations.\n *\n * @default `false`\n * @since 1.0.0\n */\n nonBlocking?: boolean;\n}\n\n/**\n * A platform-specific file sink driver.\n * @template TFile The type of the file descriptor.\n */\nexport interface FileSinkDriver<TFile> {\n /**\n * Open a file for appending and return a file descriptor.\n * @param path A path to the file to open.\n */\n openSync(path: string): TFile;\n\n /**\n * Write a chunk of data to the file.\n * @param fd The file descriptor.\n * @param chunk The data to write.\n */\n writeSync(fd: TFile, chunk: Uint8Array): void;\n\n /**\n * Write multiple chunks of data to the file in a single operation.\n * This is optional - if not implemented, falls back to multiple writeSync calls.\n * @param fd The file descriptor.\n * @param chunks Array of data chunks to write.\n */\n writeManySync?(fd: TFile, chunks: Uint8Array[]): void;\n\n /**\n * Flush the file to ensure that all data is written to the disk.\n * @param fd The file descriptor.\n */\n flushSync(fd: TFile): void;\n\n /**\n * Close the file.\n * @param fd The file descriptor.\n */\n closeSync(fd: TFile): void;\n}\n\n/**\n * A platform-specific async file sink driver.\n * @template TFile The type of the file descriptor.\n * @since 1.0.0\n */\nexport interface AsyncFileSinkDriver<TFile> extends FileSinkDriver<TFile> {\n /**\n * Asynchronously write multiple chunks of data to the file in a single operation.\n * This is optional - if not implemented, falls back to multiple writeSync calls.\n * @param fd The file descriptor.\n * @param chunks Array of data chunks to write.\n */\n writeMany?(fd: TFile, chunks: Uint8Array[]): Promise<void>;\n\n /**\n * Asynchronously flush the file to ensure that all data is written to the disk.\n * @param fd The file descriptor.\n */\n flush(fd: TFile): Promise<void>;\n\n /**\n * Asynchronously close the file.\n * @param fd The file descriptor.\n */\n close(fd: TFile): Promise<void>;\n}\n\n/**\n * Get a platform-independent file sink.\n *\n * @template TFile The type of the file descriptor.\n * @param path A path to the file to write to.\n * @param options The options for the sink and the file driver.\n * @returns A sink that writes to the file. The sink is also a disposable\n * object that closes the file when disposed. If `nonBlocking` is enabled,\n * returns a sink that also implements {@link AsyncDisposable}.\n */\nexport function getBaseFileSink<TFile>(\n path: string,\n options: FileSinkOptions & FileSinkDriver<TFile>,\n): Sink & Disposable;\nexport function getBaseFileSink<TFile>(\n path: string,\n options: FileSinkOptions & AsyncFileSinkDriver<TFile>,\n): Sink & AsyncDisposable;\nexport function getBaseFileSink<TFile>(\n path: string,\n options:\n & FileSinkOptions\n & (FileSinkDriver<TFile> | AsyncFileSinkDriver<TFile>),\n): Sink & (Disposable | AsyncDisposable) {\n const formatter = options.formatter ?? defaultTextFormatter;\n const encoder = options.encoder ?? new TextEncoder();\n const bufferSize = options.bufferSize ?? 1024 * 8; // Default buffer size of 8192 bytes\n const flushInterval = options.flushInterval ?? 5000; // Default flush interval of 5 seconds\n let fd = options.lazy ? null : options.openSync(path);\n\n // Initialize memory pool and buffer systems\n const bufferPool = new BufferPool();\n const byteBuffer = new ByteRingBuffer(bufferPool);\n const adaptiveStrategy = new AdaptiveFlushStrategy(bufferSize, flushInterval);\n let lastFlushTimestamp: number = Date.now();\n\n if (!options.nonBlocking) {\n // Blocking mode implementation\n // deno-lint-ignore no-inner-declarations\n function flushBuffer(): void {\n if (fd == null || byteBuffer.isEmpty()) return;\n\n const flushSize = byteBuffer.size();\n const currentTime = Date.now();\n const timeSinceLastFlush = currentTime - lastFlushTimestamp;\n\n const chunks = byteBuffer.flush();\n if (options.writeManySync && chunks.length > 1) {\n // Use batch write if available\n options.writeManySync(fd, chunks);\n } else {\n // Fallback to individual writes\n for (const chunk of chunks) {\n options.writeSync(fd, chunk);\n }\n }\n options.flushSync(fd);\n\n // Record flush for adaptive strategy\n adaptiveStrategy.recordFlush(flushSize, timeSinceLastFlush);\n lastFlushTimestamp = currentTime;\n }\n\n const sink: Sink & Disposable = (record: LogRecord) => {\n if (fd == null) fd = options.openSync(path);\n let formattedRecord: string | undefined;\n let encodedRecord: Uint8Array | undefined;\n\n // ULTRA FAST PATH: Direct write when buffer is empty\n if (byteBuffer.isEmpty()) {\n // Inline everything for maximum speed - avoid all function calls\n formattedRecord = formatter(record);\n encodedRecord = encoder.encode(formattedRecord);\n\n // Only use fast path for typical log sizes to avoid breaking edge cases\n if (encodedRecord.length < 200) {\n // Write directly for small logs - no complex buffering logic\n options.writeSync(fd, encodedRecord);\n options.flushSync(fd);\n const currentTime = Date.now();\n adaptiveStrategy.recordFlush(\n encodedRecord.length,\n currentTime - lastFlushTimestamp,\n );\n lastFlushTimestamp = currentTime;\n return;\n }\n }\n\n // STANDARD PATH: Complex logic for edge cases\n formattedRecord ??= formatter(record);\n encodedRecord ??= encoder.encode(formattedRecord);\n byteBuffer.append(encodedRecord);\n\n // Check for immediate flush conditions\n if (bufferSize <= 0) {\n // No buffering - flush immediately\n flushBuffer();\n } else {\n // Use adaptive strategy for intelligent flushing\n const timeSinceLastFlush = record.timestamp - lastFlushTimestamp;\n const shouldFlush = adaptiveStrategy.shouldFlush(\n byteBuffer.size(),\n timeSinceLastFlush,\n );\n\n if (shouldFlush) {\n flushBuffer();\n }\n }\n };\n sink[Symbol.dispose] = () => {\n if (fd !== null) {\n flushBuffer();\n options.closeSync(fd);\n }\n // Clean up buffer pool\n bufferPool.clear();\n };\n return sink;\n }\n\n // Non-blocking mode implementation\n const asyncOptions = options as AsyncFileSinkDriver<TFile>;\n let disposed = false;\n let activeFlush: Promise<void> | null = null;\n let flushTimer: ReturnType<typeof setInterval> | null = null;\n let bufferedNonMetaRecord = false;\n\n function reportFlushError(error: unknown): void {\n try {\n getLogger([\"logtape\", \"meta\"]).warn(\n \"Non-blocking file sink flush failed for {path}: {error}\",\n { error, path },\n );\n } catch {\n // Last resort: keep non-blocking file sinks from throwing.\n }\n }\n\n async function flushBuffer(): Promise<void> {\n if (fd == null || byteBuffer.isEmpty()) return;\n\n const flushSize = byteBuffer.size();\n const currentTime = Date.now();\n const timeSinceLastFlush = currentTime - lastFlushTimestamp;\n\n const chunks = byteBuffer.flush();\n const suppressErrorReport = !bufferedNonMetaRecord;\n bufferedNonMetaRecord = false;\n try {\n if (asyncOptions.writeMany && chunks.length > 1) {\n // Use async batch write if available\n await asyncOptions.writeMany(fd, chunks);\n } else {\n // Fallback to individual writes\n for (const chunk of chunks) {\n asyncOptions.writeSync(fd, chunk);\n }\n }\n await asyncOptions.flush(fd);\n\n // Record flush for adaptive strategy\n adaptiveStrategy.recordFlush(flushSize, timeSinceLastFlush);\n lastFlushTimestamp = currentTime;\n } catch (error) {\n if (!suppressErrorReport) reportFlushError(error);\n }\n }\n\n function scheduleFlush(): void {\n if (activeFlush || disposed) return;\n\n activeFlush = flushBuffer().finally(() => {\n activeFlush = null;\n if (!disposed && !byteBuffer.isEmpty()) scheduleFlush();\n });\n }\n\n function scheduleDirectFlush(\n flushSize: number,\n suppressErrorReport: boolean,\n ): void {\n if (activeFlush || disposed || fd == null) return;\n\n const flushFd = fd;\n const startedAt = Date.now();\n const timeSinceLastFlush = startedAt - lastFlushTimestamp;\n activeFlush = Promise.resolve()\n .then(() => asyncOptions.flush(flushFd))\n .then(() => {\n adaptiveStrategy.recordFlush(flushSize, timeSinceLastFlush);\n lastFlushTimestamp = Date.now();\n })\n .catch((error) => {\n if (!suppressErrorReport) reportFlushError(error);\n })\n .finally(() => {\n activeFlush = null;\n if (!disposed && !byteBuffer.isEmpty()) scheduleFlush();\n });\n }\n\n function startFlushTimer(): void {\n if (flushTimer !== null || disposed) return;\n\n flushTimer = setInterval(() => {\n scheduleFlush();\n }, flushInterval);\n }\n\n const nonBlockingSink: Sink & AsyncDisposable = (record: LogRecord) => {\n if (disposed) return;\n if (fd == null) fd = asyncOptions.openSync(path);\n let formattedRecord: string | undefined;\n let encodedRecord: Uint8Array | undefined;\n\n // ULTRA FAST PATH: Direct write when buffer is empty\n if (byteBuffer.isEmpty() && !activeFlush) {\n // Inline everything for maximum speed - avoid all function calls\n formattedRecord = formatter(record);\n encodedRecord = encoder.encode(formattedRecord);\n\n // Only use fast path for typical log sizes to avoid breaking edge cases\n if (encodedRecord.length < 200) {\n // Write directly for small logs - no complex buffering logic\n asyncOptions.writeSync(fd, encodedRecord);\n scheduleDirectFlush(encodedRecord.length, isMetaLoggerRecord(record));\n return;\n }\n }\n\n // STANDARD PATH: Complex logic for edge cases\n formattedRecord ??= formatter(record);\n encodedRecord ??= encoder.encode(formattedRecord);\n byteBuffer.append(encodedRecord);\n bufferedNonMetaRecord ||= !isMetaLoggerRecord(record);\n\n // Check for immediate flush conditions\n if (bufferSize <= 0) {\n // No buffering - flush immediately\n scheduleFlush();\n } else {\n // Use adaptive strategy for intelligent flushing\n const timeSinceLastFlush = record.timestamp - lastFlushTimestamp;\n const shouldFlush = adaptiveStrategy.shouldFlush(\n byteBuffer.size(),\n timeSinceLastFlush,\n );\n\n if (shouldFlush) {\n scheduleFlush();\n } else if (flushTimer === null && flushInterval > 0) {\n startFlushTimer();\n }\n }\n };\n\n nonBlockingSink[Symbol.asyncDispose] = async () => {\n disposed = true;\n if (flushTimer !== null) {\n clearInterval(flushTimer);\n flushTimer = null;\n }\n if (activeFlush !== null) await activeFlush;\n await flushBuffer();\n if (fd !== null) {\n try {\n await asyncOptions.close(fd);\n } catch {\n // Writer might already be closed or errored\n }\n }\n // Clean up buffer pool\n bufferPool.clear();\n };\n\n return nonBlockingSink;\n}\n\n/**\n * Options for the {@link getBaseRotatingFileSink} function.\n */\nexport interface RotatingFileSinkOptions extends Omit<FileSinkOptions, \"lazy\"> {\n /**\n * The maximum bytes of the file before it is rotated. 1 MiB by default.\n */\n maxSize?: number;\n\n /**\n * The maximum number of files to keep. 5 by default.\n */\n maxFiles?: number;\n}\n\n/**\n * A platform-specific rotating file sink driver.\n */\nexport interface RotatingFileSinkDriver<TFile> extends FileSinkDriver<TFile> {\n /**\n * Get the size of the file.\n * @param path A path to the file.\n * @returns The `size` of the file in bytes, in an object.\n */\n statSync(path: string): { size: number };\n\n /**\n * Rename a file.\n * @param oldPath A path to the file to rename.\n * @param newPath A path to be renamed to.\n */\n renameSync(oldPath: string, newPath: string): void;\n\n /**\n * Delete a file.\n * @param path A path to the file to delete.\n */\n unlinkSync?(path: string): void;\n}\n\n/**\n * A platform-specific async rotating file sink driver.\n * @since 1.0.0\n */\nexport interface AsyncRotatingFileSinkDriver<TFile>\n extends AsyncFileSinkDriver<TFile> {\n /**\n * Get the size of the file.\n * @param path A path to the file.\n * @returns The `size` of the file in bytes, in an object.\n */\n statSync(path: string): { size: number };\n\n /**\n * Rename a file.\n * @param oldPath A path to the file to rename.\n * @param newPath A path to be renamed to.\n */\n renameSync(oldPath: string, newPath: string): void;\n\n /**\n * Delete a file.\n * @param path A path to the file to delete.\n */\n unlinkSync?(path: string): void;\n}\n\nfunction isFileNotFoundError(error: unknown): boolean {\n if (!(error instanceof Error)) return false;\n const errorWithCode = error as Error & { code?: unknown };\n return errorWithCode.code === \"ENOENT\" || error.name === \"NotFound\";\n}\n\n/**\n * Get a platform-independent rotating file sink.\n *\n * This sink writes log records to a file, and rotates the file when it reaches\n * the `maxSize`. The rotated files are named with the original file name\n * followed by a dot and a number, starting from 1. The number is incremented\n * for each rotation, and the maximum number of files to keep is `maxFiles`.\n *\n * @param path A path to the file to write to.\n * @param options The options for the sink and the file driver.\n * @returns A sink that writes to the file. The sink is also a disposable\n * object that closes the file when disposed. If `nonBlocking` is enabled,\n * returns a sink that also implements {@link AsyncDisposable}.\n */\nexport function getBaseRotatingFileSink<TFile>(\n path: string,\n options: RotatingFileSinkOptions & RotatingFileSinkDriver<TFile>,\n): Sink & Disposable;\nexport function getBaseRotatingFileSink<TFile>(\n path: string,\n options: RotatingFileSinkOptions & AsyncRotatingFileSinkDriver<TFile>,\n): Sink & AsyncDisposable;\nexport function getBaseRotatingFileSink<TFile>(\n path: string,\n options:\n & RotatingFileSinkOptions\n & (RotatingFileSinkDriver<TFile> | AsyncRotatingFileSinkDriver<TFile>),\n): Sink & (Disposable | AsyncDisposable) {\n const formatter = options.formatter ?? defaultTextFormatter;\n const encoder = options.encoder ?? new TextEncoder();\n const maxSize = options.maxSize ?? 1024 * 1024;\n const maxFiles = options.maxFiles ?? 5;\n const bufferSize = options.bufferSize ?? 1024 * 8; // Default buffer size of 8192 chars\n const flushInterval = options.flushInterval ?? 5000; // Default flush interval of 5 seconds\n if (maxFiles <= 0 && options.unlinkSync == null) {\n throw new TypeError(\"maxFiles <= 0 requires unlinkSync support.\");\n }\n let offset: number = 0;\n try {\n const stat = options.statSync(path);\n offset = stat.size;\n } catch {\n // Continue as the offset is already 0.\n }\n let fd = options.openSync(path);\n let lastFlushTimestamp: number = Date.now();\n let buffer: string = \"\";\n\n function shouldRollover(bytes: Uint8Array): boolean {\n return offset + bytes.length > maxSize;\n }\n function performRollover(): void {\n if (maxFiles <= 0) {\n const unlinkSync = options.unlinkSync;\n if (unlinkSync == null) return;\n\n options.closeSync(fd);\n try {\n unlinkSync(path);\n } catch (error) {\n if (!isFileNotFoundError(error)) {\n try {\n offset = options.statSync(path).size;\n } catch {\n offset = 0;\n }\n fd = options.openSync(path);\n throw error;\n }\n }\n offset = 0;\n fd = options.openSync(path);\n return;\n }\n\n options.closeSync(fd);\n\n for (let i = maxFiles - 1; i > 0; i--) {\n const oldPath = `${path}.${i}`;\n const newPath = `${path}.${i + 1}`;\n try {\n options.renameSync(oldPath, newPath);\n } catch (_) {\n // Continue if the file does not exist.\n }\n }\n options.renameSync(path, `${path}.1`);\n offset = 0;\n fd = options.openSync(path);\n }\n\n if (!options.nonBlocking) {\n // Blocking mode implementation\n // deno-lint-ignore no-inner-declarations\n function flushBuffer(): void {\n if (buffer.length > 0) {\n const bytes = encoder.encode(buffer);\n buffer = \"\";\n if (shouldRollover(bytes)) performRollover();\n options.writeSync(fd, bytes);\n options.flushSync(fd);\n offset += bytes.length;\n lastFlushTimestamp = Date.now();\n }\n }\n\n const sink: Sink & Disposable = (record: LogRecord) => {\n buffer += formatter(record);\n\n const shouldFlushBySize = buffer.length >= bufferSize;\n const shouldFlushByTime = flushInterval > 0 &&\n (record.timestamp - lastFlushTimestamp) >= flushInterval;\n\n if (shouldFlushBySize || shouldFlushByTime) {\n flushBuffer();\n }\n };\n sink[Symbol.dispose] = () => {\n flushBuffer();\n options.closeSync(fd);\n };\n return sink;\n }\n\n // Non-blocking mode implementation\n const asyncOptions = options as AsyncRotatingFileSinkDriver<TFile>;\n let disposed = false;\n let activeFlush: Promise<void> | null = null;\n let flushTimer: ReturnType<typeof setInterval> | null = null;\n let bufferedNonMetaRecord = false;\n\n function reportFlushError(error: unknown): void {\n try {\n getLogger([\"logtape\", \"meta\"]).warn(\n \"Non-blocking rotating file sink flush failed for {path}: {error}\",\n { error, path },\n );\n } catch {\n // Last resort: keep non-blocking file sinks from throwing.\n }\n }\n\n async function flushBuffer(): Promise<void> {\n if (buffer.length === 0) return;\n\n const data = buffer;\n buffer = \"\";\n const suppressErrorReport = !bufferedNonMetaRecord;\n bufferedNonMetaRecord = false;\n try {\n const bytes = encoder.encode(data);\n if (shouldRollover(bytes)) performRollover();\n asyncOptions.writeSync(fd, bytes);\n await asyncOptions.flush(fd);\n offset += bytes.length;\n lastFlushTimestamp = Date.now();\n } catch (error) {\n if (!suppressErrorReport) reportFlushError(error);\n }\n }\n\n function scheduleFlush(): void {\n if (activeFlush || disposed) return;\n\n activeFlush = flushBuffer().finally(() => {\n activeFlush = null;\n });\n }\n\n function startFlushTimer(): void {\n if (flushTimer !== null || disposed) return;\n\n flushTimer = setInterval(() => {\n scheduleFlush();\n }, flushInterval);\n }\n\n const nonBlockingSink: Sink & AsyncDisposable = (record: LogRecord) => {\n if (disposed) return;\n buffer += formatter(record);\n bufferedNonMetaRecord ||= !isMetaLoggerRecord(record);\n\n const shouldFlushBySize = buffer.length >= bufferSize;\n const shouldFlushByTime = flushInterval > 0 &&\n (record.timestamp - lastFlushTimestamp) >= flushInterval;\n\n if (shouldFlushBySize || shouldFlushByTime) {\n scheduleFlush();\n } else if (flushTimer === null && flushInterval > 0) {\n startFlushTimer();\n }\n };\n\n nonBlockingSink[Symbol.asyncDispose] = async () => {\n disposed = true;\n if (flushTimer !== null) {\n clearInterval(flushTimer);\n flushTimer = null;\n }\n if (activeFlush !== null) await activeFlush;\n await flushBuffer();\n try {\n await asyncOptions.close(fd);\n } catch {\n // Writer might already be closed or errored\n }\n };\n\n return nonBlockingSink;\n}\n"],"mappings":"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"}
@@ -26,7 +26,8 @@ const denoDriver = {
26
26
  fd.close();
27
27
  },
28
28
  statSync: globalThis?.Deno.statSync,
29
- renameSync: globalThis?.Deno.renameSync
29
+ renameSync: globalThis?.Deno.renameSync,
30
+ unlinkSync: globalThis?.Deno.removeSync
30
31
  };
31
32
  /**
32
33
  * A Deno-specific async file sink driver.
@@ -1 +1 @@
1
- {"version":3,"file":"filesink.deno.d.cts","names":[],"sources":["../src/filesink.deno.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAoBA;AAsBC,cAtBY,UAsBZ,EAtBwB,sBAsBxB,CAtB+C,IAAA,CAAK,MAsBpD,CAAA;;;AAtB8C;AA4B/C;AAeC,cAfY,eAeZ,EAf6B,2BAe7B,CAfyD,IAAA,CAAK,MAe9D,CAAA;;;AAfwD;AAqBzD;AAUC,cAVY,cAUZ,EAV4B,0BAU5B,CAVuD,IAAA,CAAK,MAU5D,CAAA;;;AAVsD;AAgBvD;AAWG,cAXU,mBAWV,EAX+B,+BAW/B,CAX+D,IAAA,CAAK,MAWpE,CAAA;;;AAX8D;AAwBjE;;;;;AAGoB;AACpB;;AAEW,iBANK,WAAA,CAML,IAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAJC,eAID,CAAA,EAHR,IAGQ,GAHD,UAGC;AACR,iBAHa,WAAA,CAGb,IAAA,EAAA,MAAA,EAAA,OAAA,EADQ,eACR,GAAA;EAAI,WAAG,EAAA,IAAA;AAAe,CAAA,CAAA,EAAtB,IAAsB,GAAf,eAAe;AA2BzB;;;;;AAGoB;AACpB;;;;;AAGyB;AA0BzB;;;;AAEU,iBAnCM,mBAAA,CAmCN,IAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAjCE,uBAiCF,CAAA,EAhCP,IAgCO,GAhCA,UAgCA;AAAU,iBA/BJ,mBAAA,CA+BI,IAAA,EAAA,MAAA,EAAA,OAAA,EA7BT,uBA6BS,GAAA;EACJ,WAAA,EAAA,IAAA;CAAuB,CAAA,EA7BpC,IA6BoC,GA7B7B,eA6B6B;;;;AAEd;;;;;;;;;;;;iBALT,uBAAA,UACL,8BACR,OAAO;iBACM,uBAAA,UACL;;IACR,OAAO"}
1
+ {"version":3,"file":"filesink.deno.d.cts","names":[],"sources":["../src/filesink.deno.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAoBA;AAuBC,cAvBY,UAuBZ,EAvBwB,sBAuBxB,CAvB+C,IAAA,CAAK,MAuBpD,CAAA;;;AAvB8C;AA6B/C;AAeC,cAfY,eAeZ,EAf6B,2BAe7B,CAfyD,IAAA,CAAK,MAe9D,CAAA;;;AAfwD;AAqBzD;AAUC,cAVY,cAUZ,EAV4B,0BAU5B,CAVuD,IAAA,CAAK,MAU5D,CAAA;;;AAVsD;AAgBvD;AAWG,cAXU,mBAWV,EAX+B,+BAW/B,CAX+D,IAAA,CAAK,MAWpE,CAAA;;;AAX8D;AAwBjE;;;;;AAGoB;AACpB;;AAEW,iBANK,WAAA,CAML,IAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAJC,eAID,CAAA,EAHR,IAGQ,GAHD,UAGC;AACR,iBAHa,WAAA,CAGb,IAAA,EAAA,MAAA,EAAA,OAAA,EADQ,eACR,GAAA;EAAI,WAAG,EAAA,IAAA;AAAe,CAAA,CAAA,EAAtB,IAAsB,GAAf,eAAe;AA2BzB;;;;;AAGoB;AACpB;;;;;AAGyB;AA0BzB;;;;AAEU,iBAnCM,mBAAA,CAmCN,IAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAjCE,uBAiCF,CAAA,EAhCP,IAgCO,GAhCA,UAgCA;AAAU,iBA/BJ,mBAAA,CA+BI,IAAA,EAAA,MAAA,EAAA,OAAA,EA7BT,uBA6BS,GAAA;EACJ,WAAA,EAAA,IAAA;CAAuB,CAAA,EA7BpC,IA6BoC,GA7B7B,eA6B6B;;;;AAEd;;;;;;;;;;;;iBALT,uBAAA,UACL,8BACR,OAAO;iBACM,uBAAA,UACL;;IACR,OAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"filesink.deno.d.ts","names":[],"sources":["../src/filesink.deno.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAoBA;AAsBC,cAtBY,UAsBZ,EAtBwB,sBAsBxB,CAtB+C,IAAA,CAAK,MAsBpD,CAAA;;;AAtB8C;AA4B/C;AAeC,cAfY,eAeZ,EAf6B,2BAe7B,CAfyD,IAAA,CAAK,MAe9D,CAAA;;;AAfwD;AAqBzD;AAUC,cAVY,cAUZ,EAV4B,0BAU5B,CAVuD,IAAA,CAAK,MAU5D,CAAA;;;AAVsD;AAgBvD;AAWG,cAXU,mBAWV,EAX+B,+BAW/B,CAX+D,IAAA,CAAK,MAWpE,CAAA;;;AAX8D;AAwBjE;;;;;AAGoB;AACpB;;AAEW,iBANK,WAAA,CAML,IAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAJC,eAID,CAAA,EAHR,IAGQ,GAHD,UAGC;AACR,iBAHa,WAAA,CAGb,IAAA,EAAA,MAAA,EAAA,OAAA,EADQ,eACR,GAAA;EAAI,WAAG,EAAA,IAAA;AAAe,CAAA,CAAA,EAAtB,IAAsB,GAAf,eAAe;AA2BzB;;;;;AAGoB;AACpB;;;;;AAGyB;AA0BzB;;;;AAEU,iBAnCM,mBAAA,CAmCN,IAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAjCE,uBAiCF,CAAA,EAhCP,IAgCO,GAhCA,UAgCA;AAAU,iBA/BJ,mBAAA,CA+BI,IAAA,EAAA,MAAA,EAAA,OAAA,EA7BT,uBA6BS,GAAA;EACJ,WAAA,EAAA,IAAA;CAAuB,CAAA,EA7BpC,IA6BoC,GA7B7B,eA6B6B;;;;AAEd;;;;;;;;;;;;iBALT,uBAAA,UACL,8BACR,OAAO;iBACM,uBAAA,UACL;;IACR,OAAO"}
1
+ {"version":3,"file":"filesink.deno.d.ts","names":[],"sources":["../src/filesink.deno.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAoBA;AAuBC,cAvBY,UAuBZ,EAvBwB,sBAuBxB,CAvB+C,IAAA,CAAK,MAuBpD,CAAA;;;AAvB8C;AA6B/C;AAeC,cAfY,eAeZ,EAf6B,2BAe7B,CAfyD,IAAA,CAAK,MAe9D,CAAA;;;AAfwD;AAqBzD;AAUC,cAVY,cAUZ,EAV4B,0BAU5B,CAVuD,IAAA,CAAK,MAU5D,CAAA;;;AAVsD;AAgBvD;AAWG,cAXU,mBAWV,EAX+B,+BAW/B,CAX+D,IAAA,CAAK,MAWpE,CAAA;;;AAX8D;AAwBjE;;;;;AAGoB;AACpB;;AAEW,iBANK,WAAA,CAML,IAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAJC,eAID,CAAA,EAHR,IAGQ,GAHD,UAGC;AACR,iBAHa,WAAA,CAGb,IAAA,EAAA,MAAA,EAAA,OAAA,EADQ,eACR,GAAA;EAAI,WAAG,EAAA,IAAA;AAAe,CAAA,CAAA,EAAtB,IAAsB,GAAf,eAAe;AA2BzB;;;;;AAGoB;AACpB;;;;;AAGyB;AA0BzB;;;;AAEU,iBAnCM,mBAAA,CAmCN,IAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAjCE,uBAiCF,CAAA,EAhCP,IAgCO,GAhCA,UAgCA;AAAU,iBA/BJ,mBAAA,CA+BI,IAAA,EAAA,MAAA,EAAA,OAAA,EA7BT,uBA6BS,GAAA;EACJ,WAAA,EAAA,IAAA;CAAuB,CAAA,EA7BpC,IA6BoC,GA7B7B,eA6B6B;;;;AAEd;;;;;;;;;;;;iBALT,uBAAA,UACL,8BACR,OAAO;iBACM,uBAAA,UACL;;IACR,OAAO"}
@@ -26,7 +26,8 @@ const denoDriver = {
26
26
  fd.close();
27
27
  },
28
28
  statSync: globalThis?.Deno.statSync,
29
- renameSync: globalThis?.Deno.renameSync
29
+ renameSync: globalThis?.Deno.renameSync,
30
+ unlinkSync: globalThis?.Deno.removeSync
30
31
  };
31
32
  /**
32
33
  * A Deno-specific async file sink driver.
@@ -1 +1 @@
1
- {"version":3,"file":"filesink.deno.js","names":["denoDriver: RotatingFileSinkDriver<Deno.FsFile>","path: string","fd: Deno.FsFile","chunks: Uint8Array[]","denoAsyncDriver: AsyncRotatingFileSinkDriver<Deno.FsFile>","denoTimeDriver: TimeRotatingFileSinkDriver<Deno.FsFile>","options?: { recursive?: boolean }","denoAsyncTimeDriver: AsyncTimeRotatingFileSinkDriver<Deno.FsFile>","options: FileSinkOptions","options: RotatingFileSinkOptions","options: TimeRotatingFileSinkOptions"],"sources":["../src/filesink.deno.ts"],"sourcesContent":["import type { Sink } from \"@logtape/logtape\";\nimport { join } from \"@std/path/join\";\nimport {\n type AsyncRotatingFileSinkDriver,\n type FileSinkOptions,\n getBaseFileSink,\n getBaseRotatingFileSink,\n type RotatingFileSinkDriver,\n type RotatingFileSinkOptions,\n} from \"./filesink.base.ts\";\nimport {\n type AsyncTimeRotatingFileSinkDriver,\n getBaseTimeRotatingFileSink,\n type TimeRotatingFileSinkDriver,\n type TimeRotatingFileSinkOptions,\n} from \"./timefilesink.ts\";\n\n/**\n * A Deno-specific file sink driver.\n */\nexport const denoDriver: RotatingFileSinkDriver<Deno.FsFile> = {\n openSync(path: string) {\n return Deno.openSync(path, { create: true, append: true });\n },\n writeSync(fd, chunk) {\n fd.writeSync(chunk);\n },\n writeManySync(fd: Deno.FsFile, chunks: Uint8Array[]): void {\n // Deno doesn't have writev, but we can optimize by writing all chunks\n // then doing a single sync operation\n for (const chunk of chunks) {\n fd.writeSync(chunk);\n }\n },\n flushSync(fd) {\n fd.syncSync();\n },\n closeSync(fd) {\n fd.close();\n },\n statSync: globalThis?.Deno.statSync,\n renameSync: globalThis?.Deno.renameSync,\n};\n\n/**\n * A Deno-specific async file sink driver.\n * @since 1.0.0\n */\nexport const denoAsyncDriver: AsyncRotatingFileSinkDriver<Deno.FsFile> = {\n ...denoDriver,\n async writeMany(fd: Deno.FsFile, chunks: Uint8Array[]): Promise<void> {\n // Deno doesn't have async writev, but we can write all chunks\n // then do a single async sync\n for (const chunk of chunks) {\n await fd.write(chunk);\n }\n },\n async flush(fd) {\n await fd.sync();\n },\n close(fd) {\n return Promise.resolve(fd.close());\n },\n};\n\n/**\n * A Deno-specific time-rotating file sink driver.\n * @since 2.0.0\n */\nexport const denoTimeDriver: TimeRotatingFileSinkDriver<Deno.FsFile> = {\n ...denoDriver,\n readdirSync(path: string) {\n return [...Deno.readDirSync(path)].map((entry) => entry.name);\n },\n unlinkSync: globalThis?.Deno.removeSync,\n mkdirSync(path: string, options?: { recursive?: boolean }) {\n Deno.mkdirSync(path, options);\n },\n joinPath: join,\n};\n\n/**\n * A Deno-specific async time-rotating file sink driver.\n * @since 2.0.0\n */\nexport const denoAsyncTimeDriver: AsyncTimeRotatingFileSinkDriver<Deno.FsFile> =\n {\n ...denoAsyncDriver,\n readdirSync(path: string) {\n return [...Deno.readDirSync(path)].map((entry) => entry.name);\n },\n unlinkSync: globalThis?.Deno.removeSync,\n mkdirSync(path: string, options?: { recursive?: boolean }) {\n Deno.mkdirSync(path, options);\n },\n joinPath: join,\n };\n\n/**\n * Get a file sink.\n *\n * Note that this function is unavailable in the browser.\n *\n * @param path A path to the file to write to.\n * @param options The options for the sink.\n * @returns A sink that writes to the file. The sink is also a disposable\n * object that closes the file when disposed. If `nonBlocking` is enabled,\n * returns a sink that also implements {@link AsyncDisposable}.\n */\nexport function getFileSink(\n path: string,\n options?: FileSinkOptions,\n): Sink & Disposable;\nexport function getFileSink(\n path: string,\n options: FileSinkOptions & { nonBlocking: true },\n): Sink & AsyncDisposable;\nexport function getFileSink(\n path: string,\n options: FileSinkOptions = {},\n): Sink & (Disposable | AsyncDisposable) {\n if (options.nonBlocking) {\n return getBaseFileSink(path, { ...options, ...denoAsyncDriver });\n }\n return getBaseFileSink(path, { ...options, ...denoDriver });\n}\n\n/**\n * Get a rotating file sink.\n *\n * This sink writes log records to a file, and rotates the file when it reaches\n * the `maxSize`. The rotated files are named with the original file name\n * followed by a dot and a number, starting from 1. The number is incremented\n * for each rotation, and the maximum number of files to keep is `maxFiles`.\n *\n * Note that this function is unavailable in the browser.\n *\n * @param path A path to the file to write to.\n * @param options The options for the sink and the file driver.\n * @returns A sink that writes to the file. The sink is also a disposable\n * object that closes the file when disposed. If `nonBlocking` is enabled,\n * returns a sink that also implements {@link AsyncDisposable}.\n */\nexport function getRotatingFileSink(\n path: string,\n options?: RotatingFileSinkOptions,\n): Sink & Disposable;\nexport function getRotatingFileSink(\n path: string,\n options: RotatingFileSinkOptions & { nonBlocking: true },\n): Sink & AsyncDisposable;\nexport function getRotatingFileSink(\n path: string,\n options: RotatingFileSinkOptions = {},\n): Sink & (Disposable | AsyncDisposable) {\n if (options.nonBlocking) {\n return getBaseRotatingFileSink(path, { ...options, ...denoAsyncDriver });\n }\n return getBaseRotatingFileSink(path, { ...options, ...denoDriver });\n}\n\n/**\n * Get a time-rotating file sink.\n *\n * This sink writes log records to a file in a directory, rotating to a new\n * file based on time intervals. The filename is generated based on the\n * current date/time and the configured interval.\n *\n * Note that this function is unavailable in the browser.\n *\n * @param options The options for the sink.\n * @returns A sink that writes to the file. The sink is also a disposable\n * object that closes the file when disposed. If `nonBlocking` is\n * enabled, returns a sink that also implements {@link AsyncDisposable}.\n * @since 2.0.0\n */\nexport function getTimeRotatingFileSink(\n options: TimeRotatingFileSinkOptions,\n): Sink & Disposable;\nexport function getTimeRotatingFileSink(\n options: TimeRotatingFileSinkOptions & { nonBlocking: true },\n): Sink & AsyncDisposable;\nexport function getTimeRotatingFileSink(\n options: TimeRotatingFileSinkOptions,\n): Sink & (Disposable | AsyncDisposable) {\n if (options.nonBlocking) {\n return getBaseTimeRotatingFileSink({ ...options, ...denoAsyncTimeDriver });\n }\n return getBaseTimeRotatingFileSink({ ...options, ...denoTimeDriver });\n}\n\n// cSpell: ignore filesink\n"],"mappings":";;;;;;;;AAoBA,MAAaA,aAAkD;CAC7D,SAASC,MAAc;AACrB,SAAO,KAAK,SAAS,MAAM;GAAE,QAAQ;GAAM,QAAQ;EAAM,EAAC;CAC3D;CACD,UAAU,IAAI,OAAO;AACnB,KAAG,UAAU,MAAM;CACpB;CACD,cAAcC,IAAiBC,QAA4B;AAGzD,OAAK,MAAM,SAAS,OAClB,IAAG,UAAU,MAAM;CAEtB;CACD,UAAU,IAAI;AACZ,KAAG,UAAU;CACd;CACD,UAAU,IAAI;AACZ,KAAG,OAAO;CACX;CACD,UAAU,YAAY,KAAK;CAC3B,YAAY,YAAY,KAAK;AAC9B;;;;;AAMD,MAAaC,kBAA4D;CACvE,GAAG;CACH,MAAM,UAAUF,IAAiBC,QAAqC;AAGpE,OAAK,MAAM,SAAS,OAClB,OAAM,GAAG,MAAM,MAAM;CAExB;CACD,MAAM,MAAM,IAAI;AACd,QAAM,GAAG,MAAM;CAChB;CACD,MAAM,IAAI;AACR,SAAO,QAAQ,QAAQ,GAAG,OAAO,CAAC;CACnC;AACF;;;;;AAMD,MAAaE,iBAA0D;CACrE,GAAG;CACH,YAAYJ,MAAc;AACxB,SAAO,CAAC,GAAG,KAAK,YAAY,KAAK,AAAC,EAAC,IAAI,CAAC,UAAU,MAAM,KAAK;CAC9D;CACD,YAAY,YAAY,KAAK;CAC7B,UAAUA,MAAcK,SAAmC;AACzD,OAAK,UAAU,MAAM,QAAQ;CAC9B;CACD,UAAU;AACX;;;;;AAMD,MAAaC,sBACX;CACE,GAAG;CACH,YAAYN,MAAc;AACxB,SAAO,CAAC,GAAG,KAAK,YAAY,KAAK,AAAC,EAAC,IAAI,CAAC,UAAU,MAAM,KAAK;CAC9D;CACD,YAAY,YAAY,KAAK;CAC7B,UAAUA,MAAcK,SAAmC;AACzD,OAAK,UAAU,MAAM,QAAQ;CAC9B;CACD,UAAU;AACX;AAqBH,SAAgB,YACdL,MACAO,UAA2B,CAAE,GACU;AACvC,KAAI,QAAQ,YACV,QAAO,gBAAgB,MAAM;EAAE,GAAG;EAAS,GAAG;CAAiB,EAAC;AAElE,QAAO,gBAAgB,MAAM;EAAE,GAAG;EAAS,GAAG;CAAY,EAAC;AAC5D;AA0BD,SAAgB,oBACdP,MACAQ,UAAmC,CAAE,GACE;AACvC,KAAI,QAAQ,YACV,QAAO,wBAAwB,MAAM;EAAE,GAAG;EAAS,GAAG;CAAiB,EAAC;AAE1E,QAAO,wBAAwB,MAAM;EAAE,GAAG;EAAS,GAAG;CAAY,EAAC;AACpE;AAuBD,SAAgB,wBACdC,SACuC;AACvC,KAAI,QAAQ,YACV,QAAO,4BAA4B;EAAE,GAAG;EAAS,GAAG;CAAqB,EAAC;AAE5E,QAAO,4BAA4B;EAAE,GAAG;EAAS,GAAG;CAAgB,EAAC;AACtE"}
1
+ {"version":3,"file":"filesink.deno.js","names":["denoDriver: RotatingFileSinkDriver<Deno.FsFile>","path: string","fd: Deno.FsFile","chunks: Uint8Array[]","denoAsyncDriver: AsyncRotatingFileSinkDriver<Deno.FsFile>","denoTimeDriver: TimeRotatingFileSinkDriver<Deno.FsFile>","options?: { recursive?: boolean }","denoAsyncTimeDriver: AsyncTimeRotatingFileSinkDriver<Deno.FsFile>","options: FileSinkOptions","options: RotatingFileSinkOptions","options: TimeRotatingFileSinkOptions"],"sources":["../src/filesink.deno.ts"],"sourcesContent":["import type { Sink } from \"@logtape/logtape\";\nimport { join } from \"@std/path/join\";\nimport {\n type AsyncRotatingFileSinkDriver,\n type FileSinkOptions,\n getBaseFileSink,\n getBaseRotatingFileSink,\n type RotatingFileSinkDriver,\n type RotatingFileSinkOptions,\n} from \"./filesink.base.ts\";\nimport {\n type AsyncTimeRotatingFileSinkDriver,\n getBaseTimeRotatingFileSink,\n type TimeRotatingFileSinkDriver,\n type TimeRotatingFileSinkOptions,\n} from \"./timefilesink.ts\";\n\n/**\n * A Deno-specific file sink driver.\n */\nexport const denoDriver: RotatingFileSinkDriver<Deno.FsFile> = {\n openSync(path: string) {\n return Deno.openSync(path, { create: true, append: true });\n },\n writeSync(fd, chunk) {\n fd.writeSync(chunk);\n },\n writeManySync(fd: Deno.FsFile, chunks: Uint8Array[]): void {\n // Deno doesn't have writev, but we can optimize by writing all chunks\n // then doing a single sync operation\n for (const chunk of chunks) {\n fd.writeSync(chunk);\n }\n },\n flushSync(fd) {\n fd.syncSync();\n },\n closeSync(fd) {\n fd.close();\n },\n statSync: globalThis?.Deno.statSync,\n renameSync: globalThis?.Deno.renameSync,\n unlinkSync: globalThis?.Deno.removeSync,\n};\n\n/**\n * A Deno-specific async file sink driver.\n * @since 1.0.0\n */\nexport const denoAsyncDriver: AsyncRotatingFileSinkDriver<Deno.FsFile> = {\n ...denoDriver,\n async writeMany(fd: Deno.FsFile, chunks: Uint8Array[]): Promise<void> {\n // Deno doesn't have async writev, but we can write all chunks\n // then do a single async sync\n for (const chunk of chunks) {\n await fd.write(chunk);\n }\n },\n async flush(fd) {\n await fd.sync();\n },\n close(fd) {\n return Promise.resolve(fd.close());\n },\n};\n\n/**\n * A Deno-specific time-rotating file sink driver.\n * @since 2.0.0\n */\nexport const denoTimeDriver: TimeRotatingFileSinkDriver<Deno.FsFile> = {\n ...denoDriver,\n readdirSync(path: string) {\n return [...Deno.readDirSync(path)].map((entry) => entry.name);\n },\n unlinkSync: globalThis?.Deno.removeSync,\n mkdirSync(path: string, options?: { recursive?: boolean }) {\n Deno.mkdirSync(path, options);\n },\n joinPath: join,\n};\n\n/**\n * A Deno-specific async time-rotating file sink driver.\n * @since 2.0.0\n */\nexport const denoAsyncTimeDriver: AsyncTimeRotatingFileSinkDriver<Deno.FsFile> =\n {\n ...denoAsyncDriver,\n readdirSync(path: string) {\n return [...Deno.readDirSync(path)].map((entry) => entry.name);\n },\n unlinkSync: globalThis?.Deno.removeSync,\n mkdirSync(path: string, options?: { recursive?: boolean }) {\n Deno.mkdirSync(path, options);\n },\n joinPath: join,\n };\n\n/**\n * Get a file sink.\n *\n * Note that this function is unavailable in the browser.\n *\n * @param path A path to the file to write to.\n * @param options The options for the sink.\n * @returns A sink that writes to the file. The sink is also a disposable\n * object that closes the file when disposed. If `nonBlocking` is enabled,\n * returns a sink that also implements {@link AsyncDisposable}.\n */\nexport function getFileSink(\n path: string,\n options?: FileSinkOptions,\n): Sink & Disposable;\nexport function getFileSink(\n path: string,\n options: FileSinkOptions & { nonBlocking: true },\n): Sink & AsyncDisposable;\nexport function getFileSink(\n path: string,\n options: FileSinkOptions = {},\n): Sink & (Disposable | AsyncDisposable) {\n if (options.nonBlocking) {\n return getBaseFileSink(path, { ...options, ...denoAsyncDriver });\n }\n return getBaseFileSink(path, { ...options, ...denoDriver });\n}\n\n/**\n * Get a rotating file sink.\n *\n * This sink writes log records to a file, and rotates the file when it reaches\n * the `maxSize`. The rotated files are named with the original file name\n * followed by a dot and a number, starting from 1. The number is incremented\n * for each rotation, and the maximum number of files to keep is `maxFiles`.\n *\n * Note that this function is unavailable in the browser.\n *\n * @param path A path to the file to write to.\n * @param options The options for the sink and the file driver.\n * @returns A sink that writes to the file. The sink is also a disposable\n * object that closes the file when disposed. If `nonBlocking` is enabled,\n * returns a sink that also implements {@link AsyncDisposable}.\n */\nexport function getRotatingFileSink(\n path: string,\n options?: RotatingFileSinkOptions,\n): Sink & Disposable;\nexport function getRotatingFileSink(\n path: string,\n options: RotatingFileSinkOptions & { nonBlocking: true },\n): Sink & AsyncDisposable;\nexport function getRotatingFileSink(\n path: string,\n options: RotatingFileSinkOptions = {},\n): Sink & (Disposable | AsyncDisposable) {\n if (options.nonBlocking) {\n return getBaseRotatingFileSink(path, { ...options, ...denoAsyncDriver });\n }\n return getBaseRotatingFileSink(path, { ...options, ...denoDriver });\n}\n\n/**\n * Get a time-rotating file sink.\n *\n * This sink writes log records to a file in a directory, rotating to a new\n * file based on time intervals. The filename is generated based on the\n * current date/time and the configured interval.\n *\n * Note that this function is unavailable in the browser.\n *\n * @param options The options for the sink.\n * @returns A sink that writes to the file. The sink is also a disposable\n * object that closes the file when disposed. If `nonBlocking` is\n * enabled, returns a sink that also implements {@link AsyncDisposable}.\n * @since 2.0.0\n */\nexport function getTimeRotatingFileSink(\n options: TimeRotatingFileSinkOptions,\n): Sink & Disposable;\nexport function getTimeRotatingFileSink(\n options: TimeRotatingFileSinkOptions & { nonBlocking: true },\n): Sink & AsyncDisposable;\nexport function getTimeRotatingFileSink(\n options: TimeRotatingFileSinkOptions,\n): Sink & (Disposable | AsyncDisposable) {\n if (options.nonBlocking) {\n return getBaseTimeRotatingFileSink({ ...options, ...denoAsyncTimeDriver });\n }\n return getBaseTimeRotatingFileSink({ ...options, ...denoTimeDriver });\n}\n\n// cSpell: ignore filesink\n"],"mappings":";;;;;;;;AAoBA,MAAaA,aAAkD;CAC7D,SAASC,MAAc;AACrB,SAAO,KAAK,SAAS,MAAM;GAAE,QAAQ;GAAM,QAAQ;EAAM,EAAC;CAC3D;CACD,UAAU,IAAI,OAAO;AACnB,KAAG,UAAU,MAAM;CACpB;CACD,cAAcC,IAAiBC,QAA4B;AAGzD,OAAK,MAAM,SAAS,OAClB,IAAG,UAAU,MAAM;CAEtB;CACD,UAAU,IAAI;AACZ,KAAG,UAAU;CACd;CACD,UAAU,IAAI;AACZ,KAAG,OAAO;CACX;CACD,UAAU,YAAY,KAAK;CAC3B,YAAY,YAAY,KAAK;CAC7B,YAAY,YAAY,KAAK;AAC9B;;;;;AAMD,MAAaC,kBAA4D;CACvE,GAAG;CACH,MAAM,UAAUF,IAAiBC,QAAqC;AAGpE,OAAK,MAAM,SAAS,OAClB,OAAM,GAAG,MAAM,MAAM;CAExB;CACD,MAAM,MAAM,IAAI;AACd,QAAM,GAAG,MAAM;CAChB;CACD,MAAM,IAAI;AACR,SAAO,QAAQ,QAAQ,GAAG,OAAO,CAAC;CACnC;AACF;;;;;AAMD,MAAaE,iBAA0D;CACrE,GAAG;CACH,YAAYJ,MAAc;AACxB,SAAO,CAAC,GAAG,KAAK,YAAY,KAAK,AAAC,EAAC,IAAI,CAAC,UAAU,MAAM,KAAK;CAC9D;CACD,YAAY,YAAY,KAAK;CAC7B,UAAUA,MAAcK,SAAmC;AACzD,OAAK,UAAU,MAAM,QAAQ;CAC9B;CACD,UAAU;AACX;;;;;AAMD,MAAaC,sBACX;CACE,GAAG;CACH,YAAYN,MAAc;AACxB,SAAO,CAAC,GAAG,KAAK,YAAY,KAAK,AAAC,EAAC,IAAI,CAAC,UAAU,MAAM,KAAK;CAC9D;CACD,YAAY,YAAY,KAAK;CAC7B,UAAUA,MAAcK,SAAmC;AACzD,OAAK,UAAU,MAAM,QAAQ;CAC9B;CACD,UAAU;AACX;AAqBH,SAAgB,YACdL,MACAO,UAA2B,CAAE,GACU;AACvC,KAAI,QAAQ,YACV,QAAO,gBAAgB,MAAM;EAAE,GAAG;EAAS,GAAG;CAAiB,EAAC;AAElE,QAAO,gBAAgB,MAAM;EAAE,GAAG;EAAS,GAAG;CAAY,EAAC;AAC5D;AA0BD,SAAgB,oBACdP,MACAQ,UAAmC,CAAE,GACE;AACvC,KAAI,QAAQ,YACV,QAAO,wBAAwB,MAAM;EAAE,GAAG;EAAS,GAAG;CAAiB,EAAC;AAE1E,QAAO,wBAAwB,MAAM;EAAE,GAAG;EAAS,GAAG;CAAY,EAAC;AACpE;AAuBD,SAAgB,wBACdC,SACuC;AACvC,KAAI,QAAQ,YACV,QAAO,4BAA4B;EAAE,GAAG;EAAS,GAAG;CAAqB,EAAC;AAE5E,QAAO,4BAA4B;EAAE,GAAG;EAAS,GAAG;CAAgB,EAAC;AACtE"}
@@ -25,7 +25,8 @@ const nodeDriver = {
25
25
  flushSync: node_fs.default.fsyncSync,
26
26
  closeSync: node_fs.default.closeSync,
27
27
  statSync: node_fs.default.statSync,
28
- renameSync: node_fs.default.renameSync
28
+ renameSync: node_fs.default.renameSync,
29
+ unlinkSync: node_fs.default.unlinkSync
29
30
  };
30
31
  /**
31
32
  * A Node.js-specific async file sink driver.
@@ -1 +1 @@
1
- {"version":3,"file":"filesink.node.d.cts","names":[],"sources":["../src/filesink.node.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAsBA;AAwBa,cAxBA,UAqCZ,EArCwB,sBAwBK,CAAA,MAA2B,GAAA,IAAA,CAAA;AAmBzD;AAYA;AAqBA;;AAEY,cAtDC,eAsDD,EAtDkB,2BAsDlB,CAAA,MAAA,GAAA,IAAA,CAAA;;;AACQ;AACpB;AAA2B,cArCd,cAqCc,EArCE,0BAqCF,CAAA,MAAA,GAAA,IAAA,CAAA;;;;AAGF;AA2BT,cAvDH,mBAuDsB,EAvDD,+BAuDC,CAAA,MAAA,GAAA,IAAA,CAAA;;;;;AAGf;AACpB;;;;;AAGyB;AA0BT,iBAnEA,WAAA,CAmEuB,IAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAjE3B,eAiE2B,CAAA,EAhEpC,IAgEoC,GAhE7B,UAgE6B;AAAA,iBA/DvB,WAAA,CA+DuB,IAAA,EAAA,MAAA,EAAA,OAAA,EA7D5B,eA6D4B,GAAA;EAAA,WAC5B,EAAA,IAAA;CAA2B,CAAA,EA7DnC,IA8DA,GA9DO,eA8DP;;AAAiB;AACpB;;;;;AAEyB;;;;;;;;;iBAtCT,mBAAA,yBAEJ,0BACT,OAAO;iBACM,mBAAA,wBAEL;;IACR,OAAO;;;;;;;;;;;;;;;;iBA0BM,uBAAA,UACL,8BACR,OAAO;iBACM,uBAAA,UACL;;IACR,OAAO"}
1
+ {"version":3,"file":"filesink.node.d.cts","names":[],"sources":["../src/filesink.node.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAsBA;AAyBa,cAzBA,UAsCZ,EAtCwB,sBAyBK,CAAA,MAA2B,GAAA,IAAA,CAAA;AAmBzD;AAYA;AAqBA;;AAEY,cAtDC,eAsDD,EAtDkB,2BAsDlB,CAAA,MAAA,GAAA,IAAA,CAAA;;;AACQ;AACpB;AAA2B,cArCd,cAqCc,EArCE,0BAqCF,CAAA,MAAA,GAAA,IAAA,CAAA;;;;AAGF;AA2BT,cAvDH,mBAuDsB,EAvDD,+BAuDC,CAAA,MAAA,GAAA,IAAA,CAAA;;;;;AAGf;AACpB;;;;;AAGyB;AA0BT,iBAnEA,WAAA,CAmEuB,IAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAjE3B,eAiE2B,CAAA,EAhEpC,IAgEoC,GAhE7B,UAgE6B;AAAA,iBA/DvB,WAAA,CA+DuB,IAAA,EAAA,MAAA,EAAA,OAAA,EA7D5B,eA6D4B,GAAA;EAAA,WAC5B,EAAA,IAAA;CAA2B,CAAA,EA7DnC,IA8DA,GA9DO,eA8DP;;AAAiB;AACpB;;;;;AAEyB;;;;;;;;;iBAtCT,mBAAA,yBAEJ,0BACT,OAAO;iBACM,mBAAA,wBAEL;;IACR,OAAO;;;;;;;;;;;;;;;;iBA0BM,uBAAA,UACL,8BACR,OAAO;iBACM,uBAAA,UACL;;IACR,OAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"filesink.node.d.ts","names":[],"sources":["../src/filesink.node.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAsBA;AAwBa,cAxBA,UAqCZ,EArCwB,sBAwBK,CAAA,MAA2B,GAAA,IAAA,CAAA;AAmBzD;AAYA;AAqBA;;AAEY,cAtDC,eAsDD,EAtDkB,2BAsDlB,CAAA,MAAA,GAAA,IAAA,CAAA;;;AACQ;AACpB;AAA2B,cArCd,cAqCc,EArCE,0BAqCF,CAAA,MAAA,GAAA,IAAA,CAAA;;;;AAGF;AA2BT,cAvDH,mBAuDsB,EAvDD,+BAuDC,CAAA,MAAA,GAAA,IAAA,CAAA;;;;;AAGf;AACpB;;;;;AAGyB;AA0BT,iBAnEA,WAAA,CAmEuB,IAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAjE3B,eAiE2B,CAAA,EAhEpC,IAgEoC,GAhE7B,UAgE6B;AAAA,iBA/DvB,WAAA,CA+DuB,IAAA,EAAA,MAAA,EAAA,OAAA,EA7D5B,eA6D4B,GAAA;EAAA,WAC5B,EAAA,IAAA;CAA2B,CAAA,EA7DnC,IA8DA,GA9DO,eA8DP;;AAAiB;AACpB;;;;;AAEyB;;;;;;;;;iBAtCT,mBAAA,yBAEJ,0BACT,OAAO;iBACM,mBAAA,wBAEL;;IACR,OAAO;;;;;;;;;;;;;;;;iBA0BM,uBAAA,UACL,8BACR,OAAO;iBACM,uBAAA,UACL;;IACR,OAAO"}
1
+ {"version":3,"file":"filesink.node.d.ts","names":[],"sources":["../src/filesink.node.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAsBA;AAyBa,cAzBA,UAsCZ,EAtCwB,sBAyBK,CAAA,MAA2B,GAAA,IAAA,CAAA;AAmBzD;AAYA;AAqBA;;AAEY,cAtDC,eAsDD,EAtDkB,2BAsDlB,CAAA,MAAA,GAAA,IAAA,CAAA;;;AACQ;AACpB;AAA2B,cArCd,cAqCc,EArCE,0BAqCF,CAAA,MAAA,GAAA,IAAA,CAAA;;;;AAGF;AA2BT,cAvDH,mBAuDsB,EAvDD,+BAuDC,CAAA,MAAA,GAAA,IAAA,CAAA;;;;;AAGf;AACpB;;;;;AAGyB;AA0BT,iBAnEA,WAAA,CAmEuB,IAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAjE3B,eAiE2B,CAAA,EAhEpC,IAgEoC,GAhE7B,UAgE6B;AAAA,iBA/DvB,WAAA,CA+DuB,IAAA,EAAA,MAAA,EAAA,OAAA,EA7D5B,eA6D4B,GAAA;EAAA,WAC5B,EAAA,IAAA;CAA2B,CAAA,EA7DnC,IA8DA,GA9DO,eA8DP;;AAAiB;AACpB;;;;;AAEyB;;;;;;;;;iBAtCT,mBAAA,yBAEJ,0BACT,OAAO;iBACM,mBAAA,wBAEL;;IACR,OAAO;;;;;;;;;;;;;;;;iBA0BM,uBAAA,UACL,8BACR,OAAO;iBACM,uBAAA,UACL;;IACR,OAAO"}
@@ -24,7 +24,8 @@ const nodeDriver = {
24
24
  flushSync: fs.fsyncSync,
25
25
  closeSync: fs.closeSync,
26
26
  statSync: fs.statSync,
27
- renameSync: fs.renameSync
27
+ renameSync: fs.renameSync,
28
+ unlinkSync: fs.unlinkSync
28
29
  };
29
30
  /**
30
31
  * A Node.js-specific async file sink driver.
@@ -1 +1 @@
1
- {"version":3,"file":"filesink.node.js","names":["nodeDriver: RotatingFileSinkDriver<number | void>","path: string","fd: number","chunks: Uint8Array[]","nodeAsyncDriver: AsyncRotatingFileSinkDriver<number | void>","nodeTimeDriver: TimeRotatingFileSinkDriver<number | void>","nodeAsyncTimeDriver: AsyncTimeRotatingFileSinkDriver<\n number | void\n>","options: FileSinkOptions","options: RotatingFileSinkOptions","options: TimeRotatingFileSinkOptions"],"sources":["../src/filesink.node.ts"],"sourcesContent":["import type { Sink } from \"@logtape/logtape\";\nimport fs from \"node:fs\";\nimport { join } from \"node:path\";\nimport { promisify } from \"node:util\";\nimport {\n type AsyncRotatingFileSinkDriver,\n type FileSinkOptions,\n getBaseFileSink,\n getBaseRotatingFileSink,\n type RotatingFileSinkDriver,\n type RotatingFileSinkOptions,\n} from \"./filesink.base.ts\";\nimport {\n type AsyncTimeRotatingFileSinkDriver,\n getBaseTimeRotatingFileSink,\n type TimeRotatingFileSinkDriver,\n type TimeRotatingFileSinkOptions,\n} from \"./timefilesink.ts\";\n\n/**\n * A Node.js-specific file sink driver.\n */\nexport const nodeDriver: RotatingFileSinkDriver<number | void> = {\n openSync(path: string) {\n return fs.openSync(path, \"a\");\n },\n writeSync: fs.writeSync,\n writeManySync(fd: number, chunks: Uint8Array[]): void {\n if (chunks.length === 0) return;\n if (chunks.length === 1) {\n fs.writeSync(fd, chunks[0]);\n return;\n }\n // Use writev for multiple chunks\n fs.writevSync(fd, chunks);\n },\n flushSync: fs.fsyncSync,\n closeSync: fs.closeSync,\n statSync: fs.statSync,\n renameSync: fs.renameSync,\n};\n\n/**\n * A Node.js-specific async file sink driver.\n * @since 1.0.0\n */\nexport const nodeAsyncDriver: AsyncRotatingFileSinkDriver<number | void> = {\n ...nodeDriver,\n async writeMany(fd: number, chunks: Uint8Array[]): Promise<void> {\n if (chunks.length === 0) return;\n if (chunks.length === 1) {\n await promisify(fs.write)(fd, chunks[0]);\n return;\n }\n // Use async writev for multiple chunks\n await promisify(fs.writev)(fd, chunks);\n },\n flush: promisify(fs.fsync),\n close: promisify(fs.close),\n};\n\n/**\n * A Node.js-specific time-rotating file sink driver.\n * @since 2.0.0\n */\nexport const nodeTimeDriver: TimeRotatingFileSinkDriver<number | void> = {\n ...nodeDriver,\n readdirSync: fs.readdirSync as (path: string) => string[],\n unlinkSync: fs.unlinkSync,\n mkdirSync: fs.mkdirSync,\n joinPath: join,\n};\n\n/**\n * A Node.js-specific async time-rotating file sink driver.\n * @since 2.0.0\n */\nexport const nodeAsyncTimeDriver: AsyncTimeRotatingFileSinkDriver<\n number | void\n> = {\n ...nodeAsyncDriver,\n readdirSync: fs.readdirSync as (path: string) => string[],\n unlinkSync: fs.unlinkSync,\n mkdirSync: fs.mkdirSync,\n joinPath: join,\n};\n\n/**\n * Get a file sink.\n *\n * Note that this function is unavailable in the browser.\n *\n * @param path A path to the file to write to.\n * @param options The options for the sink.\n * @returns A sink that writes to the file. The sink is also a disposable\n * object that closes the file when disposed. If `nonBlocking` is enabled,\n * returns a sink that also implements {@link AsyncDisposable}.\n */\nexport function getFileSink(\n path: string,\n options?: FileSinkOptions,\n): Sink & Disposable;\nexport function getFileSink(\n path: string,\n options: FileSinkOptions & { nonBlocking: true },\n): Sink & AsyncDisposable;\nexport function getFileSink(\n path: string,\n options: FileSinkOptions = {},\n): Sink & (Disposable | AsyncDisposable) {\n if (options.nonBlocking) {\n return getBaseFileSink(path, { ...options, ...nodeAsyncDriver });\n }\n return getBaseFileSink(path, { ...options, ...nodeDriver });\n}\n\n/**\n * Get a rotating file sink.\n *\n * This sink writes log records to a file, and rotates the file when it reaches\n * the `maxSize`. The rotated files are named with the original file name\n * followed by a dot and a number, starting from 1. The number is incremented\n * for each rotation, and the maximum number of files to keep is `maxFiles`.\n *\n * Note that this function is unavailable in the browser.\n *\n * @param path A path to the file to write to.\n * @param options The options for the sink and the file driver.\n * @returns A sink that writes to the file. The sink is also a disposable\n * object that closes the file when disposed. If `nonBlocking` is enabled,\n * returns a sink that also implements {@link AsyncDisposable}.\n */\nexport function getRotatingFileSink(\n path: string,\n options?: RotatingFileSinkOptions,\n): Sink & Disposable;\nexport function getRotatingFileSink(\n path: string,\n options: RotatingFileSinkOptions & { nonBlocking: true },\n): Sink & AsyncDisposable;\nexport function getRotatingFileSink(\n path: string,\n options: RotatingFileSinkOptions = {},\n): Sink & (Disposable | AsyncDisposable) {\n if (options.nonBlocking) {\n return getBaseRotatingFileSink(path, { ...options, ...nodeAsyncDriver });\n }\n return getBaseRotatingFileSink(path, { ...options, ...nodeDriver });\n}\n\n/**\n * Get a time-rotating file sink.\n *\n * This sink writes log records to a file in a directory, rotating to a new\n * file based on time intervals. The filename is generated based on the\n * current date/time and the configured interval.\n *\n * Note that this function is unavailable in the browser.\n *\n * @param options The options for the sink.\n * @returns A sink that writes to the file. The sink is also a disposable\n * object that closes the file when disposed. If `nonBlocking` is\n * enabled, returns a sink that also implements {@link AsyncDisposable}.\n * @since 2.0.0\n */\nexport function getTimeRotatingFileSink(\n options: TimeRotatingFileSinkOptions,\n): Sink & Disposable;\nexport function getTimeRotatingFileSink(\n options: TimeRotatingFileSinkOptions & { nonBlocking: true },\n): Sink & AsyncDisposable;\nexport function getTimeRotatingFileSink(\n options: TimeRotatingFileSinkOptions,\n): Sink & (Disposable | AsyncDisposable) {\n if (options.nonBlocking) {\n return getBaseTimeRotatingFileSink({ ...options, ...nodeAsyncTimeDriver });\n }\n return getBaseTimeRotatingFileSink({ ...options, ...nodeTimeDriver });\n}\n\n// cSpell: ignore filesink\n"],"mappings":";;;;;;;;;;AAsBA,MAAaA,aAAoD;CAC/D,SAASC,MAAc;AACrB,SAAO,GAAG,SAAS,MAAM,IAAI;CAC9B;CACD,WAAW,GAAG;CACd,cAAcC,IAAYC,QAA4B;AACpD,MAAI,OAAO,WAAW,EAAG;AACzB,MAAI,OAAO,WAAW,GAAG;AACvB,MAAG,UAAU,IAAI,OAAO,GAAG;AAC3B;EACD;AAED,KAAG,WAAW,IAAI,OAAO;CAC1B;CACD,WAAW,GAAG;CACd,WAAW,GAAG;CACd,UAAU,GAAG;CACb,YAAY,GAAG;AAChB;;;;;AAMD,MAAaC,kBAA8D;CACzE,GAAG;CACH,MAAM,UAAUF,IAAYC,QAAqC;AAC/D,MAAI,OAAO,WAAW,EAAG;AACzB,MAAI,OAAO,WAAW,GAAG;AACvB,SAAM,UAAU,GAAG,MAAM,CAAC,IAAI,OAAO,GAAG;AACxC;EACD;AAED,QAAM,UAAU,GAAG,OAAO,CAAC,IAAI,OAAO;CACvC;CACD,OAAO,UAAU,GAAG,MAAM;CAC1B,OAAO,UAAU,GAAG,MAAM;AAC3B;;;;;AAMD,MAAaE,iBAA4D;CACvE,GAAG;CACH,aAAa,GAAG;CAChB,YAAY,GAAG;CACf,WAAW,GAAG;CACd,UAAU;AACX;;;;;AAMD,MAAaC,sBAET;CACF,GAAG;CACH,aAAa,GAAG;CAChB,YAAY,GAAG;CACf,WAAW,GAAG;CACd,UAAU;AACX;AAqBD,SAAgB,YACdL,MACAM,UAA2B,CAAE,GACU;AACvC,KAAI,QAAQ,YACV,QAAO,gBAAgB,MAAM;EAAE,GAAG;EAAS,GAAG;CAAiB,EAAC;AAElE,QAAO,gBAAgB,MAAM;EAAE,GAAG;EAAS,GAAG;CAAY,EAAC;AAC5D;AA0BD,SAAgB,oBACdN,MACAO,UAAmC,CAAE,GACE;AACvC,KAAI,QAAQ,YACV,QAAO,wBAAwB,MAAM;EAAE,GAAG;EAAS,GAAG;CAAiB,EAAC;AAE1E,QAAO,wBAAwB,MAAM;EAAE,GAAG;EAAS,GAAG;CAAY,EAAC;AACpE;AAuBD,SAAgB,wBACdC,SACuC;AACvC,KAAI,QAAQ,YACV,QAAO,4BAA4B;EAAE,GAAG;EAAS,GAAG;CAAqB,EAAC;AAE5E,QAAO,4BAA4B;EAAE,GAAG;EAAS,GAAG;CAAgB,EAAC;AACtE"}
1
+ {"version":3,"file":"filesink.node.js","names":["nodeDriver: RotatingFileSinkDriver<number | void>","path: string","fd: number","chunks: Uint8Array[]","nodeAsyncDriver: AsyncRotatingFileSinkDriver<number | void>","nodeTimeDriver: TimeRotatingFileSinkDriver<number | void>","nodeAsyncTimeDriver: AsyncTimeRotatingFileSinkDriver<\n number | void\n>","options: FileSinkOptions","options: RotatingFileSinkOptions","options: TimeRotatingFileSinkOptions"],"sources":["../src/filesink.node.ts"],"sourcesContent":["import type { Sink } from \"@logtape/logtape\";\nimport fs from \"node:fs\";\nimport { join } from \"node:path\";\nimport { promisify } from \"node:util\";\nimport {\n type AsyncRotatingFileSinkDriver,\n type FileSinkOptions,\n getBaseFileSink,\n getBaseRotatingFileSink,\n type RotatingFileSinkDriver,\n type RotatingFileSinkOptions,\n} from \"./filesink.base.ts\";\nimport {\n type AsyncTimeRotatingFileSinkDriver,\n getBaseTimeRotatingFileSink,\n type TimeRotatingFileSinkDriver,\n type TimeRotatingFileSinkOptions,\n} from \"./timefilesink.ts\";\n\n/**\n * A Node.js-specific file sink driver.\n */\nexport const nodeDriver: RotatingFileSinkDriver<number | void> = {\n openSync(path: string) {\n return fs.openSync(path, \"a\");\n },\n writeSync: fs.writeSync,\n writeManySync(fd: number, chunks: Uint8Array[]): void {\n if (chunks.length === 0) return;\n if (chunks.length === 1) {\n fs.writeSync(fd, chunks[0]);\n return;\n }\n // Use writev for multiple chunks\n fs.writevSync(fd, chunks);\n },\n flushSync: fs.fsyncSync,\n closeSync: fs.closeSync,\n statSync: fs.statSync,\n renameSync: fs.renameSync,\n unlinkSync: fs.unlinkSync,\n};\n\n/**\n * A Node.js-specific async file sink driver.\n * @since 1.0.0\n */\nexport const nodeAsyncDriver: AsyncRotatingFileSinkDriver<number | void> = {\n ...nodeDriver,\n async writeMany(fd: number, chunks: Uint8Array[]): Promise<void> {\n if (chunks.length === 0) return;\n if (chunks.length === 1) {\n await promisify(fs.write)(fd, chunks[0]);\n return;\n }\n // Use async writev for multiple chunks\n await promisify(fs.writev)(fd, chunks);\n },\n flush: promisify(fs.fsync),\n close: promisify(fs.close),\n};\n\n/**\n * A Node.js-specific time-rotating file sink driver.\n * @since 2.0.0\n */\nexport const nodeTimeDriver: TimeRotatingFileSinkDriver<number | void> = {\n ...nodeDriver,\n readdirSync: fs.readdirSync as (path: string) => string[],\n unlinkSync: fs.unlinkSync,\n mkdirSync: fs.mkdirSync,\n joinPath: join,\n};\n\n/**\n * A Node.js-specific async time-rotating file sink driver.\n * @since 2.0.0\n */\nexport const nodeAsyncTimeDriver: AsyncTimeRotatingFileSinkDriver<\n number | void\n> = {\n ...nodeAsyncDriver,\n readdirSync: fs.readdirSync as (path: string) => string[],\n unlinkSync: fs.unlinkSync,\n mkdirSync: fs.mkdirSync,\n joinPath: join,\n};\n\n/**\n * Get a file sink.\n *\n * Note that this function is unavailable in the browser.\n *\n * @param path A path to the file to write to.\n * @param options The options for the sink.\n * @returns A sink that writes to the file. The sink is also a disposable\n * object that closes the file when disposed. If `nonBlocking` is enabled,\n * returns a sink that also implements {@link AsyncDisposable}.\n */\nexport function getFileSink(\n path: string,\n options?: FileSinkOptions,\n): Sink & Disposable;\nexport function getFileSink(\n path: string,\n options: FileSinkOptions & { nonBlocking: true },\n): Sink & AsyncDisposable;\nexport function getFileSink(\n path: string,\n options: FileSinkOptions = {},\n): Sink & (Disposable | AsyncDisposable) {\n if (options.nonBlocking) {\n return getBaseFileSink(path, { ...options, ...nodeAsyncDriver });\n }\n return getBaseFileSink(path, { ...options, ...nodeDriver });\n}\n\n/**\n * Get a rotating file sink.\n *\n * This sink writes log records to a file, and rotates the file when it reaches\n * the `maxSize`. The rotated files are named with the original file name\n * followed by a dot and a number, starting from 1. The number is incremented\n * for each rotation, and the maximum number of files to keep is `maxFiles`.\n *\n * Note that this function is unavailable in the browser.\n *\n * @param path A path to the file to write to.\n * @param options The options for the sink and the file driver.\n * @returns A sink that writes to the file. The sink is also a disposable\n * object that closes the file when disposed. If `nonBlocking` is enabled,\n * returns a sink that also implements {@link AsyncDisposable}.\n */\nexport function getRotatingFileSink(\n path: string,\n options?: RotatingFileSinkOptions,\n): Sink & Disposable;\nexport function getRotatingFileSink(\n path: string,\n options: RotatingFileSinkOptions & { nonBlocking: true },\n): Sink & AsyncDisposable;\nexport function getRotatingFileSink(\n path: string,\n options: RotatingFileSinkOptions = {},\n): Sink & (Disposable | AsyncDisposable) {\n if (options.nonBlocking) {\n return getBaseRotatingFileSink(path, { ...options, ...nodeAsyncDriver });\n }\n return getBaseRotatingFileSink(path, { ...options, ...nodeDriver });\n}\n\n/**\n * Get a time-rotating file sink.\n *\n * This sink writes log records to a file in a directory, rotating to a new\n * file based on time intervals. The filename is generated based on the\n * current date/time and the configured interval.\n *\n * Note that this function is unavailable in the browser.\n *\n * @param options The options for the sink.\n * @returns A sink that writes to the file. The sink is also a disposable\n * object that closes the file when disposed. If `nonBlocking` is\n * enabled, returns a sink that also implements {@link AsyncDisposable}.\n * @since 2.0.0\n */\nexport function getTimeRotatingFileSink(\n options: TimeRotatingFileSinkOptions,\n): Sink & Disposable;\nexport function getTimeRotatingFileSink(\n options: TimeRotatingFileSinkOptions & { nonBlocking: true },\n): Sink & AsyncDisposable;\nexport function getTimeRotatingFileSink(\n options: TimeRotatingFileSinkOptions,\n): Sink & (Disposable | AsyncDisposable) {\n if (options.nonBlocking) {\n return getBaseTimeRotatingFileSink({ ...options, ...nodeAsyncTimeDriver });\n }\n return getBaseTimeRotatingFileSink({ ...options, ...nodeTimeDriver });\n}\n\n// cSpell: ignore filesink\n"],"mappings":";;;;;;;;;;AAsBA,MAAaA,aAAoD;CAC/D,SAASC,MAAc;AACrB,SAAO,GAAG,SAAS,MAAM,IAAI;CAC9B;CACD,WAAW,GAAG;CACd,cAAcC,IAAYC,QAA4B;AACpD,MAAI,OAAO,WAAW,EAAG;AACzB,MAAI,OAAO,WAAW,GAAG;AACvB,MAAG,UAAU,IAAI,OAAO,GAAG;AAC3B;EACD;AAED,KAAG,WAAW,IAAI,OAAO;CAC1B;CACD,WAAW,GAAG;CACd,WAAW,GAAG;CACd,UAAU,GAAG;CACb,YAAY,GAAG;CACf,YAAY,GAAG;AAChB;;;;;AAMD,MAAaC,kBAA8D;CACzE,GAAG;CACH,MAAM,UAAUF,IAAYC,QAAqC;AAC/D,MAAI,OAAO,WAAW,EAAG;AACzB,MAAI,OAAO,WAAW,GAAG;AACvB,SAAM,UAAU,GAAG,MAAM,CAAC,IAAI,OAAO,GAAG;AACxC;EACD;AAED,QAAM,UAAU,GAAG,OAAO,CAAC,IAAI,OAAO;CACvC;CACD,OAAO,UAAU,GAAG,MAAM;CAC1B,OAAO,UAAU,GAAG,MAAM;AAC3B;;;;;AAMD,MAAaE,iBAA4D;CACvE,GAAG;CACH,aAAa,GAAG;CAChB,YAAY,GAAG;CACf,WAAW,GAAG;CACd,UAAU;AACX;;;;;AAMD,MAAaC,sBAET;CACF,GAAG;CACH,aAAa,GAAG;CAChB,YAAY,GAAG;CACf,WAAW,GAAG;CACd,UAAU;AACX;AAqBD,SAAgB,YACdL,MACAM,UAA2B,CAAE,GACU;AACvC,KAAI,QAAQ,YACV,QAAO,gBAAgB,MAAM;EAAE,GAAG;EAAS,GAAG;CAAiB,EAAC;AAElE,QAAO,gBAAgB,MAAM;EAAE,GAAG;EAAS,GAAG;CAAY,EAAC;AAC5D;AA0BD,SAAgB,oBACdN,MACAO,UAAmC,CAAE,GACE;AACvC,KAAI,QAAQ,YACV,QAAO,wBAAwB,MAAM;EAAE,GAAG;EAAS,GAAG;CAAiB,EAAC;AAE1E,QAAO,wBAAwB,MAAM;EAAE,GAAG;EAAS,GAAG;CAAY,EAAC;AACpE;AAuBD,SAAgB,wBACdC,SACuC;AACvC,KAAI,QAAQ,YACV,QAAO,4BAA4B;EAAE,GAAG;EAAS,GAAG;CAAqB,EAAC;AAE5E,QAAO,4BAA4B;EAAE,GAAG;EAAS,GAAG;CAAgB,EAAC;AACtE"}
@@ -67,12 +67,20 @@ function getRotationKey(date, interval) {
67
67
  case "weekly": return `${getISOWeekYear(date)}-${getISOWeek(date)}`;
68
68
  }
69
69
  }
70
+ function getRotationIntervalMs(interval) {
71
+ switch (interval) {
72
+ case "hourly": return 60 * 60 * 1e3;
73
+ case "daily": return 24 * 60 * 60 * 1e3;
74
+ case "weekly": return 7 * 24 * 60 * 60 * 1e3;
75
+ }
76
+ }
70
77
  function getBaseTimeRotatingFileSink(options) {
71
78
  const formatter = options.formatter ?? __logtape_logtape.defaultTextFormatter;
72
79
  const encoder = options.encoder ?? new TextEncoder();
73
80
  const interval = options.interval ?? "daily";
74
81
  const filenameGenerator = options.filename ?? getDefaultFilename(interval);
75
82
  const maxAgeMs = options.maxAgeMs;
83
+ const cleanupInterval = maxAgeMs === void 0 ? getRotationIntervalMs(interval) : Math.min(getRotationIntervalMs(interval), maxAgeMs);
76
84
  const bufferSize = options.bufferSize ?? 1024 * 8;
77
85
  const flushInterval = options.flushInterval ?? 5e3;
78
86
  const directory = options.directory;
@@ -84,6 +92,7 @@ function getBaseTimeRotatingFileSink(options) {
84
92
  let currentRotationKey = getRotationKey(/* @__PURE__ */ new Date(), interval);
85
93
  let fd = options.openSync(currentPath);
86
94
  let lastFlushTimestamp = Date.now();
95
+ let lastCleanupTimestamp;
87
96
  let buffer = "";
88
97
  function shouldRotate() {
89
98
  const now = /* @__PURE__ */ new Date();
@@ -101,6 +110,8 @@ function getBaseTimeRotatingFileSink(options) {
101
110
  function cleanupOldFiles() {
102
111
  if (maxAgeMs === void 0) return;
103
112
  const now = Date.now();
113
+ if (lastCleanupTimestamp !== void 0 && cleanupInterval > 0 && now - lastCleanupTimestamp < cleanupInterval) return;
114
+ lastCleanupTimestamp = now;
104
115
  let files;
105
116
  try {
106
117
  files = options.readdirSync(directory);
@@ -110,7 +121,6 @@ function getBaseTimeRotatingFileSink(options) {
110
121
  for (const file of files) {
111
122
  if (!file.endsWith(".log")) continue;
112
123
  if (file === currentFilename) continue;
113
- const filePath = options.joinPath(directory, file);
114
124
  const dateMatch = file.match(/^(\d{4})-(\d{2})-(\d{2})(?:-(\d{2}))?\.log$/);
115
125
  const weekMatch = file.match(/^(\d{4})-W(\d{2})\.log$/);
116
126
  let fileDate = null;
@@ -124,9 +134,12 @@ function getBaseTimeRotatingFileSink(options) {
124
134
  fileDate = new Date(jan4);
125
135
  fileDate.setDate(jan4.getDate() - dayOfWeek + 1 + (parseInt(week, 10) - 1) * 7);
126
136
  }
127
- if (fileDate && now - fileDate.getTime() > maxAgeMs) try {
128
- options.unlinkSync(filePath);
129
- } catch {}
137
+ if (fileDate && now - fileDate.getTime() > maxAgeMs) {
138
+ const filePath = options.joinPath(directory, file);
139
+ try {
140
+ options.unlinkSync(filePath);
141
+ } catch {}
142
+ }
130
143
  }
131
144
  }
132
145
  if (!options.nonBlocking) {
@@ -66,12 +66,20 @@ function getRotationKey(date, interval) {
66
66
  case "weekly": return `${getISOWeekYear(date)}-${getISOWeek(date)}`;
67
67
  }
68
68
  }
69
+ function getRotationIntervalMs(interval) {
70
+ switch (interval) {
71
+ case "hourly": return 60 * 60 * 1e3;
72
+ case "daily": return 24 * 60 * 60 * 1e3;
73
+ case "weekly": return 7 * 24 * 60 * 60 * 1e3;
74
+ }
75
+ }
69
76
  function getBaseTimeRotatingFileSink(options) {
70
77
  const formatter = options.formatter ?? defaultTextFormatter;
71
78
  const encoder = options.encoder ?? new TextEncoder();
72
79
  const interval = options.interval ?? "daily";
73
80
  const filenameGenerator = options.filename ?? getDefaultFilename(interval);
74
81
  const maxAgeMs = options.maxAgeMs;
82
+ const cleanupInterval = maxAgeMs === void 0 ? getRotationIntervalMs(interval) : Math.min(getRotationIntervalMs(interval), maxAgeMs);
75
83
  const bufferSize = options.bufferSize ?? 1024 * 8;
76
84
  const flushInterval = options.flushInterval ?? 5e3;
77
85
  const directory = options.directory;
@@ -83,6 +91,7 @@ function getBaseTimeRotatingFileSink(options) {
83
91
  let currentRotationKey = getRotationKey(/* @__PURE__ */ new Date(), interval);
84
92
  let fd = options.openSync(currentPath);
85
93
  let lastFlushTimestamp = Date.now();
94
+ let lastCleanupTimestamp;
86
95
  let buffer = "";
87
96
  function shouldRotate() {
88
97
  const now = /* @__PURE__ */ new Date();
@@ -100,6 +109,8 @@ function getBaseTimeRotatingFileSink(options) {
100
109
  function cleanupOldFiles() {
101
110
  if (maxAgeMs === void 0) return;
102
111
  const now = Date.now();
112
+ if (lastCleanupTimestamp !== void 0 && cleanupInterval > 0 && now - lastCleanupTimestamp < cleanupInterval) return;
113
+ lastCleanupTimestamp = now;
103
114
  let files;
104
115
  try {
105
116
  files = options.readdirSync(directory);
@@ -109,7 +120,6 @@ function getBaseTimeRotatingFileSink(options) {
109
120
  for (const file of files) {
110
121
  if (!file.endsWith(".log")) continue;
111
122
  if (file === currentFilename) continue;
112
- const filePath = options.joinPath(directory, file);
113
123
  const dateMatch = file.match(/^(\d{4})-(\d{2})-(\d{2})(?:-(\d{2}))?\.log$/);
114
124
  const weekMatch = file.match(/^(\d{4})-W(\d{2})\.log$/);
115
125
  let fileDate = null;
@@ -123,9 +133,12 @@ function getBaseTimeRotatingFileSink(options) {
123
133
  fileDate = new Date(jan4);
124
134
  fileDate.setDate(jan4.getDate() - dayOfWeek + 1 + (parseInt(week, 10) - 1) * 7);
125
135
  }
126
- if (fileDate && now - fileDate.getTime() > maxAgeMs) try {
127
- options.unlinkSync(filePath);
128
- } catch {}
136
+ if (fileDate && now - fileDate.getTime() > maxAgeMs) {
137
+ const filePath = options.joinPath(directory, file);
138
+ try {
139
+ options.unlinkSync(filePath);
140
+ } catch {}
141
+ }
129
142
  }
130
143
  }
131
144
  if (!options.nonBlocking) {
@@ -1 +1 @@
1
- {"version":3,"file":"timefilesink.js","names":["date: Date","interval: TimeRotationInterval","options:\n & TimeRotatingFileSinkOptions\n & (\n | TimeRotatingFileSinkDriver<TFile>\n | AsyncTimeRotatingFileSinkDriver<TFile>\n )","currentFilename: string","currentPath: string","currentRotationKey: string","fd: TFile","lastFlushTimestamp: number","buffer: string","files: string[]","fileDate: Date | null","flushBuffer","sink: Sink & Disposable","record: LogRecord","activeFlush: Promise<void> | null","flushTimer: ReturnType<typeof setInterval> | null","nonBlockingSink: Sink & AsyncDisposable"],"sources":["../src/timefilesink.ts"],"sourcesContent":["import {\n defaultTextFormatter,\n type LogRecord,\n type Sink,\n} from \"@logtape/logtape\";\nimport type {\n AsyncFileSinkDriver,\n FileSinkDriver,\n FileSinkOptions,\n} from \"./filesink.base.ts\";\n\n/**\n * The rotation interval for time-based file sinks.\n */\nexport type TimeRotationInterval = \"hourly\" | \"daily\" | \"weekly\";\n\n/**\n * Options for the {@link getBaseTimeRotatingFileSink} function.\n */\nexport interface TimeRotatingFileSinkOptions\n extends Omit<FileSinkOptions, \"lazy\"> {\n /**\n * The directory to write log files to.\n */\n directory: string;\n\n /**\n * A function that generates the filename for the log file based on the date.\n * Default depends on `interval`:\n * - `\"daily\"`: `YYYY-MM-DD.log` (e.g., `2025-01-15.log`)\n * - `\"hourly\"`: `YYYY-MM-DD-HH.log` (e.g., `2025-01-15-09.log`)\n * - `\"weekly\"`: `YYYY-WW.log` (e.g., `2025-W03.log`)\n */\n filename?: (date: Date) => string;\n\n /**\n * The rotation interval. Defaults to `\"daily\"`.\n */\n interval?: TimeRotationInterval;\n\n /**\n * The maximum age of log files in milliseconds. Files older than this\n * will be deleted. If not specified, old files are not deleted.\n */\n maxAgeMs?: number;\n}\n\n/**\n * A platform-specific time-rotating file sink driver.\n */\nexport interface TimeRotatingFileSinkDriver<TFile>\n extends FileSinkDriver<TFile> {\n /**\n * Read the contents of a directory.\n * @param path A path to the directory.\n * @returns An array of filenames in the directory.\n */\n readdirSync(path: string): string[];\n\n /**\n * Delete a file.\n * @param path A path to the file to delete.\n */\n unlinkSync(path: string): void;\n\n /**\n * Create a directory if it doesn't exist.\n * @param path A path to the directory to create.\n * @param options Options for directory creation.\n */\n mkdirSync(path: string, options?: { recursive?: boolean }): void;\n\n /**\n * Join path segments.\n * @param paths Path segments to join.\n * @returns The joined path.\n */\n joinPath(...paths: string[]): string;\n}\n\n/**\n * A platform-specific async time-rotating file sink driver.\n * @since 2.0.0\n */\nexport interface AsyncTimeRotatingFileSinkDriver<TFile>\n extends AsyncFileSinkDriver<TFile> {\n /**\n * Read the contents of a directory.\n * @param path A path to the directory.\n * @returns An array of filenames in the directory.\n */\n readdirSync(path: string): string[];\n\n /**\n * Delete a file.\n * @param path A path to the file to delete.\n */\n unlinkSync(path: string): void;\n\n /**\n * Create a directory if it doesn't exist.\n * @param path A path to the directory to create.\n * @param options Options for directory creation.\n */\n mkdirSync(path: string, options?: { recursive?: boolean }): void;\n\n /**\n * Join path segments.\n * @param paths Path segments to join.\n * @returns The joined path.\n */\n joinPath(...paths: string[]): string;\n}\n\n/**\n * Get the ISO week number of a date.\n * @param date The date to get the week number of.\n * @returns The ISO week number (1-53).\n */\nexport function getISOWeek(date: Date): number {\n const d = new Date(\n Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()),\n );\n const dayNum = d.getUTCDay() || 7;\n d.setUTCDate(d.getUTCDate() + 4 - dayNum);\n const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));\n return Math.ceil(((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);\n}\n\n/**\n * Get the ISO week year of a date. This may differ from the calendar year\n * for dates near the start or end of a year.\n * @param date The date to get the ISO week year of.\n * @returns The ISO week year.\n */\nexport function getISOWeekYear(date: Date): number {\n const d = new Date(\n Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()),\n );\n const dayNum = d.getUTCDay() || 7;\n d.setUTCDate(d.getUTCDate() + 4 - dayNum);\n return d.getUTCFullYear();\n}\n\n/**\n * Get the default filename generator for the given interval.\n * @param interval The rotation interval.\n * @returns A function that generates a filename for a given date.\n */\nexport function getDefaultFilename(\n interval: TimeRotationInterval,\n): (date: Date) => string {\n switch (interval) {\n case \"hourly\":\n return (date: Date): string => {\n const yyyy = date.getFullYear();\n const mm = String(date.getMonth() + 1).padStart(2, \"0\");\n const dd = String(date.getDate()).padStart(2, \"0\");\n const hh = String(date.getHours()).padStart(2, \"0\");\n return `${yyyy}-${mm}-${dd}-${hh}.log`;\n };\n case \"daily\":\n return (date: Date): string => {\n const yyyy = date.getFullYear();\n const mm = String(date.getMonth() + 1).padStart(2, \"0\");\n const dd = String(date.getDate()).padStart(2, \"0\");\n return `${yyyy}-${mm}-${dd}.log`;\n };\n case \"weekly\":\n return (date: Date): string => {\n const yyyy = getISOWeekYear(date);\n const week = getISOWeek(date);\n return `${yyyy}-W${String(week).padStart(2, \"0\")}.log`;\n };\n }\n}\n\n/**\n * Get the rotation key for the given date and interval.\n * The key is used to determine when to rotate to a new file.\n * @param date The date to get the rotation key of.\n * @param interval The rotation interval.\n * @returns A string key that changes when rotation should occur.\n */\nfunction getRotationKey(date: Date, interval: TimeRotationInterval): string {\n switch (interval) {\n case \"hourly\":\n return `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}-${date.getHours()}`;\n case \"daily\":\n return `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;\n case \"weekly\":\n return `${getISOWeekYear(date)}-${getISOWeek(date)}`;\n }\n}\n\n/**\n * Get a platform-independent time-rotating file sink.\n *\n * This sink writes log records to a file in a directory, rotating to a new\n * file based on time intervals. The filename is generated based on the\n * current date/time and the configured interval.\n *\n * @template TFile The type of the file descriptor.\n * @param options The options for the sink and the file driver.\n * @returns A sink that writes to the file. The sink is also a disposable\n * object that closes the file when disposed. If `nonBlocking` is\n * enabled, returns a sink that also implements {@link AsyncDisposable}.\n */\nexport function getBaseTimeRotatingFileSink<TFile>(\n options: TimeRotatingFileSinkOptions & TimeRotatingFileSinkDriver<TFile>,\n): Sink & Disposable;\nexport function getBaseTimeRotatingFileSink<TFile>(\n options: TimeRotatingFileSinkOptions & AsyncTimeRotatingFileSinkDriver<TFile>,\n): Sink & AsyncDisposable;\nexport function getBaseTimeRotatingFileSink<TFile>(\n options:\n & TimeRotatingFileSinkOptions\n & (\n | TimeRotatingFileSinkDriver<TFile>\n | AsyncTimeRotatingFileSinkDriver<TFile>\n ),\n): Sink & (Disposable | AsyncDisposable) {\n const formatter = options.formatter ?? defaultTextFormatter;\n const encoder = options.encoder ?? new TextEncoder();\n const interval = options.interval ?? \"daily\";\n const filenameGenerator = options.filename ?? getDefaultFilename(interval);\n const maxAgeMs = options.maxAgeMs;\n const bufferSize = options.bufferSize ?? 1024 * 8;\n const flushInterval = options.flushInterval ?? 5000;\n const directory = options.directory;\n\n // Ensure directory exists\n try {\n options.mkdirSync(directory, { recursive: true });\n } catch {\n // Directory might already exist\n }\n\n let currentFilename: string = filenameGenerator(new Date());\n let currentPath: string = options.joinPath(directory, currentFilename);\n let currentRotationKey: string = getRotationKey(new Date(), interval);\n let fd: TFile = options.openSync(currentPath);\n let lastFlushTimestamp: number = Date.now();\n let buffer: string = \"\";\n\n function shouldRotate(): boolean {\n const now = new Date();\n const newKey = getRotationKey(now, interval);\n return newKey !== currentRotationKey;\n }\n\n function performRotation(): void {\n options.closeSync(fd);\n const now = new Date();\n currentFilename = filenameGenerator(now);\n currentPath = options.joinPath(directory, currentFilename);\n currentRotationKey = getRotationKey(now, interval);\n fd = options.openSync(currentPath);\n }\n\n function cleanupOldFiles(): void {\n if (maxAgeMs === undefined) return;\n\n const now = Date.now();\n let files: string[];\n try {\n files = options.readdirSync(directory);\n } catch {\n return;\n }\n\n for (const file of files) {\n if (!file.endsWith(\".log\")) continue;\n if (file === currentFilename) continue;\n\n const filePath = options.joinPath(directory, file);\n\n // Try to parse the date from the filename\n const dateMatch = file.match(\n /^(\\d{4})-(\\d{2})-(\\d{2})(?:-(\\d{2}))?\\.log$/,\n );\n const weekMatch = file.match(/^(\\d{4})-W(\\d{2})\\.log$/);\n\n let fileDate: Date | null = null;\n\n if (dateMatch) {\n const [, year, month, day, hour] = dateMatch;\n fileDate = new Date(\n parseInt(year!, 10),\n parseInt(month!, 10) - 1,\n parseInt(day!, 10),\n hour ? parseInt(hour, 10) : 0,\n );\n } else if (weekMatch) {\n const [, year, week] = weekMatch;\n // Get the date of the first day of the week\n const jan4 = new Date(parseInt(year!, 10), 0, 4);\n const dayOfWeek = jan4.getDay() || 7;\n fileDate = new Date(jan4);\n fileDate.setDate(\n jan4.getDate() - dayOfWeek + 1 + (parseInt(week!, 10) - 1) * 7,\n );\n }\n\n if (fileDate && now - fileDate.getTime() > maxAgeMs) {\n try {\n options.unlinkSync(filePath);\n } catch {\n // Ignore errors when deleting files\n }\n }\n }\n }\n\n if (!options.nonBlocking) {\n // Blocking mode implementation\n // deno-lint-ignore no-inner-declarations\n function flushBuffer(): void {\n if (buffer.length > 0) {\n if (shouldRotate()) {\n performRotation();\n }\n const bytes = encoder.encode(buffer);\n buffer = \"\";\n options.writeSync(fd, bytes);\n options.flushSync(fd);\n lastFlushTimestamp = Date.now();\n cleanupOldFiles();\n }\n }\n\n const sink: Sink & Disposable = (record: LogRecord) => {\n buffer += formatter(record);\n\n const shouldFlushBySize = buffer.length >= bufferSize;\n const shouldFlushByTime = flushInterval > 0 &&\n (record.timestamp - lastFlushTimestamp) >= flushInterval;\n\n if (shouldFlushBySize || shouldFlushByTime) {\n flushBuffer();\n }\n };\n\n sink[Symbol.dispose] = () => {\n flushBuffer();\n options.closeSync(fd);\n };\n\n return sink;\n }\n\n // Non-blocking mode implementation\n const asyncOptions = options as AsyncTimeRotatingFileSinkDriver<TFile>;\n let disposed = false;\n let activeFlush: Promise<void> | null = null;\n let flushTimer: ReturnType<typeof setInterval> | null = null;\n\n async function flushBuffer(): Promise<void> {\n if (buffer.length === 0) return;\n\n if (shouldRotate()) {\n performRotation();\n }\n\n const data = buffer;\n buffer = \"\";\n try {\n const bytes = encoder.encode(data);\n asyncOptions.writeSync(fd, bytes);\n await asyncOptions.flush(fd);\n lastFlushTimestamp = Date.now();\n cleanupOldFiles();\n } catch {\n // Silently ignore errors in non-blocking mode\n }\n }\n\n function scheduleFlush(): void {\n if (activeFlush || disposed) return;\n\n activeFlush = flushBuffer().finally(() => {\n activeFlush = null;\n });\n }\n\n function startFlushTimer(): void {\n if (flushTimer !== null || disposed) return;\n\n flushTimer = setInterval(() => {\n scheduleFlush();\n }, flushInterval);\n }\n\n const nonBlockingSink: Sink & AsyncDisposable = (record: LogRecord) => {\n if (disposed) return;\n buffer += formatter(record);\n\n const shouldFlushBySize = buffer.length >= bufferSize;\n const shouldFlushByTime = flushInterval > 0 &&\n (record.timestamp - lastFlushTimestamp) >= flushInterval;\n\n if (shouldFlushBySize || shouldFlushByTime) {\n scheduleFlush();\n } else if (flushTimer === null && flushInterval > 0) {\n startFlushTimer();\n }\n };\n\n nonBlockingSink[Symbol.asyncDispose] = async () => {\n disposed = true;\n if (flushTimer !== null) {\n clearInterval(flushTimer);\n flushTimer = null;\n }\n await flushBuffer();\n try {\n await asyncOptions.close(fd);\n } catch {\n // Writer might already be closed or errored\n }\n };\n\n return nonBlockingSink;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"timefilesink.js","names":["date: Date","interval: TimeRotationInterval","options:\n & TimeRotatingFileSinkOptions\n & (\n | TimeRotatingFileSinkDriver<TFile>\n | AsyncTimeRotatingFileSinkDriver<TFile>\n )","currentFilename: string","currentPath: string","currentRotationKey: string","fd: TFile","lastFlushTimestamp: number","lastCleanupTimestamp: number | undefined","buffer: string","files: string[]","fileDate: Date | null","flushBuffer","sink: Sink & Disposable","record: LogRecord","activeFlush: Promise<void> | null","flushTimer: ReturnType<typeof setInterval> | null","nonBlockingSink: Sink & AsyncDisposable"],"sources":["../src/timefilesink.ts"],"sourcesContent":["import {\n defaultTextFormatter,\n type LogRecord,\n type Sink,\n} from \"@logtape/logtape\";\nimport type {\n AsyncFileSinkDriver,\n FileSinkDriver,\n FileSinkOptions,\n} from \"./filesink.base.ts\";\n\n/**\n * The rotation interval for time-based file sinks.\n */\nexport type TimeRotationInterval = \"hourly\" | \"daily\" | \"weekly\";\n\n/**\n * Options for the {@link getBaseTimeRotatingFileSink} function.\n */\nexport interface TimeRotatingFileSinkOptions\n extends Omit<FileSinkOptions, \"lazy\"> {\n /**\n * The directory to write log files to.\n */\n directory: string;\n\n /**\n * A function that generates the filename for the log file based on the date.\n * Default depends on `interval`:\n * - `\"daily\"`: `YYYY-MM-DD.log` (e.g., `2025-01-15.log`)\n * - `\"hourly\"`: `YYYY-MM-DD-HH.log` (e.g., `2025-01-15-09.log`)\n * - `\"weekly\"`: `YYYY-WW.log` (e.g., `2025-W03.log`)\n */\n filename?: (date: Date) => string;\n\n /**\n * The rotation interval. Defaults to `\"daily\"`.\n */\n interval?: TimeRotationInterval;\n\n /**\n * The maximum age of log files in milliseconds. Files older than this\n * will be deleted. If not specified, old files are not deleted.\n */\n maxAgeMs?: number;\n}\n\n/**\n * A platform-specific time-rotating file sink driver.\n */\nexport interface TimeRotatingFileSinkDriver<TFile>\n extends FileSinkDriver<TFile> {\n /**\n * Read the contents of a directory.\n * @param path A path to the directory.\n * @returns An array of filenames in the directory.\n */\n readdirSync(path: string): string[];\n\n /**\n * Delete a file.\n * @param path A path to the file to delete.\n */\n unlinkSync(path: string): void;\n\n /**\n * Create a directory if it doesn't exist.\n * @param path A path to the directory to create.\n * @param options Options for directory creation.\n */\n mkdirSync(path: string, options?: { recursive?: boolean }): void;\n\n /**\n * Join path segments.\n * @param paths Path segments to join.\n * @returns The joined path.\n */\n joinPath(...paths: string[]): string;\n}\n\n/**\n * A platform-specific async time-rotating file sink driver.\n * @since 2.0.0\n */\nexport interface AsyncTimeRotatingFileSinkDriver<TFile>\n extends AsyncFileSinkDriver<TFile> {\n /**\n * Read the contents of a directory.\n * @param path A path to the directory.\n * @returns An array of filenames in the directory.\n */\n readdirSync(path: string): string[];\n\n /**\n * Delete a file.\n * @param path A path to the file to delete.\n */\n unlinkSync(path: string): void;\n\n /**\n * Create a directory if it doesn't exist.\n * @param path A path to the directory to create.\n * @param options Options for directory creation.\n */\n mkdirSync(path: string, options?: { recursive?: boolean }): void;\n\n /**\n * Join path segments.\n * @param paths Path segments to join.\n * @returns The joined path.\n */\n joinPath(...paths: string[]): string;\n}\n\n/**\n * Get the ISO week number of a date.\n * @param date The date to get the week number of.\n * @returns The ISO week number (1-53).\n */\nexport function getISOWeek(date: Date): number {\n const d = new Date(\n Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()),\n );\n const dayNum = d.getUTCDay() || 7;\n d.setUTCDate(d.getUTCDate() + 4 - dayNum);\n const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));\n return Math.ceil(((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);\n}\n\n/**\n * Get the ISO week year of a date. This may differ from the calendar year\n * for dates near the start or end of a year.\n * @param date The date to get the ISO week year of.\n * @returns The ISO week year.\n */\nexport function getISOWeekYear(date: Date): number {\n const d = new Date(\n Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()),\n );\n const dayNum = d.getUTCDay() || 7;\n d.setUTCDate(d.getUTCDate() + 4 - dayNum);\n return d.getUTCFullYear();\n}\n\n/**\n * Get the default filename generator for the given interval.\n * @param interval The rotation interval.\n * @returns A function that generates a filename for a given date.\n */\nexport function getDefaultFilename(\n interval: TimeRotationInterval,\n): (date: Date) => string {\n switch (interval) {\n case \"hourly\":\n return (date: Date): string => {\n const yyyy = date.getFullYear();\n const mm = String(date.getMonth() + 1).padStart(2, \"0\");\n const dd = String(date.getDate()).padStart(2, \"0\");\n const hh = String(date.getHours()).padStart(2, \"0\");\n return `${yyyy}-${mm}-${dd}-${hh}.log`;\n };\n case \"daily\":\n return (date: Date): string => {\n const yyyy = date.getFullYear();\n const mm = String(date.getMonth() + 1).padStart(2, \"0\");\n const dd = String(date.getDate()).padStart(2, \"0\");\n return `${yyyy}-${mm}-${dd}.log`;\n };\n case \"weekly\":\n return (date: Date): string => {\n const yyyy = getISOWeekYear(date);\n const week = getISOWeek(date);\n return `${yyyy}-W${String(week).padStart(2, \"0\")}.log`;\n };\n }\n}\n\n/**\n * Get the rotation key for the given date and interval.\n * The key is used to determine when to rotate to a new file.\n * @param date The date to get the rotation key of.\n * @param interval The rotation interval.\n * @returns A string key that changes when rotation should occur.\n */\nfunction getRotationKey(date: Date, interval: TimeRotationInterval): string {\n switch (interval) {\n case \"hourly\":\n return `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}-${date.getHours()}`;\n case \"daily\":\n return `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;\n case \"weekly\":\n return `${getISOWeekYear(date)}-${getISOWeek(date)}`;\n }\n}\n\nfunction getRotationIntervalMs(interval: TimeRotationInterval): number {\n switch (interval) {\n case \"hourly\":\n return 60 * 60 * 1000;\n case \"daily\":\n return 24 * 60 * 60 * 1000;\n case \"weekly\":\n return 7 * 24 * 60 * 60 * 1000;\n }\n}\n\n/**\n * Get a platform-independent time-rotating file sink.\n *\n * This sink writes log records to a file in a directory, rotating to a new\n * file based on time intervals. The filename is generated based on the\n * current date/time and the configured interval.\n *\n * @template TFile The type of the file descriptor.\n * @param options The options for the sink and the file driver.\n * @returns A sink that writes to the file. The sink is also a disposable\n * object that closes the file when disposed. If `nonBlocking` is\n * enabled, returns a sink that also implements {@link AsyncDisposable}.\n */\nexport function getBaseTimeRotatingFileSink<TFile>(\n options: TimeRotatingFileSinkOptions & TimeRotatingFileSinkDriver<TFile>,\n): Sink & Disposable;\nexport function getBaseTimeRotatingFileSink<TFile>(\n options: TimeRotatingFileSinkOptions & AsyncTimeRotatingFileSinkDriver<TFile>,\n): Sink & AsyncDisposable;\nexport function getBaseTimeRotatingFileSink<TFile>(\n options:\n & TimeRotatingFileSinkOptions\n & (\n | TimeRotatingFileSinkDriver<TFile>\n | AsyncTimeRotatingFileSinkDriver<TFile>\n ),\n): Sink & (Disposable | AsyncDisposable) {\n const formatter = options.formatter ?? defaultTextFormatter;\n const encoder = options.encoder ?? new TextEncoder();\n const interval = options.interval ?? \"daily\";\n const filenameGenerator = options.filename ?? getDefaultFilename(interval);\n const maxAgeMs = options.maxAgeMs;\n const cleanupInterval = maxAgeMs === undefined\n ? getRotationIntervalMs(interval)\n : Math.min(getRotationIntervalMs(interval), maxAgeMs);\n const bufferSize = options.bufferSize ?? 1024 * 8;\n const flushInterval = options.flushInterval ?? 5000;\n const directory = options.directory;\n\n // Ensure directory exists\n try {\n options.mkdirSync(directory, { recursive: true });\n } catch {\n // Directory might already exist\n }\n\n let currentFilename: string = filenameGenerator(new Date());\n let currentPath: string = options.joinPath(directory, currentFilename);\n let currentRotationKey: string = getRotationKey(new Date(), interval);\n let fd: TFile = options.openSync(currentPath);\n let lastFlushTimestamp: number = Date.now();\n let lastCleanupTimestamp: number | undefined;\n let buffer: string = \"\";\n\n function shouldRotate(): boolean {\n const now = new Date();\n const newKey = getRotationKey(now, interval);\n return newKey !== currentRotationKey;\n }\n\n function performRotation(): void {\n options.closeSync(fd);\n const now = new Date();\n currentFilename = filenameGenerator(now);\n currentPath = options.joinPath(directory, currentFilename);\n currentRotationKey = getRotationKey(now, interval);\n fd = options.openSync(currentPath);\n }\n\n function cleanupOldFiles(): void {\n if (maxAgeMs === undefined) return;\n\n const now = Date.now();\n if (\n lastCleanupTimestamp !== undefined && cleanupInterval > 0 &&\n now - lastCleanupTimestamp < cleanupInterval\n ) {\n return;\n }\n lastCleanupTimestamp = now;\n\n let files: string[];\n try {\n files = options.readdirSync(directory);\n } catch {\n return;\n }\n\n for (const file of files) {\n if (!file.endsWith(\".log\")) continue;\n if (file === currentFilename) continue;\n\n // Try to parse the date from the filename\n const dateMatch = file.match(\n /^(\\d{4})-(\\d{2})-(\\d{2})(?:-(\\d{2}))?\\.log$/,\n );\n const weekMatch = file.match(/^(\\d{4})-W(\\d{2})\\.log$/);\n\n let fileDate: Date | null = null;\n\n if (dateMatch) {\n const [, year, month, day, hour] = dateMatch;\n fileDate = new Date(\n parseInt(year!, 10),\n parseInt(month!, 10) - 1,\n parseInt(day!, 10),\n hour ? parseInt(hour, 10) : 0,\n );\n } else if (weekMatch) {\n const [, year, week] = weekMatch;\n // Get the date of the first day of the week\n const jan4 = new Date(parseInt(year!, 10), 0, 4);\n const dayOfWeek = jan4.getDay() || 7;\n fileDate = new Date(jan4);\n fileDate.setDate(\n jan4.getDate() - dayOfWeek + 1 + (parseInt(week!, 10) - 1) * 7,\n );\n }\n\n if (fileDate && now - fileDate.getTime() > maxAgeMs) {\n const filePath = options.joinPath(directory, file);\n try {\n options.unlinkSync(filePath);\n } catch {\n // Ignore errors when deleting files\n }\n }\n }\n }\n\n if (!options.nonBlocking) {\n // Blocking mode implementation\n // deno-lint-ignore no-inner-declarations\n function flushBuffer(): void {\n if (buffer.length > 0) {\n if (shouldRotate()) {\n performRotation();\n }\n const bytes = encoder.encode(buffer);\n buffer = \"\";\n options.writeSync(fd, bytes);\n options.flushSync(fd);\n lastFlushTimestamp = Date.now();\n cleanupOldFiles();\n }\n }\n\n const sink: Sink & Disposable = (record: LogRecord) => {\n buffer += formatter(record);\n\n const shouldFlushBySize = buffer.length >= bufferSize;\n const shouldFlushByTime = flushInterval > 0 &&\n (record.timestamp - lastFlushTimestamp) >= flushInterval;\n\n if (shouldFlushBySize || shouldFlushByTime) {\n flushBuffer();\n }\n };\n\n sink[Symbol.dispose] = () => {\n flushBuffer();\n options.closeSync(fd);\n };\n\n return sink;\n }\n\n // Non-blocking mode implementation\n const asyncOptions = options as AsyncTimeRotatingFileSinkDriver<TFile>;\n let disposed = false;\n let activeFlush: Promise<void> | null = null;\n let flushTimer: ReturnType<typeof setInterval> | null = null;\n\n async function flushBuffer(): Promise<void> {\n if (buffer.length === 0) return;\n\n if (shouldRotate()) {\n performRotation();\n }\n\n const data = buffer;\n buffer = \"\";\n try {\n const bytes = encoder.encode(data);\n asyncOptions.writeSync(fd, bytes);\n await asyncOptions.flush(fd);\n lastFlushTimestamp = Date.now();\n cleanupOldFiles();\n } catch {\n // Silently ignore errors in non-blocking mode\n }\n }\n\n function scheduleFlush(): void {\n if (activeFlush || disposed) return;\n\n activeFlush = flushBuffer().finally(() => {\n activeFlush = null;\n });\n }\n\n function startFlushTimer(): void {\n if (flushTimer !== null || disposed) return;\n\n flushTimer = setInterval(() => {\n scheduleFlush();\n }, flushInterval);\n }\n\n const nonBlockingSink: Sink & AsyncDisposable = (record: LogRecord) => {\n if (disposed) return;\n buffer += formatter(record);\n\n const shouldFlushBySize = buffer.length >= bufferSize;\n const shouldFlushByTime = flushInterval > 0 &&\n (record.timestamp - lastFlushTimestamp) >= flushInterval;\n\n if (shouldFlushBySize || shouldFlushByTime) {\n scheduleFlush();\n } else if (flushTimer === null && flushInterval > 0) {\n startFlushTimer();\n }\n };\n\n nonBlockingSink[Symbol.asyncDispose] = async () => {\n disposed = true;\n if (flushTimer !== null) {\n clearInterval(flushTimer);\n flushTimer = null;\n }\n await flushBuffer();\n try {\n await asyncOptions.close(fd);\n } catch {\n // Writer might already be closed or errored\n }\n };\n\n return nonBlockingSink;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logtape/file",
3
- "version": "2.2.0-dev.676+b60f1ae4",
3
+ "version": "2.2.0-dev.682+8c1c7b70",
4
4
  "description": "File sink and rotating file sink for LogTape",
5
5
  "keywords": [
6
6
  "logging",
@@ -60,7 +60,7 @@
60
60
  "dist/"
61
61
  ],
62
62
  "peerDependencies": {
63
- "@logtape/logtape": "^2.2.0-dev.676+b60f1ae4"
63
+ "@logtape/logtape": "^2.2.0-dev.682+8c1c7b70"
64
64
  },
65
65
  "devDependencies": {
66
66
  "@alinea/suite": "^0.6.3",