darudb
v1.5.0
Published
An embedded database that keeps an application's data in one local file. The Node.js binding of the DaruDB engine.
Maintainers
Readme
darudb
The Node.js package of DaruDB, an embedded database that keeps an application's data in one local file. It is a native addon over the DaruDB engine, which is written in Rust, so the same file reads the same from Node.js, Rust and Dart.
Installation
npm install darudbUsage
A schema declares the collections, and TypeScript infers every object's type from it. Transactions run a function: write commits when it returns and aborts when it throws, and read sees one commit throughout.
import { collection, Database, schema, t } from 'darudb';
const app = schema(1, {
users: collection({
name: t.string(),
email: t.string().optional().unique(),
age: t.int().default(0).index()
})
});
const db = Database.open('app.darudb', { schema: app });
db.write((txn) => {
txn.collection('users').insertMany([
{ name: 'Alice', age: 31 },
{ name: 'Bob', email: '[email protected]' }
]);
});
const adults = db.read((txn) =>
txn.collection('users').find((q) => q.where('age', '>=', 18).sortBy('age', 'desc').limit(10))
);
const same = db.read((txn) =>
txn.collection('users').find('age >= $0 SORT BY age DESC LIMIT 10', [18])
);
db.close();Each method has an asynchronous twin, openAsync, readAsync, writeAsync, syncAsync and closeAsync, which runs the engine's work on the thread pool and resolves a promise, so a server's event loop never waits for the disk:
const db = await Database.openAsync('app.darudb', { schema: app });
await db.writeAsync(async (txn) => {
await txn.collection('users').insert({ name: 'Carol' });
});
const count = await db.readAsync((txn) => txn.collection('users').count());A program that opens a file it did not write needs no schema of its own. Opened without one, a database works with the schema the file holds: storedSchema describes its collections, fields, keys and indexes in plain data, and the collections are read and written by name, with the file's checks and indexes kept as ever. An int reads as a number up to 2^53 and as a bigint beyond, since no declaration says which.
const db = Database.open('app.darudb');
for (const { name } of db.storedSchema?.collections ?? []) {
console.log(
name,
db.read((txn) => txn.collection(name).count())
);
}Objects and queries cross into the engine as bytes, one buffer per call, so a batch of objects costs one call. Raising the schema's version migrates the file when it opens, with renames, deletions and a JavaScript function of your own; the guide has the details.
A key of 32 bytes or a password in the options encrypts a new database, every page of it, and opens an encrypted one: Database.open('secret.darudb', { schema: app, password }).
Every error thrown by the package is an Error with a stable code, the same one in every language DaruDB ships to:
try {
Database.open('missing.darudb', { create: false });
} catch (error) {
if (error.code === 'NOT_FOUND') {
// Nothing exists at that path.
}
}Requirements
Node.js 20 or later. The package ships prebuilt binaries for macOS, Windows, Linux (glibc and musl), FreeBSD and Android, so installing it compiles nothing.
Building from source
An application never needs this; it is for working on the package itself. The addon is built from the Rust source in this folder and the engine in crates/darudb. You need Rust (the version in the repository's rust-toolchain.toml is installed automatically by rustup) and Node.js.
npm install
npm run buildnpm run build writes the addon for your platform and the files that load it. npm test runs the tests against it and checks the TypeScript declarations, and npm run bench measures it after a release build.
