DEPRECATED: The basic graph structure is set up with tests. Feel free to use. Cycles likely don't work, but it may be possible with a custom subdocument implementation. I decided to take a different approach in my own project.
An offline-first, lazy-loaded, reactive graph type using yjs subdocuments.
- Two-way edges
- Cycles
- Provider support
npm install y-lazy-graph
import lazyGraph from 'y-lazy-graph'
import * as Y from 'yjs'
const Node = lazyGraph({ Y })
// create a doubly linked list
const a = new Node({ data: 'a' })
const b = new Node({ data: 'b' })
const c = new Node({ data: 'c' })
a.add(b)
b.add(c)
// Outputs a JSON representation of a ⟷ b ⟷ c
console.log(a.toJSON())
Use .get()
to get an array of all linked nodes.
Because links are 2-way and symmetric by default, b.get()
returns both a and c:
// returns all linked nodes
const linkedNodes = b.get()
// Outputs [{ data: 'a' }, { data: 'c' }]
console.log(linkedNodes.map(node => node.toJSON()))
Use .add$()
to add an assymetric link.
import lazyGraph from 'y-lazy-graph'
import * as Y from 'yjs'
// tell lazyGraph that child nodes are inverse of parent nodes
const Node = lazyGraph({ Y, inversePairs: [['child', 'parent']] })
const a = new Node({ data: 'a' })
const b = new Node({ data: 'b' })
const c = new Node({ data: 'c' })
// add b as a child of a
a.add$(b, 'child')
// add c as a child of b
b.add$(c, 'child')
const children = b.get('child')
const parents = b.get('parent') // thank you inversePairs ♡
console.log(
'children',
children.map(node => node.toJSON()),
)
console.log(
'parents',
parents.map(node => node.toJSON()),
)
/*
children
[
{ data: 'c' },
]
parents
[
{ data: 'a' },
]
*/
The default export is a function that returns the Node constructor. Pass your instance of Y.
If someone knows how to import Y without getting the "Yjs was already imported" error, then I can get rid of this wrapper.
See: yjs/yjs#438
import lazyGraph from 'y-lazy-graph'
import * as Y from 'yjs'
const Node = lazyGraph({ Y })
A nestable graph node that contains a Y.Doc for syncing across clients.
constructor(data: T | Y.Doc, options: { id?: string })
data: T | Doc
- Your custom data that is attached to the node. You can also pass the Y.Doc of another Node to clone it, i.e. create a new Node instance that wraps the Doc without creating any additional data.options.id: string
- Set the id. Defaults to a new, globally unique identifier (nanoid).
Example:
const a = new Node('a')
Links two nodes together with an edge.
add(node: Node, type?: string): void
static add(a: Node, b: Node, type?: string): void
node: Node
- The node to link with the instance.type?: string
- A custom type that can be assigned to a link. Allows symmetric, asymmetric, and invertible relations.
Example:
const a = new Node('a')
const b = new Node('b')
a.add(b)
NOT IMPLEMENTED: deep
Deletes a node.
delete({ deep?: boolean }): void
Gets all linked Nodes.
get(type?: string): void
type?: string | null
- Gets linked nodes (O(1)).- If no type is given, returns untyped nodes (default).
- If a type is given, returns only linked nodes of the given type (still O(1)).
- If null is given, returns all nodes regardless of type. It is recommended to only pass null if you have mixed node types.
Example:
const a = new Node('a')
const b = new Node('b')
const c = new Node('c')
a.add(b)
a.add(c)
const linkedNodes = a.get()
/*
[
{ id: B_ID, data: 'b' },
{ id: C_ID, data: 'c' },
]
*/