Skip to main content
Glama
northernvariables

FedMCP - Federal Parliamentary Information

cache.ts4.95 kB
/** * Simple in-memory cache with TTL support * * Provides efficient caching for expensive queries like randomMPs and topSpenders. * Cache entries automatically expire based on TTL. * Includes size limits to prevent memory exhaustion from DoS attacks. */ interface CacheEntry<T> { data: T; expiresAt: number; sizeBytes: number; } /** * Configuration for cache limits */ const CACHE_LIMITS = { MAX_ENTRIES: 1000, // Maximum number of cache entries MAX_TOTAL_SIZE_MB: 100, // Maximum total cache size in MB MAX_ENTRY_SIZE_MB: 10, // Maximum size of a single entry in MB }; class QueryCache { private cache: Map<string, CacheEntry<any>> = new Map(); private totalSizeBytes: number = 0; /** * Get cached value if it exists and hasn't expired */ get<T>(key: string): T | null { const entry = this.cache.get(key); if (!entry) { return null; } // Check if expired if (Date.now() > entry.expiresAt) { this.cache.delete(key); return null; } return entry.data as T; } /** * Estimate the size of data in bytes (rough approximation) */ private estimateSize(data: any): number { const jsonString = JSON.stringify(data); // UTF-8 encoding: most characters are 1 byte, some are more // This is a rough estimate return new Blob([jsonString]).size; } /** * Evict oldest entries if cache is too full */ private evictIfNeeded(newEntrySize: number): void { const maxTotalBytes = CACHE_LIMITS.MAX_TOTAL_SIZE_MB * 1024 * 1024; // Check if adding new entry would exceed total size limit while ( this.totalSizeBytes + newEntrySize > maxTotalBytes || this.cache.size >= CACHE_LIMITS.MAX_ENTRIES ) { // Evict the oldest entry (first entry in the Map) const firstKey = this.cache.keys().next().value; if (firstKey) { const entry = this.cache.get(firstKey); if (entry) { this.totalSizeBytes -= entry.sizeBytes; } this.cache.delete(firstKey); } else { break; // No more entries to evict } } } /** * Set cache value with TTL in seconds * Enforces size limits to prevent memory exhaustion */ set<T>(key: string, data: T, ttlSeconds: number): void { const sizeBytes = this.estimateSize(data); const maxEntryBytes = CACHE_LIMITS.MAX_ENTRY_SIZE_MB * 1024 * 1024; // Reject entries that are too large if (sizeBytes > maxEntryBytes) { console.warn( `Cache entry "${key}" rejected: size ${(sizeBytes / 1024 / 1024).toFixed(2)}MB exceeds maximum ${CACHE_LIMITS.MAX_ENTRY_SIZE_MB}MB` ); return; } // Remove old entry if updating existing key const existingEntry = this.cache.get(key); if (existingEntry) { this.totalSizeBytes -= existingEntry.sizeBytes; } // Evict old entries if needed to make room this.evictIfNeeded(sizeBytes); // Add new entry const expiresAt = Date.now() + (ttlSeconds * 1000); this.cache.set(key, { data, expiresAt, sizeBytes }); this.totalSizeBytes += sizeBytes; } /** * Clear a specific cache entry */ delete(key: string): void { const entry = this.cache.get(key); if (entry) { this.totalSizeBytes -= entry.sizeBytes; } this.cache.delete(key); } /** * Clear all cache entries */ clear(): void { this.cache.clear(); this.totalSizeBytes = 0; } /** * Get cache statistics including size information */ getStats() { const now = Date.now(); const entries = Array.from(this.cache.entries()); const activeEntries = entries.filter(([_, entry]) => entry.expiresAt > now); return { totalEntries: this.cache.size, activeEntries: activeEntries.length, expiredEntries: this.cache.size - activeEntries.length, totalSizeMB: (this.totalSizeBytes / 1024 / 1024).toFixed(2), maxEntriesLimit: CACHE_LIMITS.MAX_ENTRIES, maxTotalSizeMB: CACHE_LIMITS.MAX_TOTAL_SIZE_MB, maxEntrySizeMB: CACHE_LIMITS.MAX_ENTRY_SIZE_MB, }; } /** * Remove expired entries (garbage collection) */ cleanExpired(): void { const now = Date.now(); const expiredKeys: string[] = []; for (const [key, entry] of this.cache.entries()) { if (entry.expiresAt <= now) { expiredKeys.push(key); this.totalSizeBytes -= entry.sizeBytes; } } expiredKeys.forEach(key => this.cache.delete(key)); } } // Singleton instance export const queryCache = new QueryCache(); // Run garbage collection every 5 minutes setInterval(() => { queryCache.cleanExpired(); }, 5 * 60 * 1000); // Helper function to create cache keys export function createCacheKey(queryName: string, args: Record<string, any>): string { const sortedArgs = Object.keys(args) .sort() .map(key => `${key}:${JSON.stringify(args[key])}`) .join('|'); return `${queryName}:${sortedArgs}`; }

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/northernvariables/FedMCP'

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