import type { CacheAdapter, CacheConfig } from './interfaces'; import * as fs from 'node:fs/promises'; import * as path from 'node:path'; import { existsSync, mkdirSync } from 'node:fs'; import * as crypto from 'node:crypto'; export class FileCacheAdapter implements CacheAdapter { private cacheDir: string; private prefix: string; private defaultTTL: number; constructor(config?: CacheConfig & { cacheDir?: string }) { this.cacheDir = config?.cacheDir || path.resolve(process.cwd(), '.cache'); this.prefix = config?.prefix || ''; this.defaultTTL = config?.defaultTTL || 3600; if (!existsSync(this.cacheDir)) { fs.mkdir(this.cacheDir, { recursive: true }).catch(err => { console.error(`Failed to create cache directory: ${this.cacheDir}`, err); }); } } private sanitize(key: string): string { const clean = key.replace(/[^a-z0-9]/gi, '_'); if (clean.length > 64) { return crypto.createHash('md5').update(key).digest('hex'); } return clean; } private getFilePath(key: string): string { const safeKey = this.sanitize(`${this.prefix}${key}`).toLowerCase(); return path.join(this.cacheDir, `${safeKey}.json`); } async get(key: string): Promise { const filePath = this.getFilePath(key); try { if (!existsSync(filePath)) return null; const content = await fs.readFile(filePath, 'utf8'); const data = JSON.parse(content); if (data.expiry && Date.now() > data.expiry) { await this.del(key); return null; } return data.value; } catch (error) { return null; } } async set(key: string, value: T, ttl?: number): Promise { const filePath = this.getFilePath(key); const effectiveTTL = ttl !== undefined ? ttl : this.defaultTTL; const data = { value, expiry: effectiveTTL > 0 ? Date.now() + effectiveTTL * 1000 : null, updatedAt: new Date().toISOString() }; try { await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf8'); } catch (error) { console.error(`Failed to write cache file: ${filePath}`, error); } } async del(key: string): Promise { const filePath = this.getFilePath(key); try { if (existsSync(filePath)) { await fs.unlink(filePath); } } catch (error) { // Ignore } } async clear(): Promise { try { if (existsSync(this.cacheDir)) { const files = await fs.readdir(this.cacheDir); for (const file of files) { if (file.endsWith('.json')) { await fs.unlink(path.join(this.cacheDir, file)); } } } } catch (error) { // Ignore } } }