UNPKG

@aaswe/codebase-ai

Version:

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

417 lines 15.7 kB
"use strict"; /** * Neo4j Storage Layer * * Implements the Neo4j storage interface for the Hybrid Storage Manager * with optimized graph operations and health monitoring. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Neo4jStorageLayer = void 0; const events_1 = require("events"); const neo4j_driver_1 = __importDefault(require("neo4j-driver")); const logger_1 = __importDefault(require("../../../utils/logger")); const types_1 = require("./types"); class Neo4jStorageLayer extends events_1.EventEmitter { config; layer = types_1.StorageLayer.NEO4J; driver; isInitialized = false; healthMetrics = { totalQueries: 0, successfulQueries: 0, failedQueries: 0, totalResponseTime: 0, lastHealthCheck: new Date(), connectionPool: { active: 0, idle: 0, total: 0 } }; constructor(config) { super(); this.config = config; } /** * Initialize the Neo4j storage layer */ async initialize() { try { logger_1.default.info('Initializing Neo4j Storage Layer'); // Create Neo4j driver this.driver = neo4j_driver_1.default.driver(this.config.uri, neo4j_driver_1.default.auth.basic(this.config.username, this.config.password), { maxConnectionPoolSize: this.config.maxConnectionPoolSize || 50, connectionTimeout: this.config.connectionTimeout || 30000, maxTransactionRetryTime: this.config.maxTransactionRetryTime || 30000 }); // Verify connectivity await this.verifyConnectivity(); this.isInitialized = true; logger_1.default.info('Neo4j Storage Layer initialized successfully'); this.emit('initialized'); } catch (error) { logger_1.default.error('Failed to initialize Neo4j Storage Layer:', error); throw new types_1.HybridStorageError(`Neo4j initialization failed: ${error instanceof Error ? error.message : 'Unknown error'}`, types_1.StorageLayer.NEO4J, 'initialize', error instanceof Error ? error : undefined); } } /** * Execute a query against Neo4j */ async query(query, params, context) { if (!this.isInitialized || !this.driver) { throw new types_1.HybridStorageError('Neo4j storage layer not initialized', types_1.StorageLayer.NEO4J, 'query'); } const startTime = Date.now(); let session; let timeoutHandle; try { // Create session with appropriate database session = this.driver.session({ database: this.config.database || 'neo4j', defaultAccessMode: this.determineAccessMode(query) }); // Execute query with timeout const timeout = context?.timeout || 30000; const result = await Promise.race([ session.run(query, params || {}), new Promise((_, reject) => { timeoutHandle = setTimeout(() => reject(new Error('Query timeout')), timeout); timeoutHandle.unref(); // Allow Node.js to exit even if timeout is pending }) ]); // Clear timeout if query completed successfully if (timeoutHandle) { clearTimeout(timeoutHandle); } // Process results - handle case where result.records might be undefined (mocked scenarios) const records = (result.records || []).map(record => { const obj = {}; record.keys.forEach(key => { obj[key] = this.convertNeo4jValue(record.get(key)); }); return obj; }); const executionTime = Date.now() - startTime; // Update metrics this.updateMetrics(true, executionTime); const queryResult = { data: records, source: types_1.StorageLayer.NEO4J, executionTime, cached: false, timestamp: new Date(), metadata: { summary: result.summary, recordCount: records.length, database: this.config.database || 'neo4j' } }; this.emit('query_executed', { query, params, result: queryResult, context }); return queryResult; } catch (error) { // Clear timeout if query failed if (timeoutHandle) { clearTimeout(timeoutHandle); } const executionTime = Date.now() - startTime; this.updateMetrics(false, executionTime); logger_1.default.error('Neo4j 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(`Neo4j query failed: ${errorMessage}`, types_1.StorageLayer.NEO4J, 'query', error instanceof Error ? error : undefined); } finally { // Always clear timeout handle if (timeoutHandle) { clearTimeout(timeoutHandle); } if (session) { await session.close(); } } } /** * Create a new entity in Neo4j */ async create(data, context) { const createQuery = this.buildCreateQuery(data); return this.query(createQuery.query, createQuery.params, context); } /** * Update an entity in Neo4j */ async update(id, data, context) { const updateQuery = this.buildUpdateQuery(id, data); return this.query(updateQuery.query, updateQuery.params, context); } /** * Delete an entity from Neo4j */ async delete(id, context) { const deleteQuery = ` MATCH (n) WHERE id(n) = $id DELETE n RETURN count(n) as deletedCount `; const result = await this.query(deleteQuery, { id: parseInt(id) }, context); const deleted = Array.isArray(result.data) && result.data.length > 0 && result.data[0].deletedCount > 0; return { ...result, data: deleted }; } /** * Perform health check */ async healthCheck() { const startTime = Date.now(); try { if (!this.driver) { throw new Error('Driver not initialized'); } // Test connectivity with a simple query const session = this.driver.session(); try { await session.run('RETURN 1 as test'); await session.close(); } catch (error) { await session.close(); throw error; } // Get connection pool metrics const serverInfo = await this.driver.getServerInfo(); const responseTime = Date.now() - startTime; this.healthMetrics.lastHealthCheck = new Date(); return { layer: types_1.StorageLayer.NEO4J, status: responseTime < 1000 ? 'healthy' : responseTime < 5000 ? 'degraded' : 'unhealthy', responseTime, lastCheck: new Date(), errorCount: this.healthMetrics.failedQueries, details: { serverInfo, connectionPool: this.healthMetrics.connectionPool, database: this.config.database || 'neo4j', totalQueries: this.healthMetrics.totalQueries, successRate: this.healthMetrics.totalQueries > 0 ? this.healthMetrics.successfulQueries / this.healthMetrics.totalQueries : 0 } }; } catch (error) { const responseTime = Date.now() - startTime; return { layer: types_1.StorageLayer.NEO4J, status: 'unhealthy', responseTime, lastCheck: new Date(), errorCount: this.healthMetrics.failedQueries, details: { error: error instanceof Error ? error.message : 'Unknown error', lastSuccessfulCheck: this.healthMetrics.lastHealthCheck } }; } } /** * Get storage metrics */ async getMetrics() { return { layer: types_1.StorageLayer.NEO4J, totalQueries: this.healthMetrics.totalQueries, successfulQueries: this.healthMetrics.successfulQueries, failedQueries: this.healthMetrics.failedQueries, successRate: this.healthMetrics.totalQueries > 0 ? this.healthMetrics.successfulQueries / this.healthMetrics.totalQueries : 0, averageResponseTime: this.healthMetrics.totalQueries > 0 ? this.healthMetrics.totalResponseTime / this.healthMetrics.totalQueries : 0, connectionPool: this.healthMetrics.connectionPool, isInitialized: this.isInitialized, config: { uri: this.config.uri, database: this.config.database || 'neo4j', maxConnectionPoolSize: this.config.maxConnectionPoolSize || 50 } }; } /** * Shutdown the Neo4j storage layer */ async shutdown() { try { logger_1.default.info('Shutting down Neo4j Storage Layer'); if (this.driver) { await this.driver.close(); this.driver = undefined; } this.isInitialized = false; logger_1.default.info('Neo4j Storage Layer shutdown completed'); this.emit('shutdown'); } catch (error) { logger_1.default.error('Neo4j Storage Layer shutdown failed:', error); throw error; } } // Private helper methods async verifyConnectivity() { if (!this.driver) { throw new Error('Driver not initialized'); } const session = this.driver.session(); try { await session.run('RETURN 1 as test'); logger_1.default.debug('Neo4j connectivity verified'); } finally { await session.close(); } } determineAccessMode(query) { const writeKeywords = ['CREATE', 'MERGE', 'SET', 'DELETE', 'REMOVE', 'DROP']; const upperQuery = query.toUpperCase(); return writeKeywords.some(keyword => upperQuery.includes(keyword)) ? 'WRITE' : 'READ'; } convertNeo4jValue(value) { if (value === null || value === undefined) { return value; } // Handle Neo4j specific types if (typeof value === 'object') { if (value.constructor.name === 'Integer') { return value.toNumber(); } if (value.constructor.name === 'Node') { return { id: value.identity.toNumber(), labels: value.labels, properties: this.convertNeo4jProperties(value.properties) }; } if (value.constructor.name === 'Relationship') { return { id: value.identity.toNumber(), type: value.type, start: value.start.toNumber(), end: value.end.toNumber(), properties: this.convertNeo4jProperties(value.properties) }; } if (value.constructor.name === 'Path') { return { start: this.convertNeo4jValue(value.start), end: this.convertNeo4jValue(value.end), segments: value.segments.map((segment) => ({ start: this.convertNeo4jValue(segment.start), relationship: this.convertNeo4jValue(segment.relationship), end: this.convertNeo4jValue(segment.end) })) }; } if (Array.isArray(value)) { return value.map(item => this.convertNeo4jValue(item)); } // Handle regular objects const converted = {}; for (const [key, val] of Object.entries(value)) { converted[key] = this.convertNeo4jValue(val); } return converted; } return value; } convertNeo4jProperties(properties) { const converted = {}; for (const [key, value] of Object.entries(properties)) { converted[key] = this.convertNeo4jValue(value); } return converted; } buildCreateQuery(data) { if (data.labels && data.properties) { // Node creation const labels = Array.isArray(data.labels) ? data.labels.join(':') : data.labels; const query = `CREATE (n:${labels} $properties) RETURN n`; return { query, params: { properties: data.properties } }; } else if (data.type && data.start && data.end && data.properties) { // Relationship creation const query = ` MATCH (start) WHERE id(start) = $startId MATCH (end) WHERE id(end) = $endId CREATE (start)-[r:${data.type} $properties]->(end) RETURN r `; return { query, params: { startId: data.start, endId: data.end, properties: data.properties } }; } else { // Generic node creation const query = 'CREATE (n $properties) RETURN n'; return { query, params: { properties: data } }; } } buildUpdateQuery(id, data) { // Try to parse as number, but handle string IDs gracefully const numericId = isNaN(parseInt(id)) ? null : parseInt(id); if (numericId !== null) { // Use numeric ID for Neo4j internal ID const query = ` MATCH (n) WHERE id(n) = $id SET n += $properties RETURN n `; return { query, params: { id: numericId, properties: data } }; } else { // Use string ID as a property match (fallback for test scenarios) const query = ` MATCH (n) WHERE n.id = $id OR n.uuid = $id SET n += $properties RETURN n `; return { query, params: { id: id, properties: data } }; } } updateMetrics(success, responseTime) { this.healthMetrics.totalQueries++; this.healthMetrics.totalResponseTime += responseTime; if (success) { this.healthMetrics.successfulQueries++; } else { this.healthMetrics.failedQueries++; } } } exports.Neo4jStorageLayer = Neo4jStorageLayer; exports.default = Neo4jStorageLayer; //# sourceMappingURL=Neo4jStorageLayer.js.map