@aaswe/codebase-ai
Version:
AI-Assisted Software Engineering (AASWE) - Rich codebase context for IDE LLMs
417 lines • 15.7 kB
JavaScript
"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