Overview
GenosDB provides a simple yet powerful API for managing graph data. The core CRUD operations are:
put(value, id?) - Create or update nodes
get(id, callback?) - Read node data
link(sourceId, targetId) - Create relationships
remove(id) - Delete nodes
clear() - Remove all data
All CRUD operations are asynchronous and return Promises. Use await or .then() to handle results.
Creating and Updating Data: put
The put method inserts new nodes or updates existing ones.
Basic Usage
import { gdb } from 'genosdb'
const db = await gdb ( 'my-app' )
// Create a new node (auto-generated ID)
const userId = await db . put ({
type: 'user' ,
name: 'Alice' ,
email: 'alice@example.com'
})
console . log ( 'Created user:' , userId )
// Output: Created user: a3f9k2...
Custom IDs
You can specify your own node IDs for easier reference:
// Create with custom ID
await db . put (
{ type: 'user' , name: 'Bob' },
'user:bob' // Custom ID
)
// Later, reference by ID
const { result } = await db . get ( 'user:bob' )
console . log ( result . value . name ) // 'Bob'
Use meaningful IDs (e.g., user:alice, post:123) instead of auto-generated hashes when you need to reference nodes directly.
Updating Nodes
// Initial creation
const id = await db . put ({ count: 0 }, 'counter' )
// Update the same node
await db . put ({ count: 1 }, 'counter' )
await db . put ({ count: 2 }, 'counter' )
// Final value
const { result } = await db . get ( 'counter' )
console . log ( result . value . count ) // 2
When you put with an existing ID, the entire value is replaced , not merged. To update specific fields, read the current value first.
Partial Updates
// Read current value
const { result : current } = await db . get ( 'user1' )
// Merge with new data
await db . put (
{ ... current . value , age: 31 }, // Keep existing fields, update age
'user1'
)
Real-Time Sync
With P2P enabled, put operations sync automatically:
const db = await gdb ( 'shared-app' , { rtc: true })
// Changes sync to all connected peers instantly
await db . put ({ status: 'online' }, 'user:alice' )
Reading Data: get
The get method retrieves node data by ID.
Static Read
// Get a node by ID
const { result } = await db . get ( 'user:alice' )
if ( result ) {
console . log ( 'User:' , result . value )
console . log ( 'ID:' , result . id )
console . log ( 'Edges:' , result . edges )
console . log ( 'Timestamp:' , result . timestamp )
} else {
console . log ( 'Node not found' )
}
Reactive Read
Subscribe to live updates for a specific node:
const { result , unsubscribe } = await db . get (
'user:alice' ,
( node ) => {
if ( node ) {
console . log ( 'User updated:' , node . value )
} else {
console . log ( 'User deleted' )
}
}
)
// Initial value is also available
console . log ( 'Initial:' , result )
// Later: stop listening
unsubscribe ()
Reactive get is perfect for UI components that need to stay synchronized with a single node.
Node Structure
Every node has the following structure:
{
id : 'user:alice' , // Node identifier
value : { // Your data
type : 'user' ,
name : 'Alice' ,
email : 'alice@example.com'
},
edges : [ 'group:1' , 'post:5' ], // Outgoing links
timestamp : { // Hybrid Logical Clock
physical : 1709587200000 ,
logical : 3
}
}
Creating Relationships: link
The link method creates directed edges between nodes.
Basic Linking
// Create nodes
const userId = await db . put ({ name: 'Alice' }, 'user:alice' )
const groupId = await db . put ({ name: 'Developers' }, 'group:devs' )
// Create a link: user -> group
await db . link ( 'user:alice' , 'group:devs' )
// Verify the link
const { result : user } = await db . get ( 'user:alice' )
console . log ( user . edges ) // ['group:devs']
Multiple Links
// A user can link to multiple groups
await db . link ( 'user:alice' , 'group:devs' )
await db . link ( 'user:alice' , 'group:admins' )
await db . link ( 'user:alice' , 'group:moderators' )
const { result : user } = await db . get ( 'user:alice' )
console . log ( user . edges ) // ['group:devs', 'group:admins', 'group:moderators']
Building Graph Structures
// Create a file/folder hierarchy
await db . put ({ type: 'folder' , name: 'Documents' }, 'folder:docs' )
await db . put ({ type: 'folder' , name: 'Work' }, 'folder:work' )
await db . put ({ type: 'file' , name: 'report.pdf' }, 'file:report' )
// Link folders and files
await db . link ( 'folder:docs' , 'folder:work' ) // docs -> work
await db . link ( 'folder:work' , 'file:report' ) // work -> report
// Query the graph (see graph traversal guide)
const { results } = await db . map ({
query: {
type: 'folder' ,
name: 'Documents' ,
$edge: { type: 'file' } // Find all files under Documents
}
})
console . log ( results ) // [{ id: 'file:report', value: { ... } }]
Use link to model relationships like:
User memberships (user → group)
Document hierarchies (folder → file)
Social connections (user → friend)
Content categorization (post → tag)
Deleting Data: remove
The remove method deletes a node and all references to it.
Basic Deletion
// Create a node
const id = await db . put ({ temp: true }, 'temp:data' )
// Delete it
await db . remove ( 'temp:data' )
// Verify deletion
const { result } = await db . get ( 'temp:data' )
console . log ( result ) // null
Cascade Behavior
When you remove a node:
The node itself is deleted
All incoming links (edges from other nodes to this node) are removed
The node’s outgoing links (in its edges array) are cleared
// Create nodes and links
await db . put ({ name: 'Alice' }, 'user:alice' )
await db . put ({ name: 'Developers' }, 'group:devs' )
await db . link ( 'user:alice' , 'group:devs' )
// Delete the group
await db . remove ( 'group:devs' )
// The link is automatically removed from Alice
const { result : user } = await db . get ( 'user:alice' )
console . log ( user . edges ) // [] (empty)
remove does not delete nodes that the target node links to. It only removes the edges.
Real-Time Deletion
Deletions sync across peers and trigger reactive callbacks:
const db = await gdb ( 'shared-app' , { rtc: true })
// Subscribe to a node
await db . get ( 'user:alice' , ( node ) => {
if ( node === null ) {
console . log ( 'User was deleted!' )
}
})
// On another peer: delete the node
await db . remove ( 'user:alice' )
// Callback fires with null on all subscribed peers
Clearing All Data: clear
The clear method removes all nodes, edges, and indexes from the database.
// Nuclear option: delete everything
await db . clear ()
console . log ( 'Database cleared' )
clear() is irreversible and deletes all local data. Use with caution, especially in production.
In P2P mode, clear() only affects the local peer. Other peers retain their data and will re-sync when reconnected.
Complete CRUD Example
Here’s a complete example building a simple blog:
import { gdb } from 'genosdb'
const db = await gdb ( 'blog-app' , { rtc: true })
// === CREATE ===
// Create an author
const authorId = await db . put (
{ type: 'author' , name: 'Alice' , bio: 'Tech writer' },
'author:alice'
)
// Create a post
const postId = await db . put (
{
type: 'post' ,
title: 'Getting Started with GenosDB' ,
content: 'GenosDB is a P2P graph database...' ,
publishedAt: new Date (). toISOString ()
},
'post:001'
)
// Create tags
await db . put ({ type: 'tag' , name: 'tutorial' }, 'tag:tutorial' )
await db . put ({ type: 'tag' , name: 'database' }, 'tag:database' )
// === LINK ===
// Link post to author
await db . link ( 'post:001' , 'author:alice' )
// Link post to tags
await db . link ( 'post:001' , 'tag:tutorial' )
await db . link ( 'post:001' , 'tag:database' )
// === READ ===
// Get the post with reactive updates
const { result : post , unsubscribe : unsub1 } = await db . get (
'post:001' ,
( updatedPost ) => {
console . log ( 'Post updated:' , updatedPost . value . title )
}
)
console . log ( 'Post:' , post . value )
console . log ( 'Links:' , post . edges ) // ['author:alice', 'tag:tutorial', 'tag:database']
// Query all posts by this author (using graph traversal)
const { results : authorPosts } = await db . map ({
query: {
type: 'author' ,
name: 'Alice' ,
$edge: { type: 'post' } // Traverse to linked posts
}
})
console . log ( 'Alice \' s posts:' , authorPosts )
// === UPDATE ===
// Update the post (partial update)
const { result : currentPost } = await db . get ( 'post:001' )
await db . put (
{
... currentPost . value ,
title: 'Getting Started with GenosDB (Updated)' ,
updatedAt: new Date (). toISOString ()
},
'post:001'
)
// === DELETE ===
// Remove a tag
await db . remove ( 'tag:database' )
// The link is automatically removed from the post
const { result : updatedPost } = await db . get ( 'post:001' )
console . log ( updatedPost . edges ) // ['author:alice', 'tag:tutorial']
// Clean up subscriptions
unsub1 ()
CRUD with Security Manager
When using the Security Manager, CRUD operations are automatically signed and verified:
const db = await gdb ( 'secure-app' , {
rtc: true ,
sm: { superAdmins: [ '0x1234...' ] }
})
// Login
await db . sm . loginCurrentUserWithWebAuthn ()
// Operations are now signed with user's private key
await db . put ({ secret: 'data' }, 'node1' ) // Signed automatically
// On remote peers, the signature is verified
// Invalid or unauthorized operations are rejected
Batching Operations
For bulk inserts, batch operations to reduce sync overhead:
// ❌ Inefficient: Sync after each put
for ( let i = 0 ; i < 1000 ; i ++ ) {
await db . put ({ value: i }, `node: ${ i } ` )
}
// ✅ Better: Batch without awaiting
const promises = []
for ( let i = 0 ; i < 1000 ; i ++ ) {
promises . push ( db . put ({ value: i }, `node: ${ i } ` ))
}
await Promise . all ( promises )
Save Delay Configuration
Adjust the debounce delay for persistence:
const db = await gdb ( 'my-app' , {
saveDelay: 1000 // Wait 1 second before persisting (default: 200ms)
})
// Reduces disk I/O under heavy write loads
// Trade-off: Higher risk of data loss if browser crashes
Higher saveDelay values improve write performance but increase the window for potential data loss.
Best Practices
Use Meaningful IDs Custom IDs (user:alice) are easier to work with than auto-generated hashes.
Design for Granularity Split large objects into smaller nodes to reduce conflict resolution issues.
Clean Up Subscriptions Always call unsubscribe() when done with reactive queries to prevent memory leaks.
Handle Null Results Check if result is null before accessing properties — nodes may not exist or may be deleted.
Common Patterns
Soft Delete
// Instead of remove, mark as deleted
await db . put ({ ... node , deleted: true }, nodeId )
// Filter out deleted nodes in queries
const { results } = await db . map ({
query: { type: 'post' , deleted: { $ne: true } }
})
Timestamps
// Add created/updated timestamps
const now = new Date (). toISOString ()
const id = await db . put ({
type: 'post' ,
title: 'My Post' ,
createdAt: now ,
updatedAt: now
}, 'post:1' )
// On update
const { result } = await db . get ( 'post:1' )
await db . put (
{ ... result . value , updatedAt: new Date (). toISOString () },
'post:1'
)
Inverse Links (Bidirectional)
// Create bidirectional relationship
await db . link ( 'user:alice' , 'user:bob' ) // Alice -> Bob
await db . link ( 'user:bob' , 'user:alice' ) // Bob -> Alice
// Now both can traverse to each other
Queries Learn how to query nodes with advanced filters
Graph Traversal Master the $edge operator for recursive queries
Real-Time Subscriptions Set up reactive queries with live updates
Todo App Example See CRUD operations in a complete application