Skip to main content
Glama
cameronsjo

MCP Server Template

by cameronsjo
cache.ts4.56 kB
/** * TTL-based caching layer using SQLite * * Provides get/set/delete operations with automatic expiration. * Thread-safe via SQLite transactions. */ import { getDatabase, saveDatabase } from './database.js'; import { getConfig } from '../config/index.js'; import { createLogger } from '../shared/logger.js'; const logger = createLogger('cache'); /** * Get a value from cache */ export function cacheGet<T>(key: string): T | null { const config = getConfig(); if (!config.cacheEnabled) { return null; } const db = getDatabase(); const now = Date.now(); try { const stmt = db.prepare('SELECT value, expires_at FROM cache WHERE key = ?'); stmt.bind([key]); if (stmt.step()) { const row = stmt.getAsObject() as { value: string; expires_at: number }; stmt.free(); // Check expiration if (row.expires_at < now) { // Expired, delete and return null db.run('DELETE FROM cache WHERE key = ?', [key]); logger.debug('Cache miss (expired)', { key }); return null; } // Update hit count db.run('UPDATE cache SET hit_count = hit_count + 1 WHERE key = ?', [key]); logger.debug('Cache hit', { key }); return JSON.parse(row.value) as T; } stmt.free(); logger.debug('Cache miss', { key }); return null; } catch (error) { logger.warning('Cache get error', { key, error: String(error) }); return null; } } /** * Set a value in cache with TTL */ export function cacheSet<T>(key: string, value: T, ttlSeconds?: number): void { const config = getConfig(); if (!config.cacheEnabled) { return; } const db = getDatabase(); const now = Date.now(); const ttl = ttlSeconds ?? config.cacheTtlSeconds; const expiresAt = now + ttl * 1000; try { db.run( `INSERT OR REPLACE INTO cache (key, value, expires_at, created_at, hit_count) VALUES (?, ?, ?, ?, 0)`, [key, JSON.stringify(value), expiresAt, now] ); logger.debug('Cache set', { key, ttlSeconds: ttl }); // Periodically save to disk (every 10 writes) const countResult = db.exec('SELECT COUNT(*) as count FROM cache'); if (countResult[0]?.values[0]?.[0]) { const count = countResult[0].values[0][0] as number; if (count % 10 === 0) { saveDatabase(); } } } catch (error) { logger.warning('Cache set error', { key, error: String(error) }); } } /** * Delete a value from cache */ export function cacheDelete(key: string): boolean { const config = getConfig(); if (!config.cacheEnabled) { return false; } const db = getDatabase(); try { db.run('DELETE FROM cache WHERE key = ?', [key]); const changes = db.getRowsModified(); logger.debug('Cache delete', { key, deleted: changes > 0 }); return changes > 0; } catch (error) { logger.warning('Cache delete error', { key, error: String(error) }); return false; } } /** * Clear all cache entries */ export function cacheClear(): void { const db = getDatabase(); try { db.run('DELETE FROM cache'); logger.info('Cache cleared'); } catch (error) { logger.warning('Cache clear error', { error: String(error) }); } } /** * Get cache statistics */ export function cacheStats(): { totalEntries: number; expiredEntries: number; totalHits: number; } { const db = getDatabase(); const now = Date.now(); try { const total = db.exec('SELECT COUNT(*) FROM cache'); const expired = db.exec('SELECT COUNT(*) FROM cache WHERE expires_at < ?', [now]); const hits = db.exec('SELECT SUM(hit_count) FROM cache'); return { totalEntries: (total[0]?.values[0]?.[0] as number) ?? 0, expiredEntries: (expired[0]?.values[0]?.[0] as number) ?? 0, totalHits: (hits[0]?.values[0]?.[0] as number) ?? 0, }; } catch { return { totalEntries: 0, expiredEntries: 0, totalHits: 0 }; } } /** * Cleanup expired entries */ export function cacheCleanup(): number { const db = getDatabase(); const now = Date.now(); try { db.run('DELETE FROM cache WHERE expires_at < ?', [now]); const deleted = db.getRowsModified(); if (deleted > 0) { logger.info('Cache cleanup', { deleted }); saveDatabase(); } return deleted; } catch (error) { logger.warning('Cache cleanup error', { error: String(error) }); return 0; } } /** * Create a cache key with namespace */ export function makeCacheKey(namespace: string, ...parts: string[]): string { return `${namespace}:${parts.join(':')}`; }

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/cameronsjo/mcp-server-template'

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