@harperfast/rocksdb-js
v2.10.0
Published
RocksDB binding for Node.js
Keywords
Readme
rocksdb-js
A Node.js binding for the RocksDB library.
Features
- Supports optimistic and pessimistic transactions
- Hybrid sync/async data retrieval
- Range queries return an iterable with array-like methods and lazy evaluation
- Transaction log system for recording transaction related data
- Custom stores provide ability to override default database interactions
- Efficient binary key and value encoding
- Configurable block/blob compression (LZ4, Zstd, Zlib, and more)
- Observable background errors via the
'error'event ordb.getLastError(), with in-process recovery (db.resume()) - Access to internal RocksDB statistics
- Designed for Node.js and Bun on Linux, macOS, and Windows
Example
const db = RocksDatabase.open('/path/to/db');
for (const key of ['a', 'b', 'c', 'd', 'e']) {
await db.put(key, `value ${key}`);
}
console.log(await db.get('b')); // `value b`
for (const { key, value } of db.getRange({ start: 'b', end: 'd' })) {
console.log(`${key} = ${value}`);
}
await db.transaction(async (txn: Transaction) => {
await txn.put('f', 'value f');
await txn.remove('c');
});Usage
new RocksDatabase(path, options?)
Creates a new database instance.
path: stringThe path to write the database files to. This path does not need to exist, but the parent directories do.options: object[optional]compression: string | { algorithm: string, level?: number }The block/blob compression algorithm for this column family. Pass an algorithm name — one of'none','snappy','zlib','bzip2','lz4','lz4hc', or'zstd'— or an object with analgorithmand an optionallevel(forwarded to RocksDB'scompression_opts.level; the meaning is algorithm-specific). Applies to both SST data blocks and blob files (large values). Defaults to'lz4'when the native build supports it, otherwise RocksDB's own default (Snappy when linked, else no compression). See Compression. Throws if the algorithm is not compiled into the native build — checksupportedCompressionfor the available list.compressionForAllColumnFamilies: booleanWhentrue, appliescompressionto every column family opened for the database rather than only the column family specified byname. Requires an explicitcompression. Defaults tofalse. See Compression.dbWriteBufferSize: numberThe total memtable memory budget in bytes shared across all of the database's column families. When the combined size of all memtables reaches this value, RocksDB flushes the largest one.0(the default) disables this global trigger, so per-column-familywriteBufferSizealone drives flushing. This is distinct from the process-widewriteBufferManagerSizeconfig option. Database-wide, so it binds when the path is first opened in this process: a later open of the same path — including from another worker thread — keeps the first opener's value rather than overriding or rejecting it.disableWAL: booleanWhether to disable the RocksDB write ahead log. Defaults tofalse.enableStats: booleanWhentrueand the database is open, RocksDB will captures stats that are retrieved by callingdb.getStats(). Enabling statistics imposes 5-10% in overhead. Defaults tofalse.infoLogLevel: numberThe verbosity of RocksDB's informational logging (LOG/LOG.old.*):0(debug),1(info),2(warn),3(error),4(fatal), or5(header-only). Omit to leave RocksDB's own default (INFO_LEVELin a release build of the linked RocksDB library). Seedb.logOptions.maxLogFileSize: numberThe per-file size cap, in bytes, for informational log files (LOG/LOG.old.*). RocksDB retains up to 5 of these files, so the total informational-log footprint is bounded at roughly5 * maxLogFileSize. Defaults to 16 MB (an 80 MB bound), which stops purely informational logging from growing without bound. A value of0is RocksDB's special "single unbounded log file" mode — it disables size-based rotation entirely, so the log can grow without limit; only set0if you deliberately want that (it forgoes the bounded footprint this option otherwise provides). Seedb.logOptions.maxOpenFiles: numberThe maximum number of table files RocksDB keeps open.0(the default) derives a budget from the effective per-process open-file limit (an eighth of the limit — several databases can share one process — clamped to[1024, 262144]);-1holds every table file open (the RocksDB default, which can exhaust the process file-descriptor limit when compaction falls behind under sustained ingest); a positiveint32is an explicit cap. Reads only pay a reopen cost when the number of live table files exceeds the budget, so raise the process fd limit (and with it the derived budget) for very large databases.maxWriteBufferNumber: numberThe maximum number of memtables that can be queued per column family before writes stall. Higher values absorb write bursts while flushes catch up, at the cost of memory (roughlymaxWriteBufferNumber * writeBufferSizeper column family). Defaults to16.maxWriteBufferSizeToMaintain: numberThe number of bytes of recent memtable history to keep in memory for transaction conflict checking.-1(the default) derives the value frommaxWriteBufferNumber * writeBufferSize(the RocksDB-recommended default for optimistic transactions) — except when the database has awriteBufferManagerattached, in which case it resolves to1so the manager is not filled with history it will never release. What decides this is the manager the database itself holds, not the currentwriteBufferManagerSize: a column family created later on an already-open database is clamped too, because its history is still charged to that manager. It also applies to any attached manager, not only a stalling one —writeBufferManagerAllowStallcan be changed at runtime while this value is fixed when a column family is created, and history the budget cannot reclaim is a problem either way.1is the smallest target a transactional database can be given: RocksDB's transaction wrappers rewrite a0target to the derived value, so0requests the largest history rather than none, and an explicitly requested0is normalized to1for the same reason. A positive target still retains the most recent flushed memtable per column family until that family's next write. An explicit positive value is honored as-is; sizing it against the budget and the column-family count is then yours, and a configuration whose known families already reach the budget is reported on the'log.warn'channel.name: stringThe column family name. Defaults to"default".noBlockCache: booleanWhentrue, disables the block cache. Block caching is enabled by default and the cache is shared across all database instances.parallelismThreads: numberThe number of background threads to use for flush and compaction. Defaults to1.pessimistic: booleanWhentrue, throws conflict errors when they occur instead of waiting until commit. Defaults tofalse.readOnly: booleanWhentrue, the database is opened in read-only mode. Read operations are permitted. Write operations will throw an error with codeERR_DATABASE_READONLY. Transactions are a no-op in read-only mode.flush(),flushSync(),compact()andcompactSync()are the exception: they have nothing to do when there are no writes, so they succeed as no-ops rather than throwing.A read-only open is a point-in-time snapshot: it never sees later writes. It is safe against a quiescent database, but unsafe against a live writer: the open holds no reference on the files it is about to read, so a concurrent compaction, blob GC, or flush in the writing process can delete one mid-open and the open throws with code
ERR_CONCURRENT_COMPACTION— the database is not corrupt; the reader lost a race, and retrying can succeed. Even a successful open can fail later reads the same way, because files the snapshot references are opened lazily while the writer keeps deleting obsolete ones. To follow a database another process is actively writing, open a secondary instead (secondaryPathbelow) — that is the supported mode for a live follower.secondaryPath: stringOpens the database as a secondary instance: a read-only follower of a live primary that tolerates the primary deleting files and sees new writes on eachcatchUpWithPrimary()call. This is the supported way to read a database another process is actively writing. The value is the secondary instance's own workspace directory (created if missing) where RocksDB keeps the secondary's private state — it must be outsidepath(enforced) and exclusive to one secondary instance. Exclusivity is enforced: reusing a workspace in-process for a different database is rejected at open (the same database + workspace share the one follower instance), and a kernel advisory lock on<secondaryPath>/.secondary.lock(same discipline and caveats as the backup directory lock) excludes other processes — with the same limits: on filesystems without advisory locking (flockunsupported — e.g. the FUSE/9p mounts behind Docker Desktop bind mounts) the lock degrades to a no-op, and on network filesystems with node-localflock(NFSlocal_lock, CIFS, 9p) it does not exclude across hosts, so workspace exclusivity is the caller's job there. ImpliesreadOnly: true(an explicitreadOnly: falsethrows), so all read-only behavior above applies. ForcesmaxOpenFiles: -1: every table and blob file is opened and held for the life of each version, which is what makes the primary's deletions safe — budget file descriptors accordingly on large databases (an explicitmaxOpenFilesother than-1is rejected). Column families created by the primary after the secondary opens are invisible until the secondary reopens. Note: RocksDB upstream documents secondary instances as unsupported in combination with integrated BlobDB (which this library enables for values ≥ 2KB); the pinned RocksDB build handles blob files in secondary mode — covered by native regression tests — but upstream does not guarantee the combination.statsLevel: StatsLevelControls which type of statistics to skip and reduce statistic overhead. Defaults toStatsLevel.ExceptDetailedTimers.store: StoreA custom store that handles all interaction between theRocksDatabaseorTransactioninstances and the native database interface. A store is bound to a singleRocksDatabaseinstance and cannot be shared between them. See Custom Store for more information.transactionLogMaxAgeThreshold: numberThe threshold for the transaction log file's last modified time to be older than the retention period before it is rotated to the next sequence number. Value must be between0.0and1.0. A threshold of0.0means ignore age check. Defaults to0.75.transactionLogMaxSize: numberThe target maximum size of a transaction log file. Transactions are never split across files: if the complete transaction does not fit, the log rotates before writing it. A transaction written to an empty file may exceed the target. Defaults to 16 MB.transactionLogRetention: string | numberThe number of minutes to retain transaction logs before purging. Defaults to'3d'(3 days).transactionLogsPath: stringThe path to store transaction logs. Defaults to"${db.path}/transaction_logs".verificationTable: booleanWhentrue, this column family participates in the process-global Verification Table: transaction writes to this column family invalidate the verification slot for each written key. Enable this only for column families whose records are cached (e.g. the primary column family of a table). Defaults tofalse. RequiresverificationTableEntriesto be configured before the first database is opened.writeBufferSize: numberThe per-column-family memtable size in bytes at which the memtable is sealed and flushed to an SST file. Smaller values produce more frequent, faster flushes; larger values batch more writes per SST file at the cost of memory. Defaults to16777216(16 MB).
db.close()
Closes a database. This function can be called multiple times and will only close an opened
database. A database instance can be reopened once it is closed. A flush failure leaves the native
database quarantined so shutdown() can retry without losing unflushed data; an explicit
destroy() can instead delete it. A failure while waiting for compaction to settle is reported
after native teardown completes; the optional compactOnClose pass itself is best-effort and its
errors do not fail the close, since a skipped compaction loses no data. All native close errors
emit database:closeFailed. The quarantine applies to both writable and read-only opens because
both modes share the physical path lifecycle.
const db = RocksDatabase.open('foo');
db.close();db.columns: string[]
Returns the list of column families in the RocksDB database.
const db = RocksDatabase.open('path/to/db');
console.log(db.columns); // ['default']
db.use('users');
console.log(db.columns); // ['default', 'users']db.compression: { algorithm: string, level?: number }
Returns the compression currently in effect for this database's column family, read live from
RocksDB. algorithm is a friendly name (e.g. 'lz4', 'zstd', 'none'); level is present only
when a non-default compression level is set. The database must be open. See
Compression.
const db = RocksDatabase.open('path/to/db', {
compression: { algorithm: 'zstd', level: 3 },
});
console.log(db.compression); // { algorithm: 'zstd', level: 3 }db.config(options)
Sets global database settings.
options: objectblockCacheSize: numberThe amount of memory in bytes to use to cache uncompressed blocks. Defaults to 32MB. Set to0(zero) disables block cache for future opened databases. Existing block cache for any opened databases is resized immediately. Negative values throw an error.compactOnClose: booleanWhentrue, compacts the database on close. Defaults tofalse.lifecycleWaitSeconds: numberHow long a synchronous open, destroy, or shutdown waits for a conflicting lifecycle operation already in progress on the same path (e.g. another open or close) before throwing a retryable timeout error. It does not bound the separate, intentionally unbounded wait thatdestroy()/shutdown()make for in-flight backups, checkpoints, or other async work still using the database — seedb.destroy(). Defaults to30seconds and must be a positive integer.verificationTableEntries: numberThe number of slots in the process-global Verification Table. Each slot is 8 bytes, so the default of131072(128K) slots is 1 MB. Set to0to disable the verification table. This must be configured before the first database is opened; once the table is materialized, attempts to change this value throw.writeBufferManagerAllowStall: booleanWhentrue, writes are stalled once the manager'sbuffer_sizeis exceeded, providing a hard cap on memtable memory. Whenfalse, memtables are allowed to grow past the limit and flushes are simply scheduled more aggressively. Off by default to favor write throughput over hard memory bounding. Defaults tofalse. A stall here is invisible to RocksDB's own stall counters and todb.isWriteStalled()— seegetWriteBufferManagerStats(), which also describes the watchdog that logs a sustained stall.writeBufferManagerCostToCache: booleanWhentrue, memtable memory is "charged" against the shared block cache so the block cache and write buffers draw from a single pool. During write bursts the cache shrinks to make room for memtables; once memtables flush, the cache can grow back into the reclaimed space. Defaults tofalse.writeBufferManagerSize: numberTotal memtable memory limit (bytes) shared across every database opened in this process. When set, RocksDB uses a singleWriteBufferManagerso write buffers are bounded process-wide rather than per database. Defaults to0, which means no manager. Setting0later stops new opens from attaching one, but does not detach or resize the manager a database already holds —write_buffer_manageris fixed for the life of an open database, so its memtables stay charged against that budget.
RocksDatabase.config({
blockCacheSize: 100 * 1024 * 1024, // 100MB
compactOnClose: true,
writeBufferManagerAllowStall: false,
writeBufferManagerCostToCache: false,
writeBufferManagerSize: 64 * 1024 * 1024, // 64MB
});getWriteBufferManagerStats(): WriteBufferManagerStats
Reads the live state of the WriteBufferManager.
Process-wide, not per-database. The manager is a singleton shared by every database opened in
this process, worker_threads included, so these values describe the whole process regardless of
where the call was made.
enabled: booleanWhether a manager has been created.bufferSize,memoryUsage,mutableMemoryUsage,stallActive,stallActiveMs,watchdogRunningandcolumnFamiliesare0/falsewhen not;allowStallandcostToCachestill reflect the configured setting (a manager is only created oncewriteBufferManagerSizeis also set), andinventoryAvailableistrue(there is nothing to fail to collect).bufferSize: numberThe budget in bytes (writeBufferManagerSize, read live).memoryUsage: numberTotal memtable memory in bytes charged against the manager.mutableMemoryUsage: numberThe share ofmemoryUsageheld by active (mutable) memtables; the rest is memtables awaiting flush plus retained write history.allowStall: boolean,costToCache: booleanThe manager's configuration.stallActive: booleanWhether the manager is currently stalling writes.stallActiveMs: numberHow long the current stall has been active;0when not stalled. Sampled once a second by the watchdog, so0for a stall's first second and wheneverwatchdogRunningisfalse.watchdogRunning: booleanWhether the stall watchdog thread is running.columnFamilies: numberLive column families across every database attached to this manager. A dropped column family keeps charging the manager until its last handle closes, so it is counted until then.inventoryAvailable: booleanfalsewhen the inventory could not be collected because the database registry or a column-family inventory was locked. The two inventory fields are then empty and everything else is still live; the call never blocks on database work that may itself be stalled.maxWriteBufferSizeToMaintain: Record<string, number>Effective per-column-family retained-history target (as a decimal string) to how many of those column families carry it. Effective, not requested: RocksDB rewrites a requested0for a transaction database.
bufferSize, memoryUsage, mutableMemoryUsage, stallActive and stallActiveMs are also in
db.getStats() and db.getStat() under the same
writeBufferManager. prefix, for scraping; enabled, allowStall, costToCache,
watchdogRunning, and the column-family inventory are only here, because collecting them either
walks the database registry or isn't scrape-shaped.
stallActive is the signal that distinguishes a stalled process from an idle one.
db.isWriteStalled(), the 'writeStall' event
and rocksdb.stall.micros all track RocksDB's WriteController, which a WriteBufferManager
stall never goes through.
The stall watchdog
While a manager exists with writeBufferManagerAllowStall, one low-frequency thread samples the
manager once a second. When a stall has been continuously active past
ROCKSDB_JS_WBM_STALL_WARN_MS (default 5000; 0 disables the watchdog) it writes one line
to stderr and emits it as a process-wide 'log.warn' event — once per stall episode, not per
blocked writer and not per sample:
[rocksdb-js] WriteBufferManager write stall active for 5.0s - no write can complete until memtable
memory drops below the budget. budget=661.2MB usage=662.2MB (100.2%) mutable=12.4MB (1.9%)
allowStall=true costToCache=true columnFamilies=28 maxWriteBufferSizeToMaintain={268435456:28}It needs its own thread because every other candidate is blocked by the very condition it reports:
a writer is parked inside RocksDB, RocksDB's stall callbacks are WriteController-only, and a JS
timer cannot fire on a thread parked in a synchronous write. The line goes to stderr as well as
the event so it is not lost when nothing has registered a 'log.warn' listener; route one and
ignore stderr if you would rather have it in your own log.
RocksDatabase.on('log.warn', (message) => logger.warn(message));
const wbm = getWriteBufferManagerStats();
if (wbm.stallActive) {
logger.warn(`writes stalled for ${wbm.stallActiveMs}ms: ${wbm.memoryUsage}/${wbm.bufferSize}`);
}db.isOpen(): boolean
Returns true if the database is open, otherwise false.
console.log(db.isOpen()); // true or falsedb.logOptions: { maxLogFileSize: number, infoLogLevel: number }
Returns the informational-log settings currently in effect for this database, read live from
RocksDB. These are database-wide settings (not per-column-family). maxLogFileSize is the
per-file size cap for informational log files (LOG / LOG.old.*); infoLogLevel is the logging
verbosity. The database must be open.
const db = RocksDatabase.open('path/to/db', { maxLogFileSize: 4 * 1024 * 1024 });
console.log(db.logOptions); // { maxLogFileSize: 4194304, infoLogLevel: 1 }db.name: string
Returns the database column family's name.
const db = new RocksDatabase('path/to/db');
console.log(db.name); // 'default'
const db2 = new RocksDatabase('path/to/db', { name: 'users' });
console.log(db.name); // 'users'db.open(): RocksDatabase
Opens the database at the given path. This must be called before performing any data operations.
import { RocksDatabase } from '@harperfast/rocksdb-js';
const db = new RocksDatabase('path/to/db');
db.open();There's also a static open() method for convenience that performs the same thing:
const db = RocksDatabase.open('path/to/db');db.secondaryPath: string | undefined
The secondary instance's workspace directory when the database was opened as a
secondary (a read-only follower of a live primary), or
undefined for a regular or plain read-only open.
const follower = RocksDatabase.open('/path/to/database', {
secondaryPath: '/path/to/follower-workspace',
});
console.log(follower.secondaryPath); // '/path/to/follower-workspace'
console.log(follower.readOnly); // truedb.status: 'opened' | 'closed'
Returns a string 'opened' or 'closed' indicating if the database is opened or closed.
console.log(db.status);db.use(name, options?): RocksDatabase
Returns a RocksDatabase bound to the name column family of this same database, opening — and
creating, if it does not exist — the column family on first use. This is a factory (like
useLog), not a stateful switch: the returned view is an
independent instance whose own reads and writes target its column family, while sharing the same
underlying database. Because a single RocksDB database backs every column family, a transaction,
backup, or checkpoint still spans all of them.
name: stringThe column family name.options?: objectOptions for the column family (same shape as the constructor's, minusname), overriding the options inherited from this database. Options only take effect when the view is (re)opened.
use() is get-or-create, backed by a weak cache: db.use('events') === db.use('events') while the
view is still referenced and open, and calling with this database's own column-family name
returns this. A view that has been closed (or garbage-collected — the cache does not pin it) is
transparently recreated on the next use(). Views are independent handles: closing this database
does not close them (and vice versa); the underlying database stays open until every handle is closed
or collected.
The view's store is derived by Store#createColumnFamilyStore(name, options), which builds an
independent store of the same class with its own codec state — views never share a mutable
encoder/decoder. Consequently a database configured with a pre-constructed encoder/decoder instance
cannot derive views (its name/structures would be shared and corrupt both column families); use an
encoder factory ({ Encoder }) or a named encoding instead. A custom Store whose constructor
can't be recreated from (path, options) (e.g. it takes injected dependencies) should override
createColumnFamilyStore to build its views.
const db = RocksDatabase.open('path/to/db');
const events = db.use('events');
await events.put('e1', payload);
await db.put('k', 'v'); // default column family, unaffected
console.log(events.get('e1')); // payload
console.log(db.get('e1')); // undefined — different column family
events.close();
db.close();Data Operations
db.catchUpWithPrimary(): Promise<void>
Advances a secondary instance to the primary's current state by tailing and replaying the primary's MANIFEST and WAL. A secondary does not see the primary's writes — flushed or not — until it catches up. Reads through the handle remain safe while the catch-up runs; catch-ups on the same database are serialized internally.
Throws with code ERR_NOT_SECONDARY on a database that was not opened with secondaryPath.
Column families the primary created after the secondary opened stay invisible until the secondary
reopens; ones the primary dropped remain readable until then. Catch-up advances the database
view only: transaction-log reads through a secondary serve the stores discovered at open, and a
store the primary creates afterward becomes visible only on reopen. Whether entries appended to an
already-open store are visible depends on where the writer is: a cross-process primary's appends
are not (the reader's view of the file extent is fixed at open), while a writer in the same
process shares the store object, so its appends are. Either way a log entry can describe data the
database view does not have yet — the log write completes before the RocksDB commit for every
writer, so log-leads-database is the normal direction and a consumer has to tolerate it. Serialize
catch-up calls per database. Each async call holds a libuv worker for the whole replay, and
concurrent calls queue on an internal per-database mutex while holding theirs, so a handful of
overlapping catch-ups on a backlogged follower can exhaust the default four-thread pool and stall
unrelated fs/dns/crypto work in the process. Await one before starting the next rather than
firing one per timer tick.
const primary = RocksDatabase.open('/path/to/database');
const follower = RocksDatabase.open('/path/to/database', {
secondaryPath: '/path/to/follower-workspace',
});
primary.putSync('foo', 'bar');
console.log(follower.getSync('foo')); // undefined — not caught up yet
await follower.catchUpWithPrimary();
console.log(follower.getSync('foo')); // 'bar'db.catchUpWithPrimarySync(): void
Synchronous version of catchUpWithPrimary(). The replay runs on the JS thread, so prefer the
async form unless the caller is already blocking.
follower.catchUpWithPrimarySync();db.clear(options?): Promise<number>
Asychronously removes all data in the current database.
options: objectbatchSize?: numberThe number of records to remove at once. Defaults to10000.
Returns the number of entries that were removed.
Note: This does not remove data from other column families within the same database path.
for (let i = 0; i < 10; i++) {
db.putSync(`key${i}`, `value${i}`);
}
const entriesRemoved = await db.clear();
console.log(entriesRemoved); // 10db.clearSync(options?): number
Synchronous version of db.clear().
options: objectbatchSize?: numberThe number of records to remove at once. Defaults to10000.
for (let i = 0; i < 10; i++) {
db.putSync(`key${i}`, `value${i}`);
}
const entriesRemoved = db.clearSync();
console.log(entriesRemoved); // 10db.compact(options?): Promise<void>
Compacts a range of keys in the database. In RocksDB, deleted keys are not immediately removed from the database. Instead, they are marked as deleted and a tombstone is written. This function triggers a manual compaction which removes the tombstones and reclaims space. Only one compaction per database path can be performed at a time.
options: objectstart?: KeyThe start key of the range to compact.end?: KeyThe end key of the range to compact.bottommost?: booleanAlso compact the bottommost level, rewriting every file in range. RocksDB skips that level by default when no compaction filter is installed, and it holds most of the data — so an ordinary compaction leaves it untouched. Because a changedcompressionalgorithm governs only newly written files, this is the way to re-encode data that already exists. It rewrites the whole range regardless of whether RocksDB considers it worthwhile, so it costs as much as the data is large. Defaults tofalse.
await db.compact();
await db.compact({ start: 'a', end: 'z' });
// Re-encode everything already on disk under the column family's current codec
await db.compact({ bottommost: true });On a read-only database this is a no-op: arguments are still validated, but the returned promise resolves without compacting rather than rejecting.
db.compactSync(options?): void
Synchronous version of compact(). On a read-only database it
validates its arguments and then returns without compacting, rather than throwing.
db.compactSync();
db.compactSync({ start: 'a', end: 'z' });
db.compactSync({ bottommost: true });db.destroy(): void
Completely removes a database based on the db instance's path including all data, column families,
and files on disk. Destruction owns the physical path for the process: it closes every writable and
read-only handle for that path, waits for registered backups and checkpoints to stop using the
native database, and prevents another handle from reopening the path until removal finishes. Those
waits are synchronous and can outlive lifecycleWaitSeconds once destruction has claimed the path,
because releasing the native database beneath an active copy would be unsafe.
A previously opened instance does not need to remain open, which allows an explicit destroy()
retry after failed physical cleanup. A never-opened or read-only instance cannot destroy the
database. shutdown() reports a pending cleanup tombstone but never retries deletion; only an
explicit destroy() can remove the path.
db.destroy();
console.log(fs.existsSync(db.path)); // falsedb.drop(): Promise<void>
Drops the column family the database was opened with (name). For the default column family this
clears all entries instead.
const db = RocksDatabase.open('path/to/db', { name: 'users' });
await db.drop();
db.close();Dropping column families
A drop retires the column family logically before it returns: the name is gone from
db.columns, a later open() with the same name creates a fresh, empty column family, and any
transaction that then stages a write to a handle of the dropped family, or commits one it staged
earlier, is refused whole with ERR_COLUMN_FAMILY_DROPPED (Column family "users" was dropped).
That terminal refusal releases the transaction's verification-table intents and bars further
writes or commit attempts; retained reads continue until the caller aborts the transaction, and
they still serve that transaction's own staged writes — values no commit will ever produce. A
caller that catches the refusal instead of letting it propagate must not read a value back through
the transaction and carry it forward.
If a staging call fails while the generation is being retired, retirement takes precedence and the
transaction is refused whole even when the immediate RocksDB failure was a pessimistic lock timeout.
Handles other threads still hold keep reading the dropped data until they close; a
non-transactional putSync/removeSync through such a handle is discarded.
The physical RocksDB drop is deferred behind commits already admitted when the drop lands: a
commit claims every column family its batch names before it writes its transaction-log batch and
releases them after RocksDB has applied it, and the physical drop runs from whichever
side releases last (or, for a commit a mid-flight close() tore out of its pipeline, from the next
drop, open of that name, or close on the database). With no such commit (the common case) drop()/dropSync() perform the
physical drop before returning, exactly as before. This is what keeps a drop racing another
thread's commit from latching RocksDB's fatal Invalid column family specified in write batch
error on the whole database.
Consequences to know about:
- A commit admitted before the drop completes successfully into the retiring generation, then the physical drop removes that generation. Its caller sees a successful commit, but those writes are intentionally discarded with the rest of the dropped column family; a same-name reopen creates a fresh, empty generation. If the transaction writes to a transaction log, its entries are still published; consumers must order the schema drop after those entries.
open()of a name whose previous generation is still held by an admitted commit waits for the full admission-to-reclamation interval (bounded byROCKSDB_JS_CF_RECLAIM_WAIT_MS, default30000) before creating the fresh column family; if the previous generation's physical drop failed, the open retries it once and throws with that error if it fails again.- A physical drop that fails (an I/O error writing the MANIFEST) keeps the name retired, is
retried on the next drop on the database, the next
open()of that name, or close, and is reported through the globallog.warnevent and thecolumnFamily.pendingReclaimsstat. When the failing drop was the onedrop()itself ran, the call rejects with that error as well.
What is not guaranteed: a process that exits while a physical drop is still pending, or after one failed, leaves the column family on disk under its name, and the next open of the database opens it as a live column family. A backup or checkpoint taken inside that window copies it. Callers that need a drop to survive a crash record their own durable tombstone before acknowledging it.
db.dropSync(): void
Synchronous version of db.drop(), with the same deferral contract.
const db = RocksDatabase.open('path/to/db');
db.dropSync();
db.close();db.flush(options?): Promise<void>
Flushes all in-memory data to disk asynchronously.
options: objectallowWriteStall?: booleanWhether the flush may proceed even though it will stall writes for its duration. Defaults tofalse, which — despite how that reads — means the flush waits until it can run without causing a stall, with no timeout: on a database stuck in a stall condition (immutable-memtable backlog, L0 stop trigger, an exhaustedWriteBufferManagerbudget), the returned promise never settles, and sinceflush()runs on the libuv threadpool (default size 4), a handful of concurrently stalled flushes can exhaust the pool and stall every unrelatedfs/dns/cryptocall and cold-cacheget()in the process, not only this database's. Passtrueup front, before issuing a flush you expect might stall, when it's a durability gate you'd rather have stall writers than wait indefinitely — there's no way to cancel an in-flightflush(), so this isn't a rescue for one already hung, and forcing the memtable switch can itself prolong an existing L0 stop-trigger condition rather than clear it. That cost is database-wide, covering every column family on the (process-global,worker_threads-shared) database handle, not just the caller's — and it relocates the hang rather than removing it: a stalled write blocks the database's single commit thread, which dispatches everyTransaction.commit()in order, so every commit behind it queues up too, including ones from callers that never touched flush. This is a different knob fromwriteBufferManagerAllowStall(seenew RocksDatabase()options), with nearly opposite polarity: that one governs whether theWriteBufferManagermay stall writers at all, this one governs whether one manual flush is willing to cause a stall rather than wait one out.
await db.flush();
// Chosen up front, as a durability gate willing to pay the stall cost
await db.flush({ allowWriteStall: true });On a read-only database this is a no-op: the options bag is still validated, but the returned promise resolves without flushing rather than rejecting.
db.flushSync(options?): void
Flushes all in-memory data to disk synchronously. Note that this can be an expensive operation, so
it is recommended to use flush() if you want to keep the event loop free.
options: objectSame asflush(). The defaultallowWriteStall: falsewait is taken on the calling thread, which here is the JS thread — so the hazard described there is strictly larger on this entry point, not smaller: instead of parking one libuv pool worker it freezes the event loop outright, and it holds the in-flight operation claim thatclose()waits on, so the database cannot be closed out from under it either. Preferflush()if there is any chance the database is in a stall condition.
db.flushSync();
db.flushSync({ allowWriteStall: true });On a read-only database it validates its options and then returns without flushing, rather than throwing.
db.get(key: Key, options?: GetOptions): MaybePromise<any>
Retreives the value for a given key. If the key does not exist, it will resolve undefined.
const result = await db.get('foo');
assert.equal(result, 'foo');If the value is in the memtable or block cache, get() will immediately return the value
synchronously instead of returning a promise.
const result = db.get('foo');
const value = result instanceof Promise ? await result : result;
assert.equal(result, 'foo');Note that all errors are returned as rejected promises.
See GetOptions for the available options.
When the expectedVersion option is set and the Verification Table records
a matching version for the key, get() returns the FRESH_VERSION_FLAG sentinel
(constants.FRESH_VERSION_FLAG) instead of reading the value — signalling that any value the
caller has already cached for this key is still fresh and no read was performed. Be sure to check
for this sentinel before treating the result as a value:
import { constants } from '@harperfast/rocksdb-js';
const result = db.get(key, { expectedVersion: cachedEntry.version });
if (result === constants.FRESH_VERSION_FLAG) {
// the cached value is still valid; no read occurred
return cachedEntry.value;
}db.getSync(key: Key, options?: GetOptions): any
Synchronous version of get(). Like get(), this can return the FRESH_VERSION_FLAG sentinel when
the expectedVersion option is used.
db.getEstimatedKeyCount(): number
Retrieves the estimated number of keys in the database. This is an alias for
db.getDBIntProperty('rocksdb.estimate-num-keys'); use estimateCount() for range support and a
confidence indicator.
const estimated = db.getEstimatedKeyCount();
console.log(estimated);db.estimateCount(options?: CountEstimateOptions): CountEstimate
Estimates the number of keys in the database, or within a key range, returning
{ count, confidence }. Unlike getKeysCount(), this never iterates: the estimate is derived
from RocksDB statistics (memtable stats plus approximate SST sizes converted through the entry
density of the SSTs overlapping the range), so its cost scales with the number of SSTs overlapping
the range rather than the number of keys. Reading cold table properties can do I/O through the
table cache, so narrow ranges are preferable. A start-only range is computed as the
whole-database estimate minus the complement, so it does the work of the range below start.
Accuracy improves with range size. Resolution is bounded by SST data-block granularity, so a range
narrower than a block is unreliable in either direction: it may over-report or report 0 for present
keys, and its low confidence is the signal. Recently deleted or overwritten entries may be counted
until compaction.
Estimates always reflect committed state; writes pending in a transaction are not included. Set
reverse: true to use getRange()'s reverse convention (start is the upper bound and end is
the lower bound). An inverted range (start ≥ end) returns
{ count: 0, confidence: 1 }.
confidence is a heuristic 0–1 indicator of how trustworthy count is — exactly 1 only when the
count is exact. It is derived from the estimate's resolution (data-block/memtable-sampling
granularity relative to the count), the tombstone fraction of the overlapping SSTs, and — for
start-only ranges — the error compounded by complement subtraction. Treat it as an ordering
signal (e.g. when to trust an estimate for query planning vs fall back to a heuristic), not a
statistical bound.
const { count, confidence } = db.estimateCount({ start: 'a', end: 'z' });db.createCountEstimator(options?: CountEstimatorOptions): CountEstimator
Creates an estimator that progressively refines a range count estimate while the range is being
iterated — useful for reporting a total alongside a page of results without scanning the full
range. Before any traversal, estimate() returns the pure statistical estimate (same as
estimateCount(range)). As the caller reports progress with advance(lastKey, count) (e.g. once
per page), estimate() returns the exact traversed count plus a statistical estimate of the
remainder, calibrated by the observed ratio of actual-to-estimated entries over the portion
already traversed — so the count converges toward the exact total. confidence is the
exactness-weighted blend of the traversed portion and the remainder's confidence, so it approaches 1
as the exact portion grows, although a checkpoint may decrease when calibration makes a large
correction.
Each checkpoint reads committed state, so a traversal performed against a transaction snapshot may
be calibrated against data committed after that snapshot.
When traversal completes, call finish() and estimate() returns the exact count with
confidence 1. Reverse ranges follow getRange: set start to the upper bound and end to the
lower bound, then set reverse: true. The caller owns the progress contract: cursors must move
monotonically through the range and each entry must be reported exactly once.
const range = { start: 'a', end: 'z' };
const estimator = db.createCountEstimator(range);
let lastKey;
let pageSize = 0;
for (const { key } of db.getRange({ ...range, limit: 25 })) {
lastKey = key;
pageSize++;
}
estimator.advance(lastKey, pageSize);
const { count, confidence } = estimator.estimate();db.getKeys(options?: IteratorOptions): ExtendedIterable
Retrieves all keys within a range.
for (const key of db.getKeys()) {
console.log(key);
}db.getKeysCount(options?: RangeOptions): number
Retrieves the exact number of keys in a database or a range.
const count = db.getKeysCount(); // estimated number of keys
const range = db.getKeysCount({ start: 'a', end: 'z' }); // exact number of keys in the rangedb.getMonotonicTimestamp(): number
Returns the current timestamp as a monotonically increasing timestamp in milliseconds represented as a decimal number. This process-wide clock also supplies each transaction's initial timestamp.
const ts = db.getMonotonicTimestamp();
console.log(ts); // 1764307857213.739It is a wall-clock (Unix epoch) value made strictly increasing: on a tie or a backward step of the
host clock it advances by one floating-point ulp per call until the wall clock catches up. That
keeps transaction timestamps ordered and durable, but it does not measure elapsed time — after a
backward step, differences between two calls understate real time. Use
steadyClockNow() for elapsed durations and deadlines.
db.getOldestSnapshotTimestamp(): number
Returns a number representing a unix timestamp of the oldest unreleased snapshot.
Snapshots are only created during transactions. When the database is opened in optimistic mode (the default), the snapshot will be created on the first read. When the database is opened in pessimistic mode, the snapshot will be created on the first read or write.
console.log(db.getOldestSnapshotTimestamp()); // returns `0`, no snapshots
const promise = db.transaction(async (txn) => {
// perform a write to create a snapshot
await txn.get('foo');
await setTimeout(100);
});
console.log(db.getOldestSnapshotTimestamp()); // returns `1752102248558`
await promise;
// transaction completes, snapshot released
console.log(db.getOldestSnapshotTimestamp()); // returns `0`, no snapshotsdb.getDBProperty(propertyName: string): string | undefined
Gets a RocksDB database property as a string.
propertyName: stringThe name of the property to retrieve (e.g., )'rocksdb.levelstats'.
Returns undefined if the property is not found.
const db = RocksDatabase.open('/path/to/database');
const levelStats = db.getDBProperty('rocksdb.levelstats');
const stats = db.getDBProperty('rocksdb.stats');db.getDBIntProperty(propertyName: string): number | undefined
Gets a RocksDB database property as an integer.
propertyName: stringThe name of the property to retrieve (e.g., )'rocksdb.num-blob-files'.
Returns undefined if the property is not found.
const db = RocksDatabase.open('/path/to/database');
const blobFiles = db.getDBIntProperty('rocksdb.num-blob-files');
const numKeys = db.getDBIntProperty('rocksdb.estimate-num-keys');db.isWriteStalled(): boolean
Whether RocksDB is currently applying write backpressure to this database —
delaying (rate-limiting) or fully stopping writes. Reads the live
rocksdb.is-write-stopped and rocksdb.actual-delayed-write-rate properties.
This is the authoritative, live pull counterpart to the
'writeStall' event: the event pushes per-column-family
entries into a stall (rate-limited), while this reports the current state on
demand and can never be stale. It is database-wide — the write controller is
shared across every column family, so it answers "are writes stalled anywhere"
rather than for one column family.
if (db.isWriteStalled()) {
console.warn('writes are currently throttled or blocked');
}db.getRange(options?: IteratorOptions): ExtendedIterable
Retrieves a range of keys and their values. Supports both synchronous and asynchronous iteration.
// sync
for (const { key, value } of db.getRange()) {
console.log({ key, value });
}
// async
for await (const { key, value } of db.getRange()) {
console.log({ key, value });
}
// key range
for (const { key, value } of db.getRange({ start: 'a', end: 'z' })) {
console.log({ key, value });
}Pass transaction: txn to iterate this column family through that transaction, exactly as
get() does with the same option: the iterator sees the transaction's staged writes and reads on its
snapshot, and range bounds apply to staged keys too. getKeys() and getKeysCount() accept it as
well. On a transaction (txn.getRange()) the transaction itself is the context and takes precedence
over a transaction option, as it does for txn.get(). Such an iterator is closed when the
transaction commits or aborts: a later next() throws, while return() stays a no-op. Opening a
range or counting through a transaction that has already started committing throws as well.
await db.transaction(async (txn) => {
await txn.put('c', 'staged');
for (const { key, value } of db.getRange({ start: 'a', end: 'z', transaction: txn })) {
console.log({ key, value }); // includes { key: "c", value: "staged" }
}
});db.getUserSharedBuffer(key: Key, defaultBuffer: ArrayBuffer, options?)
Creates a new buffer with the contents of defaultBuffer that can be accessed across threads. This
is useful for storing data such as flags, counters, or any ArrayBuffer-based data.
options?: objectcallback?: () => voidA optional callback is called whennotify()on the returned buffer is called.
Returns a new ArrayBuffer with two additional methods:
notify()- Invokes theoptions.callback, if specified.cancel()- Removes the callback; futurenotify()calls do nothing
Note: If a shared buffer already exists for the given key, the returned ArrayBuffer will
reference this existing shared buffer. The buffer lives as long as its column family is open (a
drop() discards it with the column family — except the default column family, which drop()
only clears, so its buffers survive): it is process-wide state, so a thread dropping or garbage
collecting its own view (or exiting) never resets it for the others. Because nothing is ever
evicted, key the buffer on a small fixed set of names rather than on unbounded data such as
record ids. The notify callback is removed when the ArrayBuffer it was registered with is
garbage collected, or by cancel().
const buffer = new Uint8Array(db.getUserSharedBuffer('isDone', new ArrayBuffer(1)));
done[0] = 0;
if (done[0] !== 1) {
done[1] = 1;
}const incrementer = new BigInt64Array(
db.getUserSharedBuffer('next-id', new BigInt64Array(1).buffer)
);
incrementer[0] = 1n;
function getNextId() {
return Atomics.add(incrementer, 0, 1n);
}db.put(key: Key, value: any, options?: PutOptions): Promise
Stores a value for a given key.
await db.put('foo', 'bar');db.putSync(key: Key, value: any, options?: PutOptions): void
Synchronous version of put().
db.remove(key: Key): Promise
Removes the value for a given key.
await db.remove('foo');db.removeSync(key: Key): void
Synchronous version of remove().
Transactions
db.transaction<T>(callback: TransactionCallback<T>, options?: TransactionOptions): Promise<T>
Executes all database operations within the specified callback within a single transaction. If the callback completes without error, the database operations are automatically committed. However, if an error is thrown during the callback, all database operations will be rolled back.
import type { Transaction } from '@harperfast/rocksdb-js';
await db.transaction(async (txn: Transaction) => {
await txn.put('foo', 'baz');
});Additionally, you may pass the transaction into any database data method:
await db.transaction(async (transaction: Transaction) => {
await db.put('foo', 'baz', { transaction });
});Note that db.transaction() returns whatever value the transaction callback returns:
const isBar = await db.transaction(async (txn: Transaction) => {
const foo = await txn.get('foo');
return foo === 'bar';
});
console.log(isBar ? 'Foo is bar' : 'Foo is not bar');db.transactionSync<T>(callback: TransactionCallback<T>, options?: TransactionOptions): T
Executes a transaction callback and commits synchronously. Once the transaction callback returns, the commit is executed synchronously and blocks the current thread until finished.
Inside a synchronous transaction, use getSync(), putSync(), and removeSync().
import type { Transaction } from '@harperfast/rocksdb-js';
db.transactionSync((txn: Transaction) => {
txn.putSync('foo', 'baz');
});Optimistic and Pessimistic Modes
rocksdb-js supports two different transaction modes: optimistic and pessimistic. The default mode
is optimistic.
- Optimistic: Conflicts detected at commit time.
- Pessimistic: Conflicts throw immediately on detection.
When a database is opened in optimistic mode, transactions are not locked and can be retried if they fail with a conflict. When a database is opened in pessimistic mode, transactions are aborted and cannot be retried if they fail with a conflict.
Optimistic mode is the default mode and is recommended for most use cases. Pessimistic mode is recommended for use cases where you need to know immediately if a conflict occurs.
If a database is opened in one mode, it cannot be opened in a different mode. An error will be thrown when trying to open it in a different mode without closing the database first.
TransactionCallback<T>
(txn: Transaction, attempt: number) => T | PromiseLike<T>
A sync or async function to encapsulate all of the transaction operations. Once the function is executed, the transaction is automatically committed. If the function returns a value, it will be returned from the transaction call.
The txn parameter is a Transaction. See the Transaction section for more
details.
The attempt parameter is the number of times the transaction has been retried.
TransactionOptions
coordinatedRetry?: booleanWhentrue, anIsBusyconflict at commit time is retried automatically instead of being rejected. Rather than retrying immediately and racing the conflicting transaction again, the retry waits until the conflicting transaction has committed and released its write intent, then re-runs the transaction body right away with no backoff delay. Requires the column family to be opened withverificationTable: true. See Verification Table. Defaults tofalse.disableSnapshot?: booleanWhether to disable snapshots. Defaults tofalse.maxRetries?: numberThe maximum number of times to retry the transaction. Defaults to3.retryOnBusy?: booleanWhether to retry the transaction if the commit fails withIsBusy. Defaults totruewhen the transaction is bound to a transaction log, otherwisefalse.
Transaction Retry Logic
The retry mechanism will only be active when the retryOnBusy option is true or when
retryOnBusy is undefined and the transaction is bound to a transaction log. The attempts starts
at 1 and ends at maxRetries.
When using a transaction log and the commit fails with ERR_BUSY, the transaction log will be in
a bad state and the transaction will need to be retried. If the max retries is reached or the
transaction is not retried, a ERR_TRANSACTION_ABANDONED error will be thrown.
Users should use the attempt transaction callback parameter to ensure duplicate transaction log
entries are not added.
When coordinatedRetry: true, the retry behavior changes for IsBusy conflicts: instead of
rejecting (or retrying immediately and potentially conflicting again), the commit waits until the
conflicting transaction has committed and released its write intent, then re-runs the transaction
body immediately with no backoff. This is still bounded by maxRetries — if the transaction has
not committed after the configured number of coordinated retries, the transaction is abandoned with
an ERR_TRANSACTION_ABANDONED error. Coordinated retry requires the column family to be opened with
verificationTable: true.
That wait is bounded so a conflicting transaction that is never committed or aborted cannot block
the commit forever: if the write intent has not been released after ROCKSDB_JS_PARK_TIMEOUT_MS
(default 5000), the commit resolves anyway and consumes a retry attempt exactly as a real release
would. A conflicting transaction held for longer than roughly maxRetries times that timeout
therefore ends in ERR_TRANSACTION_ABANDONED rather than waiting indefinitely. Deployments where
waiting is preferable to failing should raise the timeout; there is no way to disable the bound.
In particular 0 does not disable it: 0, negative, and unparseable values all fall back to the
5000 default, and a value between 1 and 49 is clamped up to 50.
Class: Transaction
The transaction callback is passed in a Transaction instance which contains all of the same data
operations methods as the RocksDatabase instance plus:
txn.abandonWrites(): voidReleases the staged writes' verification-table write intents without closing the transaction, and bars any further writes or commit. Reads (including read-your-own-writes) keep working until the transaction is aborted.txn.abort()Rolls back and closes the transaction. This method is automatically called after the transaction callback returns, so you shouldn't need to call it, but it's ok to do so. Once called, no further transaction operations are permitted. Calling this method multiple times has no effect.txn.commit(): Promise<void>Asynchronously commits the transaction and closes the transaction.txn.commitSync()Synchronously commits and closes the transaction.txn.getTimestamp(): numberRetrieves the transaction timestamp in milliseconds as a decimal. It defaults to a process-wide monotonic value assigned when the transaction was created.txn.id: numberThe read-only transaction ID. Transaction IDs are unique to the RocksDB database path, regardless the database name/column family.txn.setTimestamp(ts?: number): voidOverrides the transaction timestamp in milliseconds. If called without a timestamp, it claims a fresh monotonic value. It must be called before staging any write or transaction-log entry, and a supplied value must be finite, positive, and below8.64e15.
txn.abandonWrites(): void
Releases the staged writes' verification-table (VT) write intents without closing the transaction,
and bars any further writes or commit (commit()/commitSync()/put()/remove(), including
database-context writes via { transaction: txn }, all reject once called). Reads — including
read-your-own-writes — keep working until the transaction is aborted. Idempotent, and a no-op after
abort().
Scope is VT intents only: RocksDB's own transaction locks (pessimistic mode) are still held until the transaction is aborted.
This is for a transaction kept open only for its outstanding read iterators after its writes were
already committed elsewhere (e.g. replayed onto another transaction) — it lets the intents release
early so other writers' coordinated-retry commits stop parking on them, instead of waiting for the
handle's eventual abort().
txn.abort(): void
Rolls back and closes the transaction. This method is automatically called after the transaction callback returns, so you shouldn't need to call it, but it's ok to do so. Once called, no further transaction operations are permitted.
txn.commit(): Promise<void>
Commits and closes the transaction. This is a non-blocking operation and runs on a background thread. Once called, no further transaction operations are permitted.
txn.commitSync(): void
Synchronously commits and closes the transaction. This is a blocking operation on the main thread. Once called, no further transaction operations are permitted.
txn.getTimestamp(): number
Retrieves the transaction timestamp in milliseconds since the Unix epoch as a decimal. It defaults to a process-wide monotonic value assigned when the transaction was created. The transaction log uses this timestamp as the batch key; producers may also encode it into their own record format.
txn.id
Type: number
The transaction ID, a positive integer no greater than Number.MAX_SAFE_INTEGER. Transaction IDs
are unique to the RocksDB database path, regardless the database name/column family.
txn.setTimestamp(ts?: number): void
Overrides the transaction timestamp in milliseconds since the Unix epoch. Replication receivers and crash replay can use this to adopt an origin transaction's log key. If called without a timestamp, it claims a fresh monotonic value.
The override must happen while the transaction is pending and before any database write or
transaction-log entry is staged. A log batch that has already been written keeps its timestamp
across a coordinated retry; reapplying that same timestamp remains idempotent while pending. A
supplied value must be finite, positive, and below 8.64e15.
rocksdb-js does not define a record's value layout or version metadata. A producer that copies the
transaction timestamp into record bytes is responsible for calling setTimestamp() before reading
and encoding it.
await db.transaction(async (txn) => {
txn.setTimestamp(Date.now());
});Error Handling
db.getLastError(): BackgroundError | null
Returns the most recent BackgroundError observed on this database, or null when
none has occurred. This is the pull counterpart to the 'error' event: use it
for an on-demand check (e.g. a health probe) or to catch an error that fired before a listener was
attached. The value is historical — it is not cleared by a successful
db.resume(); reset it explicitly with
db.setLastError(null). When the returned error's writesDisabled is
true, writes are stopped until recovery.
const err = db.getLastError();
if (err?.writesDisabled) {
// ...free disk space, then...
db.resume();
}db.setLastError(error?): void
Sets or clears the last background error, mirroring the Win32 SetLastError / GetLastError pair.
- Clear — pass
null(or no argument).db.getLastError()then returnsnulland no event fires. This is how you reset the error after handling or recovering it (e.g. afterdb.resume()) — the equivalent ofSetLastError(ERROR_SUCCESS). - Set — pass an object (
{ message, severity?, severityName?, writesDisabled?, reason?, reasonName? };typedefaults to'background'). It is stored and the'error'event fires with the reconstructedBackgroundError. Useful for surfacing an application-level "this database is unusable" state, and for deterministically exercising the error path in tests.
// reset after recovery
db.resume();
db.setLastError(null);
// inject (e.g. in a test)
db.on('error', (err) => console.error(err.message));
db.setLastError({
message: 'disk quota exceeded',
severity: 2,
severityName: 'hard',
writesDisabled: true,
});db.resume(): void
Attempts to recover the database from a background error (see the 'error' event)
by calling RocksDB's DB::Resume(). Call this after the underlying condition has cleared — e.g.
once disk space has been freed. On success writes are accepted
