UNPKG

olmdb

Version:

Optimistic LMDB. A very fast embedded key/value store featuring ACID optimistic read/write-transactions, based on LMDB.

199 lines (182 loc) 7.52 kB
import * as os from "node:os"; import { dlopen } from "process"; import { fileURLToPath } from 'url'; import { dirname, resolve } from 'path'; import { existsSync } from "node:fs"; const PACKAGE_ROOT = (function() { let dir = dirname(fileURLToPath(import.meta.url)); while (dir !== dirname(dir)) { if (existsSync(resolve(dir, 'package.json'))) return dir; dir = dirname(dir); } throw new Error('package.json not found'); })(); // Take from OLMDB_BIN_DIR, or default to <package_root>/build/release const BIN_DIR = process.env.OLMDB_BIN_DIR || resolve(PACKAGE_ROOT, 'build', 'Release'); const lowlevel = {} as any; dlopen({exports: lowlevel}, `${BIN_DIR}/transaction_client.node`, os.constants.dlopen.RTLD_NOW); /** * Initializes the database system with the specified directory. * * Must be called once per JavaScript thread (i.e. once on the main thread * and once inside each Node.js / Bun `Worker` that intends to use OLMDB). * Each Worker gets its own independent native client, so concurrent * transactions across Workers are safe; all clients in the same process that * point at the same database directory share a single commit-worker daemon. * * Can be called multiple times within the same thread if directory and * commitWorkerBin are identical. * * @param directory Optional path to the database directory. If not provided, * defaults to the OLMDB_DIR environment variable or "./.olmdb". * @param commitWorkerBin Path to the commit worker binary. Defaults to * `<base_dir>/build/release/commit_worker`. * @throws DatabaseError if initialization fails */ export function init( directory?: string, commitWorkerBin: string = `${BIN_DIR}/commit_worker` ): void { lowlevel.init(directory, commitWorkerBin); } /** * Starts a new transaction for database operations. * * @returns A transaction ID (positive integer) to be used in subsequent operations * @throws DatabaseError if the transaction cannot be created */ export const startTransaction = lowlevel.startTransaction as () => number; /** * Commits the transaction with the given ID. * * If the transaction is read-only, returns the commit sequence number immediately. * If the transaction has modifications, returns a Promise that resolves to the commit sequence when the commit completes. * * @param transactionId The ID of the transaction to commit * @param reopen If true, the transaction is kept open after committing. * For read-only transactions, the read context is unchanged and the commit sequence is returned synchronously. * For write transactions, the write and read logs are cleared and the read context * is refreshed to a snapshot that includes the committed writes (and any concurrent changes). * The transaction can then be used for further reads and writes. * @returns For read-only transactions: the commit sequence number (synchronous) * For write transactions: a Promise that resolves to the commit sequence number (0 when the transaction failed due to conflicts) * @throws DatabaseError if the transaction cannot be committed */ export const commitTransaction = lowlevel.commitTransaction as (transactionId: number, reopen?: boolean) => number | Promise<number>; /** * Aborts the transaction with the given ID, discarding all changes. * * @param transactionId The ID of the transaction to abort * @throws DatabaseError if the transaction cannot be aborted */ export const abortTransaction = lowlevel.abortTransaction as (transactionId: number) => void; /** * Retrieves a value for the given key within a transaction. * * @param transactionId The ID of the transaction * @param key Key to look up * @returns The value if found, or undefined if the key doesn't exist * @throws DatabaseError if the operation fails */ export const get = lowlevel.get as ( transactionId: number, key: ArrayBufferLike ) => Readonly<ArrayBuffer> | undefined; /** * Stores a key-value pair within a transaction. * * @param transactionId The ID of the transaction * @param key Key to store * @param value Value to store * @throws DatabaseError if the operation fails */ export const put = lowlevel.put as ( transactionId: number, key: ArrayBufferLike, value: ArrayBufferLike ) => void; /** * Deletes a key-value pair within a transaction. * * @param transactionId The ID of the transaction * @param key Key to delete * @throws DatabaseError if the operation fails */ export const del = lowlevel.del as ( transactionId: number, key: ArrayBufferLike ) => void; /** * Creates an iterator for scanning a range of keys within a transaction. * * The iterator covers the half-open range `[startKey, endKey)` — `startKey` is * the inclusive lower bound and `endKey` the exclusive upper bound. This holds * regardless of direction: `reverse` only changes the order in which keys are * emitted, not which bound is inclusive. So a forward scan yields the range * ascending starting at `startKey`, while a reverse scan yields the same range * descending starting at the largest key below `endKey`. * * @param transactionId The ID of the transaction * @param startKey Optional inclusive lower bound (defaults to the first key) * @param endKey Optional exclusive upper bound (defaults to past the last key) * @param 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 */ export const createIterator = lowlevel.createIterator as ( transactionId: number, startKey?: ArrayBufferLike, endKey?: ArrayBufferLike, reverse?: boolean ) => number; /** * Reads the next key-value pair from an iterator. * * @param 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 */ export const readIterator = lowlevel.readIterator as ( iteratorId: number ) => Readonly<{ key: Readonly<ArrayBuffer>; value: Readonly<ArrayBuffer> }> | undefined; /** * Closes an iterator when it's no longer needed. * * @param iteratorId The ID of the iterator to close * @throws DatabaseError if the operation fails */ export const closeIterator = lowlevel.closeIterator as ( iteratorId: number ) => void; /** * Interface for DatabaseError, which extends the standard Error class. * Contains an additional code property for machine-readable error identification. */ export interface DatabaseError extends Error { /** * A machine-readable string code identifying the type of error. * Example codes include: "NOT_INIT", "INVALID_TRANSACTION", "KEY_TOO_LONG", etc. */ code: string; } /** * Constructor interface for DatabaseError. */ export interface DatabaseErrorConstructor { /** * Creates a new DatabaseError with the specified message and code. * * @param message Human-readable error message * @param code Machine-readable error code */ new (message: string, code: string): DatabaseError; prototype: DatabaseError; } /** * 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. */ export const DatabaseError = lowlevel.DatabaseError as DatabaseErrorConstructor;