tiny-graph-db
v1.0.4
Published
A tiny, no-external-dependency, disk-based graph database for Node.js with rich set of operations.
Downloads
4
Maintainers
Readme
TinyGraphDB
A tiny, no-external-dependencies, disk-based graph database for Node.js with rich query, traversal, batch ops, batch cosine similarity, and semantic filtering.
- Persist node-&-relation graphs in a JSON file
- Query, traverse, mutate, and semantically search graphs in JavaScript
- Cosine similarity search of nodes & edges via vector embeddings for AI/semantic-graph use cases
- Batch and hierarchical traversals, semantic+traditional queries, and stats
- Full API for CRUD, batch, similarity, statistics, import/export, and traversal
Table of Contents
Features
- ✅ Persistent storage
All nodes & edges auto-saved to a JSON file - 🔍 Search: name, metadata, ID, relation endpoints, and semantic/meta comparison
- 🧮 Cosine Similarity queries for embeddings in metadata (nodes or relations)
- 🔄 Graph Traversal, walk/batch from node, relation, or metadata; supports direction/depth/name filters
- ⬇️ Batch update/delete by search criteria (see below)
- 📈 Stats: node count, edge count, average degree
- 🔄 Import/export: snapshot/restore full graph
- ⚡ Fast, super lightweight, perfect for graph semantic search, retrieval-augmented generation, etc.
Installation
npm install tiny-graph-dbQuick Start
const TinyGraphDB = require('tiny-graph-db');
const db = new TinyGraphDB();
// Add nodes with embeddings
const nodeA = db.addNode('Paper A', { type: 'paper', embedding: [0.2, 0.1, 0.5] });
const nodeC = db.addNode('Concept X', { type: 'concept', embedding: [0.25, 0.1, 0.55] });
const nodeP = db.addNode('Author', { type: 'person', embedding: [0.9, 0.8, 0.7] });
const rel1 = db.addRelation('mentions', nodeA.id, nodeC.id, { confidence: 0.92 });
const rel2 = db.addRelation('authored_by', nodeA.id, nodeP.id, { confidence: 1.0 });
// Node search by metadata
console.log('All concepts:', db.searchNodes({ metadata: { type: 'concept' } }));
// Cosine similarity search
const qv = [0.2, 0.1, 0.52];
const similar = db.searchNodesByCosineSimilarity(qv, { threshold: 0.99 });
console.log('Semantically closest nodes:', similar);
// Traverse outgoing links from nodeA up to depth 2
const walk = db.traverseFromNode(nodeA.id, { maxDepth: 2, directions: ['outgoing'] });
console.log('Traversal:', walk);
// Batch update: update all "concept" nodes
db.updateBySearch('node', { metadata: { type: 'concept' } }, { metadata: { reviewed: true } });
// Batch delete: remove all relations with low confidence
db.deleteBySearch('relation', { metadata: { confidence: { lt: 0.95 } } });
// Save (usually auto, but explicit call)
db.flushToDisk();API
Constructor
new TinyGraphDB(filePath?: string)- filePath: Path to JSON file (default:
'./graph_data.json').
Node Operations
| Method | Description | Returns |
|---------------------------------------------------------------|--------------------------------------------------------|-----------------------|
| addNode(name, metadata = {}, flush = true) | Create node with name/metadata | Node object |
| getNode(nodeId) | Look up node by ID | Node or undefined |
| getAllNodes() | Get all nodes | Node[] |
| updateNode(nodeId, {name?, metadata?}) | Update name/metadata | Updated node |
| deleteNode(nodeId) | Remove node and all its relations | Deleted node object |
| deleteBySearch('node', conditions) | Batch delete by search | Array of removed |
Relation Operations
| Method | Description | Returns |
|---------------------------------------------------------------|--------------------------------------------------------|---------------------------|
| addRelation(name, fromNodeId, toNodeId, metadata = {}, flush = true) | Create edge between nodes | Relation object |
| getRelation(relationId) | Fetch edge by ID | Relation or undefined |
| getAllRelations() | Get all edges | Relation[] |
| updateRelation(relationId, {name?, metadata?}) | Update name/metadata | Updated relation |
| deleteRelation(relationId) | Remove relation | Deleted relation object |
| deleteBySearch('relation', conditions) | Batch delete by search | Array of removed |
Query & Search
searchNodes(conditions: SearchConditions): Node[]
searchRelations(conditions: SearchConditions): Relation[]conditions:
name: string | RegExp |{ contains: string }id,fromNodeId,toNodeIdmetadata:{ [key]: ... }supports:- equality, comparison:
{ eq, ne, gt, gte, lt, lte, contains, startsWith, endsWith, in } - cosine similarity:
{ cosineSimilarity: { queryEmbedding, threshold } }
- equality, comparison:
cosineSimilarity(top-level):{ queryEmbedding, embeddingKey, threshold }
Cosine Similarity Search
searchNodesByCosineSimilarity(queryEmbedding: number[], options?): Array
searchRelationsByCosineSimilarity(queryEmbedding: number[], options?): Array
cosineSimilarity(vecA: number[], vecB: number[]): numberqueryEmbedding: Numeric vector- Options:
embeddingKey: metadata key for vector (default:'embedding')threshold: similarity threshold (default: 0.5)limit: max results (default: 10)
Example
db.searchNodesByCosineSimilarity([0.1, 0.2, 0.3], { threshold: 0.8, limit: 3 });Graph Traversal
| Method | Description | Returns |
|----------------------------------------------------|----------------------------------------------|-----------------------------------|
| traverseFromNode(startNodeId, options) | Walks from a node, following edges (see below) | Array of [fromNode, relation, toNode] |
| traverseFromRelation(startRelationId, maxDepth?) | Starts traversal from a relation | Same as above |
| traverseFromMetadata(metadataConditions, maxDepth?) | Begins traverse from nodes/relations that match metadata | Same as above |
Options for traverseFromNode:
maxDepth: limit depth (Infinityby default)directions:['outgoing','incoming']relationName: (optional) filter by relation name
Example
db.traverseFromNode(nodeId, { maxDepth: 2, directions: ['outgoing'] });Result: Array of [fromNode, relation, toNode] triplets in visit order.
Batch Update / Delete
Update by search
updateBySearch('node' | 'relation', searchConditions, { name?, metadata? }): Array
// Example:
db.updateBySearch('node', { metadata: { genre: 'sci-fi' } }, { name: 'SF Novel' });Delete by search
deleteBySearch('node' | 'relation', searchConditions): Array
// Example:
db.deleteBySearch('relation', { metadata: { confidence: { lt: 0.9 } } });GraphRAG & Hierarchical Traversal
Hybrid search and traversal for retrieval-augmented-graph (RAG) and LLM flows
searchAndTraverse(queryEmbedding, options?): ArraySupports:
- Cosine similarity search + regular filters, for nodes/relations
- For each initial match, traverses up to N hops, directionally (optionally, end traversal on node only)
- Returns rich hierarchical JSON
Options:
embeddingKey,threshold,limit- see cosine similarityhops: Number of hops to traverse (default: 3)nodeFilters,relationFilters: Additional filterssearchNodes,searchRelations: Whether to include nodes, edges, or bothdirections: e.g.,['outgoing', 'incoming']endOnNode: bool (whether to always finish traversal on nodes)
Example:
const tree = db.searchAndTraverse([0.2, 0.1, 0.5], {
hops: 2,
searchNodes: true,
searchRelations: false,
nodeFilters: { metadata: { type: 'paper' } },
});
console.log(tree);
// Output: array of hierarchical trees, each rooted on an initial (semantic) hit, with outgoing/incoming relations, connected nodes/edges & so forthImport / Export
exportData(): { nodes: Node[], relations: Relation[] }
importData(data: { nodes, relations }): voidExport produces the full graph dataset as JSON-serializable data. Import wipes and loads supplied graph, then persists.
Utility
getNeighbors(nodeId): All neighbor nodes, with edge and direction- Returns: Array of
{ node, relation, direction }
- Returns: Array of
getStats():{ nodeCount, relationCount, avgDegree }flushToDisk(): Explicit save to disk (auto after every mutation unless usingflush = falseparam on add)rebuildNodeRelationsIndex(): Internal; rebuilds edge indices (auto-run after import)
Examples
1. Traditional Search
const book1 = db.addNode('Dune', { genre: 'sci-fi', pages: 412, published: 1965 });
const book2 = db.addNode('Foundation', { genre: 'sci-fi', pages: 255, published: 1951 });
const author1 = db.addNode('Frank Herbert', { nationality: 'US' });
// Find all US authors:
db.searchNodes({ metadata: { nationality: 'US' } });
// Find all books published pre-1960:
db.searchNodes({ metadata: { published: { lt: 1960 } } });2. Cosine Similarity Search
const doc = db.addNode('Graph Vector', { embedding: [0.2, 0.4, 0.6] });
// Find similar to [0.2, 0.41, 0.67]:
db.searchNodesByCosineSimilarity([0.2, 0.41, 0.67], { threshold: 0.95 });3. Traversals
// Walk two hops out from a node
const walk = db.traverseFromNode(doc.id, { maxDepth: 2, directions: ['outgoing'] });
// Start traversal from a relation
const traverseRels = db.traverseFromRelation(rel1.id, 3);
// Traverse from all nodes with type "paper":
db.traverseFromMetadata({ type: 'paper' }, 2);4. Batch Update & Delete
// Tag all "concept" nodes as reviewed
db.updateBySearch('node', { metadata: { type: 'concept' } }, { metadata: { reviewed: true } });
// Delete all weak relations
db.deleteBySearch('relation', { metadata: { confidence: { lt: 0.8 } } });5. Hybrid "search and traverse" (GraphRAG pattern)
// Retrieve node (by semantic match) then its 2-hop subgraph
const rag = db.searchAndTraverse([0.25, 0.1, 0.5], { hops: 2 });
console.log(JSON.stringify(rag, null, 2));6. Utilities
console.log('Stats:', db.getStats());
console.log('Neighbors of nodeA:', db.getNeighbors(nodeA.id));
// Export/import
const json = db.exportData();
db.importData(json);Performance Benchmarks
| Function | Time (ms) | Ops/sec | |----------------------------------|-----------|------------| | getNode() | 0.0001 | 8,473,743 | | traverseFromNode() | 0.0072 | 138,175 | | searchNodes() | 0.1728 | 5,787 | | searchNodesByCosineSimilarity() | 0.3456 | 2,893 |
Run benchmarks: node src/benchmark.js 1000 2000 5 or npm run benchmark -- 1000 2000 5
Contributing
- Fork the repo
- Create a branch:
git checkout -b feat/my-feature - Commit & push, then open a PR
Please file bugs/requests using GitHub Issues.
License
MIT License (see LICENSE)
Built with ♥ by freakynit
