psgc-mcp
Version:
Philippine Standard Geographic Code MCP Server - provides hierarchical geographic data for the Philippines
183 lines • 5.59 kB
JavaScript
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