UNPKG

qbo-mcp-ts

Version:

TypeScript QuickBooks Online MCP Server with enhanced features and dual transport support

283 lines 9.03 kB
"use strict"; /** * Cache service for QBOMCP-TS */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.cacheService = exports.CacheService = void 0; const config_1 = require("../utils/config"); const logger_1 = require("../utils/logger"); const fs = __importStar(require("fs/promises")); const path = __importStar(require("path")); const crypto = __importStar(require("crypto")); /** * In-memory cache with optional file persistence */ class CacheService { cache; maxSize; defaultTTL; persistPath; cleanupInterval; constructor() { const cacheConfig = config_1.config.getCacheConfig(); this.cache = new Map(); this.maxSize = cacheConfig.maxSize; this.defaultTTL = cacheConfig.ttl; if (cacheConfig.enabled) { this.persistPath = path.join(cacheConfig.dir, 'cache.json'); this.loadFromDisk(); // Start cleanup interval this.cleanupInterval = setInterval(() => { this.cleanup(); }, 60000); // Every minute } } /** * Generate cache key from input */ generateKey(input) { const data = typeof input === 'string' ? input : JSON.stringify(input); return crypto.createHash('sha256').update(data).digest('hex'); } /** * Calculate size of value in bytes */ calculateSize(value) { const str = JSON.stringify(value); return Buffer.byteLength(str, 'utf8'); } /** * Get value from cache */ async get(key) { const normalizedKey = this.generateKey(key); const entry = this.cache.get(normalizedKey); if (!entry) { logger_1.logger.cache('miss', normalizedKey); return null; } // Check if expired if (new Date() > entry.expiresAt) { logger_1.logger.cache('miss', normalizedKey, { reason: 'expired' }); this.cache.delete(normalizedKey); return null; } // Update hit count entry.hits++; logger_1.logger.cache('hit', normalizedKey, { hits: entry.hits }); return entry.value; } /** * Set value in cache */ async set(key, value, ttl) { const normalizedKey = this.generateKey(key); const expiresAt = new Date(Date.now() + (ttl || this.defaultTTL) * 1000); const size = this.calculateSize(value); // Check cache size and evict if necessary if (this.cache.size >= this.maxSize) { this.evictLRU(); } const entry = { key: normalizedKey, value, expiresAt, createdAt: new Date(), hits: 0, size, }; this.cache.set(normalizedKey, entry); logger_1.logger.cache('set', normalizedKey, { ttl: ttl || this.defaultTTL, size }); // Persist to disk if enabled if (this.persistPath) { this.saveToDisk(); } } /** * Delete value from cache */ async delete(key) { const normalizedKey = this.generateKey(key); const deleted = this.cache.delete(normalizedKey); if (deleted) { logger_1.logger.cache('delete', normalizedKey); // Persist to disk if enabled if (this.persistPath) { this.saveToDisk(); } } } /** * Clear entire cache */ async clear() { const size = this.cache.size; this.cache.clear(); logger_1.logger.cache('clear', undefined, { cleared: size }); // Clear persisted cache if (this.persistPath) { try { await fs.unlink(this.persistPath); } catch (_error) { // File might not exist } } } /** * Evict least recently used entry */ evictLRU() { let lruKey = null; let lruHits = Infinity; // let lruTime = new Date(); for (const [key, entry] of this.cache.entries()) { // Skip if expired (will be cleaned up) if (new Date() > entry.expiresAt) { continue; } // Find least recently used based on hits and creation time const score = entry.hits / ((Date.now() - entry.createdAt.getTime()) / 1000); if (score < lruHits) { lruHits = score; lruKey = key; // lruTime = entry.createdAt; } } if (lruKey) { this.cache.delete(lruKey); logger_1.logger.debug(`Evicted LRU cache entry: ${lruKey}`); } } /** * Clean up expired entries */ cleanup() { const now = new Date(); let cleaned = 0; for (const [key, entry] of this.cache.entries()) { if (now > entry.expiresAt) { this.cache.delete(key); cleaned++; } } if (cleaned > 0) { logger_1.logger.debug(`Cleaned up ${cleaned} expired cache entries`); // Persist to disk if enabled if (this.persistPath) { this.saveToDisk(); } } } /** * Load cache from disk */ async loadFromDisk() { if (!this.persistPath) return; try { const data = await fs.readFile(this.persistPath, 'utf-8'); const entries = JSON.parse(data); const now = new Date(); let loaded = 0; for (const entry of entries) { // Convert date strings back to Date objects entry.expiresAt = new Date(entry.expiresAt); entry.createdAt = new Date(entry.createdAt); // Skip expired entries if (now > entry.expiresAt) { continue; } this.cache.set(entry.key, entry); loaded++; } logger_1.logger.info(`Loaded ${loaded} cache entries from disk`); } catch (_error) { // File might not exist or be corrupted logger_1.logger.debug('No existing cache file found or failed to load'); } } /** * Save cache to disk */ async saveToDisk() { if (!this.persistPath) return; try { const entries = Array.from(this.cache.values()); await fs.writeFile(this.persistPath, JSON.stringify(entries, null, 2)); } catch (error) { logger_1.logger.error('Failed to save cache to disk', error); } } /** * Get cache statistics */ getStats() { let totalHits = 0; let totalRequests = 0; let totalSize = 0; for (const entry of this.cache.values()) { totalHits += entry.hits; totalRequests += entry.hits + 1; // +1 for initial set totalSize += entry.size; } return { size: this.cache.size, maxSize: this.maxSize, hitRate: totalRequests > 0 ? totalHits / totalRequests : 0, totalHits, totalSize, }; } /** * Shutdown cache service */ async shutdown() { if (this.cleanupInterval) { clearInterval(this.cleanupInterval); } if (this.persistPath) { await this.saveToDisk(); } } } exports.CacheService = CacheService; // Export singleton instance exports.cacheService = new CacheService(); //# sourceMappingURL=cache.js.map