@harperfast/rocksdb-js 2.7.0 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,6 +11,8 @@ A Node.js binding for the RocksDB library.
11
11
  - Custom stores provide ability to override default database interactions
12
12
  - Efficient binary key and value encoding
13
13
  - Configurable block/blob compression (LZ4, Zstd, Zlib, and more)
14
+ - Observable background errors via the `'error'` event or `db.getLastError()`, with in-process
15
+ recovery (`db.resume()`)
14
16
  - Access to internal RocksDB statistics
15
17
  - Designed for Node.js and Bun on Linux, macOS, and Windows
16
18
 
@@ -53,10 +55,32 @@ Creates a new database instance.
53
55
  when linked, else no compression). See [Compression](#compression). Throws if the algorithm is
54
56
  not compiled into the native build — check [`supportedCompression`](#supportedcompression) for
55
57
  the available list.
58
+ - `compressionForAllColumnFamilies: boolean` When `true`, applies `compression` to every column
59
+ family opened for the database rather than only the column family specified by `name`. Requires an explicit
60
+ `compression`. Defaults to `false`. See [Compression](#compression).
61
+ - `dbWriteBufferSize: number` The total memtable memory budget in bytes shared across all of the
62
+ database's column families. When the combined size of all memtables reaches this value, RocksDB
63
+ flushes the largest one. `0` (the default) disables this global trigger, so per-column-family
64
+ `writeBufferSize` alone drives flushing. This is distinct from the process-wide
65
+ [`writeBufferManagerSize`](#dbconfigoptions) config option. Database-wide, so it binds when the
66
+ path is first opened in this process: a later open of the same path — including from another
67
+ worker thread — keeps the first opener's value rather than overriding or rejecting it.
56
68
  - `disableWAL: boolean` Whether to disable the RocksDB write ahead log. Defaults to `false`.
57
69
  - `enableStats: boolean` When `true` and the database is open, RocksDB will captures stats that
58
70
  are retrieved by calling `db.getStats()`. Enabling statistics imposes 5-10% in overhead.
59
71
  Defaults to `false`.
72
+ - `infoLogLevel: number` The verbosity of RocksDB's informational logging (`LOG` /
73
+ `LOG.old.*`): `0` (debug), `1` (info), `2` (warn), `3` (error), `4` (fatal), or `5`
74
+ (header-only). Omit to leave RocksDB's own default (`INFO_LEVEL` in a release build of the
75
+ linked RocksDB library). See [`db.logOptions`](#dblogoptions-maxlogfilesize-number-infologlevel-number).
76
+ - `maxLogFileSize: number` The per-file size cap, in bytes, for informational log files (`LOG` /
77
+ `LOG.old.*`). RocksDB retains up to 5 of these files, so the total informational-log footprint
78
+ is bounded at roughly `5 * maxLogFileSize`. Defaults to 16 MB (an 80 MB bound), which stops
79
+ purely informational logging from growing without bound. A value of `0` is RocksDB's special
80
+ "single unbounded log file" mode — it **disables size-based rotation entirely**, so the log
81
+ can grow without limit; only set `0` if you deliberately want that (it forgoes the bounded
82
+ footprint this option otherwise provides). See
83
+ [`db.logOptions`](#dblogoptions-maxlogfilesize-number-infologlevel-number).
60
84
  - `maxOpenFiles: number` The maximum number of table files RocksDB keeps open. `0` (the default)
61
85
  derives a budget from the effective per-process open-file limit (an eighth of the limit —
62
86
  several databases can share one process — clamped to `[1024, 262144]`); `-1` holds every table
@@ -64,6 +88,17 @@ Creates a new database instance.
64
88
  compaction falls behind under sustained ingest); a positive `int32` is an explicit cap. Reads
65
89
  only pay a reopen cost when the number of live table files exceeds the budget, so raise the
66
90
  process fd limit (and with it the derived budget) for very large databases.
91
+ - `maxWriteBufferNumber: number` The maximum number of memtables that can be queued per column
92
+ family before writes stall. Higher values absorb write bursts while flushes catch up, at the
93
+ cost of memory (roughly `maxWriteBufferNumber * writeBufferSize` per column family). Defaults to
94
+ `16`.
95
+ - `maxWriteBufferSizeToMaintain: number` The number of bytes of recent memtable history to keep in
96
+ memory for transaction conflict checking. `-1` (the default) derives the value from
97
+ `maxWriteBufferNumber * writeBufferSize` (the RocksDB-recommended default for optimistic
98
+ transactions) — except when a stalling [`writeBufferManager`](#dbconfigoptions) is configured
99
+ (`writeBufferManagerSize > 0` with `writeBufferManagerAllowStall`), in which case it resolves to
100
+ `0` to avoid retaining memtable history the manager will never release. An explicit non-negative
101
+ value is always honored as-is.
67
102
  - `name: string` The column family name. Defaults to `"default"`.
68
103
  - `noBlockCache: boolean` When `true`, disables the block cache. Block caching is enabled by
69
104
  default and the cache is shared across all database instances.
@@ -73,7 +108,10 @@ Creates a new database instance.
73
108
  until commit. Defaults to `false`.
74
109
  - `readOnly: boolean` When `true`, the database is opened in read-only mode. Read operations are
75
110
  permitted. Write operations will throw an error with code `ERR_DATABASE_READONLY`. Transactions
76
- are a no-op in read-only mode.
111
+ are a no-op in read-only mode. [`flush()`](#dbflushoptions-promisevoid),
112
+ [`flushSync()`](#dbflushsyncoptions-void), [`compact()`](#dbcompactoptions-promisevoid) and
113
+ [`compactSync()`](#dbcompactsyncoptions-void) are the exception: they have nothing to do when
114
+ there are no writes, so they succeed as no-ops rather than throwing.
77
115
  - `statsLevel: StatsLevel` Controls which type of statistics to skip and reduce statistic
78
116
  overhead. Defaults to `StatsLevel.ExceptDetailedTimers`.
79
117
  - `store: Store` A custom store that handles all interaction between the `RocksDatabase` or
@@ -84,10 +122,9 @@ Creates a new database instance.
84
122
  modified time to be older than the retention period before it is rotated to the next sequence
85
123
  number. Value must be between `0.0` and `1.0`. A threshold of `0.0` means ignore age check.
86
124
  Defaults to `0.75`.
87
- - `transactionLogMaxSize: number` The maximum size of a transaction log file. If a log file is
88
- empty, the first log entry will always be added regardless if it's larger than the max size. If
89
- a log file is not empty and the entry is larger than the space available, the log file is
90
- rotated to the next sequence number. Defaults to 16 MB.
125
+ - `transactionLogMaxSize: number` The target maximum size of a transaction log file. Transactions
126
+ are never split across files: if the complete transaction does not fit, the log rotates before
127
+ writing it. A transaction written to an empty file may exceed the target. Defaults to 16 MB.
91
128
  - `transactionLogRetention: string | number` The number of minutes to retain transaction logs
92
129
  before purging. Defaults to `'3d'` (3 days).
93
130
  - `transactionLogsPath: string` The path to store transaction logs. Defaults to
@@ -97,6 +134,9 @@ Creates a new database instance.
97
134
  the verification slot for each written key. Enable this only for column families whose records
98
135
  are cached (e.g. the primary column family of a table). Defaults to `false`. Requires
99
136
  `verificationTableEntries` to be configured before the first database is opened.
137
+ - `writeBufferSize: number` The per-column-family memtable size in bytes at which the memtable is
138
+ sealed and flushed to an SST file. Smaller values produce more frequent, faster flushes; larger
139
+ values batch more writes per SST file at the cost of memory. Defaults to `16777216` (16 MB).
100
140
 
101
141
  ### `db.close()`
102
142
 
@@ -179,6 +219,18 @@ Returns `true` if the database is open, otherwise false.
179
219
  console.log(db.isOpen()); // true or false
180
220
  ```
181
221
 
222
+ ### `db.logOptions: { maxLogFileSize: number, infoLogLevel: number }`
223
+
224
+ Returns the informational-log settings currently in effect for this database, read live from
225
+ RocksDB. These are database-wide settings (not per-column-family). `maxLogFileSize` is the
226
+ per-file size cap for informational log files (`LOG` / `LOG.old.*`); `infoLogLevel` is the logging
227
+ verbosity. The database must be open.
228
+
229
+ ```typescript
230
+ const db = RocksDatabase.open('path/to/db', { maxLogFileSize: 4 * 1024 * 1024 });
231
+ console.log(db.logOptions); // { maxLogFileSize: 4194304, infoLogLevel: 1 }
232
+ ```
233
+
182
234
  ### `db.name: string`
183
235
 
184
236
  Returns the database column family's name.
@@ -278,9 +330,13 @@ await db.compact({ start: 'a', end: 'z' });
278
330
  await db.compact({ bottommost: true });
279
331
  ```
280
332
 
333
+ On a [read-only](#new-rocksdatabasepath-options) database this is a no-op: arguments are still
334
+ validated, but the returned promise resolves without compacting rather than rejecting.
335
+
281
336
  ### `db.compactSync(options?): void`
282
337
 
283
- Synchronous version of `compact()`.
338
+ Synchronous version of `compact()`. On a [read-only](#new-rocksdatabasepath-options) database it
339
+ validates its arguments and then returns without compacting, rather than throwing.
284
340
 
285
341
  ```typescript
286
342
  db.compactSync();
@@ -321,23 +377,63 @@ db.dropSync();
321
377
  db.close();
322
378
  ```
323
379
 
324
- ### `db.flush(): Promise<void>`
380
+ ### `db.flush(options?): Promise<void>`
325
381
 
326
382
  Flushes all in-memory data to disk asynchronously.
327
383
 
384
+ - `options: object`
385
+ - `allowWriteStall?: boolean` Whether the flush may proceed even though it will stall writes for
386
+ its duration. Defaults to `false`, which — despite how that reads — means the flush **waits**
387
+ until it can run without causing a stall, with no timeout: on a database stuck in a stall
388
+ condition (immutable-memtable backlog, L0 stop trigger, an exhausted `WriteBufferManager`
389
+ budget), the returned promise never settles, and since `flush()` runs on the libuv threadpool
390
+ (default size 4), a handful of concurrently stalled flushes can exhaust the pool and stall
391
+ every unrelated `fs`/`dns`/`crypto` call and cold-cache `get()` in the process, not only this
392
+ database's. Pass `true` up front, before issuing a flush you expect might stall, when it's a
393
+ durability gate you'd rather have stall writers than wait indefinitely — there's no way to
394
+ cancel an in-flight `flush()`, so this isn't a rescue for one already hung, and forcing the
395
+ memtable switch can itself prolong an existing L0 stop-trigger condition rather than clear it.
396
+ That cost is database-wide, covering every column family on the (process-global,
397
+ `worker_threads`-shared) database handle, not just the caller's — and it relocates the hang
398
+ rather than removing it: a stalled write blocks the database's single commit thread, which
399
+ dispatches every `Transaction.commit()` in order, so every commit behind it queues up too,
400
+ including ones from callers that never touched flush. This is a different knob from
401
+ `writeBufferManagerAllowStall` (see [`new RocksDatabase()`](#new-rocksdatabasepath-options)
402
+ options), with nearly opposite polarity: that one governs whether the `WriteBufferManager` may
403
+ stall writers at all, this one governs whether one manual flush is willing to cause a stall
404
+ rather than wait one out.
405
+
328
406
  ```typescript
329
407
  await db.flush();
408
+
409
+ // Chosen up front, as a durability gate willing to pay the stall cost
410
+ await db.flush({ allowWriteStall: true });
330
411
  ```
331
412
 
332
- ### `db.flushSync(): void`
413
+ On a [read-only](#new-rocksdatabasepath-options) database this is a no-op: the options bag is still
414
+ validated, but the returned promise resolves without flushing rather than rejecting.
415
+
416
+ ### `db.flushSync(options?): void`
333
417
 
334
418
  Flushes all in-memory data to disk synchronously. Note that this can be an expensive operation, so
335
419
  it is recommended to use `flush()` if you want to keep the event loop free.
336
420
 
421
+ - `options: object` Same as [`flush()`](#dbflushoptions-promisevoid). The default
422
+ `allowWriteStall: false` wait is taken on the calling thread, which here is the JS thread — so
423
+ the hazard described there is strictly larger on this entry point, not smaller: instead of
424
+ parking one libuv pool worker it freezes the event loop outright, and it holds the in-flight
425
+ operation claim that `close()` waits on, so the database cannot be closed out from under it
426
+ either. Prefer `flush()` if there is any chance the database is in a stall condition.
427
+
337
428
  ```typescript
338
429
  db.flushSync();
430
+
431
+ db.flushSync({ allowWriteStall: true });
339
432
  ```
340
433
 
434
+ On a [read-only](#new-rocksdatabasepath-options) database it validates its options and then returns
435
+ without flushing, rather than throwing.
436
+
341
437
  ### `db.get(key: Key, options?: GetOptions): MaybePromise<any>`
342
438
 
343
439
  Retreives the value for a given key. If the key does not exist, it will resolve `undefined`.
@@ -384,13 +480,75 @@ the `expectedVersion` option is used.
384
480
  ### `db.getEstimatedKeyCount(): number`
385
481
 
386
482
  Retrieves the estimated number of keys in the database. This is an alias for
387
- `db.getDBIntProperty('rocksdb.estimate-num-keys')`.
483
+ `db.getDBIntProperty('rocksdb.estimate-num-keys')`; use `estimateCount()` for range support and a
484
+ confidence indicator.
388
485
 
389
486
  ```typescript
390
487
  const estimated = db.getEstimatedKeyCount();
391
488
  console.log(estimated);
392
489
  ```
393
490
 
491
+ ### `db.estimateCount(options?: CountEstimateOptions): CountEstimate`
492
+
493
+ Estimates the number of keys in the database, or within a key range, returning
494
+ `{ count, confidence }`. Unlike `getKeysCount()`, this never iterates: the estimate is derived
495
+ from RocksDB statistics (memtable stats plus approximate SST sizes converted through the entry
496
+ density of the SSTs overlapping the range), so its cost scales with the number of SSTs overlapping
497
+ the range rather than the number of keys. Reading cold table properties can do I/O through the
498
+ table cache, so narrow ranges are preferable. A start-only range is computed as the
499
+ whole-database estimate minus the complement, so it does the work of the range _below_ `start`.
500
+ Accuracy improves with range size. Resolution is bounded by SST data-block granularity, so a range
501
+ narrower than a block is unreliable in either direction: it may over-report or report 0 for present
502
+ keys, and its low `confidence` is the signal. Recently deleted or overwritten entries may be counted
503
+ until compaction.
504
+ Estimates always reflect committed state; writes pending in a transaction are not included. Set
505
+ `reverse: true` to use `getRange()`'s reverse convention (`start` is the upper bound and `end` is
506
+ the lower bound). An inverted range (`start` ≥ `end`) returns
507
+ `{ count: 0, confidence: 1 }`.
508
+
509
+ `confidence` is a heuristic 0–1 indicator of how trustworthy `count` is — exactly 1 only when the
510
+ count is exact. It is derived from the estimate's resolution (data-block/memtable-sampling
511
+ granularity relative to the count), the tombstone fraction of the overlapping SSTs, and — for
512
+ start-only ranges — the error compounded by complement subtraction. Treat it as an ordering
513
+ signal (e.g. when to trust an estimate for query planning vs fall back to a heuristic), not a
514
+ statistical bound.
515
+
516
+ ```typescript
517
+ const { count, confidence } = db.estimateCount({ start: 'a', end: 'z' });
518
+ ```
519
+
520
+ ### `db.createCountEstimator(options?: CountEstimatorOptions): CountEstimator`
521
+
522
+ Creates an estimator that progressively refines a range count estimate while the range is being
523
+ iterated — useful for reporting a total alongside a page of results without scanning the full
524
+ range. Before any traversal, `estimate()` returns the pure statistical estimate (same as
525
+ `estimateCount(range)`). As the caller reports progress with `advance(lastKey, count)` (e.g. once
526
+ per page), `estimate()` returns the exact traversed count plus a statistical estimate of the
527
+ remainder, calibrated by the observed ratio of actual-to-estimated entries over the portion
528
+ already traversed — so the count converges toward the exact total. `confidence` is the
529
+ exactness-weighted blend of the traversed portion and the remainder's confidence, so it approaches 1
530
+ as the exact portion grows, although a checkpoint may decrease when calibration makes a large
531
+ correction.
532
+ Each checkpoint reads committed state, so a traversal performed against a transaction snapshot may
533
+ be calibrated against data committed after that snapshot.
534
+ When traversal completes, call `finish()` and `estimate()` returns the exact count with
535
+ confidence 1. Reverse ranges follow `getRange`: set `start` to the upper bound and `end` to the
536
+ lower bound, then set `reverse: true`. The caller owns the progress contract: cursors must move
537
+ monotonically through the range and each entry must be reported exactly once.
538
+
539
+ ```typescript
540
+ const range = { start: 'a', end: 'z' };
541
+ const estimator = db.createCountEstimator(range);
542
+ let lastKey;
543
+ let pageSize = 0;
544
+ for (const { key } of db.getRange({ ...range, limit: 25 })) {
545
+ lastKey = key;
546
+ pageSize++;
547
+ }
548
+ estimator.advance(lastKey, pageSize);
549
+ const { count, confidence } = estimator.estimate();
550
+ ```
551
+
394
552
  ### `db.getKeys(options?: IteratorOptions): ExtendedIterable`
395
553
 
396
554
  Retrieves all keys within a range.
@@ -668,6 +826,15 @@ not committed after the configured number of coordinated retries, the transactio
668
826
  an `ERR_TRANSACTION_ABANDONED` error. Coordinated retry requires the column family to be opened with
669
827
  `verificationTable: true`.
670
828
 
829
+ That wait is bounded so a conflicting transaction that is never committed or aborted cannot block
830
+ the commit forever: if the write intent has not been released after `ROCKSDB_JS_PARK_TIMEOUT_MS`
831
+ (default `5000`), the commit resolves anyway and consumes a retry attempt exactly as a real release
832
+ would. A conflicting transaction held for longer than roughly `maxRetries` times that timeout
833
+ therefore ends in `ERR_TRANSACTION_ABANDONED` rather than waiting indefinitely. Deployments where
834
+ waiting is preferable to failing should raise the timeout; there is no way to disable the bound.
835
+ In particular `0` does not disable it: `0`, negative, and unparseable values all fall back to the
836
+ `5000` default, and a value between `1` and `49` is clamped up to `50`.
837
+
671
838
  ### Class: `Transaction`
672
839
 
673
840
  The transaction callback is passed in a `Transaction` instance which contains all of the same data
@@ -744,6 +911,73 @@ await db.transaction(async (txn) => {
744
911
  });
745
912
  ```
746
913
 
914
+ ## Error Handling
915
+
916
+ ### `db.getLastError(): BackgroundError | null`
917
+
918
+ Returns the most recent [`BackgroundError`](#event-error) observed on this database, or `null` when
919
+ none has occurred. This is the **pull** counterpart to the `'error'` [event](#event-error): use it
920
+ for an on-demand check (e.g. a health probe) or to catch an error that fired before a listener was
921
+ attached. The value is historical — it is **not** cleared by a successful
922
+ [`db.resume()`](#dbresume-void); reset it explicitly with
923
+ [`db.setLastError(null)`](#dbsetlasterrorerror-void). When the returned error's `writesDisabled` is
924
+ `true`, writes are stopped until recovery.
925
+
926
+ ```typescript
927
+ const err = db.getLastError();
928
+ if (err?.writesDisabled) {
929
+ // ...free disk space, then...
930
+ db.resume();
931
+ }
932
+ ```
933
+
934
+ ### `db.setLastError(error?): void`
935
+
936
+ Sets or clears the last background error, mirroring the Win32 `SetLastError` / `GetLastError` pair.
937
+
938
+ - **Clear** — pass `null` (or no argument). [`db.getLastError()`](#dbgetlasterror-backgrounderror--null)
939
+ then returns `null` and no event fires. This is how you reset the error after handling or
940
+ recovering it (e.g. after [`db.resume()`](#dbresume-void)) — the equivalent of
941
+ `SetLastError(ERROR_SUCCESS)`.
942
+ - **Set** — pass an object (`{ message, severity?, severityName?, writesDisabled?, reason?, reasonName? }`;
943
+ `type` defaults to `'background'`). It is stored and the `'error'` [event](#event-error) fires with
944
+ the reconstructed [`BackgroundError`](#event-error). Useful for surfacing an application-level
945
+ "this database is unusable" state, and for deterministically exercising the error path in tests.
946
+
947
+ ```typescript
948
+ // reset after recovery
949
+ db.resume();
950
+ db.setLastError(null);
951
+
952
+ // inject (e.g. in a test)
953
+ db.on('error', (err) => console.error(err.message));
954
+ db.setLastError({
955
+ message: 'disk quota exceeded',
956
+ severity: 2,
957
+ severityName: 'hard',
958
+ writesDisabled: true,
959
+ });
960
+ ```
961
+
962
+ ### `db.resume(): void`
963
+
964
+ Attempts to recover the database from a background error (see the `'error'` [event](#event-error))
965
+ by calling RocksDB's `DB::Resume()`. Call this after the underlying condition has cleared — e.g.
966
+ once disk space has been freed. On success writes are accepted again (and RocksDB can resume the
967
+ obsolete-file cleanup it was blocking); on failure — the condition has not actually cleared — it
968
+ throws with the RocksDB error and the database stays read-only. A no-op on a healthy database.
969
+
970
+ Runs synchronously and may briefly block, since recovery can re-flush memtables.
971
+
972
+ ```typescript
973
+ db.on('error', (err) => {
974
+ if (err.writesDisabled) {
975
+ // ...free disk space, then...
976
+ db.resume();
977
+ }
978
+ });
979
+ ```
980
+
747
981
  ## Events
748
982
 
749
983
  ### Event: `'aftercommit'`
@@ -770,6 +1004,57 @@ The `'committed'` event is emitted after the transaction has been written. When
770
1004
  emitted, the transaction is still cleaning up. If you need to know when the transaction is fully
771
1005
  complete, use the `'aftercommit'` event.
772
1006
 
1007
+ ### Event: `'error'`
1008
+
1009
+ When a write fails at the filesystem level — a full disk, an exhausted quota — RocksDB records a
1010
+ background error and, for a hard-or-worse severity, stops accepting writes. The database emits an
1011
+ `'error'` event carrying a `BackgroundError` so a consumer can observe it and recover in-process
1012
+ instead of restarting. When `writesDisabled` is `true`, call
1013
+ [`db.resume()`](#dbresume-void) after the underlying condition clears. Register the listener with
1014
+ `db.on('error', ...)` before the database is put under load — or use the pull-based
1015
+ [`db.getLastError()`](#dbgetlasterror-backgrounderror--null) to catch an error that fired earlier.
1016
+
1017
+ `BackgroundError` is a real `Error` subclass, exported from the package, so both
1018
+ `err instanceof Error` and `err instanceof BackgroundError` hold. It extends `Error` with:
1019
+
1020
+ - `message: string` — the RocksDB error string.
1021
+ - `name: string` — always `'BackgroundError'`.
1022
+ - `type: string` — the error-class discriminator; always `'background'`.
1023
+ - `severity: number` — the RocksDB `Status::Severity`: `1` soft, `2` hard, `3` fatal,
1024
+ `4` unrecoverable.
1025
+ - `severityName: string` — `'soft'`, `'hard'`, `'fatal'`, or `'unrecoverable'`.
1026
+ - `writesDisabled: boolean` — whether RocksDB has disabled writes on the database (`severity >= 2`).
1027
+ Only then is `resume()` warranted; a soft error is auto-recoverable and leaves writes enabled.
1028
+ Distinct from opening the database in read-only mode.
1029
+ - `reason?: number` / `reasonName?: string` — the originating `BackgroundErrorReason`
1030
+ (e.g. `'flush'`, `'compaction'`), present when the error came from a reason-bearing callback.
1031
+
1032
+ ```typescript
1033
+ import { BackgroundError } from '@harperfast/rocksdb-js';
1034
+
1035
+ db.on('error', (err) => {
1036
+ if (err instanceof BackgroundError && err.writesDisabled) {
1037
+ console.error(`writes disabled (${err.severityName}): ${err.message}`);
1038
+ }
1039
+ });
1040
+ ```
1041
+
1042
+ ```typescript
1043
+ db.on('error', (err) => {
1044
+ if (err.writesDisabled) {
1045
+ // Hard-or-worse: RocksDB has disabled writes on this database. They stay
1046
+ // disabled until you clear the underlying condition and call db.resume().
1047
+ console.error(`database writes disabled (${err.severityName}): ${err.message}`);
1048
+ } else {
1049
+ // Soft error: writes are NOT disabled and RocksDB will auto-recover. This
1050
+ // is typically a transient background hiccup (e.g. a retryable I/O error
1051
+ // during flush or compaction), surfaced for visibility only — no action
1052
+ // is required.
1053
+ console.warn(`recoverable background error (${err.severityName}): ${err.message}`);
1054
+ }
1055
+ });
1056
+ ```
1057
+
773
1058
  ## Event API
774
1059
 
775
1060
  `rocksdb-js` provides a EventEmitter-like API that lets you asynchronously notify events to one or
@@ -1681,7 +1966,7 @@ snapshots of a database. A backup covers the **entire database** — every colum
1681
1966
  manifest, and (by default) the write-ahead log — so it is not scoped to an individual `Store`.
1682
1967
 
1683
1968
  A backup can be written to a local **directory** (incremental, with a management API) or streamed to
1684
- a **`WritableStream`** as a tar archive with no intermediate copy on disk. See
1969
+ a `WritableStream` as a tar archive with no intermediate copy on disk. See
1685
1970
  [docs/backups.md](docs/backups.md) for a full guide covering both modes, restore, checkpoints, and
1686
1971
  caveats.
1687
1972
 
@@ -1757,10 +2042,10 @@ required bytes — before any files are copied, so a full destination fails fast
1757
2042
  through (a mid-copy failure can leave zero usable backups). The required size is estimated
1758
2043
  conservatively as a full copy of the database's live files, plus the transaction-log snapshot when
1759
2044
  `transactionLogs` is set; since a backup only ever copies files, this can never under-estimate.
1760
- Incremental backups to a right-sized volume may therefore be over-rejected — set `checkDiskSpace:
1761
- false` to skip the check. It also self-disables when free space can't be determined (e.g. some
1762
- network filesystems report `0`), so it never blocks a backup on an untrustworthy number. Only
1763
- directory-target backups are checked; the streaming backup path is unaffected.
2045
+ Incremental backups to a right-sized volume may therefore be over-rejected — set
2046
+ `checkDiskSpace: false` to skip the check. It also self-disables when free space can't be determined
2047
+ (e.g. some network filesystems report `0`), so it never blocks a backup on an untrustworthy number.
2048
+ Only directory-target backups are checked; the streaming backup path is unaffected.
1764
2049
 
1765
2050
  ### `db.backup(stream: WritableStream<Uint8Array>, options?: BackupStreamOptions): Promise<void>`
1766
2051