Skip to main content
Glama
cache.ts6.4 kB
/** * 檔案分析快取系統 * 避免重複解析未修改的檔案,提升大型專案的分析效能 */ import { createHash } from 'crypto'; import { readFileSync, writeFileSync, existsSync, statSync } from 'fs'; import { join } from 'path'; import { ParsedFile } from '../parsers/index.js'; /** * 快取項目 */ interface CacheEntry { hash: string; mtime: number; result: ParsedFile; timestamp: number; } /** * 快取統計 */ interface CacheStats { hits: number; misses: number; size: number; } /** * 檔案快取管理器 */ export class FileCache { private cache: Map<string, CacheEntry> = new Map(); private stats: CacheStats = { hits: 0, misses: 0, size: 0 }; private cacheDir: string; private cacheFile: string; private maxAge: number; // 快取有效期(毫秒) private maxSize: number; // 最大快取項目數 constructor(options: { cacheDir?: string; maxAge?: number; maxSize?: number; } = {}) { this.cacheDir = options.cacheDir || '.devadvisor-cache'; this.cacheFile = join(this.cacheDir, 'analysis-cache.json'); this.maxAge = options.maxAge || 24 * 60 * 60 * 1000; // 預設 24 小時 this.maxSize = options.maxSize || 1000; // 預設最多 1000 個項目 } /** * 計算檔案內容的雜湊值 */ private getFileHash(content: string): string { return createHash('md5').update(content).digest('hex'); } /** * 取得檔案的修改時間 */ private getFileMtime(filePath: string): number { try { return statSync(filePath).mtimeMs; } catch { return 0; } } /** * 從快取取得分析結果 */ get(filePath: string, content: string): ParsedFile | null { const entry = this.cache.get(filePath); if (!entry) { this.stats.misses++; return null; } // 檢查快取是否過期 const now = Date.now(); if (now - entry.timestamp > this.maxAge) { this.cache.delete(filePath); this.stats.misses++; return null; } // 檢查檔案是否已修改(透過雜湊比較) const currentHash = this.getFileHash(content); if (entry.hash !== currentHash) { this.cache.delete(filePath); this.stats.misses++; return null; } // 快取命中 this.stats.hits++; return entry.result; } /** * 快速檢查(只用修改時間,不計算雜湊) */ quickCheck(filePath: string): ParsedFile | null { const entry = this.cache.get(filePath); if (!entry) { return null; } // 檢查修改時間 const currentMtime = this.getFileMtime(filePath); if (currentMtime !== entry.mtime) { return null; } return entry.result; } /** * 儲存分析結果到快取 */ set(filePath: string, content: string, result: ParsedFile): void { // 檢查快取大小,必要時清理舊項目 if (this.cache.size >= this.maxSize) { this.evictOldEntries(); } this.cache.set(filePath, { hash: this.getFileHash(content), mtime: this.getFileMtime(filePath), result, timestamp: Date.now() }); this.stats.size = this.cache.size; } /** * 清除指定檔案的快取 */ invalidate(filePath: string): void { this.cache.delete(filePath); this.stats.size = this.cache.size; } /** * 清除所有快取 */ clear(): void { this.cache.clear(); this.stats = { hits: 0, misses: 0, size: 0 }; } /** * 清除過期的快取項目 */ private evictOldEntries(): void { const now = Date.now(); const entriesToDelete: string[] = []; // 找出過期的項目 for (const [key, entry] of this.cache) { if (now - entry.timestamp > this.maxAge) { entriesToDelete.push(key); } } // 如果過期項目不足以釋放空間,刪除最舊的項目 if (entriesToDelete.length < this.maxSize * 0.1) { const entries = Array.from(this.cache.entries()) .sort((a, b) => a[1].timestamp - b[1].timestamp); const toRemove = Math.floor(this.maxSize * 0.2); // 移除 20% for (let i = 0; i < toRemove && i < entries.length; i++) { entriesToDelete.push(entries[i][0]); } } // 執行刪除 for (const key of entriesToDelete) { this.cache.delete(key); } this.stats.size = this.cache.size; } /** * 取得快取統計 */ getStats(): CacheStats & { hitRate: string } { const total = this.stats.hits + this.stats.misses; const hitRate = total > 0 ? `${((this.stats.hits / total) * 100).toFixed(1)}%` : 'N/A'; return { ...this.stats, hitRate }; } /** * 將快取持久化到磁碟 */ saveToDisk(): void { try { const data = { version: 1, timestamp: Date.now(), entries: Array.from(this.cache.entries()).map(([key, value]) => ({ key, hash: value.hash, mtime: value.mtime, timestamp: value.timestamp // 不儲存 result,因為太大且包含無法序列化的內容 })) }; // 確保目錄存在 const { mkdirSync } = require('fs'); mkdirSync(this.cacheDir, { recursive: true }); writeFileSync(this.cacheFile, JSON.stringify(data, null, 2)); } catch (error) { // 忽略快取儲存失敗 console.error('快取儲存失敗:', error); } } /** * 從磁碟載入快取元資料(只載入 hash 和 mtime,不載入結果) */ loadFromDisk(): void { try { if (!existsSync(this.cacheFile)) { return; } const data = JSON.parse(readFileSync(this.cacheFile, 'utf-8')); // 檢查版本 if (data.version !== 1) { return; } // 檢查是否過期 if (Date.now() - data.timestamp > this.maxAge) { return; } // 只載入元資料,不載入結果 // 這允許我們在下次分析時快速檢查檔案是否已變更 // 但實際的 ParsedFile 結果仍需重新計算 } catch (error) { // 忽略載入失敗 } } } /** * 建立全域快取實例 */ let globalCache: FileCache | null = null; export function getGlobalCache(): FileCache { if (!globalCache) { globalCache = new FileCache(); } return globalCache; } export function resetGlobalCache(): void { globalCache = null; }

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/mukiwu/dev-advisor-mcp'

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