mcp-quickbase
Version:
Work with Quickbase via Model Context Protocol
211 lines • 6.26 kB
JavaScript
;
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