@logtape/file 2.2.0-dev.759 → 2.2.0-dev.761

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.
@@ -1,4 +1,5 @@
1
1
  const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
2
+ const require_snapshot = require('./snapshot.cjs');
2
3
  const __logtape_logtape = require_rolldown_runtime.__toESM(require("@logtape/logtape"));
3
4
 
4
5
  //#region src/filesink.base.ts
@@ -240,7 +241,7 @@ function getBaseFileSink(path, options) {
240
241
  }
241
242
  bufferPool.clear();
242
243
  };
243
- return sink;
244
+ return require_snapshot.markSinkAsImmediate(sink);
244
245
  }
245
246
  const asyncOptions = options;
246
247
  let disposed = false;
@@ -340,7 +341,7 @@ function getBaseFileSink(path, options) {
340
341
  } catch {}
341
342
  bufferPool.clear();
342
343
  };
343
- return nonBlockingSink;
344
+ return require_snapshot.markSinkAsImmediate(nonBlockingSink);
344
345
  }
345
346
  function isFileNotFoundError(error) {
346
347
  if (!(error instanceof Error)) return false;
@@ -422,7 +423,7 @@ function getBaseRotatingFileSink(path, options) {
422
423
  flushBuffer$1();
423
424
  options.closeSync(fd);
424
425
  };
425
- return sink;
426
+ return require_snapshot.markSinkAsImmediate(sink);
426
427
  }
427
428
  const asyncOptions = options;
428
429
  let disposed = false;
@@ -488,7 +489,7 @@ function getBaseRotatingFileSink(path, options) {
488
489
  await asyncOptions.close(fd);
489
490
  } catch {}
490
491
  };
491
- return nonBlockingSink;
492
+ return require_snapshot.markSinkAsImmediate(nonBlockingSink);
492
493
  }
493
494
 
494
495
  //#endregion
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"filesink.base.d.cts","names":[],"sources":["../src/filesink.base.ts"],"sourcesContent":[],"mappings":";;;;;;AAiRA;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 +1 @@
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
+ {"version":3,"file":"filesink.base.d.ts","names":[],"sources":["../src/filesink.base.ts"],"sourcesContent":[],"mappings":";;;;;;AAiRA;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,3 +1,4 @@
1
+ import { markSinkAsImmediate } from "./snapshot.js";
1
2
  import { defaultTextFormatter, getLogger } from "@logtape/logtape";
2
3
 
3
4
  //#region src/filesink.base.ts
@@ -239,7 +240,7 @@ function getBaseFileSink(path, options) {
239
240
  }
240
241
  bufferPool.clear();
241
242
  };
242
- return sink;
243
+ return markSinkAsImmediate(sink);
243
244
  }
244
245
  const asyncOptions = options;
245
246
  let disposed = false;
@@ -339,7 +340,7 @@ function getBaseFileSink(path, options) {
339
340
  } catch {}
340
341
  bufferPool.clear();
341
342
  };
342
- return nonBlockingSink;
343
+ return markSinkAsImmediate(nonBlockingSink);
343
344
  }
344
345
  function isFileNotFoundError(error) {
345
346
  if (!(error instanceof Error)) return false;
@@ -421,7 +422,7 @@ function getBaseRotatingFileSink(path, options) {
421
422
  flushBuffer$1();
422
423
  options.closeSync(fd);
423
424
  };
424
- return sink;
425
+ return markSinkAsImmediate(sink);
425
426
  }
426
427
  const asyncOptions = options;
427
428
  let disposed = false;
@@ -487,7 +488,7 @@ function getBaseRotatingFileSink(path, options) {
487
488
  await asyncOptions.close(fd);
488
489
  } catch {}
489
490
  };
490
- return nonBlockingSink;
491
+ return markSinkAsImmediate(nonBlockingSink);
491
492
  }
492
493
 
493
494
  //#endregion
@@ -1 +1 @@
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 if (!disposed && buffer.length > 0) 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 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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"}
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\";\nimport { markSinkAsImmediate } from \"./snapshot.ts\";\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 markSinkAsImmediate(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 markSinkAsImmediate(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 markSinkAsImmediate(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 if (!disposed && buffer.length > 0) 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 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 markSinkAsImmediate(nonBlockingSink);\n}\n"],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+
2
+ //#region src/snapshot.ts
3
+ const immediateSinkSymbol = Symbol.for("LogTape.sinkSnapshotPolicy.immediate");
4
+ function markSinkAsImmediate(sink) {
5
+ Object.defineProperty(sink, immediateSinkSymbol, { value: true });
6
+ return sink;
7
+ }
8
+
9
+ //#endregion
10
+ exports.markSinkAsImmediate = markSinkAsImmediate;
@@ -0,0 +1,10 @@
1
+ //#region src/snapshot.ts
2
+ const immediateSinkSymbol = Symbol.for("LogTape.sinkSnapshotPolicy.immediate");
3
+ function markSinkAsImmediate(sink) {
4
+ Object.defineProperty(sink, immediateSinkSymbol, { value: true });
5
+ return sink;
6
+ }
7
+
8
+ //#endregion
9
+ export { markSinkAsImmediate };
10
+ //# sourceMappingURL=snapshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot.js","names":["sink: T"],"sources":["../src/snapshot.ts"],"sourcesContent":["import type { Sink } from \"@logtape/logtape\";\n\nconst immediateSinkSymbol = Symbol.for(\n \"LogTape.sinkSnapshotPolicy.immediate\",\n);\n\nexport function markSinkAsImmediate<T extends Sink>(sink: T): T {\n Object.defineProperty(sink, immediateSinkSymbol, {\n value: true,\n });\n return sink;\n}\n"],"mappings":";AAEA,MAAM,sBAAsB,OAAO,IACjC,uCACD;AAED,SAAgB,oBAAoCA,MAAY;AAC9D,QAAO,eAAe,MAAM,qBAAqB,EAC/C,OAAO,KACR,EAAC;AACF,QAAO;AACR"}
@@ -1,22 +1,22 @@
1
1
  const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
2
+ const require_snapshot = require('./snapshot.cjs');
2
3
  const __logtape_logtape = require_rolldown_runtime.__toESM(require("@logtape/logtape"));
3
4
  const node_fs = require_rolldown_runtime.__toESM(require("node:fs"));
4
- const node_stream = require_rolldown_runtime.__toESM(require("node:stream"));
5
+ const node_events = require_rolldown_runtime.__toESM(require("node:events"));
5
6
 
6
7
  //#region src/streamfilesink.ts
7
8
  /**
8
9
  * Create a high-performance stream-based file sink that writes log records to a file.
9
10
  *
10
- * This sink uses Node.js PassThrough streams piped to WriteStreams for optimal
11
- * I/O performance. It leverages the Node.js stream infrastructure to provide
12
- * automatic backpressure management, efficient buffering, and asynchronous writes
13
- * without blocking the main thread.
11
+ * This sink writes formatted records directly to a Node.js `WriteStream`. It
12
+ * leverages Node.js stream buffering for efficient asynchronous writes without
13
+ * blocking the main thread.
14
14
  *
15
15
  * ## Performance Characteristics
16
16
  *
17
17
  * - **High Performance**: Optimized for high-volume logging scenarios
18
18
  * - **Non-blocking**: Uses asynchronous I/O that doesn't block the main thread
19
- * - **Memory Efficient**: Automatic backpressure prevents memory buildup
19
+ * - **Memory Efficient**: Uses the file stream buffer directly
20
20
  * - **Stream-based**: Leverages Node.js native stream optimizations
21
21
  *
22
22
  * ## When to Use
@@ -24,7 +24,7 @@ const node_stream = require_rolldown_runtime.__toESM(require("node:stream"));
24
24
  * Use this sink when you need:
25
25
  * - High-performance file logging for production applications
26
26
  * - Non-blocking I/O behavior for real-time applications
27
- * - Automatic backpressure handling for high-volume scenarios
27
+ * - Stream-buffered writes for high-volume scenarios
28
28
  * - Simple file output without complex buffering configuration
29
29
  *
30
30
  * For more control over buffering behavior, consider using {@link getFileSink}
@@ -61,36 +61,37 @@ const node_stream = require_rolldown_runtime.__toESM(require("node:stream"));
61
61
  function getStreamFileSink(path, options = {}) {
62
62
  const highWaterMark = options.highWaterMark ?? 16384;
63
63
  const formatter = options.formatter ?? __logtape_logtape.defaultTextFormatter;
64
- const passThrough = new node_stream.PassThrough({
65
- highWaterMark,
66
- objectMode: false
64
+ const writeStream = (0, node_fs.createWriteStream)(path, {
65
+ flags: "a",
66
+ highWaterMark
67
67
  });
68
- const writeStream = (0, node_fs.createWriteStream)(path, { flags: "a" });
69
- passThrough.pipe(writeStream);
70
68
  let disposed = false;
69
+ let streamError;
70
+ const handleStreamError = (error) => {
71
+ streamError = error;
72
+ };
73
+ writeStream.on("error", handleStreamError);
71
74
  const sink = (record) => {
72
75
  if (disposed) return;
73
- passThrough.write(formatter(record));
76
+ writeStream.write(formatter(record));
74
77
  };
75
78
  sink[Symbol.asyncDispose] = async () => {
76
79
  if (disposed) return;
77
80
  disposed = true;
78
- if (passThrough.writableNeedDrain) await new Promise((resolve) => {
79
- passThrough.once("drain", resolve);
80
- });
81
- await new Promise((resolve) => {
82
- passThrough.once("finish", resolve);
83
- passThrough.end();
84
- });
85
- await new Promise((resolve) => {
86
- if (writeStream.closed || writeStream.destroyed) {
87
- resolve();
88
- return;
89
- }
90
- writeStream.once("close", resolve);
91
- });
81
+ try {
82
+ if (streamError != null) throw streamError;
83
+ if (writeStream.writableNeedDrain && !writeStream.writableEnded) await (0, node_events.once)(writeStream, "drain");
84
+ if (streamError != null) throw streamError;
85
+ if (writeStream.closed || writeStream.destroyed) return;
86
+ const closePromise = (0, node_events.once)(writeStream, "close");
87
+ writeStream.end();
88
+ await closePromise;
89
+ if (streamError != null) throw streamError;
90
+ } finally {
91
+ writeStream.off("error", handleStreamError);
92
+ }
92
93
  };
93
- return sink;
94
+ return require_snapshot.markSinkAsImmediate(sink);
94
95
  }
95
96
 
96
97
  //#endregion
@@ -6,18 +6,18 @@ import { Sink, TextFormatter } from "@logtape/logtape";
6
6
  * Options for the {@link getStreamFileSink} function.
7
7
  *
8
8
  * This interface configures the high-performance stream-based file sink that
9
- * uses Node.js PassThrough streams for optimal I/O performance with automatic
10
- * backpressure management.
9
+ * writes directly to Node.js file streams for optimal I/O performance with
10
+ * native stream buffering.
11
11
  *
12
12
  * @since 1.0.0
13
13
  */
14
14
  interface StreamFileSinkOptions {
15
15
  /**
16
- * High water mark for the PassThrough stream buffer in bytes.
16
+ * High water mark for the file stream buffer in bytes.
17
17
  *
18
- * This controls the internal buffer size of the PassThrough stream.
19
- * Higher values can improve performance for high-volume logging but use
20
- * more memory. Lower values reduce memory usage but may impact performance.
18
+ * This controls the internal buffer size of the underlying file stream.
19
+ * Higher values can improve performance for high-volume logging but use more
20
+ * memory. Lower values reduce memory usage but may impact performance.
21
21
  *
22
22
  * @default 16384
23
23
  * @since 1.0.0
@@ -38,16 +38,15 @@ interface StreamFileSinkOptions {
38
38
  /**
39
39
  * Create a high-performance stream-based file sink that writes log records to a file.
40
40
  *
41
- * This sink uses Node.js PassThrough streams piped to WriteStreams for optimal
42
- * I/O performance. It leverages the Node.js stream infrastructure to provide
43
- * automatic backpressure management, efficient buffering, and asynchronous writes
44
- * without blocking the main thread.
41
+ * This sink writes formatted records directly to a Node.js `WriteStream`. It
42
+ * leverages Node.js stream buffering for efficient asynchronous writes without
43
+ * blocking the main thread.
45
44
  *
46
45
  * ## Performance Characteristics
47
46
  *
48
47
  * - **High Performance**: Optimized for high-volume logging scenarios
49
48
  * - **Non-blocking**: Uses asynchronous I/O that doesn't block the main thread
50
- * - **Memory Efficient**: Automatic backpressure prevents memory buildup
49
+ * - **Memory Efficient**: Uses the file stream buffer directly
51
50
  * - **Stream-based**: Leverages Node.js native stream optimizations
52
51
  *
53
52
  * ## When to Use
@@ -55,7 +54,7 @@ interface StreamFileSinkOptions {
55
54
  * Use this sink when you need:
56
55
  * - High-performance file logging for production applications
57
56
  * - Non-blocking I/O behavior for real-time applications
58
- * - Automatic backpressure handling for high-volume scenarios
57
+ * - Stream-buffered writes for high-volume scenarios
59
58
  * - Simple file output without complex buffering configuration
60
59
  *
61
60
  * For more control over buffering behavior, consider using {@link getFileSink}
@@ -1 +1 @@
1
- {"version":3,"file":"streamfilesink.d.cts","names":[],"sources":["../src/streamfilesink.ts"],"sourcesContent":[],"mappings":";;;;;;AAkBA;AAgFA;;;;;AAGyB;UAnFR,qBAAA;;;;;;;;;;;;;;;;;;;;;;uBAuBM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAyDP,iBAAA,yBAEL,wBACR,OAAO"}
1
+ {"version":3,"file":"streamfilesink.d.cts","names":[],"sources":["../src/streamfilesink.ts"],"sourcesContent":[],"mappings":";;;;;;AAmBA;AA+EA;;;;;AAGyB;UAlFR,qBAAA;;;;;;;;;;;;;;;;;;;;;;uBAuBM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwDP,iBAAA,yBAEL,wBACR,OAAO"}
@@ -6,18 +6,18 @@ import { Sink, TextFormatter } from "@logtape/logtape";
6
6
  * Options for the {@link getStreamFileSink} function.
7
7
  *
8
8
  * This interface configures the high-performance stream-based file sink that
9
- * uses Node.js PassThrough streams for optimal I/O performance with automatic
10
- * backpressure management.
9
+ * writes directly to Node.js file streams for optimal I/O performance with
10
+ * native stream buffering.
11
11
  *
12
12
  * @since 1.0.0
13
13
  */
14
14
  interface StreamFileSinkOptions {
15
15
  /**
16
- * High water mark for the PassThrough stream buffer in bytes.
16
+ * High water mark for the file stream buffer in bytes.
17
17
  *
18
- * This controls the internal buffer size of the PassThrough stream.
19
- * Higher values can improve performance for high-volume logging but use
20
- * more memory. Lower values reduce memory usage but may impact performance.
18
+ * This controls the internal buffer size of the underlying file stream.
19
+ * Higher values can improve performance for high-volume logging but use more
20
+ * memory. Lower values reduce memory usage but may impact performance.
21
21
  *
22
22
  * @default 16384
23
23
  * @since 1.0.0
@@ -38,16 +38,15 @@ interface StreamFileSinkOptions {
38
38
  /**
39
39
  * Create a high-performance stream-based file sink that writes log records to a file.
40
40
  *
41
- * This sink uses Node.js PassThrough streams piped to WriteStreams for optimal
42
- * I/O performance. It leverages the Node.js stream infrastructure to provide
43
- * automatic backpressure management, efficient buffering, and asynchronous writes
44
- * without blocking the main thread.
41
+ * This sink writes formatted records directly to a Node.js `WriteStream`. It
42
+ * leverages Node.js stream buffering for efficient asynchronous writes without
43
+ * blocking the main thread.
45
44
  *
46
45
  * ## Performance Characteristics
47
46
  *
48
47
  * - **High Performance**: Optimized for high-volume logging scenarios
49
48
  * - **Non-blocking**: Uses asynchronous I/O that doesn't block the main thread
50
- * - **Memory Efficient**: Automatic backpressure prevents memory buildup
49
+ * - **Memory Efficient**: Uses the file stream buffer directly
51
50
  * - **Stream-based**: Leverages Node.js native stream optimizations
52
51
  *
53
52
  * ## When to Use
@@ -55,7 +54,7 @@ interface StreamFileSinkOptions {
55
54
  * Use this sink when you need:
56
55
  * - High-performance file logging for production applications
57
56
  * - Non-blocking I/O behavior for real-time applications
58
- * - Automatic backpressure handling for high-volume scenarios
57
+ * - Stream-buffered writes for high-volume scenarios
59
58
  * - Simple file output without complex buffering configuration
60
59
  *
61
60
  * For more control over buffering behavior, consider using {@link getFileSink}
@@ -1 +1 @@
1
- {"version":3,"file":"streamfilesink.d.ts","names":[],"sources":["../src/streamfilesink.ts"],"sourcesContent":[],"mappings":";;;;;;AAkBA;AAgFA;;;;;AAGyB;UAnFR,qBAAA;;;;;;;;;;;;;;;;;;;;;;uBAuBM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAyDP,iBAAA,yBAEL,wBACR,OAAO"}
1
+ {"version":3,"file":"streamfilesink.d.ts","names":[],"sources":["../src/streamfilesink.ts"],"sourcesContent":[],"mappings":";;;;;;AAmBA;AA+EA;;;;;AAGyB;UAlFR,qBAAA;;;;;;;;;;;;;;;;;;;;;;uBAuBM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwDP,iBAAA,yBAEL,wBACR,OAAO"}
@@ -1,21 +1,21 @@
1
+ import { markSinkAsImmediate } from "./snapshot.js";
1
2
  import { defaultTextFormatter } from "@logtape/logtape";
2
3
  import { createWriteStream } from "node:fs";
3
- import { PassThrough } from "node:stream";
4
+ import { once } from "node:events";
4
5
 
5
6
  //#region src/streamfilesink.ts
6
7
  /**
7
8
  * Create a high-performance stream-based file sink that writes log records to a file.
8
9
  *
9
- * This sink uses Node.js PassThrough streams piped to WriteStreams for optimal
10
- * I/O performance. It leverages the Node.js stream infrastructure to provide
11
- * automatic backpressure management, efficient buffering, and asynchronous writes
12
- * without blocking the main thread.
10
+ * This sink writes formatted records directly to a Node.js `WriteStream`. It
11
+ * leverages Node.js stream buffering for efficient asynchronous writes without
12
+ * blocking the main thread.
13
13
  *
14
14
  * ## Performance Characteristics
15
15
  *
16
16
  * - **High Performance**: Optimized for high-volume logging scenarios
17
17
  * - **Non-blocking**: Uses asynchronous I/O that doesn't block the main thread
18
- * - **Memory Efficient**: Automatic backpressure prevents memory buildup
18
+ * - **Memory Efficient**: Uses the file stream buffer directly
19
19
  * - **Stream-based**: Leverages Node.js native stream optimizations
20
20
  *
21
21
  * ## When to Use
@@ -23,7 +23,7 @@ import { PassThrough } from "node:stream";
23
23
  * Use this sink when you need:
24
24
  * - High-performance file logging for production applications
25
25
  * - Non-blocking I/O behavior for real-time applications
26
- * - Automatic backpressure handling for high-volume scenarios
26
+ * - Stream-buffered writes for high-volume scenarios
27
27
  * - Simple file output without complex buffering configuration
28
28
  *
29
29
  * For more control over buffering behavior, consider using {@link getFileSink}
@@ -60,36 +60,37 @@ import { PassThrough } from "node:stream";
60
60
  function getStreamFileSink(path, options = {}) {
61
61
  const highWaterMark = options.highWaterMark ?? 16384;
62
62
  const formatter = options.formatter ?? defaultTextFormatter;
63
- const passThrough = new PassThrough({
64
- highWaterMark,
65
- objectMode: false
63
+ const writeStream = createWriteStream(path, {
64
+ flags: "a",
65
+ highWaterMark
66
66
  });
67
- const writeStream = createWriteStream(path, { flags: "a" });
68
- passThrough.pipe(writeStream);
69
67
  let disposed = false;
68
+ let streamError;
69
+ const handleStreamError = (error) => {
70
+ streamError = error;
71
+ };
72
+ writeStream.on("error", handleStreamError);
70
73
  const sink = (record) => {
71
74
  if (disposed) return;
72
- passThrough.write(formatter(record));
75
+ writeStream.write(formatter(record));
73
76
  };
74
77
  sink[Symbol.asyncDispose] = async () => {
75
78
  if (disposed) return;
76
79
  disposed = true;
77
- if (passThrough.writableNeedDrain) await new Promise((resolve) => {
78
- passThrough.once("drain", resolve);
79
- });
80
- await new Promise((resolve) => {
81
- passThrough.once("finish", resolve);
82
- passThrough.end();
83
- });
84
- await new Promise((resolve) => {
85
- if (writeStream.closed || writeStream.destroyed) {
86
- resolve();
87
- return;
88
- }
89
- writeStream.once("close", resolve);
90
- });
80
+ try {
81
+ if (streamError != null) throw streamError;
82
+ if (writeStream.writableNeedDrain && !writeStream.writableEnded) await once(writeStream, "drain");
83
+ if (streamError != null) throw streamError;
84
+ if (writeStream.closed || writeStream.destroyed) return;
85
+ const closePromise = once(writeStream, "close");
86
+ writeStream.end();
87
+ await closePromise;
88
+ if (streamError != null) throw streamError;
89
+ } finally {
90
+ writeStream.off("error", handleStreamError);
91
+ }
91
92
  };
92
- return sink;
93
+ return markSinkAsImmediate(sink);
93
94
  }
94
95
 
95
96
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"streamfilesink.js","names":["path: string","options: StreamFileSinkOptions","sink: Sink & AsyncDisposable","record: LogRecord"],"sources":["../src/streamfilesink.ts"],"sourcesContent":["import {\n defaultTextFormatter,\n type LogRecord,\n type Sink,\n type TextFormatter,\n} from \"@logtape/logtape\";\nimport { createWriteStream } from \"node:fs\";\nimport { PassThrough } from \"node:stream\";\n\n/**\n * Options for the {@link getStreamFileSink} function.\n *\n * This interface configures the high-performance stream-based file sink that\n * uses Node.js PassThrough streams for optimal I/O performance with automatic\n * backpressure management.\n *\n * @since 1.0.0\n */\nexport interface StreamFileSinkOptions {\n /**\n * High water mark for the PassThrough stream buffer in bytes.\n *\n * This controls the internal buffer size of the PassThrough stream.\n * Higher values can improve performance for high-volume logging but use\n * more memory. Lower values reduce memory usage but may impact performance.\n *\n * @default 16384\n * @since 1.0.0\n */\n readonly highWaterMark?: number;\n\n /**\n * A custom formatter for log records.\n *\n * If not specified, the default text formatter will be used, which formats\n * records in the standard LogTape format with timestamp, level, category,\n * and message.\n *\n * @default defaultTextFormatter\n * @since 1.0.0\n */\n readonly formatter?: TextFormatter;\n}\n\n/**\n * Create a high-performance stream-based file sink that writes log records to a file.\n *\n * This sink uses Node.js PassThrough streams piped to WriteStreams for optimal\n * I/O performance. It leverages the Node.js stream infrastructure to provide\n * automatic backpressure management, efficient buffering, and asynchronous writes\n * without blocking the main thread.\n *\n * ## Performance Characteristics\n *\n * - **High Performance**: Optimized for high-volume logging scenarios\n * - **Non-blocking**: Uses asynchronous I/O that doesn't block the main thread\n * - **Memory Efficient**: Automatic backpressure prevents memory buildup\n * - **Stream-based**: Leverages Node.js native stream optimizations\n *\n * ## When to Use\n *\n * Use this sink when you need:\n * - High-performance file logging for production applications\n * - Non-blocking I/O behavior for real-time applications\n * - Automatic backpressure handling for high-volume scenarios\n * - Simple file output without complex buffering configuration\n *\n * For more control over buffering behavior, consider using {@link getFileSink}\n * instead, which provides options for buffer size, flush intervals, and\n * non-blocking modes.\n *\n * ## Example\n *\n * ```typescript\n * import { configure } from \"@logtape/logtape\";\n * import { getStreamFileSink } from \"@logtape/file\";\n *\n * await configure({\n * sinks: {\n * file: getStreamFileSink(\"app.log\", {\n * highWaterMark: 32768 // 32KB buffer for high-volume logging\n * })\n * },\n * loggers: [\n * { category: [\"myapp\"], sinks: [\"file\"] }\n * ]\n * });\n * ```\n *\n * @param path The path to the file to write logs to. The file will be created\n * if it doesn't exist, or appended to if it does exist.\n * @param options Configuration options for the stream-based sink.\n * @returns A sink that writes formatted log records to the specified file.\n * The returned sink implements `AsyncDisposable` for proper resource cleanup\n * that waits for all data to be flushed to disk.\n *\n * @since 1.0.0\n */\nexport function getStreamFileSink(\n path: string,\n options: StreamFileSinkOptions = {},\n): Sink & AsyncDisposable {\n const highWaterMark = options.highWaterMark ?? 16384;\n const formatter = options.formatter ?? defaultTextFormatter;\n\n // Create PassThrough stream for optimal performance\n const passThrough = new PassThrough({\n highWaterMark,\n objectMode: false,\n });\n\n // Create WriteStream immediately (not lazy)\n const writeStream = createWriteStream(path, { flags: \"a\" });\n\n // Pipe PassThrough to WriteStream for automatic backpressure handling\n passThrough.pipe(writeStream);\n\n let disposed = false;\n\n // Stream-based sink function for high performance\n const sink: Sink & AsyncDisposable = (record: LogRecord) => {\n if (disposed) return;\n\n // Direct write to PassThrough stream\n passThrough.write(formatter(record));\n };\n\n // Asynchronous disposal with sequential stream closure\n sink[Symbol.asyncDispose] = async () => {\n if (disposed) return;\n disposed = true;\n\n // Wait for PassThrough to drain if needed\n if (passThrough.writableNeedDrain) {\n await new Promise<void>((resolve) => {\n passThrough.once(\"drain\", resolve);\n });\n }\n\n // End the PassThrough stream first and wait for it to finish\n await new Promise<void>((resolve) => {\n passThrough.once(\"finish\", resolve);\n passThrough.end();\n });\n\n // Wait for WriteStream to finish and close (piped streams auto-close)\n await new Promise<void>((resolve) => {\n if (writeStream.closed || writeStream.destroyed) {\n resolve();\n return;\n }\n writeStream.once(\"close\", resolve);\n });\n };\n\n return sink;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkGA,SAAgB,kBACdA,MACAC,UAAiC,CAAE,GACX;CACxB,MAAM,gBAAgB,QAAQ,iBAAiB;CAC/C,MAAM,YAAY,QAAQ,aAAa;CAGvC,MAAM,cAAc,IAAI,YAAY;EAClC;EACA,YAAY;CACb;CAGD,MAAM,cAAc,kBAAkB,MAAM,EAAE,OAAO,IAAK,EAAC;AAG3D,aAAY,KAAK,YAAY;CAE7B,IAAI,WAAW;CAGf,MAAMC,OAA+B,CAACC,WAAsB;AAC1D,MAAI,SAAU;AAGd,cAAY,MAAM,UAAU,OAAO,CAAC;CACrC;AAGD,MAAK,OAAO,gBAAgB,YAAY;AACtC,MAAI,SAAU;AACd,aAAW;AAGX,MAAI,YAAY,kBACd,OAAM,IAAI,QAAc,CAAC,YAAY;AACnC,eAAY,KAAK,SAAS,QAAQ;EACnC;AAIH,QAAM,IAAI,QAAc,CAAC,YAAY;AACnC,eAAY,KAAK,UAAU,QAAQ;AACnC,eAAY,KAAK;EAClB;AAGD,QAAM,IAAI,QAAc,CAAC,YAAY;AACnC,OAAI,YAAY,UAAU,YAAY,WAAW;AAC/C,aAAS;AACT;GACD;AACD,eAAY,KAAK,SAAS,QAAQ;EACnC;CACF;AAED,QAAO;AACR"}
1
+ {"version":3,"file":"streamfilesink.js","names":["path: string","options: StreamFileSinkOptions","streamError: Error | undefined","error: Error","sink: Sink & AsyncDisposable","record: LogRecord"],"sources":["../src/streamfilesink.ts"],"sourcesContent":["import {\n defaultTextFormatter,\n type LogRecord,\n type Sink,\n type TextFormatter,\n} from \"@logtape/logtape\";\nimport { once } from \"node:events\";\nimport { createWriteStream } from \"node:fs\";\nimport { markSinkAsImmediate } from \"./snapshot.ts\";\n\n/**\n * Options for the {@link getStreamFileSink} function.\n *\n * This interface configures the high-performance stream-based file sink that\n * writes directly to Node.js file streams for optimal I/O performance with\n * native stream buffering.\n *\n * @since 1.0.0\n */\nexport interface StreamFileSinkOptions {\n /**\n * High water mark for the file stream buffer in bytes.\n *\n * This controls the internal buffer size of the underlying file stream.\n * Higher values can improve performance for high-volume logging but use more\n * memory. Lower values reduce memory usage but may impact performance.\n *\n * @default 16384\n * @since 1.0.0\n */\n readonly highWaterMark?: number;\n\n /**\n * A custom formatter for log records.\n *\n * If not specified, the default text formatter will be used, which formats\n * records in the standard LogTape format with timestamp, level, category,\n * and message.\n *\n * @default defaultTextFormatter\n * @since 1.0.0\n */\n readonly formatter?: TextFormatter;\n}\n\n/**\n * Create a high-performance stream-based file sink that writes log records to a file.\n *\n * This sink writes formatted records directly to a Node.js `WriteStream`. It\n * leverages Node.js stream buffering for efficient asynchronous writes without\n * blocking the main thread.\n *\n * ## Performance Characteristics\n *\n * - **High Performance**: Optimized for high-volume logging scenarios\n * - **Non-blocking**: Uses asynchronous I/O that doesn't block the main thread\n * - **Memory Efficient**: Uses the file stream buffer directly\n * - **Stream-based**: Leverages Node.js native stream optimizations\n *\n * ## When to Use\n *\n * Use this sink when you need:\n * - High-performance file logging for production applications\n * - Non-blocking I/O behavior for real-time applications\n * - Stream-buffered writes for high-volume scenarios\n * - Simple file output without complex buffering configuration\n *\n * For more control over buffering behavior, consider using {@link getFileSink}\n * instead, which provides options for buffer size, flush intervals, and\n * non-blocking modes.\n *\n * ## Example\n *\n * ```typescript\n * import { configure } from \"@logtape/logtape\";\n * import { getStreamFileSink } from \"@logtape/file\";\n *\n * await configure({\n * sinks: {\n * file: getStreamFileSink(\"app.log\", {\n * highWaterMark: 32768 // 32KB buffer for high-volume logging\n * })\n * },\n * loggers: [\n * { category: [\"myapp\"], sinks: [\"file\"] }\n * ]\n * });\n * ```\n *\n * @param path The path to the file to write logs to. The file will be created\n * if it doesn't exist, or appended to if it does exist.\n * @param options Configuration options for the stream-based sink.\n * @returns A sink that writes formatted log records to the specified file.\n * The returned sink implements `AsyncDisposable` for proper resource cleanup\n * that waits for all data to be flushed to disk.\n *\n * @since 1.0.0\n */\nexport function getStreamFileSink(\n path: string,\n options: StreamFileSinkOptions = {},\n): Sink & AsyncDisposable {\n const highWaterMark = options.highWaterMark ?? 16384;\n const formatter = options.formatter ?? defaultTextFormatter;\n\n const writeStream = createWriteStream(path, {\n flags: \"a\",\n highWaterMark,\n });\n\n let disposed = false;\n let streamError: Error | undefined;\n const handleStreamError = (error: Error) => {\n streamError = error;\n };\n writeStream.on(\"error\", handleStreamError);\n\n const sink: Sink & AsyncDisposable = (record: LogRecord) => {\n if (disposed) return;\n\n // The hot path writes straight to the file stream. We intentionally avoid\n // awaiting backpressure here; async disposal below is the synchronization\n // point that waits for buffered records to reach the filesystem.\n writeStream.write(formatter(record));\n };\n\n sink[Symbol.asyncDispose] = async () => {\n if (disposed) return;\n disposed = true;\n\n try {\n if (streamError != null) throw streamError;\n\n if (writeStream.writableNeedDrain && !writeStream.writableEnded) {\n await once(writeStream, \"drain\");\n }\n if (streamError != null) throw streamError;\n\n if (writeStream.closed || writeStream.destroyed) return;\n\n const closePromise = once(writeStream, \"close\");\n writeStream.end();\n await closePromise;\n\n if (streamError != null) throw streamError;\n } finally {\n writeStream.off(\"error\", handleStreamError);\n }\n };\n\n return markSinkAsImmediate(sink);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkGA,SAAgB,kBACdA,MACAC,UAAiC,CAAE,GACX;CACxB,MAAM,gBAAgB,QAAQ,iBAAiB;CAC/C,MAAM,YAAY,QAAQ,aAAa;CAEvC,MAAM,cAAc,kBAAkB,MAAM;EAC1C,OAAO;EACP;CACD,EAAC;CAEF,IAAI,WAAW;CACf,IAAIC;CACJ,MAAM,oBAAoB,CAACC,UAAiB;AAC1C,gBAAc;CACf;AACD,aAAY,GAAG,SAAS,kBAAkB;CAE1C,MAAMC,OAA+B,CAACC,WAAsB;AAC1D,MAAI,SAAU;AAKd,cAAY,MAAM,UAAU,OAAO,CAAC;CACrC;AAED,MAAK,OAAO,gBAAgB,YAAY;AACtC,MAAI,SAAU;AACd,aAAW;AAEX,MAAI;AACF,OAAI,eAAe,KAAM,OAAM;AAE/B,OAAI,YAAY,sBAAsB,YAAY,cAChD,OAAM,KAAK,aAAa,QAAQ;AAElC,OAAI,eAAe,KAAM,OAAM;AAE/B,OAAI,YAAY,UAAU,YAAY,UAAW;GAEjD,MAAM,eAAe,KAAK,aAAa,QAAQ;AAC/C,eAAY,KAAK;AACjB,SAAM;AAEN,OAAI,eAAe,KAAM,OAAM;EAChC,UAAS;AACR,eAAY,IAAI,SAAS,kBAAkB;EAC5C;CACF;AAED,QAAO,oBAAoB,KAAK;AACjC"}
@@ -1,4 +1,5 @@
1
1
  const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
2
+ const require_snapshot = require('./snapshot.cjs');
2
3
  const __logtape_logtape = require_rolldown_runtime.__toESM(require("@logtape/logtape"));
3
4
 
4
5
  //#region src/timefilesink.ts
@@ -164,7 +165,7 @@ function getBaseTimeRotatingFileSink(options) {
164
165
  flushBuffer$1();
165
166
  options.closeSync(fd);
166
167
  };
167
- return sink;
168
+ return require_snapshot.markSinkAsImmediate(sink);
168
169
  }
169
170
  const asyncOptions = options;
170
171
  let disposed = false;
@@ -214,7 +215,7 @@ function getBaseTimeRotatingFileSink(options) {
214
215
  await asyncOptions.close(fd);
215
216
  } catch {}
216
217
  };
217
- return nonBlockingSink;
218
+ return require_snapshot.markSinkAsImmediate(nonBlockingSink);
218
219
  }
219
220
 
220
221
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"timefilesink.d.cts","names":[],"sources":["../src/timefilesink.ts"],"sourcesContent":[],"mappings":";;;;;;;AAcA;AAKiB,KALL,oBAAA,GAMV,QAAA,GAAA,OAAA,GAAA,QAAA;;;;AAkBW,UAnBI,2BAAA,SACP,IAkBG,CAlBE,eAkBF,EAAA,MAAA,CAAA,CAAA;EAAoB;AAlBnB;AA8Bd;EAA2C,SAAA,EAAA,MAAA;EAAA;;AACnB;AAiCxB;;;;EAC6B,QAAA,CAAA,EAAA,CAAA,IAAA,EApDT,IAoDS,EAAA,GAAA,MAAA;;;;aA/ChB;;;;;;;;;;UAYI,0CACP,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAiCR,+CACP,oBAAoB"}
1
+ {"version":3,"file":"timefilesink.d.cts","names":[],"sources":["../src/timefilesink.ts"],"sourcesContent":[],"mappings":";;;;;;;AAeA;AAKiB,KALL,oBAAA,GAMV,QAAA,GAAA,OAAA,GAAA,QAAA;;;;AAkBW,UAnBI,2BAAA,SACP,IAkBG,CAlBE,eAkBF,EAAA,MAAA,CAAA,CAAA;EAAoB;AAlBnB;AA8Bd;EAA2C,SAAA,EAAA,MAAA;EAAA;;AACnB;AAiCxB;;;;EAC6B,QAAA,CAAA,EAAA,CAAA,IAAA,EApDT,IAoDS,EAAA,GAAA,MAAA;;;;aA/ChB;;;;;;;;;;UAYI,0CACP,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAiCR,+CACP,oBAAoB"}
@@ -1 +1 @@
1
- {"version":3,"file":"timefilesink.d.ts","names":[],"sources":["../src/timefilesink.ts"],"sourcesContent":[],"mappings":";;;;;;;AAcA;AAKiB,KALL,oBAAA,GAMV,QAAA,GAAA,OAAA,GAAA,QAAA;;;;AAkBW,UAnBI,2BAAA,SACP,IAkBG,CAlBE,eAkBF,EAAA,MAAA,CAAA,CAAA;EAAoB;AAlBnB;AA8Bd;EAA2C,SAAA,EAAA,MAAA;EAAA;;AACnB;AAiCxB;;;;EAC6B,QAAA,CAAA,EAAA,CAAA,IAAA,EApDT,IAoDS,EAAA,GAAA,MAAA;;;;aA/ChB;;;;;;;;;;UAYI,0CACP,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAiCR,+CACP,oBAAoB"}
1
+ {"version":3,"file":"timefilesink.d.ts","names":[],"sources":["../src/timefilesink.ts"],"sourcesContent":[],"mappings":";;;;;;;AAeA;AAKiB,KALL,oBAAA,GAMV,QAAA,GAAA,OAAA,GAAA,QAAA;;;;AAkBW,UAnBI,2BAAA,SACP,IAkBG,CAlBE,eAkBF,EAAA,MAAA,CAAA,CAAA;EAAoB;AAlBnB;AA8Bd;EAA2C,SAAA,EAAA,MAAA;EAAA;;AACnB;AAiCxB;;;;EAC6B,QAAA,CAAA,EAAA,CAAA,IAAA,EApDT,IAoDS,EAAA,GAAA,MAAA;;;;aA/ChB;;;;;;;;;;UAYI,0CACP,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAiCR,+CACP,oBAAoB"}
@@ -1,3 +1,4 @@
1
+ import { markSinkAsImmediate } from "./snapshot.js";
1
2
  import { defaultTextFormatter } from "@logtape/logtape";
2
3
 
3
4
  //#region src/timefilesink.ts
@@ -163,7 +164,7 @@ function getBaseTimeRotatingFileSink(options) {
163
164
  flushBuffer$1();
164
165
  options.closeSync(fd);
165
166
  };
166
- return sink;
167
+ return markSinkAsImmediate(sink);
167
168
  }
168
169
  const asyncOptions = options;
169
170
  let disposed = false;
@@ -213,7 +214,7 @@ function getBaseTimeRotatingFileSink(options) {
213
214
  await asyncOptions.close(fd);
214
215
  } catch {}
215
216
  };
216
- return nonBlockingSink;
217
+ return markSinkAsImmediate(nonBlockingSink);
217
218
  }
218
219
 
219
220
  //#endregion
@@ -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","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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"}
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\";\nimport { markSinkAsImmediate } from \"./snapshot.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 markSinkAsImmediate(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 markSinkAsImmediate(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.759+9daafa66",
3
+ "version": "2.2.0-dev.761+b49eecfd",
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.759+9daafa66"
63
+ "@logtape/logtape": "^2.2.0-dev.761+b49eecfd"
64
64
  },
65
65
  "devDependencies": {
66
66
  "@alinea/suite": "^0.6.3",