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 path
  • flags - Environment flags (optional, defaults to 0)
  • keyFlag - Default key encoding for all operations (optional, defaults to Buffer)
    • Only string can be set (ordinal mode uses number/bigint separately)
  • valueFlag - Default value encoding for all operations (optional, defaults to Buffer)
    • string affects normal string values
    • number and bigint are meaningful for valueMode.multiOrdinal
  • maxDbi - Maximum number of databases (optional, default 32)
  • mode - Filesystem permissions mode (optional, default 0664)
  • geometry - Map size/geometry options (optional)
  • trackBorrowedViews - Track and detach non-empty buffers borrowed by getView() when their transaction completes (optional, default true; 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 valueString/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 typeOrdinal keys returned as
NumberNumber
BigIntBigInt
// 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 typeConfiguration levelOptionResult type
String/Binaryenv.open()keyFlag: 0Buffer
String/Binaryenv.open()keyFlag: keyFlag.stringString
OrdinalopenMap/createMapkeyMode.ordinal (Number)Number
OrdinalopenMap/createMapBigInt(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: syncPeriod is passed in seconds and may be fractional. Other options are passed as non-negative integer values. Note: maxDb and maxReaders are pre-open options. Set them through open/openSync (maxDbi, maxReaders) instead of setOption().

syncEx(force, nonblock) → number

env.syncEx(true, false);

startWrite() → Transaction

const txn = env.startWrite();

startRead() → Transaction

const txn = env.startRead();

query(requests) → Promise (Async batch operations)

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 createMap in 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 openMap in read transactions or when you're sure the database already exists. For write transactions on new environments, prefer createMap.

Note: When using ordinal keyMode, the key type in results depends on how you specify keyMode:

  • keyMode: number → keys returned as number
  • keyMode: BigInt(number) → keys returned as BigInt
  • When you pass keyMode.ordinal as a positional argument (Number/BigInt), it also updates keyFlag to number/bigint unless a numeric keyFlag was already set in env or explicitly provided.

Note: When valueMode.multiOrdinal is used and valueFlag is not specified, values are returned as number by default. Set valueFlag: MDBX_Param.valueFlag.bigint if you need BigInt on 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 for get(). 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 DataView whose bytes are owned by the active MDBX read transaction;
  • its backing buffer is the ArrayBuffer available as view.buffer;
  • detach invalidates that backing buffer. A detached buffer has byteLength === 0, and reading through its DataView is 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() or abort();
  • an untracked view is the advanced opt-out enabled by opening the environment with trackBorrowedViews: false.

The lifetime contract is strict:

  1. Pass an active read-only transaction created by the same environment. Write transactions are rejected.
  2. Read the view only before that transaction commits or aborts. A DataView does not keep its transaction alive, so retain the transaction object explicitly for the whole access period.
  3. Complete every transaction before calling env.close() or env.closeSync().
  4. 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 undefined or false, and stops when callback returns true.

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 default
  • includeStart, includeEnd - control bound inclusion
  • reverse - scan from upper bound to lower bound
  • limit - maximum number of returned items
  • offset - skip N items after initial positioning
  • getCount() ignores offset and limit and 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);

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 threads
  • MDBX_Param.envFlag.rdonly - Open database in read-only mode
  • MDBX_Param.envFlag.validation - Enable page validation
  • MDBX_Param.envFlag.exclusive - Exclusive mode
  • MDBX_Param.envFlag.accede - Open existing environment
  • MDBX_Param.envFlag.writemap - Use writable memory map
  • MDBX_Param.envFlag.nordahead - Disable OS readahead
  • MDBX_Param.envFlag.nomeminit - Disable memory initialization
  • MDBX_Param.envFlag.liforeclaim - LIFO reclaim
  • MDBX_Param.envFlag.nometasync - Disable metadata flushes
  • MDBX_Param.envFlag.safeNosync - Safe nosync mode
  • MDBX_Param.envFlag.utterlyNosync - Utterly nosync mode

Environment Options

  • MDBX_Param.envOption.maxDb - Raw MDBX_opt_max_db option id
  • MDBX_Param.envOption.maxReaders - Raw MDBX_opt_max_readers option id
  • MDBX_Param.envOption.syncBytes - Threshold for forced flushes in weak sync modes
  • MDBX_Param.envOption.syncPeriod - Sync period in seconds when used with setOption()
  • MDBX_Param.envOption.rpAugmentLimit - Raw MDBX_opt_rp_augment_limit
  • MDBX_Param.envOption.looseLimit - Raw MDBX_opt_loose_limit
  • MDBX_Param.envOption.dpReserveLimit - Raw MDBX_opt_dp_reserve_limit
  • MDBX_Param.envOption.txnDpLimit - Raw MDBX_opt_txn_dp_limit
  • MDBX_Param.envOption.txnDpInitial - Raw MDBX_opt_txn_dp_initial
  • MDBX_Param.envOption.spillMaxDenominator - Raw MDBX_opt_spill_max_denominator
  • MDBX_Param.envOption.spillMinDenominator - Raw MDBX_opt_spill_min_denominator
  • MDBX_Param.envOption.spillParent4childDenominator - Raw MDBX_opt_spill_parent4child_denominator
  • MDBX_Param.envOption.mergeThreshold - Raw MDBX_opt_merge_threshold
  • MDBX_Param.envOption.mergeThreshold16dot16Percent - Backward-compatible alias for MDBX_opt_merge_threshold
  • MDBX_Param.envOption.writethroughThreshold - Raw MDBX_opt_writethrough_threshold
  • MDBX_Param.envOption.prefaultWriteEnable - Raw MDBX_opt_prefault_write_enable
  • MDBX_Param.envOption.gcTimeLimit - Raw MDBX_opt_gc_time_limit
  • MDBX_Param.envOption.preferWafInsteadofBalance - Raw MDBX_opt_prefer_waf_insteadof_balance
  • MDBX_Param.envOption.subpageLimit - Raw MDBX_opt_subpage_limit
  • MDBX_Param.envOption.subpageRoomThreshold - Raw MDBX_opt_subpage_room_threshold
  • MDBX_Param.envOption.subpageReservePrereq - Raw MDBX_opt_subpage_reserve_prereq
  • MDBX_Param.envOption.subpageReserveLimit - Raw MDBX_opt_subpage_reserve_limit
  • MDBX_Param.envOption.splitReserve - Raw MDBX_opt_split_reserve

Copy Flags

  • MDBX_Param.copyFlag.defaults - Raw MDBX_CP_DEFAULTS
  • MDBX_Param.copyFlag.compact - Raw MDBX_CP_COMPACT
  • MDBX_Param.copyFlag.overwrite - Raw MDBX_CP_OVERWRITE

Database Modes

  • MDBX_Param.dbMode.create - Create database if it doesn't exist
  • MDBX_Param.dbMode.accede - Open existing database with any flags

Query Modes

  • MDBX_Param.queryMode.get - Read operations
  • MDBX_Param.queryMode.upsert - Base write mode (insert or update)
  • MDBX_Param.queryMode.update - Base write mode with MDBX_CURRENT
  • MDBX_Param.queryMode.insertUnique - Base write mode with MDBX_NOOVERWRITE
  • MDBX_Param.queryMode.del - Delete operations

Put Flags

  • MDBX_Param.putFlag.noOverwrite - MDBX_NOOVERWRITE
  • MDBX_Param.putFlag.noDupData - MDBX_NODUPDATA
  • MDBX_Param.putFlag.current - MDBX_CURRENT
  • MDBX_Param.putFlag.allDups - MDBX_ALLDUPS
  • MDBX_Param.putFlag.reserve - MDBX_RESERVE
  • MDBX_Param.putFlag.append - MDBX_APPEND
  • MDBX_Param.putFlag.appendDup - MDBX_APPENDDUP
  • MDBX_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 key
  • MDBX_Param.cursorMode.last - Last key
  • MDBX_Param.cursorMode.next - Next key
  • MDBX_Param.cursorMode.prev - Previous key
  • MDBX_Param.cursorMode.keyLesserThan - Keys less than target
  • MDBX_Param.cursorMode.keyLesserOrEqual - Keys less than or equal to target
  • MDBX_Param.cursorMode.keyEqual - Keys exactly equal to target
  • MDBX_Param.cursorMode.keyGreaterOrEqual - Keys greater than or equal to target
  • MDBX_Param.cursorMode.keyGreaterThan - Keys greater than target

Note: Cursor modes can be used with keysFrom() and forEach() methods to control iteration direction and filtering.

Performance Tips

  1. Use ordinal keys for integer data - much faster than string keys
  2. Batch operations - Use query API for bulk operations
  3. Reuse transactions - Keep read transactions open for multiple operations
  4. Memory mapping - MDBX uses memory-mapped files for fast I/O
  5. Transaction scope - Always pass transaction object to DBI methods
  6. Zero-copy reads - Use getView() for large values when processing stays inside a short read transaction; benchmark against get() 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