qbo-mcp-ts
Version:
TypeScript QuickBooks Online MCP Server with enhanced features and dual transport support
283 lines • 9.03 kB
JavaScript
"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