UNPKG

fortify2-js

Version:

MOST POWERFUL JavaScript Security Library! Military-grade cryptography + 19 enhanced object methods + quantum-resistant algorithms + perfect TypeScript support. More powerful than Lodash with built-in security.

625 lines (621 loc) 22.1 kB
'use strict'; var fs = require('fs'); var path = require('path'); var index = require('./index.js'); var cacheSys_utils = require('./cacheSys.utils.js'); var fs$1 = require('fs/promises'); var cache_config = require('./config/cache.config.js'); // Import check-disk-space with proper error handling let checkDiskSpace; try { checkDiskSpace = require("check-disk-space"); } catch (error) { console.warn("check-disk-space package not available, using fallback disk monitoring"); checkDiskSpace = null; } /** * Comprehensive File Cache System */ class FileCache { constructor(options = {}) { this.stats = { fileCount: 0, totalSize: 0, hits: 0, misses: 0, cleanups: 0, averageFileSize: 0, hitRate: 0, diskUsage: { used: 0, available: 0, percentage: 0, }, ageDistribution: { fresh: 0, recent: 0, old: 0, }, reads: 0, writes: 0, deletes: 0, errors: 0, totalFiles: 0, avgResponseTime: 0, lastCleanup: 0, }; this.config = { ...cache_config.DEFAULT_FILE_CACHE_CONFIG, ...options }; this.ensureBaseDirectory(); this.initializeStats(); } /** * Initialize cache statistics by scanning existing files */ async initializeStats() { try { const files = await this.getAllCacheFiles(); let totalSize = 0; let validFiles = 0; const now = Date.now(); const oneHour = 60 * 60 * 1000; const oneDay = 24 * oneHour; let fresh = 0; let recent = 0; let old = 0; for (const filePath of files) { try { const fileStats = fs.statSync(filePath); const fileContent = await fs$1.readFile(filePath, "utf8"); const parsedContent = JSON.parse(fileContent); const { metadata } = parsedContent; // Skip expired files if (now > metadata.expiresAt) { continue; } validFiles++; totalSize += fileStats.size; // Calculate age distribution const age = now - metadata.createdAt; if (age < oneHour) { fresh++; } else if (age < oneDay) { recent++; } else { old++; } } catch (error) { // Skip corrupted files continue; } } this.stats.fileCount = validFiles; this.stats.totalFiles = validFiles; this.stats.totalSize = totalSize; this.stats.averageFileSize = validFiles > 0 ? totalSize / validFiles : 0; this.stats.ageDistribution = { fresh, recent, old }; await this.updateDiskUsage(); } catch (error) { console.error("Error initializing stats:", error); } } /** * Ensure base cache directory exists */ ensureBaseDirectory() { if (!fs.existsSync(this.config.directory)) { fs.mkdirSync(this.config.directory, { recursive: true }); } } /** * Update cache statistics */ updateStats() { const totalRequests = this.stats.hits + this.stats.misses; this.stats.hitRate = totalRequests > 0 ? (this.stats.hits / totalRequests) * 100 : 0; this.stats.fileCount = this.stats.totalFiles; this.stats.averageFileSize = this.stats.totalFiles > 0 ? this.stats.totalSize / this.stats.totalFiles : 0; } /** * Update disk usage statistics */ async updateDiskUsage() { try { // Calculate cache directory size const directorySize = await this.getDirectorySize(this.config.directory); // Get real disk space information using check-disk-space if (checkDiskSpace && typeof checkDiskSpace === "function") { try { const diskSpace = await checkDiskSpace(this.config.directory); // Update disk usage with real values this.stats.diskUsage.used = directorySize; // Cache directory size this.stats.diskUsage.available = diskSpace.free; // Available disk space this.stats.diskUsage.percentage = diskSpace.size > 0 ? ((diskSpace.size - diskSpace.free) / diskSpace.size) * 100 : 0; // Percentage of total disk used } catch (diskSpaceError) { console.warn("Could not get disk space, falling back to cache size limits!"); // Fallback to cache size limits if disk space check fails this.stats.diskUsage.used = directorySize; this.stats.diskUsage.available = Math.max(0, this.config.maxCacheSize - directorySize); this.stats.diskUsage.percentage = this.config.maxCacheSize > 0 ? (directorySize / this.config.maxCacheSize) * 100 : 0; } } else { // Package not available, use fallback this.stats.diskUsage.used = directorySize; this.stats.diskUsage.available = Math.max(0, this.config.maxCacheSize - directorySize); this.stats.diskUsage.percentage = this.config.maxCacheSize > 0 ? (directorySize / this.config.maxCacheSize) * 100 : 0; } } catch (error) { console.error("Error updating disk usage:", error); // Set safe defaults on complete failure this.stats.diskUsage.used = 0; this.stats.diskUsage.available = this.config.maxCacheSize; this.stats.diskUsage.percentage = 0; } } /** * Calculate directory size recursively */ async getDirectorySize(dirPath) { let totalSize = 0; try { if (!fs.existsSync(dirPath)) return 0; const entries = fs.readdirSync(dirPath, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dirPath, entry.name); if (entry.isDirectory()) { totalSize += await this.getDirectorySize(fullPath); } else { const stats = fs.statSync(fullPath); totalSize += stats.size; } } } catch (error) { console.error("Error calculating directory size:", error); } return totalSize; } /** * Update age distribution statistics */ async updateAgeDistribution() { try { const files = await this.getAllCacheFiles(); const now = Date.now(); const oneHour = 60 * 60 * 1000; const oneDay = 24 * oneHour; let fresh = 0; let recent = 0; let old = 0; for (const filePath of files) { try { const fileContent = await fs$1.readFile(filePath, "utf8"); const parsedContent = JSON.parse(fileContent); const { metadata } = parsedContent; // Skip expired files if (now > metadata.expiresAt) { continue; } const age = now - metadata.createdAt; if (age < oneHour) { fresh++; } else if (age < oneDay) { recent++; } else { old++; } } catch (error) { // Skip corrupted files continue; } } this.stats.ageDistribution = { fresh, recent, old }; } catch (error) { console.error("Error updating age distribution:", error); } } /** * Write data to file cache */ async set(key, value, options = {}) { const startTime = Date.now(); try { const config = { ...this.config, ...options }; const filePath = index.generateFilePath(key, config); await cacheSys_utils.ensureDirectoryExists(filePath); // Prepare data const serialized = JSON.stringify(value); // Check file size limit if (serialized.length > config.maxFileSize) { throw new Error(`Data too large (max ${config.maxFileSize} bytes)`); } let processedData = serialized; let compressed = false; // Compress if enabled if (config.compress) { const result = await cacheSys_utils.compressFileData(serialized); processedData = result.data; compressed = result.compressed; } // Create metadata const now = Date.now(); const metadata = { key, createdAt: now, lastAccessed: now, expiresAt: now + config.ttl, size: processedData.length, accessCount: 0, compressed, encrypted: config.encrypt, dataType: typeof value, version: 1, }; let finalData = processedData; let encryptionData = null; // Encrypt if enabled if (config.encrypt) { const result = cacheSys_utils.encryptFileData(processedData); finalData = result.encrypted; encryptionData = { iv: result.iv, authTag: result.authTag, key: result.key, }; } // Prepare file content const fileContent = { metadata, data: finalData, encryption: encryptionData, }; const fileContentString = JSON.stringify(fileContent, null, 2); const isNewFile = !fs.existsSync(filePath); // Write file (atomic if enabled) if (config.atomic) { const tempPath = `${filePath}.tmp`; await fs$1.writeFile(tempPath, fileContentString, "utf8"); await fs$1.rename(tempPath, filePath); } else { await fs$1.writeFile(filePath, fileContentString, "utf8"); } // Update statistics this.stats.writes++; if (isNewFile) { this.stats.totalFiles++; this.stats.fileCount++; } this.stats.totalSize += fileContentString.length; // Update average response time const responseTime = Date.now() - startTime; this.stats.avgResponseTime = this.stats.writes === 1 ? responseTime : (this.stats.avgResponseTime * (this.stats.writes - 1) + responseTime) / this.stats.writes; this.updateStats(); await this.updateDiskUsage(); return true; } catch (error) { console.error("File cache write error:", error); this.stats.errors++; return false; } } /** * Read data from file cache */ async get(key, updatedContent = false) { const startTime = Date.now(); try { const filePath = index.generateFilePath(key, this.config); // Check if file exists if (!fs.existsSync(filePath)) { this.stats.misses++; this.updateStats(); return null; } // Read file const fileContent = await fs$1.readFile(filePath, "utf8"); const parsedContent = JSON.parse(fileContent); const { metadata, data, encryption } = parsedContent; // Check expiration if (Date.now() > metadata.expiresAt) { await this.delete(key); this.stats.misses++; this.updateStats(); return null; } let processedData = data; // Decrypt if needed if (metadata.encrypted && encryption) { processedData = cacheSys_utils.decryptFileData(data, encryption.iv, encryption.authTag, encryption.key); } // Decompress if needed if (metadata.compressed) { processedData = await cacheSys_utils.decompressFileData(processedData, true); } // Update metadata access info metadata.lastAccessed = Date.now(); metadata.accessCount = (metadata.accessCount || 0) + 1; // Update the file with new access info (optional - might impact performance) if (updatedContent) { const updatedContent = { metadata, data, encryption, }; await fs$1.writeFile(filePath, JSON.stringify(updatedContent, null, 2), "utf8"); } // Update statistics this.stats.reads++; this.stats.hits++; const responseTime = Date.now() - startTime; this.stats.avgResponseTime = this.stats.reads === 1 ? responseTime : (this.stats.avgResponseTime * (this.stats.reads - 1) + responseTime) / this.stats.reads; this.updateStats(); return JSON.parse(processedData); } catch (error) { console.error("File cache read error:", error); this.stats.errors++; this.stats.misses++; this.updateStats(); return null; } } /** * Delete cache entry */ async delete(key) { try { const filePath = index.generateFilePath(key, this.config); if (fs.existsSync(filePath)) { const fileStats = fs.statSync(filePath); await fs$1.unlink(filePath); this.stats.deletes++; this.stats.totalFiles = Math.max(0, this.stats.totalFiles - 1); this.stats.fileCount = Math.max(0, this.stats.fileCount - 1); this.stats.totalSize = Math.max(0, this.stats.totalSize - fileStats.size); this.updateStats(); await this.updateDiskUsage(); return true; } return false; } catch (error) { console.error("File cache delete error:", error); this.stats.errors++; return false; } } /** * Check if key exists and is not expired */ async has(key) { try { const filePath = index.generateFilePath(key, this.config); if (!fs.existsSync(filePath)) { return false; } const fileContent = await fs$1.readFile(filePath, "utf8"); const parsedContent = JSON.parse(fileContent); const { metadata } = parsedContent; if (Date.now() > metadata.expiresAt) { await this.delete(key); return false; } return true; } catch (error) { return false; } } /** * Clear all cache files */ async clear() { try { if (fs.existsSync(this.config.directory)) { await this.deleteDirectory(this.config.directory); fs.mkdirSync(this.config.directory, { recursive: true }); } this.stats = { fileCount: 0, totalSize: 0, hits: 0, misses: 0, cleanups: 0, averageFileSize: 0, hitRate: 0, diskUsage: { used: 0, available: 0, percentage: 0, }, ageDistribution: { fresh: 0, recent: 0, old: 0, }, reads: 0, writes: 0, deletes: 0, errors: 0, totalFiles: 0, avgResponseTime: 0, lastCleanup: Date.now(), }; } catch (error) { console.error("File cache clear error:", error); } } /** * Recursively delete directory */ async deleteDirectory(dir) { const entries = await fs$1.readdir(dir, { withFileTypes: true }); await Promise.all(entries.map(async (entry) => { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { await this.deleteDirectory(fullPath); } else { await fs$1.unlink(fullPath); } })); await fs$1.rmdir(dir); } /** * Cleanup expired entries */ async cleanup(_options = {}) { let cleaned = 0; let errors = 0; let totalSize = 0; try { const files = await this.getAllCacheFiles(); for (const filePath of files) { try { const fileContent = await fs$1.readFile(filePath, "utf8"); const parsedContent = JSON.parse(fileContent); const { metadata } = parsedContent; // Check if expired if (Date.now() > metadata.expiresAt) { const fileStats = fs.statSync(filePath); await fs$1.unlink(filePath); cleaned++; totalSize += fileStats.size; } } catch (error) { errors++; console.error(`Error processing cache file ${filePath}:`, error); } } this.stats.lastCleanup = Date.now(); this.stats.cleanups++; this.stats.totalFiles = Math.max(0, this.stats.totalFiles - cleaned); this.stats.fileCount = Math.max(0, this.stats.fileCount - cleaned); this.stats.totalSize = Math.max(0, this.stats.totalSize - totalSize); this.updateStats(); await this.updateDiskUsage(); await this.updateAgeDistribution(); } catch (error) { console.error("File cache cleanup error:", error); errors++; this.stats.errors++; } return { cleaned, errors, totalSize }; } /** * Get all cache files recursively */ async getAllCacheFiles() { const files = []; const scanDirectory = async (dir) => { if (!fs.existsSync(dir)) return; const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { await scanDirectory(fullPath); } else if (entry.name.endsWith(this.config.extension)) { files.push(fullPath); } } }; await scanDirectory(this.config.directory); return files; } /** * Get cache statistics with real-time updates */ async getStats() { await this.updateDiskUsage(); await this.updateAgeDistribution(); this.updateStats(); return { ...this.stats }; } /** * Get cache size information */ get size() { return { files: this.stats.totalFiles, bytes: this.stats.totalSize, }; } /** * Get detailed cache information */ async getCacheInfo() { const stats = await this.getStats(); const issues = []; const recommendations = []; // Health checks if (stats.hitRate < 50) { issues.push("Low cache hit rate"); recommendations.push("Consider increasing TTL or reviewing cache keys"); } if (stats.diskUsage.percentage > 90) { issues.push("High disk usage"); recommendations.push("Run cleanup or increase cache size limit"); } if (stats.errors > stats.reads * 0.1) { issues.push("High error rate"); recommendations.push("Check file system permissions and disk space"); } return { config: this.config, stats, health: { healthy: issues.length === 0, issues, recommendations, }, }; } } exports.FileCache = FileCache; //# sourceMappingURL=cacheSys.js.map