UNPKG

pit-manager

Version:

Centralized prompt management system for Human Behavior AI agents

378 lines 13.7 kB
"use strict"; /** * Core storage engine for PIT TypeScript - Git-like content-addressable storage. */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.StorageEngine = void 0; const fs = __importStar(require("fs/promises")); const path = __importStar(require("path")); const better_sqlite3_1 = __importDefault(require("better-sqlite3")); const exceptions_1 = require("../core/exceptions"); const objects_1 = require("./objects"); class StorageEngine { objectsPath; refsPath; metadataDbPath; db = null; // Per-hash locks for concurrent operations (simplified for TypeScript) objectLocks = new Map(); constructor(repoPath) { this.objectsPath = path.join(repoPath, 'objects'); this.refsPath = path.join(repoPath, 'refs'); this.metadataDbPath = path.join(repoPath, 'metadata.db'); // Ensure the repo directory exists before creating database try { require('fs').mkdirSync(repoPath, { recursive: true }); } catch (error) { // Directory might already exist, ignore } // Initialize synchronously to avoid async constructor issues this.initMetadataDb(); // Other directories will be created as needed during operations } async ensureDirectories() { // Ensure directories exist - called as needed during operations await fs.mkdir(this.objectsPath, { recursive: true }); await fs.mkdir(this.refsPath, { recursive: true }); await fs.mkdir(path.join(this.refsPath, 'heads'), { recursive: true }); await fs.mkdir(path.join(this.refsPath, 'tags'), { recursive: true }); } initMetadataDb() { this.db = new better_sqlite3_1.default(this.metadataDbPath); // Initialize metadata database this.db.exec(` CREATE TABLE IF NOT EXISTS objects ( hash TEXT PRIMARY KEY, type TEXT NOT NULL, size INTEGER NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, ref_count INTEGER DEFAULT 0 ) `); this.db.exec(` CREATE TABLE IF NOT EXISTS refs ( name TEXT PRIMARY KEY, hash TEXT NOT NULL, type TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) `); // Create indexes for performance this.db.exec('CREATE INDEX IF NOT EXISTS idx_objects_type ON objects(type)'); this.db.exec('CREATE INDEX IF NOT EXISTS idx_objects_created_at ON objects(created_at)'); this.db.exec('CREATE INDEX IF NOT EXISTS idx_refs_type ON refs(type)'); } async getObjectLock(hash) { const existingLock = this.objectLocks.get(hash); if (existingLock) { await existingLock; } let resolveLock; const lock = new Promise((resolve) => { resolveLock = resolve; }); this.objectLocks.set(hash, lock); // Auto-cleanup after operation setTimeout(() => { this.objectLocks.delete(hash); resolveLock(); }, 0); } /** * Store an object and return its hash. */ async storeObject(obj) { const hash = obj.hash(); // Use per-hash lock to prevent race conditions await this.getObjectLock(hash); // Check if object already exists const objectPath = this.getObjectPath(hash); try { await fs.access(objectPath); return hash; // Object already exists } catch { // Object doesn't exist, continue with storage } try { // Ensure directories exist await this.ensureDirectories(); // Get compressed content const content = await obj.getRawContent(); // Create directory for object (first 2 chars of hash) const objectDir = path.dirname(objectPath); await fs.mkdir(objectDir, { recursive: true }); // Write object to file await fs.writeFile(objectPath, content); // Update metadata database if (this.db) { const stmt = this.db.prepare('INSERT OR REPLACE INTO objects (hash, type, size, created_at, ref_count) VALUES (?, ?, ?, ?, ?)'); stmt.run(hash, obj.objectType, content.length, new Date().toISOString(), 0); } return hash; } catch (error) { throw new exceptions_1.StorageError(`Failed to store object ${hash}: ${error}`); } } /** * Load an object by hash. */ async loadObject(hash) { const objectPath = this.getObjectPath(hash); try { const content = await fs.readFile(objectPath); return (0, objects_1.deserializeObject)(content); } catch (error) { if (error.code === 'ENOENT') { throw new exceptions_1.ObjectNotFoundError(`Object ${hash} not found`); } throw new exceptions_1.CorruptedObjectError(`Failed to load object ${hash}: ${error.message}`); } } /** * Check if an object exists. */ async objectExists(hash) { const objectPath = this.getObjectPath(hash); try { await fs.access(objectPath); return true; } catch { return false; } } /** * Store a reference (branch, tag, etc.). */ async storeRef(name, hash, type = 'branch') { // Avoid double nesting - if name already starts with 'refs/', don't add another refs/ prefix let refPath; if (name.startsWith('refs/') && this.refsPath.endsWith('refs')) { // Use parent path to avoid refs/refs/ refPath = path.join(path.dirname(this.refsPath), name); } else { refPath = path.join(this.refsPath, name); } try { // Create directory for ref if needed const refDir = path.dirname(refPath); await fs.mkdir(refDir, { recursive: true }); // Write ref file await fs.writeFile(refPath, hash, 'utf8'); // Update metadata database if (this.db) { const stmt = this.db.prepare('INSERT OR REPLACE INTO refs (name, hash, type, updated_at) VALUES (?, ?, ?, ?)'); stmt.run(name, hash, type, new Date().toISOString()); } } catch (error) { throw new exceptions_1.StorageError(`Failed to store ref ${name}: ${error}`); } } /** * Load a reference by name. */ async loadRef(name) { // Handle both relative and absolute ref paths let refPath; if (name.startsWith('refs/') && this.refsPath.endsWith('refs')) { refPath = path.join(path.dirname(this.refsPath), name); } else { refPath = path.join(this.refsPath, name); } try { const hash = await fs.readFile(refPath, 'utf8'); return hash.trim(); } catch (error) { if (error.code === 'ENOENT') { throw new exceptions_1.RefNotFoundError(`Reference ${name} not found`); } throw new exceptions_1.StorageError(`Failed to load ref ${name}: ${error.message}`); } } /** * Check if a reference exists. */ async refExists(name) { // Handle both relative and absolute ref paths let refPath; if (name.startsWith('refs/') && this.refsPath.endsWith('refs')) { refPath = path.join(path.dirname(this.refsPath), name); } else { refPath = path.join(this.refsPath, name); } try { await fs.access(refPath); return true; } catch { return false; } } /** * List all references of a given type. */ async listRefs(type) { if (!this.db) { throw new exceptions_1.StorageError('Database not initialized'); } let query = 'SELECT name, hash, type FROM refs'; const params = []; if (type) { query += ' WHERE type = ?'; params.push(type); } query += ' ORDER BY name'; const stmt = this.db.prepare(query); const rows = stmt.all(...params); return rows.map((row) => ({ name: row.name, hash: row.hash, type: row.type })); } /** * Delete a reference. */ async deleteRef(name) { // Handle both relative and absolute ref paths let refPath; if (name.startsWith('refs/') && this.refsPath.endsWith('refs')) { refPath = path.join(path.dirname(this.refsPath), name); } else { refPath = path.join(this.refsPath, name); } try { await fs.unlink(refPath); // Remove from metadata database if (this.db) { const stmt = this.db.prepare('DELETE FROM refs WHERE name = ?'); stmt.run(name); } } catch (error) { if (error.code === 'ENOENT') { throw new exceptions_1.RefNotFoundError(`Reference ${name} not found`); } throw new exceptions_1.StorageError(`Failed to delete ref ${name}: ${error.message}`); } } /** * Get storage statistics. */ async getStats() { if (!this.db) { throw new exceptions_1.StorageError('Database not initialized'); } const stats = { objectCount: 0, refCount: 0, totalSize: 0, objectsByType: {} }; const stmt1 = this.db.prepare('SELECT COUNT(*) as count, SUM(size) as total FROM objects'); const stmt2 = this.db.prepare('SELECT COUNT(*) as count FROM refs'); const stmt3 = this.db.prepare('SELECT type, COUNT(*) as count FROM objects GROUP BY type'); const row1 = stmt1.get(); stats.objectCount = row1?.count || 0; stats.totalSize = row1?.total || 0; const row2 = stmt2.get(); stats.refCount = row2?.count || 0; const rows3 = stmt3.all(); for (const row of rows3) { stats.objectsByType[row.type] = row.count; } return stats; } /** * Cleanup old objects (garbage collection). */ async cleanup(maxAge = 30 * 24 * 60 * 60 * 1000) { if (!this.db) { throw new exceptions_1.StorageError('Database not initialized'); } const cutoffDate = new Date(Date.now() - maxAge).toISOString(); // Find old objects - note: no accessed_at column in our schema const stmt = this.db.prepare('SELECT hash FROM objects WHERE created_at < ?'); const rows = stmt.all(cutoffDate); let cleanedCount = 0; for (const row of rows) { try { const objectPath = this.getObjectPath(row.hash); await fs.unlink(objectPath); const deleteStmt = this.db.prepare('DELETE FROM objects WHERE hash = ?'); deleteStmt.run(row.hash); cleanedCount++; } catch (error) { // Continue cleaning other objects console.warn(`Failed to clean object ${row.hash}: ${error}`); } } return cleanedCount; } /** * Close the storage engine and database connections. */ async close() { if (this.db) { this.db.close(); this.db = null; } } getObjectPath(hash) { // Store objects in subdirectories based on first 2 characters of hash // This prevents too many files in a single directory const subdir = hash.substring(0, 2); const filename = hash.substring(2); return path.join(this.objectsPath, subdir, filename); } } exports.StorageEngine = StorageEngine; //# sourceMappingURL=engine.js.map