Skip to main content
Glama
edge-manager.ts10.5 kB
import { Neo4jClient } from './neo4j-client.js'; import { CodeEdge, QueryResult } from '../types.js'; export class EdgeManager { constructor(private client: Neo4jClient) {} async addEdge(edge: CodeEdge): Promise<CodeEdge> { let query = ` MATCH (source:CodeNode {id: $source, project_id: $project_id}), (target:CodeNode {id: $target, project_id: $project_id}) CREATE (source)-[r:${edge.type.toUpperCase()} { id: $id, project_id: $project_id, type: $type, attributes_json: $attributes_json }]->(target) RETURN r, source.id as sourceId, target.id as targetId `; const params = { id: edge.id, project_id: edge.project_id, type: edge.type, source: edge.source, target: edge.target, attributes_json: JSON.stringify(edge.attributes || {}) }; let result = await this.client.runQuery(query, this.ensurePlainObject(params)); // If the exact target isn't found and this is an implements relationship, // try to find the interface by name within the same project if (result.records.length === 0 && edge.type === 'implements') { const targetName = edge.target.split('.').pop(); // Get just the interface name const findInterfaceQuery = ` MATCH (source:CodeNode {id: $source, project_id: $project_id}), (target:Interface {project_id: $project_id}) WHERE target.name = $targetName CREATE (source)-[r:${edge.type.toUpperCase()} { id: $id, project_id: $project_id, type: $type, attributes_json: $attributes_json }]->(target) RETURN r, source.id as sourceId, target.id as targetId `; const findParams = { ...params, targetName }; result = await this.client.runQuery(findInterfaceQuery, this.ensurePlainObject(findParams)); } if (result.records.length === 0) { throw new Error('Failed to create edge - source or target node not found'); } return this.recordToEdge(result.records[0]); } async updateEdge(edgeId: string, projectId: string, updates: Partial<CodeEdge>): Promise<CodeEdge> { const setParts: string[] = []; const parameters: Record<string, any> = { id: edgeId }; Object.entries(updates).forEach(([key, value], index) => { if (key !== 'id' && key !== 'source' && key !== 'target' && value !== undefined) { const paramKey = `update_${index}`; setParts.push(`r.${key} = $${paramKey}`); parameters[paramKey] = value; } }); if (setParts.length === 0) { throw new Error('No valid updates provided'); } const query = ` MATCH (source)-[r {id: $id, project_id: $project_id}]->(target) SET ${setParts.join(', ')} RETURN r, source.id as sourceId, target.id as targetId `; parameters.project_id = projectId; const result = await this.client.runQuery(query, parameters); if (result.records.length === 0) { throw new Error(`Edge with id ${edgeId} not found`); } return this.recordToEdge(result.records[0]); } async getEdge(edgeId: string, projectId: string): Promise<CodeEdge | null> { const query = ` MATCH (source)-[r {id: $id, project_id: $project_id}]->(target) RETURN r, source.id as sourceId, target.id as targetId `; const result = await this.client.runQuery(query, { id: edgeId, project_id: projectId }); if (result.records.length === 0) { return null; } return this.recordToEdge(result.records[0]); } async deleteEdge(edgeId: string, projectId: string): Promise<boolean> { const query = ` MATCH ()-[r {id: $id, project_id: $project_id}]->() DELETE r RETURN count(r) as deleted `; const result = await this.client.runQuery(query, { id: edgeId, project_id: projectId }); return result.records[0].get('deleted').toNumber() > 0; } async findEdgesByType(type: CodeEdge['type'], projectId: string): Promise<CodeEdge[]> { const query = ` MATCH (source)-[r:${type.toUpperCase()} {project_id: $project_id}]->(target) RETURN r, source.id as sourceId, target.id as targetId `; const result = await this.client.runQuery(query, { project_id: projectId }); return result.records.map(record => this.recordToEdge(record)); } async findEdgesBySource(sourceId: string, projectId: string): Promise<CodeEdge[]> { const query = ` MATCH (source {id: $sourceId, project_id: $project_id})-[r {project_id: $project_id}]->(target) RETURN r, source.id as sourceId, target.id as targetId `; const result = await this.client.runQuery(query, { sourceId, project_id: projectId }); return result.records.map(record => this.recordToEdge(record)); } async findEdgesByTarget(targetId: string, projectId: string): Promise<CodeEdge[]> { const query = ` MATCH (source)-[r {project_id: $project_id}]->(target {id: $targetId, project_id: $project_id}) RETURN r, source.id as sourceId, target.id as targetId `; const result = await this.client.runQuery(query, { targetId, project_id: projectId }); return result.records.map(record => this.recordToEdge(record)); } async findEdgesBetween(sourceId: string, targetId: string, projectId: string): Promise<CodeEdge[]> { const query = ` MATCH (source {id: $sourceId, project_id: $project_id})-[r {project_id: $project_id}]->(target {id: $targetId, project_id: $project_id}) RETURN r, source.id as sourceId, target.id as targetId `; const result = await this.client.runQuery(query, { sourceId, targetId, project_id: projectId }); return result.records.map(record => this.recordToEdge(record)); } async getAllEdges(projectId: string): Promise<CodeEdge[]> { const query = ` MATCH (source)-[r {project_id: $project_id}]->(target) RETURN r, source.id as sourceId, target.id as targetId LIMIT 1000 `; const result = await this.client.runQuery(query, { project_id: projectId }); return result.records.map(record => this.recordToEdge(record)); } // Complex queries for code analysis async findClassesThatCallMethod(methodName: string, projectId: string): Promise<string[]> { const query = ` MATCH (class:CodeNode {type: 'class', project_id: $project_id})-[:CONTAINS {project_id: $project_id}]->(method1:CodeNode {project_id: $project_id}) -[:CALLS {project_id: $project_id}]->(method2:CodeNode {name: $methodName, project_id: $project_id}) RETURN DISTINCT class.name as className ORDER BY className `; const result = await this.client.runQuery(query, { methodName, project_id: projectId }); return result.records.map(record => record.get('className')); } async findClassesThatImplementInterface(interfaceName: string, projectId: string): Promise<string[]> { const query = ` MATCH (class:CodeNode {type: 'class', project_id: $project_id})-[:IMPLEMENTS {project_id: $project_id}]-> (interface:CodeNode {type: 'interface', name: $interfaceName, project_id: $project_id}) RETURN class.name as className ORDER BY className `; const result = await this.client.runQuery(query, { interfaceName, project_id: projectId }); return result.records.map(record => record.get('className')); } async findInheritanceHierarchy(className: string, projectId: string): Promise<string[]> { const query = ` MATCH path = (child:CodeNode {name: $className, project_id: $project_id})-[:EXTENDS* {project_id: $project_id}]-> (ancestor:CodeNode {project_id: $project_id}) RETURN [node IN nodes(path) | node.name] as hierarchy `; const result = await this.client.runQuery(query, { className, project_id: projectId }); return result.records.length > 0 ? result.records[0].get('hierarchy') : []; } // Cross-project methods (use with caution) async findEdgesByTypeAcrossProjects(type: CodeEdge['type']): Promise<CodeEdge[]> { const query = ` MATCH (source)-[r:${type.toUpperCase()}]->(target) RETURN r, source.id as sourceId, target.id as targetId ORDER BY r.project_id `; const result = await this.client.runQuery(query); return result.records.map(record => this.recordToEdge(record)); } async getAllEdgesAcrossProjects(): Promise<CodeEdge[]> { const query = ` MATCH (source)-[r]->(target) RETURN r, source.id as sourceId, target.id as targetId ORDER BY r.project_id, r.type LIMIT 1000 `; const result = await this.client.runQuery(query); return result.records.map(record => this.recordToEdge(record)); } async findCrossProjectDependencies(): Promise<CodeEdge[]> { const query = ` MATCH (source:CodeNode)-[r]->(target:CodeNode) WHERE source.project_id <> target.project_id RETURN r, source.id as sourceId, target.id as targetId ORDER BY source.project_id, target.project_id `; const result = await this.client.runQuery(query); return result.records.map(record => this.recordToEdge(record)); } private recordToEdge(record: any): CodeEdge { const relationship = record.get('r'); const properties = relationship.properties; return { id: properties.id, project_id: properties.project_id, type: properties.type, source: record.get('sourceId'), target: record.get('targetId'), attributes: properties.attributes_json ? JSON.parse(properties.attributes_json) : {} }; } private ensurePlainObject(value: any): any { if (value === null || value === undefined) { return value; } // Handle Maps first (before JSON serialization) if (value instanceof Map) { const obj: any = {}; for (const [k, v] of value.entries()) { obj[k] = this.ensurePlainObject(v); } return obj; } if (Array.isArray(value)) { return value.map(item => this.ensurePlainObject(item)); } if (value && typeof value === 'object' && value.constructor === Object) { const obj: any = {}; for (const [k, v] of Object.entries(value)) { obj[k] = this.ensurePlainObject(v); } return obj; } // Try JSON serialization for other complex objects try { return JSON.parse(JSON.stringify(value)); } catch (error) { return value; } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/JonnoC/CodeRAG'

If you have feedback or need assistance with the MCP directory API, please join our Discord server