UNPKG

gather-ts

Version:

A powerful code analysis and packaging tool designed for creating AI-friendly code representations for javascript and typescript projects.

228 lines 8.88 kB
"use strict"; // src/core/tokenization/TokenCache.ts var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TokenCache = void 0; const crypto_1 = __importDefault(require("crypto")); const errors_1 = require("@/errors"); const services_1 = require("@/types/services"); class TokenCache extends services_1.BaseService { constructor(projectRoot, deps, options = {}) { super(); this.deps = deps; this.cache = {}; this.stats = { totalEntries: 0, oldestEntry: null, newestEntry: null, totalSize: 0, hitCount: 0, missCount: 0, invalidations: 0, }; this.projectRoot = projectRoot; this.maxCacheAge = options.maxCacheAge || 7 * 24 * 60 * 60 * 1000; // 7 days default this.debug = options.debug || false; this.cachePath = this.deps.fileSystem.joinPath(projectRoot, ".gather-ts", "token-cache.json"); } async initialize() { this.logDebug("Initializing TokenCache"); try { await super.initialize(); if (!this.deps.fileSystem.exists(this.projectRoot)) { throw new errors_1.ValidationError("Project root does not exist", { projectRoot: this.projectRoot, }); } await this.initializeCachePath(); await this.loadCache(); } catch (error) { throw new errors_1.CacheError(`Failed to initialize TokenCache: ${error instanceof Error ? error.message : String(error)}`, "write"); } } async initializeCachePath() { try { const cacheDir = this.deps.fileSystem.getDirName(this.cachePath); if (!this.deps.fileSystem.exists(cacheDir)) { await this.deps.fileSystem.createDirectory(cacheDir, true); this.logDebug(`Created cache directory at ${cacheDir}`); } } catch (error) { throw new errors_1.CacheError(`Failed to initialize cache path: ${error instanceof Error ? error.message : String(error)}`, "initialize"); } } cleanup() { this.logDebug("Cleaning up TokenCache"); this.saveCache(); super.cleanup(); } logDebug(message) { if (this.debug) { this.deps.logger.debug(message); } } handleError(operation, error, key) { const message = error instanceof Error ? error.message : String(error); const cacheError = new errors_1.CacheError(`Cache ${operation} failed: ${message}`, operation, key); this.deps.logger.error(cacheError.message); throw cacheError; } validateCacheEntry(entry) { if (!entry || typeof entry !== "object") { return false; } const candidate = entry; return (typeof candidate.hash === "string" && typeof candidate.tokens === "number" && typeof candidate.lastUpdated === "string" && !isNaN(Date.parse(candidate.lastUpdated))); } getRelativeCachePath(absolutePath) { return this.deps.fileSystem.getRelativePath(this.projectRoot, absolutePath); } async loadCache() { try { if (this.deps.fileSystem.exists(this.cachePath)) { const content = await this.deps.fileSystem.readFile(this.cachePath); const parsed = JSON.parse(content); if (typeof parsed !== "object" || parsed === null) { throw new errors_1.ValidationError("Invalid cache file structure"); } const validatedCache = {}; const now = Date.now(); let expiredCount = 0; let invalidCount = 0; Object.entries(parsed).forEach(([key, entry]) => { if (!this.validateCacheEntry(entry)) { invalidCount++; this.logDebug(`Invalid cache entry for ${key}, skipping`); return; } const entryAge = now - Date.parse(entry.lastUpdated); if (entryAge > this.maxCacheAge) { expiredCount++; this.stats.invalidations++; this.logDebug(`Skipping expired cache entry for ${key}`); return; } validatedCache[key] = entry; }); this.cache = validatedCache; this.updateStats(); this.logDebug(`Loaded ${Object.keys(this.cache).length} valid cache entries ` + `(${expiredCount} expired, ${invalidCount} invalid)`); } } catch (error) { this.handleError("read", error); } } async saveCache() { try { await this.deps.fileSystem.writeFile(this.cachePath, JSON.stringify(this.cache, null, 2)); this.logDebug(`Saved ${Object.keys(this.cache).length} cache entries`); } catch (error) { this.handleError("write", error); } } computeHash(content) { try { return crypto_1.default.createHash("md5").update(content).digest("hex"); } catch (error) { this.handleError("hash", error); } } getCachedTokenCount(filePath, content) { if (!filePath || typeof filePath !== "string") { throw new errors_1.ValidationError("Invalid file path provided", { filePath }); } try { const relativePath = this.getRelativeCachePath(filePath); const hash = this.computeHash(content); const cached = this.cache[relativePath]; if (cached && cached.hash === hash) { const entryAge = Date.now() - Date.parse(cached.lastUpdated); if (entryAge > this.maxCacheAge) { this.logDebug(`Cache entry for ${relativePath} has expired`); delete this.cache[relativePath]; this.stats.invalidations++; this.stats.missCount++; return null; } this.stats.hitCount++; return cached.tokens; } this.stats.missCount++; return null; } catch (error) { this.handleError("read", error, filePath); } } cacheTokenCount(filePath, content, tokens) { if (!filePath || typeof filePath !== "string") { throw new errors_1.ValidationError("Invalid file path provided", { filePath }); } if (typeof tokens !== "number" || isNaN(tokens) || tokens < 0) { throw new errors_1.ValidationError("Invalid token count provided", { filePath, tokens, }); } try { const relativePath = this.getRelativeCachePath(filePath); const entry = { hash: this.computeHash(content), tokens, lastUpdated: new Date().toISOString(), }; this.cache[relativePath] = entry; this.updateStats(); this.saveCache(); this.logDebug(`Cached ${tokens} tokens for ${relativePath}`); } catch (error) { this.handleError("write", error, filePath); } } clear() { try { this.cache = {}; this.updateStats(); if (this.deps.fileSystem.exists(this.cachePath)) { this.deps.fileSystem.deleteFile(this.cachePath); this.logDebug("Cleared cache file"); } } catch (error) { this.handleError("clear", error); } } updateStats() { const entries = Object.values(this.cache); let oldestDate = Date.now(); let newestDate = 0; entries.forEach((entry) => { const timestamp = Date.parse(entry.lastUpdated); oldestDate = Math.min(oldestDate, timestamp); newestDate = Math.max(newestDate, timestamp); }); this.stats.totalEntries = entries.length; this.stats.oldestEntry = entries.length > 0 ? new Date(oldestDate).toISOString() : null; this.stats.newestEntry = entries.length > 0 ? new Date(newestDate).toISOString() : null; this.stats.totalSize = Buffer.byteLength(JSON.stringify(this.cache)); } getCacheStats() { return { ...this.stats }; } } exports.TokenCache = TokenCache; //# sourceMappingURL=TokenCache.js.map