@harperfast/rocksdb-js 2.5.0 → 2.6.1

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
@@ -10,6 +10,7 @@ A Node.js binding for the RocksDB library.
10
10
  - Transaction log system for recording transaction related data
11
11
  - Custom stores provide ability to override default database interactions
12
12
  - Efficient binary key and value encoding
13
+ - Configurable block/blob compression (LZ4, Zstd, Zlib, and more)
13
14
  - Access to internal RocksDB statistics
14
15
  - Designed for Node.js and Bun on Linux, macOS, and Windows
15
16
 
@@ -43,6 +44,15 @@ Creates a new database instance.
43
44
  - `path: string` The path to write the database files to. This path does not need to exist, but the
44
45
  parent directories do.
45
46
  - `options: object` [optional]
47
+ - `compression: string | { algorithm: string, level?: number }` The block/blob compression
48
+ algorithm for this column family. Pass an algorithm name — one of `'none'`, `'snappy'`,
49
+ `'zlib'`, `'bzip2'`, `'lz4'`, `'lz4hc'`, or `'zstd'` — or an object with an
50
+ `algorithm` and an optional `level` (forwarded to RocksDB's `compression_opts.level`; the
51
+ meaning is algorithm-specific). Applies to both SST data blocks and blob files (large values).
52
+ Defaults to `'lz4'` when the native build supports it, otherwise RocksDB's own default (Snappy
53
+ when linked, else no compression). See [Compression](#compression). Throws if the algorithm is
54
+ not compiled into the native build — check [`supportedCompression`](#supportedcompression) for
55
+ the available list.
46
56
  - `disableWAL: boolean` Whether to disable the RocksDB write ahead log. Defaults to `false`.
47
57
  - `enableStats: boolean` When `true` and the database is open, RocksDB will captures stats that
48
58
  are retrieved by calling `db.getStats()`. Enabling statistics imposes 5-10% in overhead.
@@ -67,7 +77,8 @@ Creates a new database instance.
67
77
  - `statsLevel: StatsLevel` Controls which type of statistics to skip and reduce statistic
68
78
  overhead. Defaults to `StatsLevel.ExceptDetailedTimers`.
69
79
  - `store: Store` A custom store that handles all interaction between the `RocksDatabase` or
70
- `Transaction` instances and the native database interface. See [Custom Store](#custom-store) for
80
+ `Transaction` instances and the native database interface. A store is bound to a single
81
+ `RocksDatabase` instance and cannot be shared between them. See [Custom Store](#custom-store) for
71
82
  more information.
72
83
  - `transactionLogMaxAgeThreshold: number` The threshold for the transaction log file's last
73
84
  modified time to be older than the retention period before it is rotated to the next sequence
@@ -109,6 +120,18 @@ const db2 = new RocksDatabase('path/to/db', { name: 'users' });
109
120
  console.log(db.columns); // ['default', 'users']
110
121
  ```
111
122
 
123
+ ### `db.compression: { algorithm: string, level?: number }`
124
+
125
+ Returns the compression currently in effect for this database's column family, read live from
126
+ RocksDB. `algorithm` is a friendly name (e.g. `'lz4'`, `'zstd'`, `'none'`); `level` is present only
127
+ when a non-default compression level is set. The database must be open. See
128
+ [Compression](#compression).
129
+
130
+ ```typescript
131
+ const db = RocksDatabase.open('path/to/db', { compression: { algorithm: 'zstd', level: 3 } });
132
+ console.log(db.compression); // { algorithm: 'zstd', level: 3 }
133
+ ```
134
+
112
135
  ### `db.config(options)`
113
136
 
114
137
  Sets global database settings.
@@ -911,6 +934,77 @@ An object is a record with the following properties:
911
934
  - `standardDeviation: number` A double containing the standard deviation.
912
935
  - `sum: number` An unsigned 64-bit integer containing the sum of all values.
913
936
 
937
+ ## Compression
938
+
939
+ RocksDB compresses data blocks in SST files (and, in `rocksdb-js`, blob files that hold large
940
+ values) using a configurable algorithm. Compression trades CPU for disk space and, because it
941
+ reduces bytes read from disk, often improves read throughput on I/O-bound workloads.
942
+
943
+ The following algorithm names are recognized. Which ones are actually usable depends on the native
944
+ build — see **Availability** below.
945
+
946
+ | Name | Notes |
947
+ | -------- | ----------------------------------------------------------------------------- |
948
+ | `none` | No compression. Always available. |
949
+ | `snappy` | Fast, modest ratio. RocksDB's own stock default when linked. |
950
+ | `zlib` | Higher ratio, slower. Supports `level` (see zlib's manual). |
951
+ | `bzip2` | High ratio, slow; rarely worth it over zstd. |
952
+ | `lz4` | Very fast, modest ratio. `rocksdb-js` default when available. |
953
+ | `lz4hc` | LZ4 high-compression variant: better ratio, slower writes, same fast reads. |
954
+ | `zstd` | Best ratio-for-speed of the set; supports `level` (higher = smaller, slower). |
955
+
956
+ Set the algorithm per column family with the `compression` option when opening a database:
957
+
958
+ ```typescript
959
+ // Algorithm name
960
+ const db = RocksDatabase.open('/path/to/db', { compression: 'zstd' });
961
+
962
+ // Or an object with an explicit level
963
+ const db2 = RocksDatabase.open('/path/to/db2', { compression: { algorithm: 'zstd', level: 3 } });
964
+
965
+ // Disable compression entirely
966
+ const db3 = RocksDatabase.open('/path/to/db3', { compression: 'none' });
967
+ ```
968
+
969
+ **Default.** When `compression` is omitted, `rocksdb-js` defaults to **LZ4** if the native build
970
+ supports it. LZ4 is fast with a modest compression ratio, making it a good general-purpose default.
971
+ If LZ4 is not compiled in, the default falls back to RocksDB's own default — Snappy when it is
972
+ linked, otherwise no compression. (RocksDB's stock default is Snappy; `rocksdb-js` overrides it to
973
+ LZ4.) Read the algorithm actually in effect with the `db.compression` getter.
974
+
975
+ **Availability.** The set of algorithms depends on which compression libraries the native binding
976
+ was compiled against, so it varies by build. Always check
977
+ [`supportedCompression`](#supportedcompression) at runtime — opening with an unavailable algorithm
978
+ throws. `'none'` is always available.
979
+
980
+ **Scope and mutability.** Compression is genuinely **per-column-family**. Each `RocksDatabase`
981
+ targets one column family (`name`), and its `compression` applies only to that CF — opening one CF
982
+ never changes another's algorithm, regardless of open order. A column family keeps its own
983
+ compression across a close/reopen (a plain reopen inherits it); the algorithm is dynamically
984
+ changeable, so an explicit change governs files written afterward while existing SST/blob files keep
985
+ their original compression until rewritten by compaction. It also applies to blob files (large
986
+ values), whose compression otherwise defaults to none.
987
+
988
+ Because a column family's compression is fixed while it is open, if the same column family is opened
989
+ a second time in the same process (another `RocksDatabase` on the same path/`name`, including from a
990
+ `worker_thread`) with an **explicitly different** algorithm or level, the second open **throws** — a
991
+ plain reopen (no `compression`) instead inherits the live setting.
992
+
993
+ ### `supportedCompression`
994
+
995
+ A module-level, frozen array of the compression algorithm names compiled into the loaded native
996
+ binding. The set is fixed for a given binary. `'none'` is always present.
997
+
998
+ ```typescript
999
+ import { supportedCompression } from '@harperfast/rocksdb-js';
1000
+
1001
+ console.log(supportedCompression); // e.g. ['none', 'snappy', 'lz4', 'zstd']
1002
+
1003
+ if (supportedCompression.includes('zstd')) {
1004
+ // safe to open with { compression: 'zstd' }
1005
+ }
1006
+ ```
1007
+
914
1008
  ## Exclusive Locking
915
1009
 
916
1010
  `rocksdb-js` includes a handful of functions for executing thread-safe mutually exclusive functions.
@@ -1767,6 +1861,13 @@ await db.put('foo', 'bar');
1767
1861
  console.log(await db.get('foo'));
1768
1862
  ```
1769
1863
 
1864
+ > [!NOTE]
1865
+ > A `Store` is bound to a single `RocksDatabase` instance. Passing a store that another
1866
+ > `RocksDatabase` already owns throws `Store is already in use by another RocksDatabase instance`.
1867
+ > The claim is durable across `db.close()`, so reopening the original owner does not release it.
1868
+ > (Sharing a store with the owning database's `Transaction` instances is expected and handled
1869
+ > internally.)
1870
+
1770
1871
  > [!IMPORTANT]
1771
1872
  > If your custom store overrides `putSync()` without calling `super.putSync()` and it performs its
1772
1873
  > own `this.encodeKey(key)`, then you MUST encode the VALUE before you encode the KEY.
package/dist/index.cjs CHANGED
@@ -87,6 +87,15 @@ const removeGlobalListener = binding.removeListener;
87
87
  const globalListenerCount = binding.listenerCount;
88
88
  const globalNotify = binding.notify;
89
89
  const constants = binding.constants;
90
+ /**
91
+ * The friendly names of every compression algorithm compiled into the loaded
92
+ * RocksDB native binding, e.g. `['none', 'snappy', 'lz4', 'zstd']`. The set is
93
+ * fixed for a given binary (it depends on which compression libraries RocksDB
94
+ * was linked against), so this is a static list. `'none'` (no compression) is
95
+ * always present. Use it to validate a `compression` option or to pick an
96
+ * algorithm that is actually available at runtime.
97
+ */
98
+ const supportedCompression = Object.freeze(binding.supportedCompression);
90
99
  const NativeDatabase = binding.Database;
91
100
  const NativeIterator = binding.Iterator;
92
101
  const NativeTransaction = binding.Transaction;
@@ -1228,13 +1237,55 @@ const ITERATOR_STATE = new Uint32Array(ITERATOR_STATE_BUFFER.buffer, ITERATOR_ST
1228
1237
  const MAX_KEY_SIZE = 1024 * 1024;
1229
1238
  const SAVE_BUFFER_SIZE = 8192;
1230
1239
  /**
1240
+ * Normalizes the public `compression` option into the primitive fields the
1241
+ * native layer expects. When `option` is omitted, or is an object without an
1242
+ * `algorithm`, returns an empty object and lets the native layer apply the
1243
+ * default (LZ4 when the build supports it, else RocksDB's own default).
1244
+ * Throws a `TypeError` for a malformed option and an
1245
+ * `Error` for an algorithm that isn't supported by this build.
1246
+ */
1247
+ function normalizeCompression(option) {
1248
+ if (option === void 0 || option === null) return {};
1249
+ let algorithm;
1250
+ let level;
1251
+ if (typeof option === "string") algorithm = option;
1252
+ else if (typeof option === "object" && !Array.isArray(option)) {
1253
+ if (option.algorithm === void 0 || option.algorithm === null) {
1254
+ if (option.level !== void 0 && option.level !== null) throw new TypeError("Compression level cannot be specified without an algorithm");
1255
+ return {};
1256
+ }
1257
+ algorithm = option.algorithm;
1258
+ if (option.level !== void 0 && option.level !== null) {
1259
+ const raw = option.level;
1260
+ const coerced = typeof raw === "number" ? raw : typeof raw === "string" && raw.trim() !== "" ? Number(raw) : NaN;
1261
+ if (!Number.isInteger(coerced) || coerced < -2147483648 || coerced > 2147483647) throw new TypeError(`compression level must be a 32-bit integer, got ${JSON.stringify(option.level)}`);
1262
+ level = coerced;
1263
+ }
1264
+ } else throw new TypeError("compression must be an algorithm name or an { algorithm, level } object");
1265
+ if (!supportedCompression.includes(algorithm)) throw new Error(`Unsupported compression algorithm "${algorithm}". This build supports: ${supportedCompression.join(", ")}`);
1266
+ return {
1267
+ compression: algorithm,
1268
+ compressionLevel: level
1269
+ };
1270
+ }
1271
+ /**
1231
1272
  * A store wraps the `NativeDatabase` binding and database settings so that a
1232
1273
  * single database instance can be shared between the main `RocksDatabase`
1233
1274
  * instance and the `Transaction` instance.
1234
1275
  *
1235
- * This store should not be shared between `RocksDatabase` instances.
1276
+ * A store is bound to a single `RocksDatabase` instance and must not be shared
1277
+ * between `RocksDatabase` instances. Each `RocksDatabase` claims its store on
1278
+ * construction (see `claim()`); passing a store that another `RocksDatabase`
1279
+ * already owns throws. (Sharing with the owning database's `Transaction`
1280
+ * instances is expected and allowed.)
1236
1281
  */
1237
1282
  var Store = class {
1283
+ /**
1284
+ * Whether a `RocksDatabase` has claimed this store. Durable across
1285
+ * close/reopen (unlike the handle's open state), so a store reused after its
1286
+ * owner temporarily closed is still recognized as in-use. See `claim()`.
1287
+ */
1288
+ #claimed = false;
1238
1289
  /**
1239
1290
  * The database instance.
1240
1291
  */
@@ -1249,6 +1300,11 @@ var Store = class {
1249
1300
  */
1250
1301
  decoderCopies = false;
1251
1302
  /**
1303
+ * The compression algorithm (or `{ algorithm, level }`) requested for this
1304
+ * store's column family. Normalized and applied when the database opens.
1305
+ */
1306
+ compression;
1307
+ /**
1252
1308
  * Whether to disable the write ahead log.
1253
1309
  */
1254
1310
  disableWAL;
@@ -1406,6 +1462,7 @@ var Store = class {
1406
1462
  if (options !== void 0 && options !== null && typeof options !== "object") throw new TypeError("Database options must be an object");
1407
1463
  const { keyEncoding, readKey, writeKey } = initKeyEncoder(options?.keyEncoding, options?.keyEncoder);
1408
1464
  this.db = new NativeDatabase();
1465
+ this.compression = options?.compression;
1409
1466
  this.dbWriteBufferSize = options?.dbWriteBufferSize;
1410
1467
  this.decoder = options?.decoder ?? null;
1411
1468
  this.disableWAL = options?.disableWAL ?? false;
@@ -1445,6 +1502,16 @@ var Store = class {
1445
1502
  this.db.close();
1446
1503
  }
1447
1504
  /**
1505
+ * Claims this store for a `RocksDatabase`. A store is bound to a single
1506
+ * database instance, so claiming a store that another `RocksDatabase` has
1507
+ * already claimed throws. The claim is durable across close/reopen, so a
1508
+ * store reused after its owner temporarily closed still throws.
1509
+ */
1510
+ claim() {
1511
+ if (this.#claimed) throw new Error("Store is already in use by another RocksDatabase instance");
1512
+ this.#claimed = true;
1513
+ }
1514
+ /**
1448
1515
  * Compacts the entire key range of the database asynchronously.
1449
1516
  * This triggers manual compaction which removes tombstones and reclaims space.
1450
1517
  *
@@ -1706,7 +1773,10 @@ var Store = class {
1706
1773
  */
1707
1774
  open() {
1708
1775
  if (this.db.opened) return true;
1776
+ const { compression, compressionLevel } = normalizeCompression(this.compression);
1709
1777
  this.db.open(this.path, {
1778
+ compression,
1779
+ compressionLevel,
1710
1780
  dbWriteBufferSize: this.dbWriteBufferSize,
1711
1781
  disableWAL: this.disableWAL,
1712
1782
  enableStats: this.enableStats,
@@ -2008,9 +2078,12 @@ var RocksDatabase = class RocksDatabase extends DBI {
2008
2078
  */
2009
2079
  #name;
2010
2080
  constructor(pathOrStore, options) {
2011
- if (typeof pathOrStore === "string") super(new Store(pathOrStore, options));
2012
- else if (pathOrStore instanceof Store) super(pathOrStore);
2081
+ let store;
2082
+ if (typeof pathOrStore === "string") store = new Store(pathOrStore, options);
2083
+ else if (pathOrStore instanceof Store) store = pathOrStore;
2013
2084
  else throw new TypeError("Invalid database path or store");
2085
+ store.claim();
2086
+ super(store);
2014
2087
  this.#name = options?.name ?? "default";
2015
2088
  }
2016
2089
  /**
@@ -2124,6 +2197,22 @@ var RocksDatabase = class RocksDatabase extends DBI {
2124
2197
  return this.store.db.columns;
2125
2198
  }
2126
2199
  /**
2200
+ * The compression currently in effect for this database's column family, read
2201
+ * live from RocksDB, as `{ algorithm, level? }`. `algorithm` is a friendly
2202
+ * name (e.g. `'lz4'`, `'zstd'`, `'none'`); `level` is present only when a
2203
+ * non-default compression level is set. The database must be open. Defaults to
2204
+ * LZ4 when the native build supports it.
2205
+ *
2206
+ * @example
2207
+ * ```typescript
2208
+ * const db = RocksDatabase.open('/path/to/db', { compression: { algorithm: 'zstd', level: 3 } });
2209
+ * db.compression; // { algorithm: 'zstd', level: 3 }
2210
+ * ```
2211
+ */
2212
+ get compression() {
2213
+ return this.store.db.getCompression();
2214
+ }
2215
+ /**
2127
2216
  * Set global database settings.
2128
2217
  *
2129
2218
  * @param options - The options for the database.
@@ -2953,7 +3042,7 @@ function loadLastPosition(transactionLog, readUncommitted) {
2953
3042
  //#region src/index.ts
2954
3043
  const versions = {
2955
3044
  rocksdb: version,
2956
- "rocksdb-js": "2.5.0"
3045
+ "rocksdb-js": "2.6.1"
2957
3046
  };
2958
3047
 
2959
3048
  //#endregion
@@ -2972,6 +3061,7 @@ exports.parseTransactionLog = parseTransactionLog;
2972
3061
  exports.registryStatus = registryStatus;
2973
3062
  exports.shutdown = shutdown;
2974
3063
  exports.stats = stats;
3064
+ exports.supportedCompression = supportedCompression;
2975
3065
  exports.tryFileLock = tryFileLock;
2976
3066
  exports.validateTransactionLogStore = validateTransactionLogStore;
2977
3067
  exports.versions = versions;