@harperfast/rocksdb-js 2.10.0 → 2.11.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/dist/index.cjs CHANGED
@@ -130,7 +130,17 @@ const NativeDatabase = binding.Database;
130
130
  const NativeIterator = binding.Iterator;
131
131
  const NativeTransaction = binding.Transaction;
132
132
  const TransactionLog = binding.TransactionLog;
133
- const registryStatus = binding.registryStatus;
133
+ /**
134
+ * Returns the status of every database currently held by the native registry.
135
+ * Walks every registered descriptor, so it samples live state rather than
136
+ * reading a stored property.
137
+ */
138
+ const getRegistryStatus = binding.registryStatus;
139
+ /**
140
+ * @deprecated Use {@link getRegistryStatus} instead. This alias will be removed
141
+ * in v3.0.0.
142
+ */
143
+ const registryStatus = getRegistryStatus;
134
144
  /**
135
145
  * Flushes every open database and waits for outstanding compactions, then
136
146
  * releases the registry.
@@ -194,6 +204,12 @@ const coolTransactionLogs = binding.coolTransactionLogs;
194
204
  */
195
205
  const transactionLogMapCount = binding.transactionLogMapCount;
196
206
  /**
207
+ * Number of coordinated-retry wake callbacks registered on verification-table
208
+ * locks across the process. Internal — used by tests to verify that a park
209
+ * ending by timeout, env teardown, or close leaves no registration behind.
210
+ */
211
+ const lockWakeCallbackCount = binding.lockWakeCallbackCount;
212
+ /**
197
213
  * Test-only: force the next `count` transaction commits to fail with TryAgain (rolled back, so
198
214
  * no data is committed), reproducing a stranded-snapshot conflict deterministically. Pass 0 to
199
215
  * disarm. Used by the ERR_TRY_AGAIN retry regression test.
@@ -1731,6 +1747,7 @@ var Store = class {
1731
1747
  /**
1732
1748
  * The target maximum size of a transaction log before rotation. Transactions
1733
1749
  * are never split across files, so one transaction may exceed this target.
1750
+ * Defaults to 16 MiB. `0` (formerly unlimited) is rejected at open.
1734
1751
  */
1735
1752
  transactionLogMaxSize;
1736
1753
  /**
@@ -1745,6 +1762,11 @@ var Store = class {
1745
1762
  */
1746
1763
  transactionLogsPath;
1747
1764
  /**
1765
+ * The transaction log whose batch keys this process originates; seeds the
1766
+ * monotonic timestamp floor at open. See `StoreOptions.timestampFloorLog`.
1767
+ */
1768
+ timestampFloorLog;
1769
+ /**
1748
1770
  * Whether this store's column family participates in the VerificationTable.
1749
1771
  */
1750
1772
  verificationTable;
@@ -1803,6 +1825,7 @@ var Store = class {
1803
1825
  this.transactionLogMaxSize = options?.transactionLogMaxSize;
1804
1826
  this.transactionLogRetention = options?.transactionLogRetention;
1805
1827
  this.transactionLogsPath = options?.transactionLogsPath;
1828
+ this.timestampFloorLog = options?.timestampFloorLog;
1806
1829
  this.verificationTable = options?.verificationTable;
1807
1830
  this.writeBufferSize = options?.writeBufferSize;
1808
1831
  this.writeKey = writeKey;
@@ -2167,6 +2190,7 @@ var Store = class {
2167
2190
  transactionLogMaxSize: this.transactionLogMaxSize,
2168
2191
  transactionLogRetentionMs: this.transactionLogRetention ? parseDuration(this.transactionLogRetention) : void 0,
2169
2192
  transactionLogsPath: this.transactionLogsPath,
2193
+ timestampFloorLog: this.timestampFloorLog,
2170
2194
  verificationTable: this.verificationTable,
2171
2195
  writeBufferSize: this.writeBufferSize
2172
2196
  });
@@ -2648,6 +2672,66 @@ var RocksDatabase = class RocksDatabase extends DBI {
2648
2672
  return this.store.db.getCompression();
2649
2673
  }
2650
2674
  /**
2675
+ * Dynamically changes the compression algorithm (and optional level) for
2676
+ * this database's column family on an already-open database — no close, no
2677
+ * reopen, and it does not conflict with another handle that already has
2678
+ * this column family open (unlike an explicit open-time `compression`
2679
+ * mismatch). That other handle's live options change too, silently,
2680
+ * without notice; a later explicit reopen of it is compared against the
2681
+ * new value, so a stale request it still holds can then conflict. Backed
2682
+ * by RocksDB's `DB::SetOptions()`, which documents both `compression` and
2683
+ * `blob_compression_type` as mutable column family options.
2684
+ *
2685
+ * This governs only *newly written* files (the next flush and any future
2686
+ * compaction output) going forward; SST and blob files already on disk
2687
+ * keep their existing compression until they are rewritten by a later
2688
+ * compaction. `compact()`'s default options can skip already-bottommost
2689
+ * files; use `compact({ bottommost: true })` to rewrite them under the new
2690
+ * codec. This is the live-mutation counterpart to the open-time
2691
+ * `compression` option — see the README's Compression section for when to
2692
+ * use each.
2693
+ *
2694
+ * @example
2695
+ * ```typescript
2696
+ * const db = RocksDatabase.open('/path/to/db', { compression: 'none' });
2697
+ * db.setCompression({ algorithm: 'zstd', level: 19 });
2698
+ * // new writes are now compressed with zstd; existing SSTs are untouched
2699
+ * // until compacted.
2700
+ * ```
2701
+ */
2702
+ setCompression(compression) {
2703
+ const { compression: algorithm, compressionLevel } = normalizeCompression(compression);
2704
+ if (algorithm === void 0) throw new TypeError("setCompression requires a compression algorithm");
2705
+ try {
2706
+ this.store.db.setCompression(algorithm, compressionLevel);
2707
+ } catch (error) {
2708
+ if (error?.appliedInMemoryOnly) this.syncStoreCompressionFromLive();
2709
+ throw error;
2710
+ }
2711
+ this.syncStoreCompressionFromLive();
2712
+ }
2713
+ /**
2714
+ * Resyncs the store's cached open-time compression option from the live
2715
+ * database state after `setCompression()`. Also clears
2716
+ * `compressionForAllColumnFamilies`: that flag means "apply this option to
2717
+ * every column family opened for this database", which no longer describes
2718
+ * a live change scoped to a single column family — leaving it set would
2719
+ * restamp every other family with this algorithm on the next
2720
+ * same-instance `close()` + `open()`.
2721
+ *
2722
+ * The flag clear is unconditional (a plain assignment, so it cannot itself
2723
+ * fail) and comes first: a concurrent close racing the live-state read
2724
+ * below throws "Database not open" from the getter, and that read failing
2725
+ * must not also skip the flag clear -- otherwise the same restamp this
2726
+ * method exists to prevent survives on the next open().
2727
+ */
2728
+ syncStoreCompressionFromLive() {
2729
+ this.store.compressionForAllColumnFamilies = false;
2730
+ try {
2731
+ this.store.compression = this.compression;
2732
+ } catch {}
2733
+ }
2734
+ /**
2651
2735
  * The informational-log settings currently in effect for this database,
2652
2736
  * read live from RocksDB, as `{ maxLogFileSize, infoLogLevel }`. These are
2653
2737
  * database-wide settings (not per-column-family): `maxLogFileSize` bounds
@@ -3651,18 +3735,11 @@ function findResyncPosition(dataView, from, dataEnd) {
3651
3735
  }
3652
3736
  }
3653
3737
  /**
3654
- * Builds the report for a broken frame, with the offset iteration should continue from. Resolves
3655
- * the written extent here rather than per frame: `getLogFileSize` crosses into native and takes
3656
- * the store mutex, which a healthy read must never pay.
3738
+ * Builds the report for a broken frame, with the offset iteration should continue from.
3657
3739
  */
3658
3740
  function corruptFrame(message, transactionLog, dataView, logBuffer, position, limit, size, readUncommitted) {
3659
3741
  let dataEnd = limit;
3660
- if (readUncommitted) try {
3661
- const writtenExtent = transactionLog.getLogFileSize(logBuffer.logId);
3662
- dataEnd = writtenExtent > 0 ? Math.min(logBuffer.length, writtenExtent) : 0;
3663
- } catch {
3664
- dataEnd = 0;
3665
- }
3742
+ if (readUncommitted) dataEnd = Math.min(logBuffer.length, logBuffer.size ?? readableExtent(logBuffer));
3666
3743
  const resyncPosition = findResyncPosition(dataView, position + 1, dataEnd);
3667
3744
  return {
3668
3745
  error: new CorruptFrameError(message, logBuffer.logId, position, resyncPosition),
@@ -3692,7 +3769,7 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
3692
3769
  let logId = latestLogId;
3693
3770
  let position = 0;
3694
3771
  let dataView;
3695
- let logBuffer = this._currentLogBuffer;
3772
+ let logBuffer = this._currentLogBuffer?.deref();
3696
3773
  let foundExactStart = false;
3697
3774
  if (start === void 0 && !startFromLastFlushed) {
3698
3775
  position = size;
@@ -3710,7 +3787,7 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
3710
3787
  }
3711
3788
  if (logBuffer === void 0 || logBuffer.logId !== logId) {
3712
3789
  logBuffer = getLogMemoryMap(this, logId);
3713
- if (logBuffer && latestLogId === logId && !readUncommitted) this._currentLogBuffer = logBuffer;
3790
+ if (logBuffer && latestLogId === logId && !readUncommitted && this._currentLogBuffer?.deref() !== logBuffer) this._currentLogBuffer = new WeakRef(logBuffer);
3714
3791
  if (logBuffer === void 0) {
3715
3792
  logBuffer = Buffer.alloc(0);
3716
3793
  logBuffer.logId = 0;
@@ -3720,9 +3797,11 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
3720
3797
  }
3721
3798
  }
3722
3799
  dataView = logBuffer.dataView;
3723
- if (latestLogId !== logId) {
3724
- size = logBuffer.size;
3725
- if (size === void 0) size = logBuffer.size = this.getLogFileSize(logId);
3800
+ if (latestLogId < logId) size = 0;
3801
+ else if (latestLogId > logId) {
3802
+ const cachedSize = logBuffer.size;
3803
+ if (cachedSize === void 0) size = logBuffer.size = readableExtent(logBuffer);
3804
+ else size = cachedSize;
3726
3805
  }
3727
3806
  return {
3728
3807
  [Symbol.iterator]() {
@@ -3732,15 +3811,15 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
3732
3811
  let timestamp;
3733
3812
  if (position >= size) {
3734
3813
  const { logId: latestLogId, size: latestSize } = loadLastPosition(transactionLog, !!readUncommitted);
3735
- size = latestSize;
3814
+ size = latestLogId < logBuffer.logId ? 0 : latestSize;
3736
3815
  if (latestLogId > logBuffer.logId) {
3737
- size = logBuffer.size ?? (logBuffer.size = transactionLog.getLogFileSize(logBuffer.logId));
3816
+ size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
3738
3817
  if (position >= size) {
3739
- const nextLogBuffer = getLogMemoryMap(transactionLog, logBuffer.logId + 1);
3818
+ const nextLogBuffer = nextReadableLogBuffer(transactionLog, logBuffer.logId, latestLogId);
3740
3819
  if (nextLogBuffer) {
3741
3820
  dataView = nextLogBuffer.dataView;
3742
3821
  logBuffer = nextLogBuffer;
3743
- if (latestLogId > logBuffer.logId) size = logBuffer.size ?? (logBuffer.size = transactionLog.getLogFileSize(logBuffer.logId));
3822
+ if (latestLogId > logBuffer.logId) size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
3744
3823
  else size = latestSize;
3745
3824
  position = TRANSACTION_LOG_FILE_HEADER_SIZE;
3746
3825
  }
@@ -3791,19 +3870,25 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
3791
3870
  };
3792
3871
  if (position >= size) {
3793
3872
  const { logId: latestLogId, size: latestSize } = loadLastPosition(transactionLog, !!readUncommitted);
3794
- size = latestSize;
3873
+ size = latestLogId < logBuffer.logId ? 0 : latestSize;
3795
3874
  if (latestLogId > logBuffer.logId) {
3796
- const nextLogBuffer = getLogMemoryMap(transactionLog, logBuffer.logId + 1);
3875
+ size = logBuffer.size ?? readableExtent(logBuffer);
3876
+ if (!readUncommitted) logBuffer.size = size;
3877
+ if (position < size) continue;
3878
+ const nextLogBuffer = nextReadableLogBuffer(transactionLog, logBuffer.logId, latestLogId);
3797
3879
  if (!nextLogBuffer) return {
3798
3880
  done: true,
3799
3881
  value: void 0
3800
3882
  };
3801
3883
  logBuffer = nextLogBuffer;
3802
3884
  dataView = logBuffer.dataView;
3803
- size = logBuffer.size;
3804
- if (size == void 0) {
3805
- size = transactionLog.getLogFileSize(logBuffer.logId);
3806
- if (!readUncommitted) logBuffer.size = size;
3885
+ if (latestLogId === logBuffer.logId) size = latestSize;
3886
+ else {
3887
+ const cachedSize = logBuffer.size;
3888
+ if (cachedSize === void 0) {
3889
+ size = readableExtent(logBuffer);
3890
+ if (!readUncommitted) logBuffer.size = size;
3891
+ } else size = cachedSize;
3807
3892
  }
3808
3893
  position = TRANSACTION_LOG_FILE_HEADER_SIZE;
3809
3894
  }
@@ -3816,6 +3901,35 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
3816
3901
  }
3817
3902
  };
3818
3903
  } });
3904
+ /**
3905
+ * Maps the next readable segment after `fromLogId`, skipping a run retention has deleted —
3906
+ * stopping at the hole wedges the iterator there for every later poll (invariant 30).
3907
+ *
3908
+ * The jump target is `_nextLogId()`, the successor over the store's registered segments — *not*
3909
+ * `_findPosition(0)`, which names the bottom of the contiguous run ending at the current segment
3910
+ * and so lands *past* a survivor sitting between two holes.
3911
+ */
3912
+ function nextReadableLogBuffer(transactionLog, fromLogId, latestLogId) {
3913
+ let candidateLogId = fromLogId + 1;
3914
+ while (candidateLogId <= latestLogId) {
3915
+ const logBuffer = getLogMemoryMap(transactionLog, candidateLogId);
3916
+ if (logBuffer) {
3917
+ if (candidateLogId === latestLogId || readableExtent(logBuffer) > TRANSACTION_LOG_FILE_HEADER_SIZE) return logBuffer;
3918
+ } else if (transactionLog.getLogFileSize(candidateLogId) > TRANSACTION_LOG_FILE_HEADER_SIZE) return;
3919
+ candidateLogId = transactionLog._nextLogId(candidateLogId);
3920
+ if (candidateLogId === 0) return;
3921
+ }
3922
+ }
3923
+ /**
3924
+ * The bound for reading `logBuffer`. Native advances this append-owned extent only after a
3925
+ * successful write. It remains attached to the mapping after purge forgets the file object, so
3926
+ * readers retain committed history without interpreting a partial failed append as another frame.
3927
+ */
3928
+ function readableExtent(logBuffer) {
3929
+ const extent = logBuffer.readableExtent;
3930
+ if (extent === void 0) throw new Error(`Transaction log buffer ${logBuffer.logId} has no readableExtent`);
3931
+ return Math.min(logBuffer.length, extent);
3932
+ }
3819
3933
  function getLogMemoryMap(transactionLog, logId) {
3820
3934
  if (logId <= 0) return;
3821
3935
  let logBuffer = transactionLog._logBuffers.get(logId)?.deref();
@@ -3861,7 +3975,7 @@ function loadLastPosition(transactionLog, readUncommitted) {
3861
3975
  //#region src/index.ts
3862
3976
  const versions = {
3863
3977
  rocksdb: version,
3864
- "rocksdb-js": "2.10.0"
3978
+ "rocksdb-js": "2.11.0"
3865
3979
  };
3866
3980
 
3867
3981
  //#endregion
@@ -3879,6 +3993,7 @@ exports.constants = constants;
3879
3993
  exports.coolTransactionLogs = coolTransactionLogs;
3880
3994
  exports.currentThreadId = currentThreadId;
3881
3995
  exports.fileLockRelease = fileLockRelease;
3996
+ exports.getRegistryStatus = getRegistryStatus;
3882
3997
  exports.getWriteBufferManagerStats = getWriteBufferManagerStats;
3883
3998
  exports.parseTransactionLog = parseTransactionLog;
3884
3999
  exports.registryStatus = registryStatus;