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

simple-graphdb

v4.0.0

Published

A lightweight graph database for TypeScript with a pluggable storage provider architecture, support for directed/undirected graphs, BFS/DFS traversal, topological sort, DAG detection, and Mermaid diagram export.

Downloads

990

Readme

simple-graphdb

A lightweight graph database for TypeScript with a pluggable storage provider architecture, support for directed/undirected graphs, BFS/DFS traversal, topological sort, DAG detection, and Mermaid diagram export.

Installation

npm install simple-graphdb

Quick Start

import { Graph } from 'simple-graphdb';

// Create a new graph
const graph = new Graph();

// Add nodes with type and properties
const pythonCourse = graph.addNode('Course', { name: 'Python', duration: 40 });
const chapter1 = graph.addNode('Chapter', { name: 'Basics', order: 1 });
const author = graph.addNode('Author', { name: 'John Doe' });

// Add directed edges with relationship types
graph.addEdge(pythonCourse.id, chapter1.id, 'CONTAINS');
graph.addEdge(author.id, pythonCourse.id, 'AUTHOR_OF');

// Navigate the graph
const chapters = graph.getChildren(pythonCourse.id);  // [chapter1]
const courses = graph.getParents(author.id);         // [pythonCourse]

// Find nodes by type
const courses = graph.getNodesByType('Course');

// Find path between nodes
const paths = graph.traverse(pythonCourse.id, chapter1.id, { method: 'bfs' }); // [[courseId, chapterId]]

// Type-filtered traversal
const personPaths = graph.traverse(author.id, pythonCourse.id, {
  nodeTypes: ['Author'],
  edgeTypes: ['AUTHOR_OF']
});

// Wildcard traversal - find all authors of a course
const authors = graph.traverse('*', pythonCourse.id, {
  edgeTypes: ['AUTHOR_OF']
}); // [[authorId1, courseId], [authorId2, courseId]]

// Find all reachable nodes from a source
const allPaths = graph.traverse(pythonCourse.id, '*'); // [[courseId, child1], [courseId, child2], ...]

// Check if graph is a DAG
graph.isDAG(); // true

API Reference

Graph Class

Node Operations

| Method | Description | |--------|-------------| | addNode(type: string, properties?: Record<string, unknown>): Node | Add a new node with type label | | removeNode(id: string, cascade?: boolean): boolean | Remove node; cascade removes incident edges | | getNode(id: string): Node \| undefined | Get node by id | | hasNode(id: string): boolean | Check if node exists | | getNodes(): readonly Node[] | Get all nodes | | getNodesByType(type: string): Node[] | Get all nodes of a given type | | getNodesByProperty(key: string, value: unknown, options?: { nodeType?: string }): Node[] | Get nodes by property value, optionally filtered by node type |

Edge Operations

| Method | Description | |--------|-------------| | addEdge(sourceId: string, targetId: string, type: string, properties?: Record<string, unknown>): Edge | Add a directed edge with relationship type | | removeEdge(id: string): boolean | Remove edge by id | | getEdge(id: string): Edge \| undefined | Get edge by id | | hasEdge(id: string): boolean | Check if edge exists | | getEdges(): readonly Edge[] | Get all edges | | getEdgesByType(type: string): Edge[] | Get all edges of a given relationship type |

Navigation

| Method | Description | |--------|-------------| | getParents(nodeId: string, options?: { nodeType?: string; edgeType?: string }): Node[] | Get parent nodes with optional type filters | | getChildren(nodeId: string, options?: { nodeType?: string; edgeType?: string }): Node[] | Get child nodes with optional type filters | | getEdgesFrom(sourceId: string, options?: { edgeType?: string }): Edge[] | Get outgoing edges with optional type filter | | getEdgesTo(targetId: string, options?: { edgeType?: string }): Edge[] | Get incoming edges with optional type filter | | getDirectEdgesBetween(sourceId: string, targetId: string, options?: { edgeType?: string }): Edge[] | Get direct edges between two nodes |

Traversal & Analysis

| Method | Description | |--------|-------------| | traverse(sourceId: string \| string[], targetId: string \| string[], options?: TraversalOptions): string[][] \| null | Find path(s) between the given source and target node(s), using the selected method and optional nodeTypes and edgeTypes filters | | isDAG(): boolean | Check if graph is a Directed Acyclic Graph | | topologicalSort(): string[] \| null | Get nodes in topological order (DAGs only); returns null if cycles exist |

TraversalOptions Interface

interface TraversalOptions {
  method?: 'bfs' | 'dfs';           // default: 'bfs'
  nodeTypes?: string[];              // filter by node types (['*'] = all, default)
  edgeTypes?: string[];              // filter by edge types (['*'] = all, default)
  maxResults?: number;              // limit number of paths returned (default: 100)
}

Wildcard Support

The traverse() method supports wildcards for flexible path finding:

// Find a path to a specific target from each matching source
graph.traverse('*', targetId);

// Find a path from a specific source to each reachable target
graph.traverse(sourceId, '*');

// Find a representative path for each matching source/target pair in the graph
graph.traverse('*', '*');

// Find a path for each matching source/target combination
graph.traverse(['id1', 'id2'], ['id3', 'id4']);

Serialization & Admin

| Method | Description | |--------|-------------| | exportJSON(): GraphData | Serialize graph to a JSON-compatible object | | static importJSON(data: GraphData, storageProvider?: IStorageProvider): Graph | Reconstruct a graph from data with optional custom storage provider | | clear(): void | Remove all nodes and edges | | toJSON(): GraphData | ⚠️ Deprecated — use exportJSON() | | static fromJSON(data: GraphData): Graph | ⚠️ Deprecated — use importJSON() |

GraphToMermaid - Mermaid Diagram Generation

Convert your graph to Mermaid flowchart syntax for visualization:

import { Graph, GraphToMermaid } from 'simple-graphdb';

// Create a simple social graph
const graph = new Graph();
const alice = graph.addNode('Person', { name: 'Alice' });
const bob = graph.addNode('Person', { name: 'Bob' });
const carol = graph.addNode('Person', { name: 'Carol' });

graph.addEdge(alice.id, bob.id, 'KNOWS');
graph.addEdge(bob.id, carol.id, 'KNOWS');
graph.addEdge(alice.id, carol.id, 'FRIEND_OF');

// Generate Mermaid diagram
const mermaid = new GraphToMermaid(graph);
console.log(mermaid.toString());

Output:

flowchart TD
    abc123["Person | abc123"]
    def456["Person | def456"]
    ghi789["Person | ghi789"]
    abc123 -->|"KNOWS"| def456
    def456 -->|"KNOWS"| ghi789
    abc123 -->|"FRIEND_OF"| ghi789

GraphToMermaid Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | showProperties | boolean | false | Include node properties in labels | | includeEdgeLabels | boolean | true | Show edge types on arrows | | direction | 'TD' \\| 'LR' | 'TD' | Flowchart direction (top-down or left-right) |

// With custom options
const mermaid = new GraphToMermaid(graph, {
  showProperties: true,
  includeEdgeLabels: true,
  direction: 'LR'  // left-to-right layout
});

You can also create GraphToMermaid from JSON data:

const jsonData = graph.toJSON();
const mermaid = new GraphToMermaid(JSON.stringify(jsonData));

Node Class

const node = graph.addNode('Course', { name: 'Python', duration: 40 });

node.id;         // 'uuid-xxxx-xxxx'
node.type;       // 'Course'
node.properties; // { name: 'Python', duration: 40 }
node.toJSON();   // { id: '...', type: 'Course', properties: { name: 'Python', duration: 40 } }

Edge Class

const edge = graph.addEdge(sourceId, targetId, 'CONTAINS', { order: 1 });

edge.id;         // 'uuid-xxxx-xxxx'
edge.sourceId;   // 'source-node-id'
edge.targetId;   // 'target-node-id'
edge.type;       // 'CONTAINS'
edge.properties;  // { order: 1 }
edge.toJSON();   // { id: '...', sourceId: '...', targetId: '...', type: 'CONTAINS', properties: { order: 1 } }

Error Handling

import { Graph, NodeAlreadyExistsError, NodeNotFoundError, EdgeAlreadyExistsError, NodeHasEdgesError } from 'simple-graphdb';

const graph = new Graph();
const node = graph.addNode('Course', { name: 'Python' });

try {
  // Node IDs are auto-generated, so duplicates come from JSON
  const data = { nodes: [{ id: 'same-id', type: 'A', properties: {} }, { id: 'same-id', type: 'B', properties: {} }], edges: [] };
  Graph.fromJSON(data); // Throws NodeAlreadyExistsError
} catch (e) {
  if (e instanceof NodeAlreadyExistsError) {
    console.log(e.message); // "Node with id 'same-id' already exists"
  }
}

try {
  // Missing node reference
  const data = { nodes: [{ id: 'node1', type: 'Test', properties: {} }], edges: [{ id: 'e1', sourceId: 'non-existent', targetId: 'node1', type: 'LINKS', properties: {} }] };
  Graph.fromJSON(data); // Throws NodeNotFoundError
} catch (e) {
  if (e instanceof NodeNotFoundError) {
    console.log(e.message); // "Node with id 'non-existent' not found"
  }
}
const alice = graph.addNode('Person', { name: 'Alice' });
const bob = graph.addNode('Person', { name: 'Bob' });
graph.addEdge(alice.id, bob.id, 'KNOWS');

try {
  graph.removeNode(alice.id); // Throws NodeHasEdgesError — node still has incident edges
} catch (e) {
  if (e instanceof NodeHasEdgesError) {
    console.log(e.message); // "Cannot remove node '...': it still has 1 incident edge(s). Use cascade=true to also remove them."
  }
}

// To remove with all incident edges:
graph.removeNode(alice.id, true); // cascade removes KNOWS edge too

Available errors:

  • NodeAlreadyExistsError
  • EdgeAlreadyExistsError
  • NodeNotFoundError
  • EdgeNotFoundError
  • NodeHasEdgesError — thrown by removeNode(id) when the node has incident edges and cascade is not true

Serialization & Persistence

import { Graph } from 'simple-graphdb';
import fs from 'fs';

const graph = new Graph();
// ... build your graph ...

// Export to JSON
const data = graph.exportJSON();
fs.writeFileSync('graph.json', JSON.stringify(data, null, 2));

// Import from JSON
const raw = JSON.parse(fs.readFileSync('graph.json', 'utf-8'));
const restored = Graph.importJSON(raw);

Migration from v3: toJSON() and fromJSON() are still available but deprecated. Rename calls to exportJSON() and importJSON() respectively.

Cascade Delete

By default, removing a node does not remove incident edges. Use cascade: true to remove them:

const a = graph.addNode('Person', { name: 'A' });
const b = graph.addNode('Person', { name: 'B' });
const edgeId = graph.addEdge(a.id, b.id, 'KNOWS').id;

// Without cascade - edge remains
graph.removeNode(a.id);
graph.hasEdge(edgeId); // true

// With cascade - edge is also removed
graph.removeNode(b.id, true);
graph.hasEdge(edgeId); // false

Development

# Install dependencies
npm install

# Build TypeScript
npm run build

# Run tests
npm test

Testing

The test suite includes:

  • tests/graph/ - Graph core functionality tests (139 tests, organized by feature)
    • Graph.node.test.ts - Node Operations (15 tests)
    • Graph.edge.test.ts - Edge Operations (15 tests)
    • Graph.traverse.test.ts - BFS/DFS traversal (20 tests)
    • Graph.serialization.test.ts - Serialization round-trip (5 tests)
    • Graph.isDAG.test.ts - Cycle detection (10 tests)
    • Graph.topologicalSort.test.ts - Topological ordering (10 tests)
    • Graph.fromJSON.test.ts - JSON validation (6 tests)
    • Graph.clear.test.ts - Graph clearing (1 test)
  • tests/SocialGraph.test.ts - Facebook-style social graph with People, Posts, Photos, Comments (75 tests)
  • tests/EducationGraph.test.ts - Education domain graph tests (37 tests)
  • tests/data/education-graph.json - Education domain graph with Courses, Chapters, Sections, Exams, Tests, Authors, Publishers, and Tags

License

MIT