BasedDb
BasedDb is a powerful node graph based database solution that supports various
data types, references, edges, and operations. It also offers concurrency
handling, client-server architecture support, and more.
Features
- Schema definition and management
- Data creation, querying, updating, and deletion
- Supported field types
string
text, locale aware multi-language text
binary strings
timestamp
- numeric types:
number (double-precision floating-point), int8, uint8, int16, uint16, int32, uint64
boolean
alias
enum
- row and columnar vectors:
vector and colvec
cardinality set
- References and edge properties for advanced data modeling
- Concurrency support for high-load scenarios
- Client-server design for distributed systems
- Checksum, analytics, and expiration features
- Async block based backups, i.e. only dirty blocks needs to be written on save
Install
Prerequisites:
- recent GNU make
- gcc 14.2 on Linux or clang 17.0.0 on macOS
- zig 0.14.0
- npm & node.js v22.14.0 or newer
npm i
npm run get-napi
npm run build
Testing
Run all tests + ldb + build c, zig and js
npm run test
Run specific test file - does substring matching
npm run test -- range.js
Run specific test file & run specific test
npm run test -- range.js:range
Different flavours of test:
Only builds zig
npm run test-zig
Builds nothing only runs tests
npm run test-fast
API Documentation
This documentation covers setup, basic CRUD, querying, graph relational traversal, and advanced performance practices for @based/sdk/db.
1. Setup & Schema Definition
The database requires a schema before most operations. The schema defines types, properties, locales, and constraints. BasedDb is a unique hybrid document store and graph database that extensively utilizes adjacency lists for high-speed references.
import { BasedDb } from '@based/sdk/db'
const db = new BasedDb({
path: './db-directory',
})
await db.start({ clean: true })
await db.setSchema({
locales: {
en: { required: true },
nl: { fallback: ['en'] },
},
types: {
user: {
props: {
name: 'string',
email: { type: 'alias', format: 'email', required: true },
age: 'uint32',
bio: 'text',
recentVisits: {
type: 'references',
capped: 50,
items: { ref: 'article' },
},
friends: {
items: {
ref: 'user',
prop: 'friends',
$friendshipLevel: 'uint8',
},
},
status: { type: 'enum', enum: ['active', 'inactive', 'pending'] },
embedding: { type: 'vector', size: 1536, baseType: 'float32' },
},
hooks: {
create: (payload) => {
if (payload.age < 18) throw new Error('Must be 18+')
},
},
},
article: {
props: {
title: 'text',
body: 'text',
},
},
},
})
2. Mutations (Create, Update, Delete)
Mutations in BasedDb are highly synchronized and batched on the same engine tick.
Create & Tmp IDs
When you create, it instantly returns a Tmp ID. These IDs can be manipulated or bound synchronously without awaiting the engine to .drain().
const userId = await db.create('user', {
name: 'Alice',
email: 'alice@example.com',
})
const charlieId = await db.create('user', {
name: 'Charlie',
email: 'charlie@example.com',
friends: [
{ id: userId, $friendshipLevel: 5 },
],
})
Update
await db.update('user', userId, { age: 31, status: 'active' })
await db.update('user', { id: userId, age: 32 })
await db.update('user', userId, { age: { increment: 1 } })
await db.update('user', charlieId, {
friends: {
add: [newFriendId],
delete: [oldFriendId],
update: [{ id: userId, $friendshipLevel: 10 }],
},
})
await db.update('user', charlieId, { friends: [userId] })
Upsert
Intelligently update if the node exists or create it if it doesn't, based on an alias field defined in the schema.
await db.upsert('user', {
email: 'alice@example.com',
name: 'Alice Smith',
age: 33,
})
Delete
Deleting nodes natively manages tearing down bi-directional connections in the graph adjacency list. For strictly coupled records, use dependent: true in the schema to enact cascading engine-level deletes.
await db.delete('user', userId)
Expiration (TTL)
Instead of relying on application-level background workers to prune data, allow the engine to expire it natively.
db.expire('user', userId, 600)
3. Querying & Adjacency List Traversal
The .query() method starts building an execution request.
Core Rule: Always query through the known ID of a reference rather than scanning the target collection and filtering (which behaves as a slow O(N) scan). Direct ID lookups bypass filtering entirely (O(log N)).
db.query('user', userId)
db.query('user', [812739, 918237, 102938])
db.query('user', { email: 'alice@example.com' })
db.query('user')
.include(...fields)
Specifies which fields to traverse and return. Supports nested sub-queries and extracting metadata or specific language locales.
await db.query('user', userId).include('name', 'age').get()
await db
.query('user', userId)
.include(
(s) => s('friends').include('name', '$friendshipLevel'),
)
.get()
await db.query('user', userId).include('bio.en').get()
await db.query('article').include('title', 'body', { end: 50 }).get()
await db.query('article').include('body', { meta: true }).get()
.filter(field, operator, value, options?)
Evaluate and eliminate records. You should always place your most restrictive filters first to reduce the evaluation pipeline early.
Operators: =, !=, >, <, >=, <=, includes (or has), like, exists.
await db.query('user').filter('status', '=', 'active').get()
await db.query('user').filter('role', '=', [1, 2, 3]).get()
await db.query('user').filter('age', '>=', 18).get()
await db.query('user').filter('isActive').get()
await db
.query('user', userId)
.include((s) =>
s('friends').filter('$friendshipLevel', '>', 5).include('name'),
)
.get()
.sort(field, direction?)
Performance Note: Sorting a top-level collection uses a stored physical index. Sorting references via .include() is computed entirely on-the-fly and bears heavy performance implications for large lists.
Always prefer sorting by id rather than a createdAt timestamp. id inherently represents chronological insertion order and guarantees an instant linear engine traversal.
await db.query('user').sort('id', 'desc').get()
await db.query('user').sort('name', 'asc').get()
.search(term, fields, options?)
Utilizes a highly optimized Hamming distance algorithm for full-text queries (exceptional for catching typos). Text spaces act as logical AND operators.
await db.query('article').search('italy nigeria', 'title', 'body').get()
await db.query('article').search('giraffe', { title: 1, body: 0.5 }).get()
const embedding = new Float32Array([...])
await db.query('data')
.search(embedding, 'embedding', { fn: 'cosineSimilarity', score: 0.8 })
.get()
.range(start, end)
Returns records bound between two integer indices for fast pagination offsets.
await db.query('user').sort('id', 'desc').range(0, 25).get()
await db.query('user').sort('id', 'desc').range(25, 50).get()
4. Advanced Features
Aggregations & Group By
BasedDb natively supports sum(), count(), avg(), min(), max(), stddev(), var(), and harmonicMean().
const activeCount = await db
.query('user')
.filter('isActive', '=', true)
.count()
.get()
const regionalAverages = await db
.query('vote')
.avg('score')
.groupBy('region')
.get()
const userSummary = await db
.query('user', userId)
.include((q) => q('friends').count())
.get()
Cardinality (HyperLogLog++)
Track the estimated number of uniquely occurring elements without large list memory bloat by defining the cardinality schema type.
await db.update('analytics', eventId, { uniqueUsers: ['UserA', 'UserB'] })
const result = await db.query('analytics', eventId).include('uniqueUsers').get()
Subscriptions
Subscriptions hook directly into the database engine's evaluation tree without relying on polling cycles.
const unsubscribe = db.query('project', projectId).subscribe((data) => {
console.log('Push update received:', data.toObject())
})
unsubscribe()
Error Handling and Validation
The engine validates transactions strictly against your defined schema. Operations circumventing min, max, maxBytes, format, types, or making structural request errors will synchronously throw highly predictable exceptions out of the C-bindings layer.
try {
await db.update('user', 99999999, { status: 'invalid' })
} catch (error) {
if (error.message.includes('does not exist')) {
} else if (error.message.includes('Invalid value')) {
}
}
Persistence Operations
await db.save()
await db.drain()
await db.stop()
await db.destroy()