@actionforge/graph-ns
v0.1.0
Published
A typed directed-graph namespace over graphlib — single-inheritance successor chains, predecessor/successor queries, and topological/bottom-up ordering
Maintainers
Readme
@actionforge/graph-ns
A typed wrapper around graphlib for directed graphs, with single-inheritance chain queries and topological / bottom-up ordering on top.
Why this exists
This class was originally written to model refract element type hierarchies
(string → element → object), and its docs named everything "element".
But the graph operations were never refract-specific — the same code was
already being used to drive dependency-graph-ordered release workflows.
So the vocabulary here is plain graph terminology, and the one piece of logic
that was refract-specific — a hardcoded root node literally named 'object'
— is now inferred or configurable. Nothing else about the behaviour changed
except the bug fixes listed below.
Two things it is good at:
- Type/class hierarchies — single inheritance, "what does this derive from, all the way up".
- Dependency graphs — "what order do I build/release these in".
Install
npm install @actionforge/graph-nsUsage
import { GraphNamespace } from '@actionforge/graph-ns'
const ns = new GraphNamespace()
ns.setEdge(['string', 'number', 'boolean'], 'element')
ns.setEdge('element', 'object')
ns.successorChain('string') // ['element', 'object']
ns.topsort() // leaves first
ns.bottomsort() // ['object', 'element', ...] — root firstAs a dependency graph, where an edge means "depends on":
const deps = new GraphNamespace()
deps.setEdge('app', 'ui').setEdge('ui', 'core')
deps.bottomsort() // ['core', 'ui', 'app'] — safe release order
deps.successorChain('app') // ['ui', 'core'] — full transitive chainAPI
| Member | Purpose |
| --- | --- |
| new GraphNamespace({ graph? }) | Empty directed graph, or adopt an existing graphlib one |
| graph | The underlying graphlib graph, for anything not wrapped here |
| nodes / hasNode(n) | Membership |
| setEdge(parent, child) | Add edges; either side may be an array (chainable) |
| successor(n) | The sole successor, or undefined; throws if ambiguous |
| successors(n) / predecessors(n) | Neighbours, always an array |
| sources() / sinks() | Nodes with no in / no out edges |
| isAcyclic() | Cycle check |
| topsort() / bottomsort() | Ordering; throws on a cycle |
| successorChain(n, { root? }) | Walk to the root, inclusive |
setEdge accepts arrays on either side and adds every combination:
ns.setEdge(['a', 'b'], ['c', 'd']) // a→c, a→d, b→c, b→dsuccessorChain infers the root from the graph's sole sink. Pass root
explicitly when the graph has more than one sink.
Bugs fixed during extraction
The JavaScript original had four real defects, each now covered by a test:
- The
graphsetter did nothing.get graphreturned the graph, but the matching setter assigned to an unrelated private field, sons.graph = someGraphsilently no-opped. The setter is gone — pass{ graph }to the constructor. successor()threw aTypeErrorfor an unknown node. graphlib returnsundefinedrather than[], and the original called.lengthon it directly. It now returnsundefined.successorChain()looped forever on a dead end. The loop condition wassuccessor !== sink; oncesuccessorbecameundefinedthat stayed true and it pushedundefinedindefinitely. It now throws, and also detects cycles.successorChain()hardcoded the root as'object'and threw for any hierarchy that did not use that name. The root is now inferred from the graph's sink, or given explicitly.
The lodash-es dependency (used for isArray, head, and isEmpty) is also
gone — those are one-liners in modern JavaScript.
Compatibility
Namespace is still exported as a deprecated alias of GraphNamespace, and
GraphNamespace is also the default export, so both of these keep working:
import GraphNamespace from '@actionforge/graph-ns'
import { Namespace } from '@actionforge/graph-ns'License
MIT © Andrew Cates
