Spaces:
Paused
Paused
| import neo4j, { Driver, Session } from 'neo4j-driver'; | |
| export interface GraphNode { | |
| id: string; | |
| labels: string[]; | |
| properties: Record<string, any>; | |
| } | |
| export interface GraphRelationship { | |
| id: string; | |
| type: string; | |
| startNodeId: string; | |
| endNodeId: string; | |
| properties: Record<string, any>; | |
| } | |
| export class Neo4jService { | |
| private driver: Driver | null = null; | |
| private uri: string; | |
| private username: string; | |
| private password: string; | |
| constructor() { | |
| this.uri = process.env.NEO4J_URI || 'bolt://localhost:7687'; | |
| // Support both NEO4J_USER and NEO4J_USERNAME for compatibility | |
| this.username = process.env.NEO4J_USER || process.env.NEO4J_USERNAME || 'neo4j'; | |
| this.password = process.env.NEO4J_PASSWORD || 'password'; | |
| } | |
| async connect(): Promise<void> { | |
| try { | |
| this.driver = neo4j.driver( | |
| this.uri, | |
| neo4j.auth.basic(this.username, this.password), | |
| { | |
| maxConnectionPoolSize: 50, | |
| connectionAcquisitionTimeout: 60000, | |
| } | |
| ); | |
| await this.driver.verifyConnectivity(); | |
| console.log('✅ Neo4j connected successfully', this.uri); | |
| } catch (error) { | |
| console.error('❌ Failed to connect to Neo4j', error); | |
| throw error; | |
| } | |
| } | |
| async disconnect(): Promise<void> { | |
| if (this.driver) { | |
| await this.driver.close(); | |
| this.driver = null; | |
| console.log('Neo4j disconnected'); | |
| } | |
| } | |
| async close(): Promise<void> { | |
| await this.disconnect(); | |
| } | |
| private getSession(): Session { | |
| if (!this.driver) { | |
| throw new Error('Neo4j driver not initialized. Call connect() first.'); | |
| } | |
| return this.driver.session(); | |
| } | |
| async createNode(labels: string[], properties: Record<string, any>): Promise<GraphNode> { | |
| const session = this.getSession(); | |
| try { | |
| const labelsStr = labels.map(l => `:${l}`).join(''); | |
| const result = await session.run( | |
| `CREATE (n${labelsStr} $properties) RETURN n`, | |
| { properties } | |
| ); | |
| const node = result.records[0].get('n'); | |
| return { | |
| id: node.elementId, | |
| labels: node.labels, | |
| properties: node.properties, | |
| }; | |
| } finally { | |
| await session.close(); | |
| } | |
| } | |
| async createRelationship( | |
| startNodeId: string, | |
| endNodeId: string, | |
| type: string, | |
| properties: Record<string, any> = {} | |
| ): Promise<GraphRelationship> { | |
| const session = this.getSession(); | |
| try { | |
| // Use elementId lookup instead of id() | |
| const result = await session.run( | |
| `MATCH (a), (b) | |
| WHERE elementId(a) = $startId AND elementId(b) = $endId | |
| CREATE (a)-[r:${type} $properties]->(b) | |
| RETURN r`, | |
| { startId: startNodeId, endId: endNodeId, properties } | |
| ); | |
| const rel = result.records[0].get('r'); | |
| return { | |
| id: rel.elementId, | |
| type: rel.type, | |
| startNodeId: rel.startNodeElementId, | |
| endNodeId: rel.endNodeElementId, | |
| properties: rel.properties, | |
| }; | |
| } finally { | |
| await session.close(); | |
| } | |
| } | |
| async findNodes(label: string, properties: Record<string, any> = {}): Promise<GraphNode[]> { | |
| const session = this.getSession(); | |
| try { | |
| const whereClause = Object.keys(properties).length > 0 | |
| ? 'WHERE ' + Object.keys(properties).map(k => `n.${k} = $${k}`).join(' AND ') | |
| : ''; | |
| const result = await session.run( | |
| `MATCH (n:${label}) ${whereClause} RETURN n`, | |
| properties | |
| ); | |
| return result.records.map(record => { | |
| const node = record.get('n'); | |
| return { | |
| id: node.elementId, | |
| labels: node.labels, | |
| properties: node.properties, | |
| }; | |
| }); | |
| } finally { | |
| await session.close(); | |
| } | |
| } | |
| async runQuery(query: string, parameters: Record<string, any> = {}): Promise<any[]> { | |
| const session = this.getSession(); | |
| try { | |
| const result = await session.run(query, parameters); | |
| return result.records.map(record => record.toObject()); | |
| } finally { | |
| await session.close(); | |
| } | |
| } | |
| async getNodeById(nodeId: string): Promise<GraphNode | null> { | |
| const session = this.getSession(); | |
| try { | |
| const result = await session.run( | |
| 'MATCH (n) WHERE elementId(n) = $id RETURN n', | |
| { id: nodeId } | |
| ); | |
| if (result.records.length === 0) return null; | |
| const node = result.records[0].get('n'); | |
| return { | |
| id: node.elementId, | |
| labels: node.labels, | |
| properties: node.properties, | |
| }; | |
| } finally { | |
| await session.close(); | |
| } | |
| } | |
| async deleteNode(nodeId: string): Promise<void> { | |
| const session = this.getSession(); | |
| try { | |
| await session.run( | |
| 'MATCH (n) WHERE elementId(n) = $id DETACH DELETE n', | |
| { id: nodeId } | |
| ); | |
| } finally { | |
| await session.close(); | |
| } | |
| } | |
| async getNodeRelationships(nodeId: string): Promise<GraphRelationship[]> { | |
| const session = this.getSession(); | |
| try { | |
| const result = await session.run( | |
| `MATCH (n)-[r]-(m) | |
| WHERE elementId(n) = $id | |
| RETURN r`, | |
| { id: nodeId } | |
| ); | |
| return result.records.map(record => { | |
| const rel = record.get('r'); | |
| return { | |
| id: rel.elementId, | |
| type: rel.type, | |
| startNodeId: rel.startNodeElementId, | |
| endNodeId: rel.endNodeElementId, | |
| properties: rel.properties, | |
| }; | |
| }); | |
| } finally { | |
| await session.close(); | |
| } | |
| } | |
| async healthCheck(): Promise<boolean> { | |
| try { | |
| if (!this.driver) return false; | |
| await this.driver.verifyConnectivity(); | |
| return true; | |
| } catch (error) { | |
| console.error('Neo4j health check failed', error); | |
| return false; | |
| } | |
| } | |
| } | |
| export const neo4jService = new Neo4jService(); | |