Skip to main content
Glama
QueryCache.tsโ€ข8.09 kB
/** * QueryCache - LRU cache for database query results * * Provides an in-memory caching layer with: * - LRU (Least Recently Used) eviction policy * - TTL (Time To Live) for automatic expiration * - Cache statistics (hits, misses, evictions, hit rate) * - Smart invalidation support * * Use this to cache expensive database queries like conversation searches, * file timelines, and decision lookups. * * @example * ```typescript * const cache = new QueryCache({ maxSize: 100, ttlMs: 60000 }); * * // Store query result * cache.set('conversations:all', conversations); * * // Retrieve from cache * const cached = cache.get('conversations:all'); * if (cached) { * return cached; // Cache hit * } * * // Cache miss - query database * const result = await queryDatabase(); * cache.set('conversations:all', result); * return result; * ``` */ /** * Configuration options for QueryCache */ export interface QueryCacheConfig { /** Maximum number of entries to store */ maxSize: number; /** Time to live in milliseconds before entries expire */ ttlMs: number; } /** * Cache entry with timestamp for TTL */ interface CacheEntry<T> { /** Cached value */ value: T; /** Timestamp when entry was created/updated */ timestamp: number; } /** * Cache statistics for monitoring */ export interface CacheStats { /** Number of cache hits */ hits: number; /** Number of cache misses */ misses: number; /** Number of entries evicted */ evictions: number; /** Cache hit rate (0-1) */ hitRate: number; /** Current cache size */ size: number; /** Maximum cache size */ maxSize: number; } /** * LRU cache with TTL support for database query results. * * Uses a Map for O(1) access and maintains LRU order by moving * accessed entries to the end of the Map. */ export class QueryCache { private cache: Map<string, CacheEntry<unknown>>; private readonly config: QueryCacheConfig; private stats = { hits: 0, misses: 0, evictions: 0, }; /** * Create a new QueryCache. * * @param config - Cache configuration * @throws {Error} If configuration is invalid * * @example * ```typescript * // Create cache with 100 entries, 1 minute TTL * const cache = new QueryCache({ maxSize: 100, ttlMs: 60000 }); * ``` */ constructor(config: Partial<QueryCacheConfig> = {}) { // Default configuration this.config = { maxSize: config.maxSize ?? 100, ttlMs: config.ttlMs ?? 300000, // 5 minutes default }; // Validate configuration if (this.config.maxSize <= 0) { throw new Error("maxSize must be greater than 0"); } if (this.config.ttlMs <= 0) { throw new Error("ttlMs must be greater than 0"); } this.cache = new Map(); } /** * Store a value in the cache. * * If the key already exists, updates the value and resets TTL. * If cache is full, evicts the least recently used entry. * * @param key - Cache key * @param value - Value to cache * * @example * ```typescript * cache.set('user:123', { id: 123, name: 'Alice' }); * ``` */ set<T>(key: string, value: T): void { // Remove old entry if exists (to update position) if (this.cache.has(key)) { this.cache.delete(key); } // Evict LRU entry if at capacity if (this.cache.size >= this.config.maxSize) { const firstKey = this.cache.keys().next().value; if (firstKey !== undefined) { this.cache.delete(firstKey); this.stats.evictions++; } } // Add new entry at end (most recently used) this.cache.set(key, { value: value as unknown, timestamp: Date.now(), }); } /** * Retrieve a value from the cache. * * Returns undefined if key doesn't exist or entry has expired. * Updates access order (moves entry to end as most recently used). * * @param key - Cache key * @returns Cached value or undefined * * @example * ```typescript * const user = cache.get<User>('user:123'); * if (user) { * console.log('Cache hit:', user.name); * } else { * console.log('Cache miss'); * } * ``` */ get<T>(key: string): T | undefined { const entry = this.cache.get(key); if (!entry) { this.stats.misses++; return undefined; } // Check if expired if (this.isExpired(entry)) { this.cache.delete(key); this.stats.misses++; return undefined; } // Move to end (most recently used) this.cache.delete(key); this.cache.set(key, entry); this.stats.hits++; return entry.value as T; } /** * Check if a key exists in the cache. * * Returns false if key doesn't exist or entry has expired. * Updates access order (moves entry to end as most recently used). * * @param key - Cache key * @returns True if key exists and not expired * * @example * ```typescript * if (cache.has('user:123')) { * const user = cache.get('user:123'); * } * ``` */ has(key: string): boolean { const entry = this.cache.get(key); if (!entry) { return false; } // Check if expired if (this.isExpired(entry)) { this.cache.delete(key); return false; } // Move to end (most recently used) this.cache.delete(key); this.cache.set(key, entry); return true; } /** * Delete a key from the cache. * * Does nothing if key doesn't exist. * * @param key - Cache key to delete * * @example * ```typescript * cache.delete('user:123'); * ``` */ delete(key: string): void { this.cache.delete(key); } /** * Clear all entries from the cache. * * Resets the cache but preserves statistics. * * @example * ```typescript * cache.clear(); // Remove all cached data * ``` */ clear(): void { this.cache.clear(); } /** * Get current cache size. * * @returns Number of entries in cache * * @example * ```typescript * console.log(`Cache contains ${cache.size()} entries`); * ``` */ size(): number { // Clean up expired entries first this.cleanupExpired(); return this.cache.size; } /** * Get cache statistics. * * Provides insight into cache performance: * - Hit/miss counts * - Hit rate percentage * - Eviction count * - Current size * * @returns Cache statistics object * * @example * ```typescript * const stats = cache.getStats(); * console.log(`Hit rate: ${(stats.hitRate * 100).toFixed(1)}%`); * console.log(`Evictions: ${stats.evictions}`); * ``` */ getStats(): CacheStats { const totalRequests = this.stats.hits + this.stats.misses; const hitRate = totalRequests > 0 ? this.stats.hits / totalRequests : 0; return { hits: this.stats.hits, misses: this.stats.misses, evictions: this.stats.evictions, hitRate, size: this.cache.size, maxSize: this.config.maxSize, }; } /** * Reset statistics counters. * * Clears hit/miss/eviction counts but preserves cached data. * * @example * ```typescript * cache.resetStats(); // Start fresh statistics * ``` */ resetStats(): void { this.stats = { hits: 0, misses: 0, evictions: 0, }; } /** * Check if an entry has expired based on TTL. * * @param entry - Cache entry to check * @returns True if entry is expired * @internal */ private isExpired(entry: CacheEntry<unknown>): boolean { const age = Date.now() - entry.timestamp; return age > this.config.ttlMs; } /** * Remove all expired entries from the cache. * * Called automatically by size() to keep cache clean. * * @internal */ private cleanupExpired(): void { const keysToDelete: string[] = []; for (const [key, entry] of this.cache.entries()) { if (this.isExpired(entry)) { keysToDelete.push(key); } } for (const key of keysToDelete) { this.cache.delete(key); } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/xiaolai/claude-writers-aid-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server