verdictdb
v0.1.0
Published
High-performance Node.js & TypeScript bindings for Verdict — the local-first embedded database with propose → verdict → commit safety pipeline, cryptographic hash chain audit, and instant rollback.
Maintainers
Readme
verdictdb
High-performance Node.js & TypeScript bindings for Verdict (VDB) — the local-first embedded database with a
propose → verdict → commitsafety pipeline, cryptographic hash-chain audit, and instant rollback.
🧭 Documentation Navigation
| 🏠 Root | ⚙️ Core Engine | 💻 CLI / REPL | 🔌 MCP Server | 🤖 AI Skill | |---|---|---|---|---| | Main README | verdict-core | verdict-cli | verdictdb | verdict-skill |
| 📖 In-Depth Guides | 📊 Comparisons | 🛡️ Security | 🤝 Contributing | |---|---|---|---| | Integration Guide | CLI Tutorial & Cheatsheet | Security Architecture | Contributing Guide |
What is Verdict?
Verdict is a local-first relational embedded database where no mutation can touch disk without an authorized verdict.
Every write goes through:
query (SQL or NL) → propose() → Verdict (dry-run diff) → commit(verdict.id) → diskThis gives you:
- ✅ Atomic, ACID writes — nothing partial ever lands on disk
- ✅ Instant point-in-time rollback —
undo(commit_id)is microseconds - ✅ Cryptographic SHA-256 Merkle audit trail — tamper-evident commit log
- ✅ Standard ANSI SQL + Natural Language — interchangeable query dialects
- ✅ Sub-60µs query latency — redb B-Tree, no IPC, no network
Installation
npm install verdictdbNote:
verdictdbis a native Node.js addon compiled from Rust via napi-rs. It requires Rust to be installed to build from source, or pre-built binaries are provided for macOS, Linux, and Windows.
Quick Start
import { VerdictDb } from 'verdictdb';
// Open (or create) a local .vdb database file
const db = VerdictDb.open('./myapp.vdb');Creating Tables (DDL)
Verdict supports Standard ANSI SQL CREATE TABLE with PRIMARY KEY and REFERENCES for foreign keys:
// Create a parent table
db.propose(`CREATE TABLE customers (
id VARCHAR PRIMARY KEY,
name VARCHAR,
email VARCHAR,
plan VARCHAR
)`);
// Create a child table with a foreign key reference
db.propose(`CREATE TABLE orders (
id VARCHAR PRIMARY KEY,
customer_id VARCHAR REFERENCES customers(id),
total_cents INT,
status VARCHAR
)`);Or use the Natural Language DSL:
db.propose('create table customers with id:str, name:str, email:str, plan:str');
db.propose('create table orders with id:str, customer_id:str references customers.id, total_cents:num, status:str');⚠️ DDL operations return a
Verdictwithrisk: "SchemaChange"that must be committed to take effect:
const v = JSON.parse(db.propose(`CREATE TABLE users (id VARCHAR PRIMARY KEY, name VARCHAR, age INT)`));
db.commit(v.id); // commits the schema change to diskInserting Data
Key Generation
Verdict has a built-in time-sorted key generator:
key()— generatesvdb_k_<sortable>(globally unique, time-ordered)key("prefix")— generatesprefix_<sortable>(e.g.cust_...,ord_...)- Omitted PK — auto-generated with table-name prefix
// SQL INSERT with key() generator
const v = JSON.parse(db.propose(
`INSERT INTO customers (id, name, email, plan) VALUES (key('cust'), 'Acme Corp', '[email protected]', 'Enterprise')`
));
// v.diff.rows_after[0].id => "cust_01a0f5d3bca8..."
db.commit(v.id);
// SQL INSERT — omit PK and it's auto-generated
const v2 = JSON.parse(db.propose(
`INSERT INTO orders (customer_id, total_cents, status) VALUES ('cust_01a0f5d3bca8', 99900, 'pending')`
));
db.commit(v2.id);Natural language alternative:
const v = JSON.parse(db.propose(
`add customers with id=key("cust"), name=Acme Corp, [email protected], plan=Enterprise`
));
db.commit(v.id);Reading Data (SELECT / Queries)
All reads return a Verdict with risk: "Read". No commit needed.
// Standard SQL SELECT
const result = JSON.parse(db.propose(`SELECT * FROM customers WHERE plan = 'Enterprise'`));
console.log(result.diff.rows_after); // array of matching rows
// SQL JOIN across tables
const joined = JSON.parse(db.propose(
`SELECT * FROM orders JOIN customers ON orders.customer_id = customers.id`
));
console.log(joined.diff.rows_after);
// SQL COUNT
const count = JSON.parse(db.propose(`SELECT COUNT(*) FROM orders`));
console.log(count.diff.affected);
// Natural Language equivalents
db.propose('show all customers');
db.propose('find orders where status = pending');
db.propose('show orders for customer cust_acme'); // FK navigation (forward)
db.propose('show customer for order ord_abc123'); // FK navigation (reverse)Updating & Deleting
// SQL UPDATE
const updateV = JSON.parse(db.propose(
`UPDATE orders SET status = 'fulfilled' WHERE id = 'ord_abc123'`
));
// updateV.diff.rows_before — shows what will change
// updateV.diff.rows_after — shows what it becomes
const committed = JSON.parse(db.commit(updateV.id));
// SQL DELETE (scoped)
const deleteV = JSON.parse(db.propose(`DELETE FROM orders WHERE id = 'ord_abc123'`));
db.commit(deleteV.id);
// SQL DELETE ALL (quarantined as BroadWrite — requires explicit decision)
const broadV = JSON.parse(db.propose(`DELETE FROM orders`));
console.log(broadV.risk); // "BroadWrite" — check before committing!
if (broadV.risk !== 'BroadWrite') {
db.commit(broadV.id);
}Point-in-Time Rollback (undo)
Every commit() returns a commit_id. Pass it to undo() to instantly restore the prior state:
const verdict = JSON.parse(db.propose(`UPDATE users SET age = 999 WHERE name = 'Alice'`));
const result = JSON.parse(db.commit(verdict.id));
// Oops — roll back immediately
const undone = JSON.parse(db.undo(result.commit_id));
console.log(undone.status); // "undone"Inspecting the Schema
const schema = JSON.parse(db.schema());
// {
// tables: [
// {
// name: "customers",
// primary_key: "id",
// columns: [{ name: "id", value_type: "Str" }, ...],
// foreign_keys: []
// },
// {
// name: "orders",
// primary_key: "id",
// columns: [{ name: "customer_id", value_type: "Str" }, ...],
// foreign_keys: [{ column: "customer_id", foreign_table: "customers", foreign_column: "id" }]
// }
// ]
// }
schema.tables.forEach(table => {
console.log(`\nTable: ${table.name} (PK: ${table.primary_key})`);
table.columns.forEach(col => console.log(` ${col.name}: ${col.value_type}`));
table.foreign_keys.forEach(fk =>
console.log(` FK: ${fk.column} → ${fk.foreign_table}.${fk.foreign_column}`)
);
});Full Example: Express.js REST API
import express from 'express';
import { VerdictDb, Verdict, CommitResult } from 'verdictdb';
const app = express();
app.use(express.json());
const db = VerdictDb.open('./data/app.vdb');
// Bootstrap schema on startup
function bootstrapSchema() {
const tables = [
`CREATE TABLE customers (id VARCHAR PRIMARY KEY, name VARCHAR, email VARCHAR, plan VARCHAR)`,
`CREATE TABLE orders (id VARCHAR PRIMARY KEY, customer_id VARCHAR REFERENCES customers(id), total_cents INT, status VARCHAR)`,
];
for (const ddl of tables) {
try {
const v = JSON.parse(db.propose(ddl)) as Verdict;
if (v.risk === 'SchemaChange') db.commit(v.id);
} catch (_) { /* table already exists */ }
}
}
bootstrapSchema();
// POST /customers — create a customer
app.post('/customers', (req, res) => {
const { name, email, plan } = req.body;
try {
const verdict = JSON.parse(
db.propose(`INSERT INTO customers (id, name, email, plan) VALUES (key('cust'), '${name}', '${email}', '${plan}')`)
) as Verdict;
if (verdict.risk === 'BroadWrite') {
return res.status(400).json({ error: 'Unexpectedly broad mutation — rejected.' });
}
const result = JSON.parse(db.commit(verdict.id)) as CommitResult;
const createdRow = verdict.diff.rows_after[0];
res.status(201).json({ customer: createdRow, commit_id: result.commit_id });
} catch (err: any) {
res.status(400).json({ error: err.message });
}
});
// GET /customers — list all customers
app.get('/customers', (_req, res) => {
const verdict = JSON.parse(db.propose('SELECT * FROM customers')) as Verdict;
res.json({ customers: verdict.diff.rows_after });
});
// GET /customers/:id/orders — FK navigation
app.get('/customers/:id/orders', (req, res) => {
const verdict = JSON.parse(
db.propose(`show orders for customer ${req.params.id}`)
) as Verdict;
res.json({ orders: verdict.diff.rows_after });
});
// DELETE /orders/:id — delete with undo capability
app.delete('/orders/:id', (req, res) => {
const verdict = JSON.parse(
db.propose(`DELETE FROM orders WHERE id = '${req.params.id}'`)
) as Verdict;
const result = JSON.parse(db.commit(verdict.id)) as CommitResult;
res.json({ deleted: true, commit_id: result.commit_id, undo_with: `POST /undo/${result.commit_id}` });
});
// POST /undo/:commitId — point-in-time rollback
app.post('/undo/:commitId', (req, res) => {
try {
const result = JSON.parse(db.undo(req.params.commitId));
res.json(result);
} catch (err: any) {
res.status(400).json({ error: err.message });
}
});
app.listen(3000, () => console.log('Verdict API running on http://localhost:3000'));Full Example: Next.js 15 App Router (Server Actions)
// lib/db.ts
import { VerdictDb } from 'verdictdb';
import path from 'path';
// Singleton — reused across server-side renders
let _db: VerdictDb | null = null;
export function getDb(): VerdictDb {
if (!_db) {
_db = VerdictDb.open(path.join(process.cwd(), 'data', 'app.vdb'));
}
return _db;
}// app/actions/users.ts
'use server';
import { getDb } from '@/lib/db';
import { revalidatePath } from 'next/cache';
import { Verdict, CommitResult } from 'verdictdb';
export async function createUser(formData: FormData) {
const db = getDb();
const name = formData.get('name') as string;
const email = formData.get('email') as string;
const age = Number(formData.get('age'));
const verdict = JSON.parse(
db.propose(`INSERT INTO users (name, email, age) VALUES ('${name}', '${email}', ${age})`)
) as Verdict;
const commit = JSON.parse(db.commit(verdict.id)) as CommitResult;
revalidatePath('/users');
return { user: verdict.diff.rows_after[0], commitId: commit.commit_id };
}
export async function listUsers() {
const db = getDb();
const verdict = JSON.parse(db.propose('SELECT * FROM users')) as Verdict;
return verdict.diff.rows_after;
}
export async function undoLastAction(commitId: string) {
const db = getDb();
return JSON.parse(db.undo(commitId));
}// app/users/page.tsx
import { listUsers } from '../actions/users';
export default async function UsersPage() {
const users = await listUsers();
return (
<ul>
{users.map((u: any) => (
<li key={u.id}>{u.name} — {u.email}</li>
))}
</ul>
);
}Standalone Functional API
For functional-style usage, all methods are also available as standalone functions:
import { verdictOpen, verdictPropose, verdictCommit, verdictUndo, verdictSchema } from 'verdictdb';
const db = verdictOpen('./app.vdb');
const verdict = JSON.parse(verdictPropose(db, 'add users with name=Alice, age=30'));
const commit = JSON.parse(verdictCommit(db, verdict.id));
// Roll back
verdictUndo(db, commit.commit_id);
// Inspect schema
const schema = JSON.parse(verdictSchema(db));Building from Source
If pre-built binaries are not available for your platform:
# Prerequisites: Rust >= 1.75, Node.js >= 16
git clone https://github.com/senapati484/verdict.git
cd verdict/crates/verdict-node
npm install
npm run build # compiles the native Rust addonLicense
Licensed under the Apache License, Version 2.0. Copyright 2026 senapati484.
