UNPKG

psgc-mcp

Version:

Philippine Standard Geographic Code MCP Server - provides hierarchical geographic data for the Philippines

183 lines 5.59 kB
import { promises as fs } from 'fs'; import os from 'os'; import path from 'path'; import { API_CONFIG } from '../types/index.js'; /** * Cache service for storing API responses */ export class CacheService { memoryCache = new Map(); config; cacheDir; constructor(config) { this.config = config; this.cacheDir = path.join(os.tmpdir(), 'psgc-mcp-cache'); } /** * Initialize cache directory */ async initialize() { try { await fs.mkdir(this.cacheDir, { recursive: true }); } catch (error) { console.warn('Failed to create cache directory:', error); } } /** * Generate cache key */ generateKey(endpoint) { return Buffer.from(endpoint).toString('base64').replace(/[/+=]/g, '_'); } /** * Check if cache entry is valid */ isValid(timestamp) { return Date.now() - timestamp < this.config.ttl; } /** * Get data from cache */ async get(key) { // Check memory cache first const memoryEntry = this.memoryCache.get(key); if (memoryEntry && this.isValid(memoryEntry.timestamp)) { return memoryEntry.data; } // Check disk cache if persist is enabled if (this.config.persist) { try { const filePath = path.join(this.cacheDir, this.generateKey(key)); const data = await fs.readFile(filePath, 'utf-8'); const parsed = JSON.parse(data); if (this.isValid(parsed.timestamp)) { // Update memory cache this.memoryCache.set(key, parsed); return parsed.data; } else { // Remove expired cache file await fs.unlink(filePath); } } catch { // File doesn't exist or is invalid } } return null; } /** * Store data in cache */ async set(key, data) { const entry = { data, timestamp: Date.now() }; // Store in memory cache this.memoryCache.set(key, entry); // Store in disk cache if persist is enabled if (this.config.persist) { try { const filePath = path.join(this.cacheDir, this.generateKey(key)); await fs.writeFile(filePath, JSON.stringify(entry)); } catch (error) { console.warn('Failed to write cache file:', error); } } // Enforce max size limit if (this.config.maxSize && this.memoryCache.size > this.config.maxSize) { this.cleanup(); } } /** * Remove specific cache entry */ async remove(key) { this.memoryCache.delete(key); if (this.config.persist) { try { const filePath = path.join(this.cacheDir, this.generateKey(key)); await fs.unlink(filePath); } catch { // File doesn't exist } } } /** * Clear all cache entries */ async clear() { this.memoryCache.clear(); if (this.config.persist) { try { const files = await fs.readdir(this.cacheDir); await Promise.all(files.map((file) => fs.unlink(path.join(this.cacheDir, file)))); } catch (error) { console.warn('Failed to clear cache directory:', error); } } } /** * Clean up expired cache entries */ async cleanup() { // Clean memory cache for (const [key, entry] of this.memoryCache.entries()) { if (!this.isValid(entry.timestamp)) { this.memoryCache.delete(key); } } // Clean disk cache if (this.config.persist) { try { const files = await fs.readdir(this.cacheDir); await Promise.all(files.map(async (file) => { const filePath = path.join(this.cacheDir, file); try { const data = await fs.readFile(filePath, 'utf-8'); const parsed = JSON.parse(data); if (!this.isValid(parsed.timestamp)) { await fs.unlink(filePath); } } catch { // Invalid file, remove it await fs.unlink(filePath); } })); } catch (error) { console.warn('Failed to cleanup cache directory:', error); } } } /** * Get cache statistics */ async getStats() { let diskEntries = 0; if (this.config.persist) { try { const files = await fs.readdir(this.cacheDir); diskEntries = files.length; } catch { // Directory doesn't exist } } return { memoryEntries: this.memoryCache.size, diskEntries, totalSize: this.memoryCache.size + diskEntries, }; } } // Export singleton instance export const cacheService = new CacheService({ ttl: API_CONFIG.CACHE_TTL, maxSize: 1000, // Maximum 1000 entries persist: true, // Persist to disk }); //# sourceMappingURL=cache.service.js.map