3.2 KiB
graph/graph.ts
(no submodules)
Purpose
Implements the graph utilities that operate on a WorkspaceGraph built from a
list of PackageRecords. Covers the analyses every project tool eventually
needs: build a forward / reverse adjacency map, compute a topological
dependency-first order, detect cycles, gather transitive deps or dependents,
and identify graph roots (no deps) and leaves (no dependents).
Edges where the target is not also a node are silently dropped at build time.
This is the same posture as scripts/folder-reorg/generate-manifest.ts —
necessary because Suaveplan migrates in waves and partial-manifest
snapshots are routine.
Features
buildGraph— populates forward and reverse adjacency in one pass; emitsoverview.graph_builds_totaltopologicalOrder— Kahn's algorithm with stable lexicographic tie-breaking; throwsOVERVIEW.CYCLE_DETECTEDon a cycle (error carriescontext.cycle)detectCycles— Tarjan's SCC, returns every cycle as a sortedstring[]transitiveDependencies/transitiveDependents— BFS over the appropriate direction; throwsOVERVIEW.UNKNOWN_PACKAGEfor missing inputsleafPackages— no dependents (no incoming edges)rootPackages— no deps (no outgoing edges)
Usage: Basic
import { buildGraph, topologicalOrder } from "@suaveplan/overview";
const graph = buildGraph(records);
const order = topologicalOrder(graph);
// `order[0]` has no workspace deps; the last entry depends on everything.
Usage: Advanced
import {
buildGraph,
detectCycles,
transitiveDependencies,
transitiveDependents,
leafPackages,
rootPackages,
} from "@suaveplan/overview";
const graph = buildGraph(records);
const cycles = detectCycles(graph);
if (cycles.length > 0) {
console.error("Cycles detected:", cycles);
}
const blastRadius = transitiveDependents(graph, "@suaveplan/error");
const supplyChain = transitiveDependencies(graph, "@suaveplan/api");
const leaves = leafPackages(graph);
const roots = rootPackages(graph);
topologicalOrder is deterministic — two callers with the same graph see the
same order — so it can drive reproducible build plans and CI fan-outs.
API Reference
buildGraph(records): WorkspaceGraphtopologicalOrder(graph): string[]— throwsOVERVIEW.CYCLE_DETECTEDdetectCycles(graph): string[][]transitiveDependencies(graph, pkg): string[]— throwsOVERVIEW.UNKNOWN_PACKAGEtransitiveDependents(graph, pkg): string[]— throwsOVERVIEW.UNKNOWN_PACKAGEleafPackages(graph): string[]rootPackages(graph): string[]
Implementation Notes
topologicalOrder initialises a remaining-out-degree counter per node and
drains nodes with zero out-degree first; the queue is kept sorted on insertion
so the output is stable across runs. detectCycles uses a recursive
Tarjan implementation — the workspace size is bounded (hundreds of packages),
so the stack-depth budget is comfortable. BFS in transitiveDependencies /
transitiveDependents uses an array-based queue with Array.prototype.shift
— acceptable at workspace scale; if the package ever has to handle tens of
thousands of nodes the queue can be swapped for a head/tail index pair.