feat(graph/queries): implement query methods — hasCycles, findCycles, topologicalOrder, dependencies, dependents, taskCount, getTask
- hasCycles(): uses graphology-dag.hasCycle() as fast boolean check - findCycles(): SCC pre-check + custom 3-color DFS for cycle path extraction - topologicalOrder(): graphology-dag.topologicalSort + CircularDependencyError on cycle - dependencies/dependents: inNeighbors/outNeighbors with TaskNotFoundError - taskCount(): graph.order, getTask(): node attributes or undefined - 45 unit tests covering all acceptance criteria
This commit is contained in:
@@ -2,6 +2,15 @@
|
||||
|
||||
import { DirectedGraph } from 'graphology';
|
||||
import type { TaskGraphNodeAttributes, TaskGraphEdgeAttributes, TaskGraphSerialized } from '../schema/index.js';
|
||||
import {
|
||||
hasCycles as _hasCycles,
|
||||
findCycles as _findCycles,
|
||||
topologicalOrder as _topologicalOrder,
|
||||
dependencies as _dependencies,
|
||||
dependents as _dependents,
|
||||
taskCount as _taskCount,
|
||||
getTask as _getTask,
|
||||
} from './queries.js';
|
||||
|
||||
/**
|
||||
* Internal graph type alias for the graphology DirectedGraph with our attribute types.
|
||||
@@ -130,4 +139,76 @@ export class TaskGraph {
|
||||
graph._graph.import(data);
|
||||
return graph;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Query methods
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Check whether the graph contains any cycles.
|
||||
*
|
||||
* Uses `graphology-dag.hasCycle()` as a fast boolean check.
|
||||
*/
|
||||
hasCycles(): boolean {
|
||||
return _hasCycles(this._graph);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all cycle paths in the graph.
|
||||
*
|
||||
* Uses `stronglyConnectedComponents()` as a fast pre-check, then runs a
|
||||
* custom 3-color DFS (WHITE/GREY/BLACK) to extract cycle paths.
|
||||
*
|
||||
* Returns **one representative cycle per back edge**, not an exhaustive
|
||||
* enumeration of all simple cycles. Each inner array is an ordered node
|
||||
* sequence where the last node has an edge back to the first:
|
||||
* `[A, B, C]` means A → B → C → A.
|
||||
*/
|
||||
findCycles(): string[][] {
|
||||
return _findCycles(this._graph);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return task IDs in topological (prerequisite → dependent) order.
|
||||
*
|
||||
* Uses `graphology-dag.topologicalSort()` for the actual sort.
|
||||
*
|
||||
* @throws {CircularDependencyError} When the graph is cyclic, with `cycles`
|
||||
* populated from `findCycles()`.
|
||||
*/
|
||||
topologicalOrder(): string[] {
|
||||
return _topologicalOrder(this._graph);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the prerequisite task IDs for a given task.
|
||||
*
|
||||
* @throws {TaskNotFoundError} If `taskId` doesn't exist in the graph.
|
||||
*/
|
||||
dependencies(taskId: string): string[] {
|
||||
return _dependencies(this._graph, taskId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the dependent task IDs for a given task.
|
||||
*
|
||||
* @throws {TaskNotFoundError} If `taskId` doesn't exist in the graph.
|
||||
*/
|
||||
dependents(taskId: string): string[] {
|
||||
return _dependents(this._graph, taskId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of tasks (nodes) in the graph.
|
||||
*/
|
||||
taskCount(): number {
|
||||
return _taskCount(this._graph);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the attributes of a task node, or `undefined` if it doesn't exist.
|
||||
*/
|
||||
getTask(taskId: string): TaskGraphNodeAttributes | undefined {
|
||||
return _getTask(this._graph, taskId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user