olmdb
Version:
Optimistic LMDB. A very fast embedded key/value store featuring ACID optimistic read/write-transactions, based on LMDB.
917 lines (581 loc) • 34.8 kB
Markdown
# OLMDB - Optimistic LMDB
**A very fast embedded key/value store featuring ACID transactions using optimistic locking, based on LMDB.**
OLMDB is a high-performance, embedded on-disk key/value store that combines the speed and reliability of LMDB with optimistic concurrency control and fast batched commits. It provides ACID transactions with automatic retries for handling concurrent access patterns, making it ideal for applications that need performance, consistency and parallel (long running) read/write transactions.
**Features:**
- 🚀 **High Performance**: Built on LMDB, one of the fastest embedded databases, providing zero-copy data access. Read/write commits are automatically batched to enhance throughput.
- 🔒 **ACID Transactions**: Full transaction support with optimistic locking and built-in retry for race conditions.
- 📦 **Zero Dependencies**: Minimal footprint, running on both Node and Bun. No server process to manage.
- 🔧 **Simple API**: Near-instantaneous synchronous database reads and updates. Promise-based commits.
Compared to [lmdb-js](https://github.com/kriszyp/lmdb-js) (which I only learned about *after* having finished this project 😅), OLMDB...
- Uses optimistic locking to allow long-running parallel read/write transactions.
- Can aggregate write-operations from multiple processes into a single LMDB transaction, improving write throughput.
- Does not serialize your data for you, only working on binary data and strings.
- Is not as battle-hardened.
**Important caveat**: OLMDB (probably) only runs on Linux currently.
## Quick Demo
```typescript
import { transact, put, getString, del, scan, asString } from 'olmdb';
async function main() {
// Basic operations (database auto-initializes)
const result = await transact(() => {
// Store data
put('user:1', JSON.stringify({ name: 'Alice', age: 30 }));
put('user:2', JSON.stringify({ name: 'Bob', age: 25 }));
put('user:3', JSON.stringify({ name: 'Charlie', age: 59 }));
// Read back (uncommitted transaction-local) data
const user = JSON.parse(getString('user:1') || '{}');
console.log(user); // { name: 'Alice', age: 30 }
// Delete data
del('user:2');
return user;
});
// Scan through data, starting at 'user:2' (meaning 'user:3' comes first as 'user:2' does not exist)
await transact(() => {
for (const { key, value } of scan({start: 'user:2'})) {
console.log(key, value);
}
});
}
main().catch(console.error);
```
## Installation
```bash
npm install olmdb
# or
bun add olmdb
```
**Requirements:**
- Linux (contributions to support other platforms are very welcome!)
- Node.js 14+ or Bun runtime
- GCC for native module compilation
## High-level API Tutorial
### Initialization
The database auto-initializes on first use, but you can explicitly initialize it specifying a directory for the database file.
```typescript
import { init } from 'olmdb';
init('./my-app-data'); // Custom data directory
```
The call to `init` may happen only once, and should be done before the first call to `transact`.
Without an explicit directory, the `OLMDB_DIR` environment variable is used or if that isn't set `./.olmdb` is used.
### Basic Operations
All database operations must be performed within a transaction:
```typescript
import { transact, put, get, getString, getBuffer, del } from 'olmdb';
let promise = transact(() => {
// Store different data types
put('string-key', 'string-value');
put('buffer-key', new Uint8Array([1, 2, 3, 4]));
put('json-key', JSON.stringify({ complex: 'data' }));
// Read data with different return types
const asBytes = get('string-key'); // Uint8Array
const asString = getString('string-key'); // "string-value"
const asBuffer = getBuffer('string-key'); // ArrayBuffer (zero-copy!)
// Delete entries
del('unwanted-key');
return asString;
});
// The `promise` resolves to the value that our function returned (`asString`).
```
### Data Type Handling
OLMDB provides convenience methods for different data types with zero-copy ArrayBuffer operations:
```typescript
import { transact, put, get, getString, getBuffer } from 'olmdb';
await transact(() => {
// Store any data type
put('text', 'Hello World');
put('binary', new Uint8Array([0xFF, 0xEE, 0xDD]));
// Read as different types
const asBytes = get('text'); // Uint8Array (zero-copy!)
const asBuffer = getBuffer('text'); // ArrayBuffer (zero-copy!)
const asString = getString('text'); // "Hello World" (decoded)
console.log(asBytes, asBuffer, asString);
});
```
Zero-copy means that the `Uint8Array` or `ArrayBuffer` directly points at LMDB's memory mapped data, so it'll be very fast even for large keys and values. Note that keys can only be 511 bytes long, while values can be up to 4 gigabytes.
**However**, the data is only guaranteed to be valid *for the duration of the transaction*! If you were to read from a `Uint8Array` or `ArrayBuffer` some time after the function you provided to `transact()` has finished, you may get back garbage/unrelated data (as LMDB pages and commit buffers are being recycled)!! So don't do that!
I'm considering marking all data buffers returned within a transaction as 'detached' when commit is called (so that any later access will throw an exception), but this involves a fair amount of bookkeeping and thus performance. I'll try this once we have a good way to measure performance.
### Iteration
```typescript
import { transact, scan, asString, asBuffer } from 'olmdb';
transact(() => {
// Scan all entries
for (const { key, value } of scan()) {
console.log(`Key: ${asString(key.buffer)}, Value: ${asString(value.buffer)}`);
}
// Scan a specific range, with type conversion
for (const { key, value } of scan({
start: 'user:',
end: 'user;', // Note: ';' comes after ':' in ASCII
keyConvert: asString,
valueConvert: asString
})) {
console.log(`User: ${key} = ${value}`);
}
// Reverse iteration
for (const { key, value } of scan({
reverse: true,
keyConvert: asString,
valueConvert: asString
})) {
console.log(`${key}: ${value}`);
}
// Collection helper methods
let xs = scan({keyConvert: asString}).map((kv) => kv.key.includes('x')).toArray();
console.log(xs);
// Manual iterator control
const iterator = scan({start: 'prefix:'});
const first = iterator.next();
if (!first.done) {
console.log('First entry:', first.value);
}
// Closing iterators when not iterating them till the end is optional,
// as they will auto-close on transaction end, but recommended if many
// iterators are created (in a loop).
iterator.close();
});
```
### Transaction retries
```typescript
import { transact, put, getString } from 'olmdb';
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function main() {
await transact(() => {
put('counter', '42');
});
const result1 = transact(async () => {
// This will be retried up to 6 times if another transaction
// modifies the same data concurrently
const value = parseInt(getString('counter') || '0') + 1;
console.log('transaction1: put counter', value);
await sleep(1000);
put('counter', value.toString());
return value;
});
const result2 = transact(async () => {
await sleep(500);
const value = parseInt(getString('counter') || '0') + 10;
console.log('transaction2: put counter', value);
put('counter', value.toString());
return value;
});
console.log(await Promise.all([result1, result2]));
}
main();
```
Expected output:
```
transaction1: put counter 43
transaction2: put counter 52
transaction1: put counter 53
[53, 52]
```
What this demonstrates is a race condition, gracefully handled by a retry:
- The result1 transaction will read `counter` and then wait for a second, before writing back an updated value.
- In the meantime, the result2 transaction will have read and updated the `counter`.
- On write-commit, the result1 transaction notices that `counter` has changed since it has read it, invalidating the transaction, and running the provided function again. This time it reads 52 instead of 42.
OLMDB retries a transaction 6 times before giving up (and throwing an exception).
## Performance
Performance seems to be predictably strong, for various loads and data sizes.
To get some idea, on my mediocre laptop with a store prefilled with 1 million values of 100 bytes each, I get:
1) 1 read + 0 writes per txn, 1 process, 1 task per process: 420,000 txn/s.
2) 1 read + 0 writes per txn, 16 processes, 1 task per process: 2,495,000 txn/s.
3) 10 reads + 0 writes per txn, 1 process, 1 task per process: 67,000 txn/s.
4) 10 reads + 0 writes per txn, 16 processes, 1 task per process: 403,000 txn/s.
5) 10 reads + 2 writes per txn, 1 process, 1 task per process: 100 txn/s.
6) 10 reads + 2 writes per txn, 1 process, 1000 tasks per process: 6,000 txn/s.
7) 10 reads + 2 writes per txn, 16 processes, 1000 tasks per process: 16,000 txn/s.
Scaling up the number of tasks per process wouldn't improve performance for read-only transactions, as they're fully synchronous anyway. The performance for 5) is pretty bad, but as expected, as each transaction requires a full sync() to disk (apparently taking about 10ms on my system) before starting the next transaction.
## Architecture
### Components
OLMDB consists of five parts, each building on the one below:
- The high-level TypeScript API (`olmdb.ts`) for this library.
- The low-level TypeScript API (`lowlevel.ts`) that loads and exposes the API provided by the NAPI module.
- The NAPI module (`napi.c`) that provides a low-level JavaScript API for the OLMDB client library.
- The OLMDB client library (`transaction_client.c`) that provides optimistic read/write transactions on top of LMDB. It has no relation to JavaScript. Database reads are performed synchronously. Commits for read/write transactions are performed asyncronously by the commit worker daemon.
- The commit worker daemon (`commit_worker.c`), shared by all clients for a database and spawned automatically by the first client. It processes queued read/write transactions and commits them to LMDB.
### Transactions
The native module manages transactions (start, commit, abort), and all read (get, scan) and write (put, del) operations must happen within such a transaction. Multiple transactions can run simultaneously from the same JavaScript process, allowing work for different clients to be interleaved (using async/await or callbacks).
### Reads
For each OLMDB transaction, a corresponding read-only LMDB transaction is created. That gives us a consistent snapshot view of the store, from which all reads (get, scan) are done. For every read from LMDB, we log into memory associated with the transaction which key was read and a checksum of its value. We'll use this for commits, as explained below.
### Writes
When writes (put, del) are performed, they are initially only stored in-memory in a buffer local to this transaction. Any further reads will first search the buffer, before consulting the underlying LMDB store, such that a transaction can read back its own writes. The buffer is organized as a skiplist, so that searching the buffer is very fast even when a transaction contains many writes.
### Aborts and commits
If a transaction is aborted, the write buffer is just discarded and nothing is saved to LMDB. In case of a read-only transaction commit, we do the same. This happens synchronously. For read/write commits, we queue the transaction, including its read and write buffers for processing by the *commit worker daemon* (explained below). Once a commit has been processed, its callback function is invoked with the commit status as an argument (from the main thread).
### Commit worker daemon
The commit worker daemon is a single-threaded process that runs in the background, processing queued read/write transactions. It is spawned automatically by the first OLMDB client that accesses the database, and it will run until 10s after the last client has disconnected. Clients connect to the daemon using a unix domain socket (in the abstract socket
namespace). Clients store their transaction buffers in a shared memory region, which is also opened by the commit worker daemon. The socket is only used to communicate the address of each transaction to be committed to the daemon, and to signal "at least one transaction has been processed" back to the client.
Once one or more read/write commits have been queued, the write-commit thread will start a read/write LMDB transaction. Note that LMDB only allows for a single read/write transaction at a time, so that's why we do this from a single thread and we try to process transactions as quickly as we can.
Within a single LMDB transaction, we can run a bunch of OLMDB transactions as follows:
1. First replay all of the *reads* the transaction has performed (and stored in the read buffer) in its initial phase, verifying that the results are still the same based on the stored checksums. If not, the transaction has been raced by another transaction. Writes for this transaction will not be performed, and the 'raced' status is communicated back to committing code.
2. All of the writes are performed on LMDB. This should never fail, under normal circumstances. The 'success' status is communicated back to the committing code.
After a batch of OLMDB transactions has been processed, the LMDB transaction is committed, and work can be started on the next batch. Batching commits can improve write performance immensely, as writes to pages in the top regions of b-tree and file syncs can usually be shared between transactions.
### Retryable transactions
The high-level API, written in TypeScript provides a `transact(func)` method that runs a function within the context of an OLMDB transaction. This transaction context is preserved in [AsyncLocalStorage](https://nodejs.org/api/async_context.html#class-asynclocalstorage), so that you can just call functions like `put` and `get` without having to pass along a transaction object. This can be convenient when database accesses are deeply nested.
After the provided function is done running, `transact()` does an OLMDB commit, and (for read/write transactions) registers a callback function. In case it gets called with a 'raced' status, the function will be executed again within a new transaction context. This happens up to six times before giving up.
### Scaling
You can run as many JavaScript processes, and within each as many concurrent request handlers as you want. LMDB allows multiple processes (on the same machine) to safely run read-only transaction in the same database file simultaneously. All read/write operations are committed to LMDB by the single threaded commit worker daemon, which may be a bottleneck for write-heavy workloads, though it's highly efficient and sure therefore scale to thousands of transactions per second.
In the `benchmark` directory you can find the start of a benchmarking suite to get some feel for performance.
OLMDB can only scale vertically, on a single server. In case you eventually need to scale beyond a single server, that can only be done at the application level.
## API Reference
The high-level API provides a promise-based, type-safe interface with automatic transaction retries and convenient data type conversions.
The following is auto-generated from `src/olmdb.ts`:
### setTransactionData · [function](https://github.com/vanviegen/olmdb/blob/main/src/olmdb.ts#L149)
Attach some arbitrary user data to the current transaction context, which is
attached to the currently running (async) task.
**Signature:** `(key: symbol, value: any) => void`
**Parameters:**
- `key: symbol` - - A symbol key to store data in the current transaction context.
- `value: any` - - The value to store.
**Throws:**
- If called outside of a transaction context.
**Examples:**
```typescript
const MY_SYMBOL = Symbol("myKey");
await transact(async () => {
setTransactionData(MY_SYMBOL, "myValue");
await somethingAsync(); // Can be interleaved with other transactions
const value = getTransactionData(MY_SYMBOL);
console.log(value); // "myValue"
});
```
### getTransactionData · [function](https://github.com/vanviegen/olmdb/blob/main/src/olmdb.ts#L160)
Retrieves data from the current transaction context.
**Signature:** `(key: symbol) => any`
**Parameters:**
- `key: symbol` - - A symbol key to retrieve data from the current transaction context.
**Returns:** - The value associated with the key, or undefined if not set.
**Throws:**
- If called outside of a transaction context.
### get · [function](https://github.com/vanviegen/olmdb/blob/main/src/olmdb.ts#L184)
Retrieves a value from the database by key within the current transaction.
**Signature:** `(key: Data) => Uint8Array<ArrayBufferLike>`
**Parameters:**
- `key: Data` - - The key to look up as a Uint8Array, ArrayBuffer, or string.
**Returns:** The value associated with the key as a Uint8Array, or undefined if not found.
**Throws:**
- If called outside of a transaction context.
- With code "INVALID_TRANSACTION" if transaction is invalid or already closed.
- With code "KEY_TOO_LONG" if key exceeds maximum allowed length.
- With code "LMDB-{code}" for LMDB-specific errors.
### getBuffer · [function](https://github.com/vanviegen/olmdb/blob/main/src/olmdb.ts#L199)
Retrieves a value from the database by key within the current transaction.
**Signature:** `(key: Data) => ArrayBuffer`
**Parameters:**
- `key: Data` - - The key to look up as a Uint8Array, ArrayBuffer, or string.
**Returns:** The value associated with the key as an ArrayBuffer, or undefined if not found.
**Throws:**
- If called outside of a transaction context.
- With code "INVALID_TRANSACTION" if transaction is invalid or already closed.
- With code "KEY_TOO_LONG" if key exceeds maximum allowed length.
- With code "LMDB-{code}" for LMDB-specific errors.
### getString · [function](https://github.com/vanviegen/olmdb/blob/main/src/olmdb.ts#L213)
Retrieves a value from the database by key within the current transaction and decodes it as a string.
**Signature:** `(key: Data) => string`
**Parameters:**
- `key: Data` - - The key to look up as a Uint8Array, ArrayBuffer, or string.
**Returns:** The value associated with the key as a UTF-8 decoded string, or undefined if not found.
**Throws:**
- If called outside of a transaction context.
- With code "INVALID_TRANSACTION" if transaction is invalid or already closed.
- With code "KEY_TOO_LONG" if key exceeds maximum allowed length.
- With code "LMDB-{code}" for LMDB-specific errors.
### put · [function](https://github.com/vanviegen/olmdb/blob/main/src/olmdb.ts#L228)
Stores a key-value pair in the database within the current transaction.
**Signature:** `(key: Data, val: Data) => void`
**Parameters:**
- `key: Data` - - The key to store as a Uint8Array, ArrayBuffer, or string.
- `val: Data` - - The value to associate with the key as a Uint8Array, ArrayBuffer, or string.
**Throws:**
- If called outside of a transaction context.
- With code "INVALID_TRANSACTION" if transaction is invalid or already closed.
- With code "KEY_TOO_LONG" if key exceeds maximum allowed length.
- With code "LMDB-{code}" for LMDB-specific errors.
### del · [function](https://github.com/vanviegen/olmdb/blob/main/src/olmdb.ts#L241)
Deletes a key-value pair from the database within the current transaction.
**Signature:** `(key: Data) => void`
**Parameters:**
- `key: Data` - - The key to delete as a Uint8Array, ArrayBuffer, or string.
**Throws:**
- If called outside of a transaction context.
- With code "INVALID_TRANSACTION" if transaction is invalid or already closed.
- With code "KEY_TOO_LONG" if key exceeds maximum allowed length.
- With code "LMDB-{code}" for LMDB-specific errors.
### init · [function](https://github.com/vanviegen/olmdb/blob/main/src/olmdb.ts#L260)
Initialize the database with the specified directory path.
This function may only be called once. If it is not called before the first transact(),
the database will be automatically initialized with the default directory.
**Signature:** `(dbDir?: string) => void`
**Parameters:**
- `dbDir?: string` - - Optional directory path for the database (defaults to environment variable $OLMDB_DIR or "./.olmdb").
**Throws:**
- With code "DUP_INIT" if database is already initialized.
- With code "CREATE_DIR_FAILED" if directory creation fails.
- With code "LMDB-{code}" for LMDB-specific errors.
**Examples:**
```typescript
init("./my-database");
```
### onRevert · [function](https://github.com/vanviegen/olmdb/blob/main/src/olmdb.ts#L272)
Registers a callback to be executed when the current transaction is reverted (aborted due to error).
The callback will be executed outside of transaction context.
**Signature:** `(callback: () => void) => void`
**Parameters:**
- `callback: () => void` - - Function to execute when transaction is reverted
**Throws:**
- If called outside of a transaction context
### onCommit · [function](https://github.com/vanviegen/olmdb/blob/main/src/olmdb.ts#L284)
Registers a callback to be executed when the current transaction commits successfully.
The callback will be executed outside of transaction context.
**Signature:** `(callback: () => void) => void`
**Parameters:**
- `callback: () => void` - - Function to execute when transaction commits
**Throws:**
- If called outside of a transaction context
### transact · [function](https://github.com/vanviegen/olmdb/blob/main/src/olmdb.ts#L317)
Executes a function within a database transaction context.
All database operations (get, put, del) must be performed within a transaction.
Transactions are automatically committed if the function completes successfully,
or aborted if an error occurs. Failed transactions may be automatically retried
up to 6 times in case of validation conflicts.
**Signature:** `<T>(fn: () => T) => Promise<T>`
**Type Parameters:**
- `T` - The return type of the transaction function
**Parameters:**
- `fn: () => T` - - The function to execute within the transaction context
**Returns:** A promise that resolves with the function's return value
**Throws:**
- If nested transactions are attempted
- With code "RACING_TRANSACTION" if the transaction fails after retries due to conflicts
- With code "TRANSACTION_FAILED" if the transaction fails for other reasons
- With code "TXN_LIMIT" if maximum number of transactions is reached
- With code "LMDB-{code}" for LMDB-specific errors
**Examples:**
```typescript
const result = await transact(() => {
const value = get(keyBytes);
if (value) {
put(keyBytes, newValueBytes);
}
return value;
});
```
### scan · [function](https://github.com/vanviegen/olmdb/blob/main/src/olmdb.ts#L453)
Creates an iterator to scan through database entries within the current transaction.
The iterator implements the standard TypeScript iterator protocol and can be used with for...of loops.
Supports both forward and reverse iteration with optional start and end boundaries.
**Signature:** `<K = Uint8Array<ArrayBufferLike>, V = Uint8Array<ArrayBufferLike>>(opts?: ScanOptions<K, V>) => DbIterator<K, V>`
**Type Parameters:**
- `K = Uint8Array` - The type to convert keys to (defaults to Uint8Array).
- `V = Uint8Array` - The type to convert values to (defaults to Uint8Array).
**Parameters:**
- `opts: ScanOptions<K,V>` (optional) - - Configuration options for the scan operation.
**Returns:** A DbIterator instance.
**Throws:**
- If called outside of a transaction context.
- With code "INVALID_TRANSACTION" if transaction is invalid or already closed.
- With code "KEY_TOO_LONG" if start or end key exceeds maximum allowed length.
- With code "ITERATOR_LIMIT" if maximum number of iterators is reached.
- With code "OUT_OF_MEMORY" if memory allocation fails.
**Examples:**
```typescript
await transact(() => {
// Iterate over all entries
for (const { key, value } of scan()) {
console.log('Key:', key, 'Value:', value);
}
// Iterate with string conversion
for (const { key, value } of scan({
keyConvert: asString,
valueConvert: asString
})) {
console.log('Key:', key, 'Value:', value);
}
// Iterate with boundaries
for (const { key, value } of scan({
start: "prefix_",
end: "prefix~"
})) {
console.log('Key:', key, 'Value:', value);
}
// Manual iteration control
const iter = scan({ reverse: true });
const first = iter.next();
iter.close(); // Always close when done early
});
```
### asArray · [function](https://github.com/vanviegen/olmdb/blob/main/src/olmdb.ts#L477)
Converts an ArrayBuffer to a Uint8Array.
Helper function for use with scan() keyConvert and valueConvert options.
**Signature:** `(buffer: ArrayBuffer) => Uint8Array<ArrayBufferLike>`
**Parameters:**
- `buffer: ArrayBuffer` - - The ArrayBuffer to convert.
**Returns:** A new Uint8Array view of the buffer.
### asBuffer · [function](https://github.com/vanviegen/olmdb/blob/main/src/olmdb.ts#L486)
Returns the ArrayBuffer as-is.
Helper function for use with scan() keyConvert and valueConvert options.
**Signature:** `(buffer: ArrayBuffer) => ArrayBuffer`
**Parameters:**
- `buffer: ArrayBuffer` - - The ArrayBuffer to return.
**Returns:** The same ArrayBuffer.
### asString · [function](https://github.com/vanviegen/olmdb/blob/main/src/olmdb.ts#L495)
Converts an ArrayBuffer to a UTF-8 decoded string.
Helper function for use with scan() keyConvert and valueConvert options.
**Signature:** `(buffer: ArrayBuffer) => string`
**Parameters:**
- `buffer: ArrayBuffer` - - The ArrayBuffer to decode.
**Returns:** A UTF-8 decoded string.
### DatabaseError · [constant](https://github.com/vanviegen/olmdb/blob/main/src/olmdb.ts#L179)
The DatabaseError class is used to represent errors that occur during database operations.
It extends the built-in Error class and has a machine readable error code string property.
The lowlevel API will throw DatabaseError instances for all database-related errors.
Invalid function arguments will throw TypeError.
**Value:** `DatabaseErrorConstructor`
### Data · [type](https://github.com/vanviegen/olmdb/blob/main/src/olmdb.ts#L14)
Union type representing the supported data formats for keys and values.
Can be a Uint8Array, ArrayBuffer, or string.
**Type:** `Uint8Array | ArrayBuffer | string`
### DbEntry · [interface](https://github.com/vanviegen/olmdb/blob/main/src/olmdb.ts#L339)
Represents a key-value pair returned by the iterator
**Type Parameters:**
- `K`
- `V`
#### dbEntry.key · [member](https://github.com/vanviegen/olmdb/blob/main/src/olmdb.ts#L340)
**Type:** `K`
#### dbEntry.value · [member](https://github.com/vanviegen/olmdb/blob/main/src/olmdb.ts#L341)
**Type:** `V`
### DbIterator · [class](https://github.com/vanviegen/olmdb/blob/main/src/olmdb.ts#L347)
Database iterator that implements the standard TypeScript iterator protocol
**Type Parameters:**
- `K`
- `V`
#### dbIterator.iteratorId · [property](https://github.com/vanviegen/olmdb/blob/main/src/olmdb.ts#L348)
**Type:** `number`
#### dbIterator.convertKey · [property](https://github.com/vanviegen/olmdb/blob/main/src/olmdb.ts#L349)
**Type:** `(buffer: ArrayBuffer) => K`
#### dbIterator.convertValue · [property](https://github.com/vanviegen/olmdb/blob/main/src/olmdb.ts#L350)
**Type:** `(buffer: ArrayBuffer) => V`
#### dbIterator.[Symbol.iterator] · [method](https://github.com/vanviegen/olmdb/blob/main/src/olmdb.ts#L358)
**Signature:** `() => DbIterator<K, V>`
**Parameters:**
#### dbIterator.next · [method](https://github.com/vanviegen/olmdb/blob/main/src/olmdb.ts#L369)
Advances the iterator to the next key-value pair.
**Signature:** `() => IteratorResult<DbEntry<K, V>, any>`
**Parameters:**
**Returns:** An IteratorResult with the next DbEntry or done: true if no more entries.
**Throws:**
- With code "INVALID_ITERATOR" if iterator is invalid or already closed.
- With code "LMDB-{code}" for LMDB-specific errors.
#### dbIterator.close · [method](https://github.com/vanviegen/olmdb/blob/main/src/olmdb.ts#L393)
Closes the iterator and frees its resources.
Should be called when done iterating to prevent resource leaks.
**Signature:** `() => void`
**Parameters:**
**Throws:**
- With code "INVALID_ITERATOR" if iterator is invalid or already closed.
## Low-level API Reference
The low-level API is what's exposed by the native module. It's a somewhat less convenient than the high-level API, but it's a good starting point if you require a different abstraction.
The following is auto-generated from `src/lowlevel.ts`:
### init · [function](https://github.com/vanviegen/olmdb/blob/main/src/lowlevel.ts#L26)
Initializes the database system with the specified directory.
**Signature:** `(onCommit: (transactionId: number, success: boolean) => void, directory?: string, commitWorkerBin?: string) => void`
**Parameters:**
- `onCommit: (transactionId: number, success: boolean) => void` - Callback function that will be invoked when an asynchronous
transaction commit completes. The callback receives the transaction ID and
whether the commit succeeded.
- `directory?: string` - Optional path to the database directory. If not provided,
defaults to the OLMDB_DIR environment variable or "./.olmdb".
- `commitWorkerBin: string` (optional) - Path to the commit worker binary. Defaults to
`<base_dir>/build/release/commit_worker`.
**Throws:**
- DatabaseError if initialization fails
### startTransaction · [function](https://github.com/vanviegen/olmdb/blob/main/src/lowlevel.ts#L40)
Starts a new transaction for database operations.
**Signature:** `() => number`
**Returns:** A transaction ID (positive integer) to be used in subsequent operations
**Throws:**
- DatabaseError if the transaction cannot be created
### commitTransaction · [function](https://github.com/vanviegen/olmdb/blob/main/src/lowlevel.ts#L54)
Commits the transaction with the given ID.
If the transaction is read-only, commit will complete immediately and return true.
If the transaction has modifications, it will be queued for asynchronous commit and return false.
When the commit is processed, the onCommit callback provided to init() will be invoked.
**Signature:** `(transactionId: number) => boolean`
**Parameters:**
- `transactionId` - The ID of the transaction to commit
**Returns:** true if committed immediately (read-only transaction),
false if queued for async commit
**Throws:**
- DatabaseError if the transaction cannot be committed
### abortTransaction · [function](https://github.com/vanviegen/olmdb/blob/main/src/lowlevel.ts#L62)
Aborts the transaction with the given ID, discarding all changes.
**Signature:** `(transactionId: number) => void`
**Parameters:**
- `transactionId` - The ID of the transaction to abort
**Throws:**
- DatabaseError if the transaction cannot be aborted
### get · [function](https://github.com/vanviegen/olmdb/blob/main/src/lowlevel.ts#L72)
Retrieves a value for the given key within a transaction.
**Signature:** `(transactionId: number, key: ArrayBufferLike) => ArrayBuffer`
**Parameters:**
- `transactionId` - The ID of the transaction
- `key` - Key to look up
**Returns:** The value if found, or undefined if the key doesn't exist
**Throws:**
- DatabaseError if the operation fails
### put · [function](https://github.com/vanviegen/olmdb/blob/main/src/lowlevel.ts#L85)
Stores a key-value pair within a transaction.
**Signature:** `(transactionId: number, key: ArrayBufferLike, value: ArrayBufferLike) => void`
**Parameters:**
- `transactionId` - The ID of the transaction
- `key` - Key to store
- `value` - Value to store
**Throws:**
- DatabaseError if the operation fails
### del · [function](https://github.com/vanviegen/olmdb/blob/main/src/lowlevel.ts#L98)
Deletes a key-value pair within a transaction.
**Signature:** `(transactionId: number, key: ArrayBufferLike) => void`
**Parameters:**
- `transactionId` - The ID of the transaction
- `key` - Key to delete
**Throws:**
- DatabaseError if the operation fails
### createIterator · [function](https://github.com/vanviegen/olmdb/blob/main/src/lowlevel.ts#L113)
Creates an iterator for scanning a range of keys within a transaction.
**Signature:** `(transactionId: number, startKey?: ArrayBufferLike, endKey?: ArrayBufferLike, reverse?: boolean) => number`
**Parameters:**
- `transactionId` - The ID of the transaction
- `startKey` - Optional key to start iteration from (inclusive)
- `endKey` - Optional key to end iteration at (exclusive)
- `reverse` - If true, keys are returned in descending order
**Returns:** An iterator ID to be used with readIterator() and closeIterator()
**Throws:**
- DatabaseError if the operation fails
### readIterator · [function](https://github.com/vanviegen/olmdb/blob/main/src/lowlevel.ts#L127)
Reads the next key-value pair from an iterator.
**Signature:** `(iteratorId: number) => { key: ArrayBuffer; value: ArrayBuffer; }`
**Parameters:**
- `iteratorId` - The ID of the iterator
**Returns:** An object containing the key and value, or undefined if iteration is complete
**Throws:**
- DatabaseError if the operation fails
### closeIterator · [function](https://github.com/vanviegen/olmdb/blob/main/src/lowlevel.ts#L137)
Closes an iterator when it's no longer needed.
**Signature:** `(iteratorId: number) => void`
**Parameters:**
- `iteratorId` - The ID of the iterator to close
**Throws:**
- DatabaseError if the operation fails
### DatabaseError · [constant](https://github.com/vanviegen/olmdb/blob/main/src/lowlevel.ts#L174)
The DatabaseError class is used to represent errors that occur during database operations.
It extends the built-in Error class and has a machine readable error code string property.
The lowlevel API will throw DatabaseError instances for all database-related errors.
Invalid function arguments will throw TypeError.
**Value:** `DatabaseErrorConstructor`
### DatabaseErrorConstructor · [interface](https://github.com/vanviegen/olmdb/blob/main/src/lowlevel.ts#L156)
Constructor interface for DatabaseError.
#### databaseErrorConstructor.new · [constructor](https://github.com/vanviegen/olmdb/blob/main/src/lowlevel.ts#L163)
Creates a new DatabaseError with the specified message and code.
#### databaseErrorConstructor.prototype · [member](https://github.com/vanviegen/olmdb/blob/main/src/lowlevel.ts#L164)
**Type:** `DatabaseError`