UNPKG

@aaswe/codebase-ai

Version:

AI-Assisted Software Engineering (AASWE) - Rich codebase context for IDE LLMs

547 lines 20.9 kB
"use strict"; /** * RDF Files Storage Layer * * Implements RDF file-based storage for the Hybrid Storage Manager * with file watching, backup capabilities, and TTL parsing. */ 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.RDFStorageLayer = void 0; const events_1 = require("events"); const fs_1 = require("fs"); const path = __importStar(require("path")); const chokidar = __importStar(require("chokidar")); const glob_1 = require("glob"); const logger_1 = __importDefault(require("../../../utils/logger")); const types_1 = require("./types"); class RDFStorageLayer extends events_1.EventEmitter { config; layer = types_1.StorageLayer.RDF_FILES; files = new Map(); watcher; syncTimer; isInitialized = false; metrics = { totalFiles: 0, totalSize: 0, totalQueries: 0, successfulQueries: 0, failedQueries: 0, totalResponseTime: 0, lastSync: new Date(), fileChanges: 0 }; constructor(config) { super(); this.config = config; } /** * Initialize the RDF storage layer */ async initialize() { try { logger_1.default.info('Initializing RDF Storage Layer'); // Ensure base directory exists await fs_1.promises.mkdir(this.config.baseDirectory, { recursive: true }); // Load existing RDF files await this.loadRDFFiles(); // Setup file watching if enabled if (this.config.watchForChanges) { this.setupFileWatcher(); } // Setup sync timer if configured if (this.config.syncInterval) { this.setupSyncTimer(); } // Setup backup directory if enabled if (this.config.backupEnabled && this.config.backupDirectory) { await fs_1.promises.mkdir(this.config.backupDirectory, { recursive: true }); } this.isInitialized = true; logger_1.default.info('RDF Storage Layer initialized successfully'); this.emit('initialized'); } catch (error) { logger_1.default.error('Failed to initialize RDF Storage Layer:', error); throw new types_1.HybridStorageError(`RDF storage initialization failed: ${error instanceof Error ? error.message : 'Unknown error'}`, types_1.StorageLayer.RDF_FILES, 'initialize', error instanceof Error ? error : undefined); } } /** * Execute a query against RDF files */ async query(query, params, context) { if (!this.isInitialized) { throw new types_1.HybridStorageError('RDF storage layer not initialized', types_1.StorageLayer.RDF_FILES, 'query'); } const startTime = Date.now(); try { // Execute query against RDF files const results = await this.executeRDFQuery(query, params || {}); const executionTime = Date.now() - startTime; this.updateMetrics(true, executionTime); const queryResult = { data: results, source: types_1.StorageLayer.RDF_FILES, executionTime, cached: false, timestamp: new Date(), metadata: { filesSearched: this.files.size, resultCount: Array.isArray(results) ? results.length : 1, totalFiles: this.metrics.totalFiles } }; this.emit('query_executed', { query, params, result: queryResult, context }); return queryResult; } catch (error) { const executionTime = Date.now() - startTime; this.updateMetrics(false, executionTime); logger_1.default.error('RDF query failed:', error); const errorMessage = error instanceof Error ? error.message : 'Unknown error'; this.emit('query_failed', { query, params, error, context }); throw new types_1.HybridStorageError(`RDF query failed: ${errorMessage}`, types_1.StorageLayer.RDF_FILES, 'query', error instanceof Error ? error : undefined); } } /** * Create a new RDF file */ async create(data, _context) { const fileName = this.generateFileName(data); const filePath = path.join(this.config.baseDirectory, fileName); // Convert data to TTL format const ttlContent = this.convertToTTL(data); // Create backup if enabled if (this.config.backupEnabled) { await this.createBackup(filePath); } // Write file await fs_1.promises.writeFile(filePath, ttlContent, 'utf-8'); // Update internal tracking const stats = await fs_1.promises.stat(filePath); const rdfFile = { path: filePath, content: ttlContent, lastModified: stats.mtime, size: stats.size, parsed: data }; this.files.set(filePath, rdfFile); this.updateFileMetrics(); const result = { data: { filePath, ...data }, source: types_1.StorageLayer.RDF_FILES, executionTime: 1, cached: false, timestamp: new Date() }; this.emit('file_created', { filePath, size: stats.size }); return result; } /** * Update an RDF file */ async update(filePath, data, _context) { const existingFile = this.files.get(filePath); if (!existingFile) { throw new types_1.HybridStorageError(`RDF file not found: ${filePath}`, types_1.StorageLayer.RDF_FILES, 'update'); } // Create backup if enabled if (this.config.backupEnabled) { await this.createBackup(filePath); } // Merge with existing data const existingData = existingFile.parsed || this.parseTTL(existingFile.content); const mergedData = { ...existingData, ...data }; // Convert to TTL and write const ttlContent = this.convertToTTL(mergedData); await fs_1.promises.writeFile(filePath, ttlContent, 'utf-8'); // Update tracking const stats = await fs_1.promises.stat(filePath); existingFile.content = ttlContent; existingFile.lastModified = stats.mtime; existingFile.size = stats.size; existingFile.parsed = mergedData; this.updateFileMetrics(); const result = { data: { filePath, ...mergedData }, source: types_1.StorageLayer.RDF_FILES, executionTime: 1, cached: false, timestamp: new Date() }; this.emit('file_updated', { filePath, size: stats.size }); return result; } /** * Delete an RDF file */ async delete(filePath, _context) { const existingFile = this.files.get(filePath); if (!existingFile) { return { data: false, source: types_1.StorageLayer.RDF_FILES, executionTime: 1, cached: false, timestamp: new Date() }; } // Create backup if enabled if (this.config.backupEnabled) { await this.createBackup(filePath); } // Delete file await fs_1.promises.unlink(filePath); // Remove from tracking this.files.delete(filePath); this.updateFileMetrics(); this.emit('file_deleted', { filePath }); return { data: true, source: types_1.StorageLayer.RDF_FILES, executionTime: 1, cached: false, timestamp: new Date() }; } /** * Perform health check */ async healthCheck() { const startTime = Date.now(); try { // Check if base directory is accessible await fs_1.promises.access(this.config.baseDirectory); // Check if we can read files const testFiles = Array.from(this.files.keys()).slice(0, 3); for (const filePath of testFiles) { await fs_1.promises.access(filePath); } const responseTime = Date.now() - startTime; return { layer: types_1.StorageLayer.RDF_FILES, status: responseTime < 100 ? 'healthy' : responseTime < 500 ? 'degraded' : 'unhealthy', responseTime, lastCheck: new Date(), errorCount: this.metrics.failedQueries, details: { totalFiles: this.metrics.totalFiles, totalSizeMB: this.metrics.totalSize / (1024 * 1024), baseDirectory: this.config.baseDirectory, watchingEnabled: this.config.watchForChanges, backupEnabled: this.config.backupEnabled, lastSync: this.metrics.lastSync, fileChanges: this.metrics.fileChanges, successRate: this.metrics.totalQueries > 0 ? this.metrics.successfulQueries / this.metrics.totalQueries : 0 } }; } catch (error) { const responseTime = Date.now() - startTime; return { layer: types_1.StorageLayer.RDF_FILES, status: 'unhealthy', responseTime, lastCheck: new Date(), errorCount: this.metrics.failedQueries, details: { error: error instanceof Error ? error.message : 'Unknown error', baseDirectory: this.config.baseDirectory } }; } } /** * Get storage metrics */ async getMetrics() { return { layer: types_1.StorageLayer.RDF_FILES, ...this.metrics, averageResponseTime: this.metrics.totalQueries > 0 ? this.metrics.totalResponseTime / this.metrics.totalQueries : 0, successRate: this.metrics.totalQueries > 0 ? this.metrics.successfulQueries / this.metrics.totalQueries : 0, averageFileSize: this.metrics.totalFiles > 0 ? this.metrics.totalSize / this.metrics.totalFiles : 0, config: { baseDirectory: this.config.baseDirectory, filePattern: this.config.filePattern, watchForChanges: this.config.watchForChanges, syncInterval: this.config.syncInterval, backupEnabled: this.config.backupEnabled } }; } /** * Shutdown the RDF storage layer */ async shutdown() { try { logger_1.default.info('Shutting down RDF Storage Layer'); // Stop file watcher if (this.watcher) { await this.watcher.close(); this.watcher = undefined; } // Clear sync timer if (this.syncTimer) { clearInterval(this.syncTimer); this.syncTimer = undefined; } // Clear file cache this.files.clear(); this.isInitialized = false; logger_1.default.info('RDF Storage Layer shutdown completed'); this.emit('shutdown'); } catch (error) { logger_1.default.error('RDF Storage Layer shutdown failed:', error); throw error; } } // Private helper methods async loadRDFFiles() { try { const pattern = path.join(this.config.baseDirectory, this.config.filePattern); const filePaths = await (0, glob_1.glob)(pattern); for (const filePath of filePaths) { try { const content = await fs_1.promises.readFile(filePath, 'utf-8'); const stats = await fs_1.promises.stat(filePath); const rdfFile = { path: filePath, content, lastModified: stats.mtime, size: stats.size }; this.files.set(filePath, rdfFile); } catch (error) { logger_1.default.warn(`Failed to load RDF file ${filePath}:`, error); } } this.updateFileMetrics(); logger_1.default.info(`Loaded ${this.files.size} RDF files`); } catch (error) { logger_1.default.error('Failed to load RDF files:', error); throw error; } } setupFileWatcher() { const watchPattern = path.join(this.config.baseDirectory, this.config.filePattern); this.watcher = chokidar.watch(watchPattern, { persistent: true, ignoreInitial: true }); this.watcher.on('add', (filePath) => { this.handleFileChange('add', filePath); }); this.watcher.on('change', (filePath) => { this.handleFileChange('change', filePath); }); this.watcher.on('unlink', (filePath) => { this.handleFileChange('unlink', filePath); }); this.watcher.on('error', (error) => { logger_1.default.error('File watcher error:', error); this.emit('watcher_error', error); }); logger_1.default.debug('File watcher setup completed'); } async handleFileChange(event, filePath) { try { this.metrics.fileChanges++; if (event === 'unlink') { this.files.delete(filePath); this.emit('file_removed', { filePath }); } else { const content = await fs_1.promises.readFile(filePath, 'utf-8'); const stats = await fs_1.promises.stat(filePath); const rdfFile = { path: filePath, content, lastModified: stats.mtime, size: stats.size }; this.files.set(filePath, rdfFile); this.emit('file_changed', { filePath, event, size: stats.size }); } this.updateFileMetrics(); } catch (error) { logger_1.default.error(`Failed to handle file change for ${filePath}:`, error); } } setupSyncTimer() { if (!this.config.syncInterval) return; this.syncTimer = setInterval(() => { this.syncFiles().catch(error => { logger_1.default.error('File sync failed:', error); }); }, this.config.syncInterval); } async syncFiles() { try { await this.loadRDFFiles(); this.metrics.lastSync = new Date(); this.emit('files_synced', { fileCount: this.files.size }); } catch (error) { logger_1.default.error('Failed to sync files:', error); } } async executeRDFQuery(query, params) { const results = []; // Simple query processing for RDF files for (const [filePath, rdfFile] of this.files) { try { // Parse RDF content if not already parsed if (!rdfFile.parsed) { rdfFile.parsed = this.parseTTL(rdfFile.content); } // Apply query logic if (this.matchesQuery(rdfFile.parsed, query, params)) { results.push({ filePath, data: rdfFile.parsed, lastModified: rdfFile.lastModified, size: rdfFile.size }); } } catch (error) { logger_1.default.warn(`Failed to process RDF file ${filePath}:`, error); } } return results; } matchesQuery(data, query, params) { const lowerQuery = query.toLowerCase(); // Simple text-based matching if (lowerQuery.includes('select') || lowerQuery.includes('find')) { // Apply parameter filters for (const [key, value] of Object.entries(params)) { if (data[key] !== value) { return false; } } return true; } // Default: include all files return true; } parseTTL(content) { // Simple TTL parser - in a real implementation, use a proper RDF library const data = {}; const lines = content.split('\n'); for (const line of lines) { const trimmed = line.trim(); if (trimmed.startsWith('#') || !trimmed) continue; // Simple triple parsing const match = trimmed.match(/(\S+)\s+(\S+)\s+(.+)\s*\./); if (match) { const [, subject, predicate, object] = match; if (!data[subject]) data[subject] = {}; data[subject][predicate] = object.replace(/[";]/g, ''); } } return data; } convertToTTL(data) { // Simple TTL generation - in a real implementation, use a proper RDF library let ttl = '@prefix : <http://example.org/> .\n\n'; for (const [subject, predicates] of Object.entries(data)) { if (typeof predicates === 'object' && predicates !== null) { for (const [predicate, object] of Object.entries(predicates)) { ttl += `:${subject} :${predicate} "${object}" .\n`; } } } return ttl; } generateFileName(data) { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const id = data.id || data.name || 'unknown'; return `${id}-${timestamp}.module-knowledge.ttl`; } async createBackup(filePath) { if (!this.config.backupEnabled || !this.config.backupDirectory) return; try { const fileName = path.basename(filePath); const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const backupFileName = `${timestamp}-${fileName}`; const backupPath = path.join(this.config.backupDirectory, backupFileName); await fs_1.promises.copyFile(filePath, backupPath); logger_1.default.debug(`Created backup: ${backupPath}`); } catch (error) { logger_1.default.warn(`Failed to create backup for ${filePath}:`, error); } } updateFileMetrics() { this.metrics.totalFiles = this.files.size; this.metrics.totalSize = Array.from(this.files.values()).reduce((sum, file) => sum + file.size, 0); } updateMetrics(success, responseTime) { this.metrics.totalQueries++; this.metrics.totalResponseTime += responseTime; if (success) { this.metrics.successfulQueries++; } else { this.metrics.failedQueries++; } } } exports.RDFStorageLayer = RDFStorageLayer; exports.default = RDFStorageLayer; //# sourceMappingURL=RDFStorageLayer.js.map