npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@statelyai/graph

v0.12.0

Published

A TypeScript-first graph library with plain JSON-serializable objects

Downloads

2,925

Readme

@statelyai/graph

A TypeScript graph library built on plain JSON objects. Supports directed/undirected graphs, hierarchical nodes, graph algorithms, visual properties, and serialization to DOT, GraphML, Mermaid, and more.

Made from our experience at stately.ai, where we build visual tools for complex systems.

Install

npm install @statelyai/graph

Optional peers are only needed for specific adapters:

| Package | Needed for | | ----------------- | --------------------------------------------------- | | fast-xml-parser | @statelyai/graph/gexf, @statelyai/graph/graphml | | dotparser | @statelyai/graph/dot parsing | | cytoscape | Cytoscape integration tests and consumer typing | | d3-force | D3 force integration tests and consumer typing | | elkjs | @statelyai/graph/elk | | zod | @statelyai/graph/schemas |

Highlights

  • Plain JSON graphs with no runtime wrappers required
  • Standalone functions with a consistent get*/gen*/is*/add* naming model
  • Directed, undirected, hierarchical, and visual graph support
  • Ports for node-editor and dataflow-style graphs
  • Algorithms for traversal, paths, centrality, communities, connectivity, isomorphism, ordering, MST, and walks
  • Diff/patch utilities for graph state changes
  • Multi-format conversion via package subpaths
  • Small, fast test suite with broad format coverage

Quick Start

Graphs are plain JSON-serializable objects. All operations are standalone functions — no classes, no DOM, no rendering engine.

import {
  createGraph,
  addNode,
  addEdge,
  getShortestPath,
} from '@statelyai/graph';

const graph = createGraph({
  nodes: [
    { id: 'a', label: 'Start' },
    { id: 'b', label: 'Middle' },
    { id: 'c', label: 'End' },
  ],
  edges: [
    { id: 'e1', sourceId: 'a', targetId: 'b' },
    { id: 'e2', sourceId: 'b', targetId: 'c' },
  ],
});

// Mutate in place
addNode(graph, { id: 'd', label: 'Shortcut' });
addEdge(graph, { id: 'e3', sourceId: 'a', targetId: 'd' });

// Algorithms work on the plain object
const path = getShortestPath(graph, { from: 'a', to: 'c' });

Graph Manipulation

Look up, add, delete, and update nodes and edges. Query neighbors, predecessors, successors, degree, and more.

import {
  getNode,
  deleteNode,
  getNeighbors,
  getSources,
} from '@statelyai/graph';

const node = getNode(graph, 'a'); // lookup by id
deleteNode(graph, 'd'); // removes node + connected edges
const neighbors = getNeighbors(graph, 'a'); // adjacent nodes
const roots = getSources(graph); // nodes with no incoming edges

Batch operations (addEntities, deleteEntities, updateEntities) let you apply multiple changes at once.

Hierarchy

Nodes support parent-child relationships for compound/nested graphs. Query children, ancestors, descendants, depth, and least common ancestor. Use flatten() to decompose into a flat leaf-node graph.

import { createGraph, getChildren, getLCA, flatten } from '@statelyai/graph';

const graph = createGraph({
  nodes: [
    { id: 'a' },
    { id: 'b', initialNodeId: 'b1' },
    { id: 'b1', parentId: 'b' },
    { id: 'b2', parentId: 'b' },
    { id: 'c' },
  ],
  edges: [
    { id: 'e1', sourceId: 'a', targetId: 'b' }, // resolves to a -> b1
    { id: 'e2', sourceId: 'b1', targetId: 'b2' },
    { id: 'e3', sourceId: 'b', targetId: 'c' }, // expands from all leaves of b
  ],
});

const children = getChildren(graph, 'b'); // [b1, b2]
const flat = flatten(graph); // only leaf nodes, edges resolved

Ports

Ports are optional named connection points on nodes. They are useful for flow-based systems, node editors, and dataflow graphs where edges need to target a specific input or output.

import { createGraph, getEdgesByPort, getPorts } from '@statelyai/graph';

const graph = createGraph({
  nodes: [
    {
      id: 'fetch',
      ports: [{ name: 'result', direction: 'out' }],
    },
    {
      id: 'render',
      ports: [{ name: 'input', direction: 'in' }],
    },
  ],
  edges: [
    {
      id: 'e1',
      sourceId: 'fetch',
      sourcePort: 'result',
      targetId: 'render',
      targetPort: 'input',
    },
  ],
});

getPorts(graph, 'fetch'); // [{ name: 'result', ... }]
getEdgesByPort(graph, 'render', 'input'); // [e1]

Schema Validation

Use the @statelyai/graph/schemas subpath when you want runtime validation or JSON Schema generation.

import { GraphSchema, getGraphIssues, isGraph } from '@statelyai/graph/schemas';

const unknownValue: unknown = JSON.parse(input);

if (isGraph(unknownValue)) {
  // fully typed Graph
} else {
  console.error(getGraphIssues(unknownValue));
}

const parsed = GraphSchema.parse(unknownValue);

Algorithms

Includes traversal (BFS, DFS, preorder/postorder), pathfinding (shortest path, simple paths, all-pairs shortest paths, A*), centrality/link analysis (degree, closeness, betweenness, PageRank, HITS, eigenvector), community detection (label propagation, Girvan-Newman, greedy modularity, modularity scoring), cycle detection, connected/strongly-connected components, bridges, articulation points, biconnected components, isomorphism, topological sort, minimum spanning tree, and more. Many algorithms have lazy generator variants (gen*) for early exit.

import {
  bfs,
  dfs,
  hasPath,
  isAcyclic,
  getShortestPath,
  getCycles,
  getTopologicalSort,
  getConnectedComponents,
  getMinimumSpanningTree,
  getPageRank,
  getLabelPropagationCommunities,
  genGirvanNewmanCommunities,
  getBridges,
  isIsomorphic,
} from '@statelyai/graph';

for (const node of bfs(graph, 'a')) {
  /* breadth-first */
}
for (const node of dfs(graph, 'a')) {
  /* depth-first */
}

hasPath(graph, 'a', 'c'); // reachability
isAcyclic(graph); // cycle check
getShortestPath(graph, { from: 'a', to: 'c' }); // single shortest path
getTopologicalSort(graph); // topological order (or null)
getConnectedComponents(graph); // connected components
getMinimumSpanningTree(graph, { weight: (e) => e.data?.weight ?? 1 }); // MST
getPageRank(graph); // link analysis scores
getLabelPropagationCommunities(graph); // community detection
[...genGirvanNewmanCommunities(graph)]; // lazy community splits
getBridges(graph); // bridge edges
isIsomorphic(graph, otherGraph); // structural equivalence

Diff & Walks

Beyond classic graph algorithms, the library also includes utilities for evolving and exploring graph state:

  • getDiff(), getPatches(), applyPatches() for graph change tracking
  • genRandomWalk(), genWeightedRandomWalk(), and coverage helpers for model-based testing and simulation
  • getSubgraph() and reverseGraph() for structural transforms

Visual Graphs

createVisualGraph() guarantees x, y, width, height on all nodes and edges (default 0).

import { createVisualGraph } from '@statelyai/graph';

const diagram = createVisualGraph({
  direction: 'right',
  nodes: [
    { id: 'a', x: 0, y: 0, width: 120, height: 60, shape: 'rectangle' },
    { id: 'b', x: 200, y: 0, width: 120, height: 60, shape: 'ellipse' },
  ],
  edges: [{ id: 'e1', sourceId: 'a', targetId: 'b', width: 100, height: 100 }],
});

Format Conversion

Import and export graphs to many formats. Converters are available as subpath imports.

import { toDOT } from '@statelyai/graph/dot';
import { fromGEXF } from '@statelyai/graph/gexf';
import { toCytoscapeJSON } from '@statelyai/graph/cytoscape';
import { toD3Graph } from '@statelyai/graph/d3';

const dot = toDOT(graph); // Graphviz DOT
const cytoData = toCytoscapeJSON(graph); // Cytoscape.js JSON
const d3Data = toD3Graph(graph); // D3.js { nodes, links }
const imported = fromGEXF(gexfXmlString); // GEXF (Gephi)

Supported formats: Cytoscape.js JSON, D3.js JSON, JSON Graph Format, GEXF, GraphML, GML, TGF, DOT, Mermaid (flowchart, state, sequence, class, ER, mindmap, block, Ishikawa), ELK, xyflow, adjacency list, and edge list.

Each bidirectional format also has a converter object:

import { cytoscapeConverter } from '@statelyai/graph/cytoscape';

const cyto = cytoscapeConverter.to(graph);
const back = cytoscapeConverter.from(cyto);

Format Support

| Format | Hierarchy | Ports | Visual | Round-trip | Notes | | ------------------- | --------- | ------- | ------- | ---------- | -------------------------------------------------------------------------- | | adjacency-list | none | none | none | partial | Connectivity only; edge metadata is lost. | | cytoscape | full | full | partial | partial | Ports round-trip through element data. | | d3 | none | full | partial | partial | Ports round-trip through node/link objects. | | dot | partial | partial | partial | partial | Edge port ids round-trip, but :port:compass mapping is still incomplete. | | edge-list | none | none | none | partial | Endpoints only. | | elk | full | full | full | partial | Best for layout exchange. | | gexf | full | full | partial | partial | Ports round-trip via custom attributes. | | gml | full | full | partial | partial | Ports round-trip through JSON-stringified metadata. | | graphml | full | full | partial | partial | Ports round-trip through <data> fields. | | jgf | full | full | none | partial | Ports round-trip through metadata. | | tgf | none | none | none | partial | Minimal ids and labels only. | | xyflow | none | full | full | partial | Ports map directly to handles. | | mermaid/block | partial | none | partial | partial | Syntax-driven, not port-aware. | | mermaid/class | none | none | none | partial | Class syntax is stored conservatively. | | mermaid/er | none | none | none | partial | Focuses on entities and cardinality. | | mermaid/flowchart | partial | none | partial | partial | linkStyle indices are fragile. | | mermaid/ishikawa | full | none | none | partial | Preserves hierarchy, not fishbone layout. | | mermaid/mindmap | full | none | partial | partial | Icon syntax is not fully re-emitted. | | mermaid/sequence | partial | none | none | partial | Actor links and menu syntax are incomplete. | | mermaid/state | full | none | partial | partial | State notes are still lossy. |

Some formats have optional peer dependencies: fast-xml-parser (GEXF, GraphML) and dotparser (DOT). All other formats are dependency-free.

Format-specific docs live alongside the source:

Examples

The repo includes runnable examples under examples/:

  • Flow-based math shows ports, topological ordering, and value propagation.
  • Async workflow models an n8n/Zapier-style workflow with ports and dependency-aware execution.

Development

pnpm install
pnpm verify

See CONTRIBUTING.md for contributor conventions, format-module checklist, and release notes guidance.

Why this library?

Graph file formats define how to store graphs. Visualization libraries define how to render them. This library is the computational layer in between: plain JSON objects in, algorithms and mutations, plain JSON objects out.

GEXF file → fromGEXF() → Graph → run algorithms, mutate → toCytoscapeJSON() → render

Your Graph is a plain object that survives JSON.stringify, structuredClone, postMessage, and localStorage without adapters.

License

MIT