class Graph { constructor() { this.nodes = new Map(); // nodeId -> nodeData this.edges = new Map(); // nodeId -> Set of neighbor nodeIds this.edgeData = new Map(); // key `${from}->${to}` -> data } addNode(id, data = {}) { if (this.nodes.has(id)) { throw new Error(`Node with id ${id} already exists`); } this.nodes.set(id, data); this.edges.set(id, new Set()); } addEdge(from, to, data = {}) { if (!this.nodes.has(from) || !this.nodes.has(to)) { throw new Error(`Both nodes must exist to add an edge`); } this.edges.get(from).add(to); const key = `${from}->${to}`; this.edgeData.set(key, data); } getNeighbors(id) { if (!this.nodes.has(id)) { throw new Error(`Node with id ${id} does not exist`); } return Array.from(this.edges.get(id)); } getNode(id) { return this.nodes.get(id); } getAllNodes() { return Array.from(this.nodes.keys()); } getAllEdges() { const edges = []; for (const [from, neighbors] of this.edges.entries()) { for (const to of neighbors) { const key = `${from}->${to}`; edges.push({ from, to, data: this.edgeData.get(key) }); } } return edges; } getEdgeData(from, to) { const key = `${from}->${to}`; return this.edgeData.get(key); } // Reflection methods getProperties() { return Object.getOwnPropertyNames(this); } getMethods() { const proto = Object.getPrototypeOf(this); return Object.getOwnPropertyNames(proto).filter( (name) => typeof this[name] === 'function' && name !== 'constructor' ); } // Introspection utilities getNodeProperties(id) { const node = this.nodes.get(id); return node ? Object.keys(node) : null; } getEdgeProperties(from, to) { const data = this.getEdgeData(from, to); return data ? Object.keys(data) : null; } } module.exports = Graph;