Node.js bindings¶
DecentDB ships two in-tree Node packages under bindings/node/:
bindings/node/decentdb— thedecentdb-nativeN-API addon plus a thinDatabase/Statementwrapperbindings/node/knex-decentdb— a Knex dialect that rewrites Knex?placeholders to DecentDB's$Nparameter style
Native library requirement¶
Both packages load the DecentDB shared library at runtime.
Build it from the repository root:
Then point Node at the shared library with DECENTDB_NATIVE_LIB_PATH:
- Linux:
target/debug/libdecentdb.so - macOS:
target/debug/libdecentdb.dylib - Windows:
target/debug/decentdb.dll
Native package quick start¶
const { Database, timestampMicros } = require('decentdb-native');
const db = Database.openOrCreate('app.ddb');
db.exec('CREATE TABLE users (id INT64 PRIMARY KEY, name TEXT, created_at TIMESTAMP)');
db.exec('INSERT INTO users VALUES ($1, $2, $3)', [1, 'Ada', timestampMicros(Date.now())]);
const rows = db.exec('SELECT id, name FROM users ORDER BY id').rows;
console.log(rows);
console.log(db.inTransaction);
console.log(Database.abiVersion(), Database.version());
db.close();
Important parameter note:
decentdb-nativeexpects DecentDB-native placeholders:$1,$2, ...- it intentionally rejects JDBC/SQLite-style
?placeholders - use
knex-decentdbwhen you want automatic?rewriting
Open modes¶
The native wrapper now exposes explicit open-mode helpers:
Database.openOrCreate(path)Database.openExisting(path)Database.create(path)new Database({ path, mode: 'openOrCreate' | 'open' | 'create' })
Native open options can include write-queue settings such as write_queue_enabled=true, write_queue_capacity=128, and write_queue_default_timeout_ms=1000. The wrapper also accepts camel-cased constructor fields such as writeQueueEnabled and writeQueueCapacity. Use the object constructor for profiles or other native open options:
const db = new Database({
path: 'app.ddb',
mode: 'openOrCreate',
options: { profile: 'embedded_fast', cache_size: '64MB' },
});
The N-API layer maps queue status codes distinctly, including timeout, canceled, queue-full, and queue-closed outcomes. Database.execQueued(sql) submits unbound self-contained write SQL to ddb_db_execute_queued, and Database.writeQueueMetrics() returns the native queue counters. Parameterized prepared statements remain on the direct path until the C ABI grows a queued prepared-statement contract.
Native wrapper API highlights¶
Database:
exec(sql, bindings?)execAsync(sql, bindings?)execQueued(sql, { timeoutMs }?)writeQueueMetrics()prepare(sql)beginTransaction(),commitTransaction(),rollbackTransaction()checkpoint()saveAs(destPath)inTransactionlistTables(),getTableColumns(name),listIndexes()getTableDdl(name),listViewsInfo(),listViews(),getViewDdl(name),listTriggers()getToolingMetadata(),describeQueryContract(sql)- instance getters:
abiVersion,engineVersion - static helpers:
Database.abiVersion(),Database.version(),Database.evictSharedWal(path)
Statement:
bindAll([...])- typed bind support for
null,bigint, safe-integernumber,boolean,string,Buffer/Uint8Array,{ unscaled, scale },Date, andtimestampMicros(...) step()stepRowView()rowArray()rowsAffected()columnNames()fetchRowsI64TextF64(maxRows)/fetchRowsI64TextF64Number(maxRows)reBindInt64Execute(value)reBindTextInt64Execute(text, value)reBindInt64TextExecute(value, text)
Both Database and Statement also register a FinalizationRegistry safety net so dropped objects do not permanently leak native handles, but explicit close() / finalize() is still preferred.
Result Type Mapping¶
Rows returned by exec(), execAsync(), stepRowView(), and rowArray() use these JavaScript shapes:
| DecentDB type | JavaScript result value |
|---|---|
INT64 | bigint |
FLOAT64 | number |
BOOL | boolean |
TEXT | string |
BLOB, UUID, GEOMETRY, GEOGRAPHY | Buffer |
DECIMAL | { unscaled: bigint, scale: number } |
TIMESTAMP | number milliseconds since Unix epoch |
ENUM | "typeId:labelId" string |
IPADDR / INET | canonical string |
CIDR | canonical string |
DATE | YYYY-MM-DD string |
TIME | HH:MM:SS.ffffff string |
TIMESTAMPTZ | UTC ISO-like string ending in Z |
INTERVAL | "months days micros" string |
MACADDR / MACADDR8 | canonical lowercase string |
String parameters can be used for typed semantic columns when SQL provides the target column context.
Knex usage¶
const knex = require('knex');
const { Client_DecentDB } = require('knex-decentdb');
const db = knex({
client: Client_DecentDB,
connection: { filename: 'app.ddb' },
useNullAsDefault: true,
});
knex-decentdb now safely skips ? inside:
- single-quoted strings
- double-quoted identifiers/strings
--line comments/* ... */block comments
Validation commands¶
Native package tests:
cd bindings/node/decentdb
DECENTDB_NATIVE_LIB_PATH=/absolute/path/to/target/debug/libdecentdb.so npm test
Knex package tests:
cd bindings/node/knex-decentdb
DECENTDB_NATIVE_LIB_PATH=/absolute/path/to/target/debug/libdecentdb.so npm test
Benchmarks:
cd bindings/node/decentdb
DECENTDB_NATIVE_LIB_PATH=/absolute/path/to/target/debug/libdecentdb.so npm run benchmark:fetch -- --count 100000 --point-reads 5000 --fetchmany-batch 1024 --db-prefix node_native_bench_fetch
cd ../knex-decentdb
DECENTDB_NATIVE_LIB_PATH=/absolute/path/to/target/debug/libdecentdb.so npm run benchmark:fetch -- --count 100000 --point-reads 5000 --fetchmany-batch 1024 --db-prefix node_knex_bench_fetch
Smoke path:
Current limitations¶
- generic async iteration still dispatches one libuv worker per row via
stmtNextAsync; large scans should prefer bulk fetch helpers where possible - generic result-handle APIs (
ddb_db_execute+ddb_result_*) are not exposed yet - generic batch APIs (
ddb_stmt_execute_batch_i64,ddb_stmt_execute_batch_typed) are not exposed yet - the Knex fast path is still benchmark-oriented for specific
(int64, text, float64)shapes - DecentDB's engine contract is still one writer / many readers per process; do not share one writable connection across uncontrolled concurrent writers