UNPKG

prompt-version-manager

Version:

Centralized prompt management system for Human Behavior AI agents

438 lines 16.2 kB
"use strict"; /** * Core storage engine for PVM 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; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.StorageEngine = void 0; const fs = __importStar(require("fs/promises")); const path = __importStar(require("path")); const sqlite3 = __importStar(require("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 sqlite3.Database(this.metadataDbPath); // Initialize metadata database this.db.serialize(() => { this.db.run(` 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.run(` 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.run('CREATE INDEX IF NOT EXISTS idx_objects_type ON objects(type)'); this.db.run('CREATE INDEX IF NOT EXISTS idx_objects_created_at ON objects(created_at)'); this.db.run('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) { await new Promise((resolve, reject) => { this.db.run('INSERT OR REPLACE INTO objects (hash, type, size, created_at, ref_count) VALUES (?, ?, ?, ?, ?)', [hash, obj.objectType, content.length, new Date().toISOString(), 0], (err) => { if (err) reject(err); else resolve(); }); }); } 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) { await new Promise((resolve, reject) => { this.db.run('INSERT OR REPLACE INTO refs (name, hash, type, updated_at) VALUES (?, ?, ?, ?)', [name, hash, type, new Date().toISOString()], (err) => { if (err) reject(err); else resolve(); }); }); } } 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'); } return new Promise((resolve, reject) => { let query = 'SELECT name, hash, type FROM refs'; const params = []; if (type) { query += ' WHERE type = ?'; params.push(type); } query += ' ORDER BY name'; this.db.all(query, params, (err, rows) => { if (err) { reject(new exceptions_1.StorageError(`Failed to list refs: ${err}`)); } else { resolve(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) { await new Promise((resolve, reject) => { this.db.run('DELETE FROM refs WHERE name = ?', [name], (err) => { if (err) reject(err); else resolve(); }); }); } } 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'); } return new Promise((resolve, reject) => { const stats = { objectCount: 0, refCount: 0, totalSize: 0, objectsByType: {} }; // Get object count and total size this.db.get('SELECT COUNT(*) as count, SUM(size) as total FROM objects', (err, row) => { if (err) { reject(new exceptions_1.StorageError(`Failed to get stats: ${err}`)); return; } stats.objectCount = row.count || 0; stats.totalSize = row.total || 0; // Get ref count this.db.get('SELECT COUNT(*) as count FROM refs', (err, row) => { if (err) { reject(new exceptions_1.StorageError(`Failed to get stats: ${err}`)); return; } stats.refCount = row.count || 0; // Get objects by type this.db.all('SELECT type, COUNT(*) as count FROM objects GROUP BY type', (err, rows) => { if (err) { reject(new exceptions_1.StorageError(`Failed to get stats: ${err}`)); return; } for (const row of rows) { stats.objectsByType[row.type] = row.count; } resolve(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(); return new Promise((resolve, reject) => { // Find old objects this.db.all('SELECT hash FROM objects WHERE accessed_at < ? AND created_at < ?', [cutoffDate, cutoffDate], async (err, rows) => { if (err) { reject(new exceptions_1.StorageError(`Failed to cleanup: ${err}`)); return; } let cleanedCount = 0; for (const row of rows) { try { const objectPath = this.getObjectPath(row.hash); await fs.unlink(objectPath); await new Promise((resolve, reject) => { this.db.run('DELETE FROM objects WHERE hash = ?', [row.hash], (err) => { if (err) reject(err); else resolve(); }); }); cleanedCount++; } catch (error) { // Continue cleaning other objects console.warn(`Failed to clean object ${row.hash}: ${error}`); } } resolve(cleanedCount); }); }); } /** * Close the storage engine and database connections. */ async close() { if (this.db) { await new Promise((resolve, reject) => { this.db.close((err) => { if (err) reject(err); else resolve(); }); }); 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