UNPKG

vibelogger

Version:

AI-Native Logging for LLM Agent Development - TypeScript/Node.js Implementation

182 lines 5.59 kB
/** * File system utilities for VibeCoding Logger * * Handles file operations with thread safety and error handling, * similar to Python implementation but using Node.js patterns */ import { promises as fs, constants } from 'fs'; import { dirname, resolve } from 'path'; /** * Thread-safe file operations manager */ export class FileSystemManager { static writeQueues = new Map(); /** * Ensure directory exists, creating it if necessary */ static async ensureDirectory(filePath) { try { const dir = dirname(resolve(filePath)); await fs.mkdir(dir, { recursive: true }); } catch (error) { // If error is not "already exists", throw it if (error.code !== 'EEXIST') { throw error; } } } /** * Check if file exists */ static async fileExists(filePath) { try { await fs.access(filePath, constants.F_OK); return true; } catch { return false; } } /** * Get file size in MB */ static async getFileSizeMb(filePath) { try { const stats = await fs.stat(filePath); return stats.size / (1024 * 1024); } catch { return 0; } } /** * Append text to file with thread safety * Uses a queue per file to prevent concurrent writes */ static async appendToFile(filePath, content) { const resolvedPath = resolve(filePath); // Get or create queue for this file const existingQueue = this.writeQueues.get(resolvedPath) || Promise.resolve(); // Chain the new write operation const newQueue = existingQueue .then(async () => { try { await fs.appendFile(resolvedPath, content, 'utf8'); return { success: true }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : String(error) }; } }) .catch((error) => ({ success: false, error: error instanceof Error ? error.message : String(error) })); // Update the queue this.writeQueues.set(resolvedPath, newQueue); // Clean up completed queues after a delay newQueue.finally(() => { setTimeout(() => { if (this.writeQueues.get(resolvedPath) === newQueue) { this.writeQueues.delete(resolvedPath); } }, 1000); }); return await newQueue; } /** * Rotate log file if it exceeds size limit */ static async rotateFileIfNeeded(filePath, maxSizeMb) { try { const resolvedPath = resolve(filePath); if (!(await this.fileExists(resolvedPath))) { return { success: true }; } const currentSizeMb = await this.getFileSizeMb(resolvedPath); if (currentSizeMb <= maxSizeMb) { return { success: true }; } // Generate rotated filename with timestamp const timestamp = new Date().toISOString() .replace(/[-:]/g, '') .replace('T', '_') .split('.')[0]; const rotatedPath = `${resolvedPath}.${timestamp}`; // Rename current file await fs.rename(resolvedPath, rotatedPath); return { success: true, rotated: true }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : String(error) }; } } /** * Write entire content to file (for batch operations) */ static async writeFile(filePath, content) { try { const resolvedPath = resolve(filePath); await fs.writeFile(resolvedPath, content, 'utf8'); return { success: true }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : String(error) }; } } /** * Read entire file content */ static async readFile(filePath) { try { const resolvedPath = resolve(filePath); const content = await fs.readFile(resolvedPath, 'utf8'); return { success: true, content }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : String(error) }; } } /** * Read file line by line (for loading existing logs) */ static async *readFileLines(filePath) { try { const content = await fs.readFile(resolve(filePath), 'utf8'); const lines = content.split('\n'); for (const line of lines) { const trimmed = line.trim(); if (trimmed) { yield trimmed; } } } catch (error) { // If file doesn't exist or can't be read, just return without yielding return; } } /** * Clean up completed write queues (for memory management) */ static clearCompletedQueues() { this.writeQueues.clear(); } } //# sourceMappingURL=fileSystem.js.map