UNPKG

meta-log-db

Version:

Native database package for Meta-Log (ProLog, DataLog, R5RS)

195 lines (165 loc) 4.26 kB
/** * DAG Manager * * Manages Directed Acyclic Graph operations for MetaLogNodes */ import { DAG } from './types.js'; import { MetaLogNode, CID } from '../metalog-node/types.js'; /** * DAG Manager * * Handles DAG operations: LCA finding, ancestor/descendant queries, node management */ export class DAGManager { private dag: DAG; constructor(dag?: DAG) { this.dag = dag || { nodes: new Map(), edges: new Map(), roots: new Set(), heads: new Set() }; } /** * Get DAG instance */ getDAG(): DAG { return this.dag; } /** * Add node to DAG * * @param node - MetaLogNode to add */ addNode(node: MetaLogNode): void { this.dag.nodes.set(node.cid, node); // Update edges (child → parents) if (node.parent !== 'genesis') { const children = this.dag.edges.get(node.parent) || []; if (!children.includes(node.cid)) { children.push(node.cid); this.dag.edges.set(node.parent, children); } } else { // Root node this.dag.roots.add(node.cid); } // Update heads (remove parent from heads if it was a head) if (node.parent !== 'genesis' && this.dag.heads.has(node.parent)) { this.dag.heads.delete(node.parent); } // Check if this node is a head (no children yet) if (!this.dag.edges.has(node.cid)) { this.dag.heads.add(node.cid); } } /** * Find Lowest Common Ancestor (LCA) of two nodes * * @param cid1 - First node CID * @param cid2 - Second node CID * @returns LCA CID or null if no common ancestor */ findLCA(cid1: CID, cid2: CID): CID | null { const ancestors1 = this.getAncestors(cid1); const ancestors2 = this.getAncestors(cid2); // Find first common ancestor (LCA) for (const a1 of ancestors1) { if (ancestors2.includes(a1)) { return a1; } } return null; // No common ancestor (different roots) } /** * Get all ancestors of a node * * @param cid - Node CID * @returns Array of ancestor CIDs (ordered from immediate parent to root) */ getAncestors(cid: CID): CID[] { const ancestors: CID[] = []; let current: CID | undefined = cid; while (current) { const node = this.dag.nodes.get(current); if (!node || node.parent === 'genesis') { break; } ancestors.push(node.parent); current = node.parent; } return ancestors; } /** * Get all children of a node * * @param cid - Node CID * @returns Array of child CIDs */ getChildren(cid: CID): CID[] { return this.dag.edges.get(cid) || []; } /** * Get all descendants of a node (recursive) * * @param cid - Node CID * @returns Array of descendant CIDs */ getDescendants(cid: CID): CID[] { const descendants: CID[] = []; const visited = new Set<CID>(); const traverse = (current: CID) => { if (visited.has(current)) return; visited.add(current); const children = this.getChildren(current); for (const child of children) { descendants.push(child); traverse(child); } }; traverse(cid); return descendants; } /** * Get depth of a node (distance from root) * * @param cid - Node CID * @returns Depth (0 for root nodes) */ getDepth(cid: CID): number { return this.getAncestors(cid).length; } /** * Check if DAG has cycles (should always return false for valid DAG) * * @returns true if cycle detected */ hasCycles(): boolean { const visited = new Set<CID>(); const recStack = new Set<CID>(); const hasCycle = (cid: CID): boolean => { if (recStack.has(cid)) { return true; // Cycle detected } if (visited.has(cid)) { return false; } visited.add(cid); recStack.add(cid); const node = this.dag.nodes.get(cid); if (node && node.parent !== 'genesis') { if (hasCycle(node.parent)) { return true; } } recStack.delete(cid); return false; }; for (const cid of this.dag.nodes.keys()) { if (!visited.has(cid) && hasCycle(cid)) { return true; } } return false; } }