mdbxmou
August 12, 2026 · View on GitHub
Node.js binding for libmdbx — a fast, lightweight, embedded key-value database.
Features
- Synchronous API — Direct MDBX operations in main thread
- Asynchronous API — Background operations with async/await
- Transactions — ACID transactions with read/write modes
- Multiple key/value types — String, binary, ordinal keys and ordinal duplicate values
- Batch operations — Efficient multi-key read/write
- Memory-mapped — High-performance memory-mapped I/O
Worker Threads
The native addon may be loaded independently by the main thread and multiple
worker_threads. Each isolate must create and retain its own MDBX environment,
transactions, DBIs, and cursors, and must use a separate database path in this
release. Opening the same database path from multiple isolates of one process
is not supported or verified.
Do not pass MDBX wrappers, native pointers, borrowed DataView objects, or
their backing ArrayBuffer between isolates. Copied or serialized data may be
sent normally. Separate OS processes may open the same database under the
usual MDBX locking rules.
Installation
npm install mdbxmou
Quick Start
CommonJS:
const { MDBX_Env, MDBX_Param } = require('mdbxmou');
async function main() {
// Create environment
const env = new MDBX_Env();
await env.open({
path: './data',
keyFlag: MDBX_Param.keyFlag.string, // Default key encoding (optional)
valueFlag: MDBX_Param.valueFlag.string // Default value encoding (optional)
});
// Write data
const txn = env.startWrite();
const dbi = txn.createMap(MDBX_Param.keyMode.ordinal);
dbi.put(txn, 1, "hello");
dbi.put(txn, 2, "world");
txn.commit();
// Read data
const readTxn = env.startRead();
const readDbi = readTxn.openMap(BigInt(MDBX_Param.keyMode.ordinal));
const value = readDbi.get(readTxn, 1);
console.log(value); // "hello"
readTxn.commit();
await env.close();
}
main().catch(console.error);
ESM:
import { MDBX_Env, MDBX_Param } from "mdbxmou";
API Reference
Environment (MDBX_Env)
Constructor
const env = new MDBX_Env();
Methods
open(options) → Promise
await env.open({
path: './database', // Database directory
keyFlag: MDBX_Param.keyFlag.string, // Default key encoding (optional)
valueFlag: MDBX_Param.valueFlag.string, // Default value encoding (optional)
flags: MDBX_Param.envFlag.nostickythreads
});
Options:
path- Database directory pathflags- Environment flags (optional, defaults to0)keyFlag- Default key encoding for all operations (optional, defaults to Buffer)- Only
stringcan be set (ordinal mode usesnumber/bigintseparately)
- Only
valueFlag- Default value encoding for all operations (optional, defaults to Buffer)stringaffects normal string valuesnumberandbigintare meaningful forvalueMode.multiOrdinal
maxDbi- Maximum number of databases (optional, default32)mode- Filesystem permissions mode (optional, default0664)geometry- Map size/geometry options (optional)trackBorrowedViews- Track and detach non-empty buffers borrowed bygetView()when their transaction completes (optional, defaulttrue; keep enabled unless profiling proves that reference tracking is a bottleneck)
Note: When keyFlag or valueFlag are set at environment level, they become defaults for all subsequent operations unless explicitly overridden.
Key Type Configuration
The library uses a two-level system for configuring key types:
Level 1: Environment (keyFlag)
When opening the environment, keyFlag controls how string/binary keys are returned:
keyFlag value | String/Binary keys returned as |
|---|---|
0 (default) | Buffer |
keyFlag.string (2) | String |
// Keys returned as Buffer (default)
await env.open({ path: './data' });
// Keys returned as String
await env.open({
path: './data',
keyFlag: MDBX_Param.keyFlag.string
});
Level 2: Database (keyMode)
When opening/creating a database, the argument type determines how ordinal (integer) keys are returned:
| Argument type | Ordinal keys returned as |
|---|---|
Number | Number |
BigInt | BigInt |
// Ordinal keys as Number
const dbi = txn.openMap(MDBX_Param.keyMode.ordinal); // keyMode.ordinal = 8
dbi.keys(txn); // [0, 1, 2, 3, ...]
// Ordinal keys as BigInt
const dbi = txn.openMap(BigInt(MDBX_Param.keyMode.ordinal)); // BigInt(8)
dbi.keys(txn); // [0n, 1n, 2n, 3n, ...]
Summary
| Key type | Configuration level | Option | Result type |
|---|---|---|---|
| String/Binary | env.open() | keyFlag: 0 | Buffer |
| String/Binary | env.open() | keyFlag: keyFlag.string | String |
| Ordinal | openMap/createMap | keyMode.ordinal (Number) | Number |
| Ordinal | openMap/createMap | BigInt(keyMode.ordinal) | BigInt |
close() → Promise
await env.close();
openSync(options)
env.openSync({
path: './database',
valueFlag: MDBX_Param.valueFlag.string
});
closeSync()
env.closeSync();
setOption(option, value)
env.setOption(MDBX_Param.envOption.syncBytes, 4 * 1024 * 1024);
env.setOption(MDBX_Param.envOption.syncPeriod, 0.2); // 200 ms
Note:
syncPeriodis passed in seconds and may be fractional. Other options are passed as non-negative integer values. Note:maxDbandmaxReadersare pre-open options. Set them throughopen/openSync(maxDbi,maxReaders) instead ofsetOption().
syncEx(force, nonblock) → number
env.syncEx(true, false);
startWrite() → Transaction
const txn = env.startWrite();
startRead() → Transaction
const txn = env.startRead();
query(requests) → Promise
const result = await env.query([
{
dbi,
mode: MDBX_Param.queryMode.get,
item: [{ key: 1 }, { key: 2 }]
},
{
dbi,
mode: MDBX_Param.queryMode.upsert,
putFlag: MDBX_Param.putFlag.noOverwrite,
item: [{ key: 3, value: "v3" }]
}
]);
query() uses the passed dbi and inherits key/value settings from it. queryMode selects the operation (get, del, or base write mode), and optional putFlag adds write-only MDBX flags. In query() only noOverwrite, noDupData, current, append, and appendDup are supported.
Transaction
Methods
createMap([db_name | keyMode], [keyMode | valueMode], [valueMode]) → DBI createMap({ name, keyFlag, valueFlag, keyMode, valueMode }) → DBI
// No arguments - default DB with env key/value flags (buffer if not set)
const dbi = txn.createMap();
// One argument - keyMode only
const dbi = txn.createMap(MDBX_Param.keyMode.ordinal);
// One argument - db_name only (uses default keyMode)
const namedDbi = txn.createMap("my-table");
// Two arguments - keyMode + valueMode
const dbi = txn.createMap(MDBX_Param.keyMode.ordinal, MDBX_Param.valueMode.multi);
// Two arguments - db_name + keyMode
const namedDbi = txn.createMap("my-table", MDBX_Param.keyMode.ordinal);
// Three arguments - db_name + keyMode + valueMode
const namedDbi = txn.createMap("my-table", MDBX_Param.keyMode.ordinal, MDBX_Param.valueMode.multi);
// Object form - explicit flags/modes
const dbi = txn.createMap({
name: "my-table",
keyFlag: MDBX_Param.keyFlag.string,
valueFlag: MDBX_Param.valueFlag.string,
keyMode: MDBX_Param.keyMode.reverse,
valueMode: MDBX_Param.valueMode.multi
});
Note: Use
createMapin write transactions - it will create the database if it doesn't exist, or open it if it does. This is safer for new environments.
openMap([db_name | keyMode], [keyMode]) → DBI openMap({ name, keyFlag, valueFlag, keyMode, valueMode }) → DBI
// No arguments - default DB with env key/value flags (buffer if not set)
const dbi = txn.openMap();
// One argument - keyMode only
// Number keyMode - keys returned as numbers
const dbi = txn.openMap(MDBX_Param.keyMode.ordinal);
// BigInt keyMode - keys returned as BigInts
const dbi = txn.openMap(BigInt(MDBX_Param.keyMode.ordinal));
// One argument - db_name only (uses default keyMode)
const namedDbi = txn.openMap("my-table");
// Two arguments - db_name + keyMode
const namedDbi = txn.openMap("my-table", MDBX_Param.keyMode.ordinal);
const namedDbiBigInt = txn.openMap("my-table", BigInt(MDBX_Param.keyMode.ordinal));
// Object form - explicit flags/modes
const dbi = txn.openMap({
name: "my-table",
keyFlag: MDBX_Param.keyFlag.string,
valueFlag: MDBX_Param.valueFlag.string,
keyMode: MDBX_Param.keyMode.reverse,
valueMode: MDBX_Param.valueMode.multi,
flags: MDBX_Param.dbMode.accede
});
Note: Use
openMapin read transactions or when you're sure the database already exists. For write transactions on new environments, prefercreateMap.
Note: When using ordinal keyMode, the key type in results depends on how you specify keyMode:
keyMode: number→ keys returned asnumberkeyMode: BigInt(number)→ keys returned asBigInt- When you pass
keyMode.ordinalas a positional argument (Number/BigInt), it also updateskeyFlagto number/bigint unless a numeric keyFlag was already set in env or explicitly provided.
Note: When
valueMode.multiOrdinalis used andvalueFlagis not specified, values are returned asnumberby default. SetvalueFlag: MDBX_Param.valueFlag.bigintif you needBigInton read.
commit()
txn.commit();
commitAndStartRead()
const txn = env.startWrite();
const dbi = txn.createMap("orders");
dbi.put(txn, "order-1", "open");
txn.commitAndStartRead();
const committed = dbi.get(txn, "order-1");
txn.abort();
commitAndStartRead() keeps the same transaction wrapper active over the exact
post-commit read snapshot. It is synchronous, accepts only an active write
transaction with no open cursors, and is not available in mdbxmou/async.
Complete the resulting read transaction with commit() or abort() before
starting another write transaction on the same thread.
An exception does not always mean rollback. The write can be committed before libmdbx fails to create the read transaction. If the wrapper is inactive after an exception, verify the result with a fresh read before retrying the write.
checkpoint() -> boolean
const txn = env.startWrite();
const dbi = txn.createMap("orders");
dbi.put(txn, "order-1", "open");
const noChanges = txn.checkpoint();
// Readers can now see order-1, while txn remains an active write transaction.
txn.abort();
checkpoint() commits a dirty write batch and immediately continues the same
wrapper as a write transaction without releasing the writer lock. Matching the
native mdbx++ API, it returns false after committing changes and true when
there were no changes (MDBX_RESULT_TRUE). The method is synchronous, requires
an active top-level write transaction with no open cursors, and is not available
in mdbxmou/async.
Like commit() and abort(), every call invalidates borrowed views, including
a no-op call that returns true.
Complete the continuation transaction promptly with commit() or abort().
Until then every other writer is blocked, although new read transactions can
see the checkpointed data.
abort()
txn.abort();
DBI (Database Instance)
Methods
put(txn, key, value, [flags])
dbi.put(txn, 123, "value");
dbi.put(txn, "key", Buffer.from("binary data"));
// Insert only if key does not exist
dbi.put(txn, 123, "value", MDBX_Param.putFlag.noOverwrite);
// Fast append for sorted inserts
dbi.put(txn, 124, "value", MDBX_Param.putFlag.append);
get(txn, key) → value
const value = dbi.get(txn, 123);
const binary = dbi.get(txn, "key");
For valueMode.multiOrdinal, get() returns the first duplicate value for the key, decoded as number by default.
Zero-copy reads with getView()
getView(txn, key) → MDBX_BorrowedView | undefined
getView()is an advanced borrowed-memory API and should not be used as a default replacement forget(). Read GETVIEW.md before using it in production; the caller owns strict lifetime, no-transfer and isolate-local obligations that cannot all be enforced at runtime.
getView() returns a standard JavaScript DataView over the bytes mapped by
MDBX. Unlike get(), it does not copy or decode the value. This is intended for
large values or hot read paths where the caller can keep all access inside a
short read transaction.
const env = new MDBX_Env();
env.openSync({ path: "./data" });
try {
let dbi;
const writeTxn = env.startWrite();
try {
dbi = writeTxn.createMap();
dbi.put(writeTxn, "binary-key", Buffer.from([0x78, 0x56, 0x34, 0x12]));
writeTxn.commit();
} finally {
if (writeTxn.isActive()) {
writeTxn.abort();
}
}
const readTxn = env.startRead();
try {
const view = dbi.getView(readTxn, "binary-key");
if (view) {
console.log(view.getUint32(0, true)); // 305419896
}
} finally {
if (readTxn.isActive()) {
readTxn.abort();
}
}
} finally {
env.closeSync();
}
In this API:
- a borrowed view is a
DataViewwhose bytes are owned by the active MDBX read transaction; - its backing buffer is the
ArrayBufferavailable asview.buffer; - detach invalidates that backing buffer. A detached buffer has
byteLength === 0, and reading through itsDataViewis no longer valid; - a tracked non-empty view uses the default safety mode: the transaction
records its borrowed backing buffer and detaches it during
commit()orabort(); - an untracked view is the advanced opt-out enabled by opening the
environment with
trackBorrowedViews: false.
The lifetime contract is strict:
- Pass an active read-only transaction created by the same environment. Write transactions are rejected.
- Read the view only before that transaction commits or aborts. A
DataViewdoes not keep its transaction alive, so retain the transaction object explicitly for the whole access period. - Complete every transaction before calling
env.close()orenv.closeSync(). - Do not transfer the view's backing buffer to another JavaScript isolate. If another execution context needs the bytes, copy them into ordinary owned memory while the transaction is still active.
With the default trackBorrowedViews: true, retaining the JavaScript view
object after transaction completion is memory-safe, but its backing buffer is
detached and its data cannot be read. Missing keys return undefined. A stored
empty value has no borrowed MDBX pointer and returns an ordinary zero-length
DataView; there is therefore no buffer to track or detach. The returned bytes
are raw and do not use valueFlag decoding.
The runtime object is a normal DataView, so JavaScript still exposes setter
methods. Treat it as read-only: mutating MDBX-owned mapped bytes is outside the
contract. TypeScript uses MDBX_BorrowedView, which intentionally exposes only
the read methods.
trackBorrowedViews: false removes the transaction's reference tracking and
detach work. It is an unsafe performance option: do not store, return, capture,
or otherwise retain a view; drop every reference and finish all reads before
commit() or abort(). An untracked buffer can still appear attached after
completion even though its memory is no longer valid.
getView() is unavailable when the environment uses MDBX_WRITEMAP. Multiple
JavaScript isolates may load the addon, but each isolate must use its own
wrappers and a separate database path. A borrowed view and its backing buffer
must remain in the isolate that created them.
The reproducible benchmark and measured Linux x64 baseline are documented in PERFORMANCE.md.
del(txn, key) → boolean
const deleted = dbi.del(txn, 123);
has(txn, key) → boolean
const exists = dbi.has(txn, 123);
stat(txn) → Object
const stats = dbi.stat(txn);
// { pageSize: 4096, depth: 1, entries: 10, ... }
forEach(txn, callback)
dbi.forEach(txn, (key, value, index) => {
console.log(`${key}: ${value}`);
return false; // continue iteration (or undefined)
// return true; // stop iteration
});
Note: forEach continues scanning while callback returns
undefinedorfalse, and stops when callback returnstrue.
keys(txn) → Array
// Get all keys
const allKeys = dbi.keys(txn);
keysFrom(txn, startKey, [limit], [cursorMode]) → Array
// Get keys starting from specific key
const keys = dbi.keysFrom(txn, 42, 50); // 50 keys starting from 42
// With cursor mode
const keys = dbi.keysFrom(txn, 42, 50, 'keyGreaterOrEqual');
// BigInt keys
const bigIntKeys = dbi.keysFrom(txn, 42n, 50);
// Key equal mode (for multi-value databases)
const equalKeys = dbi.keysFrom(txn, 5, 10, 'keyEqual');
getRange(txn, [options]) → Array<{ key, value }>
const rows = dbi.getRange(txn, { start: 10, end: 15 });
// [
// { key: 10, value: ... },
// { key: 11, value: ... },
// ...
// ]
getCount(txn, [options]) → number
const total = dbi.getCount(txn, { start: 10, end: 20 });
// 11
keysRange(txn, [options]) → Array
const keys = dbi.keysRange(txn, { start: 10, end: 20, limit: 5 });
// [10, 11, 12, 13, 14]
valuesRange(txn, [options]) → Array
const values = dbi.valuesRange(txn, {
start: 10,
end: 15,
reverse: true,
includeEnd: false
});
// values for keys 14, 13, 12, 11, 10
Range options:
start,end- inclusive bounds by defaultincludeStart,includeEnd- control bound inclusionreverse- scan from upper bound to lower boundlimit- maximum number of returned itemsoffset- skip N items after initial positioninggetCount()ignoresoffsetandlimitand returns the total size of the bounded range
drop(txn, [delete_db]) → void
// Clear database contents (keep structure)
dbi.drop(txn, false);
// Delete database completely
dbi.drop(txn, true);
// Default behavior (clear contents)
dbi.drop(txn);
After a committed drop(txn, true), discard that JavaScript DBI object and
open or create a new one in a new transaction. An already active transaction
may use its old snapshot only until the drop transaction commits; afterward
the old handle reports MDBX_BAD_DBI. Reusing a stale wrapper after another
DBI has reused the same native slot, for example after same-name recreation, is
not supported.
Cursor (MDBX_Cursor)
Cursors provide low-level control for database traversal with positioning and iteration capabilities.
Creating a Cursor
const txn = env.startRead();
const dbi = txn.openMap();
const cursor = txn.openCursor(dbi);
Navigation Methods
first() → {key, value} | undefined
const item = cursor.first();
if (item) {
console.log(item.key, item.value);
}
last() → {key, value} | undefined
const item = cursor.last();
next() → {key, value} | undefined
const item = cursor.next();
prev() → {key, value} | undefined
const item = cursor.prev();
current() → {key, value} | undefined
const item = cursor.current();
Search Methods
seek(key) → {key, value} | undefined
Exact key match. Returns undefined if key not found.
const item = cursor.seek('user:123');
if (item) {
console.log('Found:', item.value);
}
seekGE(key) → {key, value} | undefined
Find first key greater or equal to given key (lower_bound).
const item = cursor.seekGE('user:100');
// Returns first key >= 'user:100'
Modification Methods
put(key, value, [flags])
Insert or update a record at cursor position.
cursor.put('newKey', 'newValue');
// With flags (MDBX_NOOVERWRITE, etc.)
cursor.put('key', 'value', MDBX_Param.putFlag.noOverwrite);
del([flags]) → boolean
Delete record at current cursor position. Returns true if deleted, false if not found.
cursor.seek('keyToDelete');
const deleted = cursor.del();
Control Methods
close()
cursor.close();
Cursor Examples
Iterate all records:
const cursor = txn.openCursor(dbi);
for (let item = cursor.first(); item; item = cursor.next()) {
console.log(item.key, item.value.toString());
}
cursor.close();
txn.abort();
Range iteration:
const cursor = txn.openCursor(dbi);
// Find all keys starting with 'user:'
for (let item = cursor.seekGE('user:'); item; item = cursor.next()) {
if (!item.key.startsWith('user:')) break;
console.log(item.key, item.value.toString());
}
cursor.close();
Pagination:
function getPage(cursor, offset, limit) {
const results = [];
let item = cursor.first();
// Skip offset
for (let i = 0; i < offset && item; i++) {
item = cursor.next();
}
// Collect limit items
for (let i = 0; i < limit && item; i++) {
results.push({ key: item.key, value: item.value.toString() });
item = cursor.next();
}
return results;
}
const txn = env.startRead();
const dbi = txn.openMap();
const cursor = txn.openCursor(dbi);
const page1 = getPage(cursor, 0, 10); // First 10 items
const page2 = getPage(cursor, 10, 10); // Next 10 items
cursor.close();
txn.abort();
Reverse iteration:
const cursor = txn.openCursor(dbi);
for (let item = cursor.last(); item; item = cursor.prev()) {
console.log(item.key, item.value.toString());
}
cursor.close();
Bulk insert with cursor:
const txn = env.startWrite();
const dbi = txn.createMap();
const cursor = txn.openCursor(dbi);
for (let i = 0; i < 1000; i++) {
cursor.put(`key${i}`, `value${i}`);
}
cursor.close();
txn.commit();
Delete with cursor:
const txn = env.startWrite();
const dbi = txn.openMap();
const cursor = txn.openCursor(dbi);
// Delete specific key
if (cursor.seek('keyToDelete')) {
cursor.del();
}
// Delete range
for (let item = cursor.seekGE('prefix:'); item; item = cursor.next()) {
if (!item.key.startsWith('prefix:')) break;
cursor.del();
}
cursor.close();
txn.commit();
Key and Value Types
Key Modes (MDBX_Param.keyMode)
- Default (0) - Buffer keys (no flags, default behavior)
- reverse - Keys sorted in reverse order
- ordinal - Integer keys (4 or 8 bytes, native endian)
Value Modes (MDBX_Param.valueMode)
- single - Single value per key (default)
- multi -
MDBX_DUPSORT, multiple values per key - multiReverse -
MDBX_DUPSORT | MDBX_REVERSEDUP - multiSamelength -
MDBX_DUPSORT | MDBX_DUPFIXED - multiOrdinal -
MDBX_DUPSORT | MDBX_DUPFIXED | MDBX_INTEGERDUP - multiReverseSamelength -
MDBX_DUPSORT | MDBX_REVERSEDUP | MDBX_DUPFIXED
Value Flags (MDBX_Param.valueFlag)
- binary - Raw binary data (default, represented by
0) - string - UTF-8 strings
- number - Numeric decode for ordinal duplicate values
- bigint - BigInt decode for ordinal duplicate values
valueFlag.number and valueFlag.bigint matter primarily for valueMode.multiOrdinal. For ordinary values, use Buffer (default) or valueFlag.string.
Examples
Basic Usage (Synchronous)
const { MDBX_Env, MDBX_Param } = require('mdbxmou');
function syncExample() {
const env = new MDBX_Env();
// Synchronous open
env.openSync({ path: './data' });
// Write transaction
const writeTxn = env.startWrite();
const dbi = writeTxn.createMap(MDBX_Param.keyMode.ordinal);
for (let i = 0; i < 1000; i++) {
dbi.put(writeTxn, i, `value_${i}`);
}
writeTxn.commit();
// Read transaction with number keys
const readTxn = env.startRead();
const readDbi = readTxn.openMap(MDBX_Param.keyMode.ordinal); // keys as numbers
const value = readDbi.get(readTxn, 42);
console.log(value); // "value_42"
// Iterate with cursor
readDbi.forEach(readTxn, (key, value, index) => {
console.log(`Key ${key} (type: ${typeof key}): ${value}`); // key is number
return index >= 9; // stop after 10 items (indices 0-9)
});
// Get specific keys
const someKeys = readDbi.keysFrom(readTxn, 100, 50);
console.log(`Keys 100-149:`, someKeys); // array of numbers
readTxn.commit();
// Synchronous close
env.closeSync();
}
async function asyncExample() {
const env = new MDBX_Env();
await env.open({ path: './data-async' });
// Write transaction
const writeTxn = env.startWrite();
const dbi = writeTxn.createMap(MDBX_Param.keyMode.ordinal);
for (let i = 0; i < 1000; i++) {
dbi.put(writeTxn, i, `value_${i}`);
}
writeTxn.commit();
// Read transaction with BigInt keys
const readTxn = env.startRead();
const readDbi = readTxn.openMap(BigInt(MDBX_Param.keyMode.ordinal)); // keys as BigInts
const value = readDbi.get(readTxn, 42);
console.log(value); // "value_42"
// Iterate with BigInt keys
readDbi.forEach(readTxn, (key, value, index) => {
console.log(`Key ${key} (type: ${typeof key}): ${value}`); // key is bigint
return index >= 9; // stop after 10 items (indices 0-9)
});
// Get BigInt keys
const bigIntKeys = readDbi.keysFrom(readTxn, 100n, 50);
console.log(`Keys 100n-149n:`, bigIntKeys); // array of BigInts
readTxn.commit();
await env.close();
}
syncExample();
asyncExample().catch(console.error);
Key Type Behavior
const { MDBX_Env, MDBX_Param } = require('mdbxmou');
function keyTypesExample() {
const env = new MDBX_Env();
env.openSync({ path: './key-types' });
const txn = env.startWrite();
const dbi = txn.createMap(MDBX_Param.keyMode.ordinal);
// Store some data
dbi.put(txn, 1, "one");
dbi.put(txn, 2, "two");
dbi.put(txn, 3, "three");
txn.commit();
// Read with number keyMode
const readTxn1 = env.startRead();
const numberDbi = readTxn1.openMap(MDBX_Param.keyMode.ordinal);
numberDbi.forEach(readTxn1, (key, value) => {
console.log(`Number key: ${key} (${typeof key})`); // number
// return undefined; // continue iteration (default)
});
readTxn1.commit();
// Read with BigInt keyMode
const readTxn2 = env.startRead();
const bigintDbi = readTxn2.openMap(BigInt(MDBX_Param.keyMode.ordinal));
bigintDbi.forEach(readTxn2, (key, value) => {
console.log(`BigInt key: ${key} (${typeof key})`); // bigint
// return false; // continue iteration
});
readTxn2.commit();
env.closeSync();
}
keyTypesExample();
Cursor Operations
const { MDBX_Env, MDBX_Param } = require('mdbxmou');
function cursorExample() {
const env = new MDBX_Env();
env.openSync({ path: './cursor-data' });
const txn = env.startWrite();
const dbi = txn.createMap(MDBX_Param.keyMode.ordinal);
// Store test data
for (let i = 0; i < 100; i++) {
dbi.put(txn, i, `value_${i}`);
}
txn.commit();
const readTxn = env.startRead();
const readDbi = readTxn.openMap(MDBX_Param.keyMode.ordinal);
// Get all keys
const allKeys = readDbi.keys(readTxn);
console.log(`Total keys: ${allKeys.length}`);
// Get limited keys - use keysFrom with limit
const firstTen = readDbi.keysFrom(readTxn, 0, 10);
console.log(`First 10 keys:`, firstTen);
// Get keys from specific position
const fromFifty = readDbi.keysFrom(readTxn, 50, 20);
console.log(`Keys 50-69:`, fromFifty);
// Reverse iteration - need manual logic or forEach
const allKeysForReverse = readDbi.keys(readTxn);
const lastTen = allKeysForReverse.slice(-10).reverse();
console.log(`Last 10 keys:`, lastTen);
// Manual iteration with forEach
let count = 0;
readDbi.forEach(readTxn, (key, value, index) => {
if (key >= 80) {
console.log(`Key ${key}: ${value}`);
count++;
}
return count >= 5; // stop after 5 items >= 80
});
readTxn.commit();
env.closeSync();
}
cursorExample();
Range Queries
const { MDBX_Env, MDBX_Param } = require('mdbxmou');
function rangeExample() {
const env = new MDBX_Env();
env.openSync({
path: './range-data',
valueFlag: MDBX_Param.valueFlag.string
});
const writeTxn = env.startWrite();
const dbi = writeTxn.createMap(MDBX_Param.keyMode.ordinal);
for (let i = 0; i < 10; i++) {
dbi.put(writeTxn, i, `value_${i}`);
}
writeTxn.commit();
const readTxn = env.startRead();
const readDbi = readTxn.openMap(MDBX_Param.keyMode.ordinal);
const rows = readDbi.getRange(readTxn, { start: 3, end: 6 });
console.log(rows);
// [
// { key: 3, value: 'value_3' },
// { key: 4, value: 'value_4' },
// { key: 5, value: 'value_5' },
// { key: 6, value: 'value_6' }
// ]
const total = readDbi.getCount(readTxn, { start: 3, end: 8 });
console.log(total); // 6
const keys = readDbi.keysRange(readTxn, {
start: 3,
end: 8,
offset: 1,
limit: 3
});
console.log(keys); // [4, 5, 6]
const values = readDbi.valuesRange(readTxn, {
start: 3,
end: 6,
reverse: true,
includeEnd: false
});
console.log(values); // ['value_5', 'value_4', 'value_3']
readTxn.commit();
env.closeSync();
}
rangeExample();
MultiOrdinal Values
const { MDBX_Env, MDBX_Param } = require('mdbxmou');
function multiOrdinalExample() {
const env = new MDBX_Env();
env.openSync({ path: './multi-ordinal-data' });
const writeTxn = env.startWrite();
const dbi = writeTxn.createMap({
name: 'dup-ids',
keyMode: MDBX_Param.keyMode.ordinal,
valueMode: MDBX_Param.valueMode.multiOrdinal
});
dbi.put(writeTxn, 5, 30);
dbi.put(writeTxn, 5, 10);
dbi.put(writeTxn, 5, 20n);
writeTxn.commit();
const readTxn = env.startRead();
const readDbi = readTxn.openMap({
name: 'dup-ids',
keyMode: MDBX_Param.keyMode.ordinal,
valueMode: MDBX_Param.valueMode.multiOrdinal
});
console.log(readDbi.get(readTxn, 5)); // 10
console.log(readDbi.valuesRange(readTxn, { start: 5, end: 5 })); // [10, 20, 30]
readTxn.commit();
env.closeSync();
}
multiOrdinalExample();
Query API (Advanced Async)
const { MDBX_Env, MDBX_Param } = require('mdbxmou');
async function queryExample() {
const env = new MDBX_Env();
await env.open({ path: './query-data' });
// Create DBI first in synchronous transaction
const writeTxn = env.startWrite();
const dbi = writeTxn.createMap(MDBX_Param.keyMode.ordinal);
writeTxn.commit();
// Async query with DBI object (not database name)
const results = await env.query([
{
dbi,
mode: MDBX_Param.queryMode.upsert,
putFlag: MDBX_Param.putFlag.noOverwrite,
item: [
{ key: 1, value: JSON.stringify({ name: "Alice" }) },
{ key: 2, value: JSON.stringify({ name: "Bob" }) }
]
},
{
dbi,
mode: MDBX_Param.queryMode.get,
item: [
{ key: 1 },
{ key: 2 }
]
}
]);
console.log('Query results:', JSON.stringify(results, null, 2));
await env.close();
}
queryExample().catch(console.error);
Async Keys API
const { MDBX_Env, MDBX_Param } = require('mdbxmou');
async function keysExample() {
const env = new MDBX_Env();
await env.open({ path: './keys-data' });
// Create DBI first
const writeTxn = env.startWrite();
const dbi = writeTxn.createMap(MDBX_Param.keyMode.ordinal);
// Add some test data
for (let i = 1; i <= 10; i++) {
dbi.put(writeTxn, i, `value-${i}`);
}
writeTxn.commit();
// Get all keys from DBI
const allKeys = await env.keys(dbi);
console.log("All keys:", allKeys);
// Get keys with DBI object parameter
const allKeys2 = await env.keys({ dbi });
console.log("All keys (object):", allKeys2);
// Get keys from multiple DBIs
const multiKeys = await env.keys([dbi, dbi]);
console.log("Multi DBI keys:", multiKeys);
// Get limited keys from specific position
const limitedKeys = await env.keys([
{ dbi, limit: 3, from: 5 }
]);
console.log("Limited keys:", limitedKeys);
await env.close();
}
keysExample().catch(console.error);
Error Handling
async function errorHandlingExample() {
let txn;
try {
const env = new MDBX_Env();
await env.open({ path: './data' });
txn = env.startWrite();
const dbi = txn.createMap(MDBX_Param.keyMode.ordinal);
// This might throw if key already exists with MDBX_NOOVERWRITE
dbi.put(txn, 123, "value");
txn.commit();
} catch (error) {
console.error('Database error:', error.message);
if (txn) txn.abort();
}
}
errorHandlingExample().catch(console.error);
Runnable README Tests
The examples above have matching runnable tests in test/. You can execute them directly:
node test/readme-quick-start.js
node test/readme-sync-example.js
node test/readme-async-example.js
node test/readme-key-types.js
node test/readme-cursor-example.js
node test/readme-range-example.js
node test/readme-query-example.js
node test/readme-keys-example.js
node test/readme-error-handling.js
Configuration Options
Available Constants (MDBX_Param)
const { MDBX_Param } = require('mdbxmou');
// Key modes
MDBX_Param.keyMode.reverse // MDBX_REVERSEKEY - reverse key order
MDBX_Param.keyMode.ordinal // MDBX_INTEGERKEY - integer keys (use with number/bigint)
// Default (0) - Buffer keys (no flags)
// Key flags (optional, control key representation)
MDBX_Param.keyFlag.string // UTF-8 string encoding
MDBX_Param.keyFlag.number // Number type (used with ordinal mode)
MDBX_Param.keyFlag.bigint // BigInt type (used with ordinal mode)
// Default - Buffer representation
// Value modes
MDBX_Param.valueMode.multi // MDBX_DUPSORT
MDBX_Param.valueMode.multiReverse // MDBX_DUPSORT | MDBX_REVERSEDUP
MDBX_Param.valueMode.multiSamelength // MDBX_DUPSORT | MDBX_DUPFIXED
MDBX_Param.valueMode.multiOrdinal // MDBX_DUPSORT | MDBX_DUPFIXED | MDBX_INTEGERDUP
MDBX_Param.valueMode.multiReverseSamelength // MDBX_DUPSORT | MDBX_REVERSEDUP | MDBX_DUPFIXED
// Value flags (optional, control value representation)
MDBX_Param.valueFlag.string // UTF-8 string values
MDBX_Param.valueFlag.number // Number values for multiOrdinal
MDBX_Param.valueFlag.bigint // BigInt values for multiOrdinal
// Default - Buffer representation
Note: For ordinal (integer) keys, use keyFlag.number or keyFlag.bigint to specify the data type.
For `valueMode.multiOrdinal`, values are returned as `number` by default, or as `bigint` when `valueFlag.bigint` is used.
Environment Flags
MDBX_Param.envFlag.nostickythreads- Don't stick reader transactions to threadsMDBX_Param.envFlag.rdonly- Open database in read-only modeMDBX_Param.envFlag.validation- Enable page validationMDBX_Param.envFlag.exclusive- Exclusive modeMDBX_Param.envFlag.accede- Open existing environmentMDBX_Param.envFlag.writemap- Use writable memory mapMDBX_Param.envFlag.nordahead- Disable OS readaheadMDBX_Param.envFlag.nomeminit- Disable memory initializationMDBX_Param.envFlag.liforeclaim- LIFO reclaimMDBX_Param.envFlag.nometasync- Disable metadata flushesMDBX_Param.envFlag.safeNosync- Safe nosync modeMDBX_Param.envFlag.utterlyNosync- Utterly nosync mode
Environment Options
MDBX_Param.envOption.maxDb- RawMDBX_opt_max_dboption idMDBX_Param.envOption.maxReaders- RawMDBX_opt_max_readersoption idMDBX_Param.envOption.syncBytes- Threshold for forced flushes in weak sync modesMDBX_Param.envOption.syncPeriod- Sync period in seconds when used withsetOption()MDBX_Param.envOption.rpAugmentLimit- RawMDBX_opt_rp_augment_limitMDBX_Param.envOption.looseLimit- RawMDBX_opt_loose_limitMDBX_Param.envOption.dpReserveLimit- RawMDBX_opt_dp_reserve_limitMDBX_Param.envOption.txnDpLimit- RawMDBX_opt_txn_dp_limitMDBX_Param.envOption.txnDpInitial- RawMDBX_opt_txn_dp_initialMDBX_Param.envOption.spillMaxDenominator- RawMDBX_opt_spill_max_denominatorMDBX_Param.envOption.spillMinDenominator- RawMDBX_opt_spill_min_denominatorMDBX_Param.envOption.spillParent4childDenominator- RawMDBX_opt_spill_parent4child_denominatorMDBX_Param.envOption.mergeThreshold- RawMDBX_opt_merge_thresholdMDBX_Param.envOption.mergeThreshold16dot16Percent- Backward-compatible alias forMDBX_opt_merge_thresholdMDBX_Param.envOption.writethroughThreshold- RawMDBX_opt_writethrough_thresholdMDBX_Param.envOption.prefaultWriteEnable- RawMDBX_opt_prefault_write_enableMDBX_Param.envOption.gcTimeLimit- RawMDBX_opt_gc_time_limitMDBX_Param.envOption.preferWafInsteadofBalance- RawMDBX_opt_prefer_waf_insteadof_balanceMDBX_Param.envOption.subpageLimit- RawMDBX_opt_subpage_limitMDBX_Param.envOption.subpageRoomThreshold- RawMDBX_opt_subpage_room_thresholdMDBX_Param.envOption.subpageReservePrereq- RawMDBX_opt_subpage_reserve_prereqMDBX_Param.envOption.subpageReserveLimit- RawMDBX_opt_subpage_reserve_limitMDBX_Param.envOption.splitReserve- RawMDBX_opt_split_reserve
Copy Flags
MDBX_Param.copyFlag.defaults- RawMDBX_CP_DEFAULTSMDBX_Param.copyFlag.compact- RawMDBX_CP_COMPACTMDBX_Param.copyFlag.overwrite- RawMDBX_CP_OVERWRITE
Database Modes
MDBX_Param.dbMode.create- Create database if it doesn't existMDBX_Param.dbMode.accede- Open existing database with any flags
Query Modes
MDBX_Param.queryMode.get- Read operationsMDBX_Param.queryMode.upsert- Base write mode (insert or update)MDBX_Param.queryMode.update- Base write mode withMDBX_CURRENTMDBX_Param.queryMode.insertUnique- Base write mode withMDBX_NOOVERWRITEMDBX_Param.queryMode.del- Delete operations
Put Flags
MDBX_Param.putFlag.noOverwrite-MDBX_NOOVERWRITEMDBX_Param.putFlag.noDupData-MDBX_NODUPDATAMDBX_Param.putFlag.current-MDBX_CURRENTMDBX_Param.putFlag.allDups-MDBX_ALLDUPSMDBX_Param.putFlag.reserve-MDBX_RESERVEMDBX_Param.putFlag.append-MDBX_APPENDMDBX_Param.putFlag.appendDup-MDBX_APPENDDUPMDBX_Param.putFlag.multiple-MDBX_MULTIPLE
For env.query() write requests, only noOverwrite, noDupData, current, append, and appendDup are supported.
Cursor Modes
MDBX_Param.cursorMode.first- First keyMDBX_Param.cursorMode.last- Last keyMDBX_Param.cursorMode.next- Next keyMDBX_Param.cursorMode.prev- Previous keyMDBX_Param.cursorMode.keyLesserThan- Keys less than targetMDBX_Param.cursorMode.keyLesserOrEqual- Keys less than or equal to targetMDBX_Param.cursorMode.keyEqual- Keys exactly equal to targetMDBX_Param.cursorMode.keyGreaterOrEqual- Keys greater than or equal to targetMDBX_Param.cursorMode.keyGreaterThan- Keys greater than target
Note: Cursor modes can be used with
keysFrom()andforEach()methods to control iteration direction and filtering.
Performance Tips
- Use ordinal keys for integer data - much faster than string keys
- Batch operations - Use query API for bulk operations
- Reuse transactions - Keep read transactions open for multiple operations
- Memory mapping - MDBX uses memory-mapped files for fast I/O
- Transaction scope - Always pass transaction object to DBI methods
- Zero-copy reads - Use
getView()for large values when processing stays inside a short read transaction; benchmark againstget()for your access pattern before disabling borrowed-view tracking. See PERFORMANCE.md for the published methodology and baseline.
License
Apache License 2.0
Documentation generated by GitHub Copilot (Claude 3.5 Sonnet) on August 27, 2025
Reviewed and approved by the library author