grad-school
npm install grad-school

grad-school is a tool for creating ad-hoc graphs, in a scripting language, and then querying them.
it's surprising how there's no super-clear way to author graph-data, or even think about.
It always hurts my head. Even simple graphs do.
Maybe i have a head-problem, or maybe JSON is just an awkward way to represent graphs.
either way...
library is like, 3kb.

Ok, graphs -
we support 3 formats:
JSON input is checked for cycles when parsed. A cycle throws an error;
direct edits to .children after parsing are not checked.
String-format
this is a pretty-flexible way to declare a graph, using indents and -> syntax. It's harder to add metadata to nodes.
It's a bit like how graphviz does it:
Common leading indentation is ignored, so template strings can be indented with your code.
let str = `
a -> a2 -> a21
-> a1
b
`
let g = grad(str).debug()
g.nodes().length // 5
flat json:
this flat, parent-indexed json is easy to make, but the graph is harder to 'see':
let nodes = [
{ id: 'a', parent: null },
{ id: 'b', parent: null },
{ id: 'a1', parent: 'a' },
{ id: 'a2', parent: 'a' },
{ id: 'a21', parent: 'a2' },
]
let g = grad(nodes).debug()
/*
→ a
→ a1
→ a2
→ a21
→ b
*/
nested json:
this is how d3 does it:
let nodes = {
children: [
{
id: 'a',
children: [{ id: 'a1' }, { id: 'a2', children: [{ id: 'a21' }] }],
},
{ id: 'b' },
],
}
let g = grad(nodes).debug()
/*
→ a
→ a1
→ a2
→ a21
→ b
*/

JS API
Core functions
For array input and property inheritance without the full chainable API, use
the smaller grad-school/core entry point:
import { fromArray, cache, fillDown, toArray } from 'grad-school/core'
const graph = fromArray([
{ id: 'noun', props: { isNoun: true } },
{ id: 'person', parent: 'noun', props: { isPerson: true } }
])
cache(graph)
fillDown(graph)
const rows = toArray(graph)
// person.props is now { isPerson: true, isNoun: true }
CommonJS is also supported:
const { fromArray, cache, fillDown, toArray } = require('grad-school/core')
fromArray(rows) returns a nested graph with a synthetic root. cache(graph)
populates ancestor and descendant IDs in _cache; call it before toArray(graph)
and again after changing the graph structure. fillDown(graph) inherits parent
properties. Both cache and fillDown mutate the graph and return undefined;
they are not chainable. Call fillDown once, since repeated calls concatenate
inherited arrays again. toArray(graph) returns flat rows with parent IDs,
retaining props and _cache.
The existing import grad from 'grad-school' API is unchanged.
Chainable API
you can also easily mess-around with the graph:
let g = grad('a -> a1')
// add new nodes
g.add('b').add(['b1', 'b2'])
g.add('c')
// remove a child through its parent
g.get('b').remove('b1')
console.log(g.get('b').children)
console.log(g.out())
Fill-down
fillDown() mutates properties in place. Call it once after setting up your properties;
calling it again concatenates inherited arrays again.
Arrays, sets, and objects merge with values of the same kind. When kinds differ,
the child's explicit value wins, including false, 0, an empty string, or null.
you can 'deduce', down the tree, and intellegently merge the properties of each node:
let str = `
a
b -> b1
c -> c1 -> c2
`
let g = grad(str)
// add some data to one node
g.get('c').props({ inC: true })
g.fillDown()
// check a c-leaf
g.get('c/c1/c2').json.props
// { inC: true }
g.get('b').json.props
// {}
it will concat arrays, merge objects and sets:
let str = `
a -> a1 -> a11
b -> b1
`
let g = grad(str)
// add data to root
g.props({ list: ['fromRoot'] })
// add data to child
g.get('a').props({ list: ['fromA'] })
g.fillDown()
// b-side has root-data
let have = g.get('b/b1').json.props
// { list: ['fromRoot'] }
// a-side has merged arrays
let have = g.get('a/a1/a11').json.props
// { list: ['fromRoot','fromA'] }
MIT