UNPKG

mcp-quickbase

Version:

Work with Quickbase via Model Context Protocol

211 lines 6.26 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CacheService = void 0; const node_cache_1 = __importDefault(require("node-cache")); const logger_1 = require("./logger"); const logger = (0, logger_1.createLogger)("cache"); /** * Cache service for API responses */ class CacheService { /** * Creates a new cache service * @param ttl Default TTL in seconds (default: 3600) * @param enabled Whether caching is enabled (default: true) */ constructor(ttl = 3600, enabled = true) { this.operationLock = Promise.resolve(); this.cache = new node_cache_1.default({ stdTTL: ttl, checkperiod: ttl * 0.2 }); this.enabled = enabled; // Register this instance for cleanup CacheService.instances.add(this); // Install cleanup handlers once if (!CacheService.cleanupHandlerInstalled) { CacheService.installCleanupHandlers(); CacheService.cleanupHandlerInstalled = true; } logger.info(`Cache initialized with TTL of ${ttl} seconds, enabled: ${enabled}`); } /** * Gets a value from the cache * @param key Cache key * @returns The cached value or undefined if not found */ get(key) { if (!this.enabled) { return undefined; } const value = this.cache.get(key); if (value) { logger.debug(`Cache hit for key: ${key}`); } else { logger.debug(`Cache miss for key: ${key}`); } return value; } /** * Sets a value in the cache * @param key Cache key * @param value Value to cache * @param ttl TTL in seconds (optional, uses default if not specified) */ set(key, value, ttl) { if (!this.enabled) { return; } if (typeof ttl === "number") { this.cache.set(key, value, ttl); } else { this.cache.set(key, value); } logger.debug(`Cache set for key: ${key}`); } /** * Removes a value from the cache * @param key Cache key */ del(key) { this.cache.del(key); logger.debug(`Cache entry deleted for key: ${key}`); } /** * Clears all cache entries */ clear() { this.cache.flushAll(); logger.info("Cache cleared"); } /** * Enables or disables the cache * @param enabled Whether the cache should be enabled */ setEnabled(enabled) { this.enabled = enabled; logger.info(`Cache ${enabled ? "enabled" : "disabled"}`); if (!enabled) { this.clear(); } } /** * Returns whether the cache is enabled */ isEnabled() { return this.enabled; } /** * Checks if a key exists in the cache * @param key Cache key * @returns True if the key exists, false otherwise */ has(key) { if (!this.enabled) { return false; } return this.cache.has(key); } /** * Removes a value from the cache (alias for del) * @param key Cache key */ delete(key) { this.del(key); } /** * Gets cache statistics * @returns Cache statistics */ getStats() { const stats = this.cache.getStats(); return { hits: stats.hits, misses: stats.misses, keys: this.cache.keys().length, }; } /** * Sets a new default TTL * @param ttl New TTL in seconds */ setTtl(ttl) { // Serialize TTL changes to prevent corruption this.operationLock = this.operationLock.then(async () => { await this.safeTtlUpdate(ttl); }); } async safeTtlUpdate(ttl) { // Wait a brief moment to allow in-flight operations to complete await new Promise((resolve) => setTimeout(resolve, 100)); const oldCache = this.cache; // Create new cache with updated TTL const newCache = new node_cache_1.default({ stdTTL: ttl, checkperiod: ttl * 0.2 }); // Migrate existing data to new cache (if any) try { const keys = oldCache.keys(); for (const key of keys) { const value = oldCache.get(key); if (value !== undefined) { newCache.set(key, value); } } } catch (error) { logger.warn("Error migrating cache data during TTL update", { error }); } // Replace the cache atomically this.cache = newCache; // Clean up old cache try { oldCache.flushAll(); oldCache.close(); } catch (error) { logger.warn("Error closing old cache instance", { error }); } logger.info(`Cache TTL updated to ${ttl} seconds`); } /** * Cleanup this cache instance */ cleanup() { try { this.cache.flushAll(); this.cache.close(); logger.debug("Cache instance cleaned up"); } catch (error) { logger.error("Error cleaning up cache", { error }); } } /** * Install process cleanup handlers (called once) */ static installCleanupHandlers() { const cleanup = () => { logger.info("Cleaning up all cache instances"); for (const instance of CacheService.instances) { instance.cleanup(); } CacheService.instances.clear(); }; process.on("exit", cleanup); process.on("SIGTERM", cleanup); process.on("SIGINT", cleanup); process.on("uncaughtException", cleanup); process.on("unhandledRejection", cleanup); } /** * Get cleanup statistics */ static getStats() { return { instances: CacheService.instances.size }; } } exports.CacheService = CacheService; CacheService.instances = new Set(); CacheService.cleanupHandlerInstalled = false; //# sourceMappingURL=cache.js.map