@harperfast/rocksdb-js 2.10.0 → 2.12.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.
@@ -534,6 +550,14 @@ const backups = {
534
550
  * Verifies a backup's file sizes, and optionally their checksums (which
535
551
  * requires reading all backed-up data).
536
552
  *
553
+ * Set `verifyTables: true` to also prove this build can read the backup
554
+ * before restoring it: every table file is opened, one at a time, and its
555
+ * footer (including `format_version`) and properties must be readable, and
556
+ * every compression algorithm it records must be supported by this build.
557
+ * A checksum-valid table written by a newer or differently built RocksDB
558
+ * otherwise passes and fails on the first read after a restore. Blob files
559
+ * are not checked.
560
+ *
537
561
  * When the backup was created with `transactionLogs: true`, the backup's
538
562
  * transaction log snapshot (`<backupDir>/transaction_logs/<backupId>/`) is
539
563
  * also validated — every log file's header and entry framing must be intact
@@ -541,7 +565,9 @@ const backups = {
541
565
  * is a verification failure). Set `verifyTransactionLogs: false` to skip.
542
566
  */
543
567
  async verify(backupDir, backupId, options) {
544
- await new Promise((resolve, reject) => nativeBackupVerify(resolve, reject, backupDir, backupId, options?.verifyWithChecksum ?? false));
568
+ const verifyTables = options?.verifyTables;
569
+ if (verifyTables !== void 0 && typeof verifyTables !== "boolean") throw new TypeError(`verifyTables must be a boolean, received ${verifyTables === null ? "null" : typeof verifyTables}`);
570
+ await new Promise((resolve, reject) => nativeBackupVerify(resolve, reject, backupDir, backupId, options?.verifyWithChecksum ?? false, verifyTables === true));
545
571
  if (options?.verifyTransactionLogs === false) return;
546
572
  const logsDir = (0, node_path.join)(backupDir, TRANSACTION_LOGS_DIRNAME, String(backupId));
547
573
  if (!await exists(logsDir)) return;
@@ -1731,6 +1757,7 @@ var Store = class {
1731
1757
  /**
1732
1758
  * The target maximum size of a transaction log before rotation. Transactions
1733
1759
  * are never split across files, so one transaction may exceed this target.
1760
+ * Defaults to 16 MiB. `0` (formerly unlimited) is rejected at open.
1734
1761
  */
1735
1762
  transactionLogMaxSize;
1736
1763
  /**
@@ -1745,6 +1772,11 @@ var Store = class {
1745
1772
  */
1746
1773
  transactionLogsPath;
1747
1774
  /**
1775
+ * The transaction log whose batch keys this process originates; seeds the
1776
+ * monotonic timestamp floor at open. See `StoreOptions.timestampFloorLog`.
1777
+ */
1778
+ timestampFloorLog;
1779
+ /**
1748
1780
  * Whether this store's column family participates in the VerificationTable.
1749
1781
  */
1750
1782
  verificationTable;
@@ -1803,6 +1835,7 @@ var Store = class {
1803
1835
  this.transactionLogMaxSize = options?.transactionLogMaxSize;
1804
1836
  this.transactionLogRetention = options?.transactionLogRetention;
1805
1837
  this.transactionLogsPath = options?.transactionLogsPath;
1838
+ this.timestampFloorLog = options?.timestampFloorLog;
1806
1839
  this.verificationTable = options?.verificationTable;
1807
1840
  this.writeBufferSize = options?.writeBufferSize;
1808
1841
  this.writeKey = writeKey;
@@ -2167,6 +2200,7 @@ var Store = class {
2167
2200
  transactionLogMaxSize: this.transactionLogMaxSize,
2168
2201
  transactionLogRetentionMs: this.transactionLogRetention ? parseDuration(this.transactionLogRetention) : void 0,
2169
2202
  transactionLogsPath: this.transactionLogsPath,
2203
+ timestampFloorLog: this.timestampFloorLog,
2170
2204
  verificationTable: this.verificationTable,
2171
2205
  writeBufferSize: this.writeBufferSize
2172
2206
  });
@@ -2648,6 +2682,66 @@ var RocksDatabase = class RocksDatabase extends DBI {
2648
2682
  return this.store.db.getCompression();
2649
2683
  }
2650
2684
  /**
2685
+ * Dynamically changes the compression algorithm (and optional level) for
2686
+ * this database's column family on an already-open database — no close, no
2687
+ * reopen, and it does not conflict with another handle that already has
2688
+ * this column family open (unlike an explicit open-time `compression`
2689
+ * mismatch). That other handle's live options change too, silently,
2690
+ * without notice; a later explicit reopen of it is compared against the
2691
+ * new value, so a stale request it still holds can then conflict. Backed
2692
+ * by RocksDB's `DB::SetOptions()`, which documents both `compression` and
2693
+ * `blob_compression_type` as mutable column family options.
2694
+ *
2695
+ * This governs only *newly written* files (the next flush and any future
2696
+ * compaction output) going forward; SST and blob files already on disk
2697
+ * keep their existing compression until they are rewritten by a later
2698
+ * compaction. `compact()`'s default options can skip already-bottommost
2699
+ * files; use `compact({ bottommost: true })` to rewrite them under the new
2700
+ * codec. This is the live-mutation counterpart to the open-time
2701
+ * `compression` option — see the README's Compression section for when to
2702
+ * use each.
2703
+ *
2704
+ * @example
2705
+ * ```typescript
2706
+ * const db = RocksDatabase.open('/path/to/db', { compression: 'none' });
2707
+ * db.setCompression({ algorithm: 'zstd', level: 19 });
2708
+ * // new writes are now compressed with zstd; existing SSTs are untouched
2709
+ * // until compacted.
2710
+ * ```
2711
+ */
2712
+ setCompression(compression) {
2713
+ const { compression: algorithm, compressionLevel } = normalizeCompression(compression);
2714
+ if (algorithm === void 0) throw new TypeError("setCompression requires a compression algorithm");
2715
+ try {
2716
+ this.store.db.setCompression(algorithm, compressionLevel);
2717
+ } catch (error) {
2718
+ if (error?.appliedInMemoryOnly) this.syncStoreCompressionFromLive();
2719
+ throw error;
2720
+ }
2721
+ this.syncStoreCompressionFromLive();
2722
+ }
2723
+ /**
2724
+ * Resyncs the store's cached open-time compression option from the live
2725
+ * database state after `setCompression()`. Also clears
2726
+ * `compressionForAllColumnFamilies`: that flag means "apply this option to
2727
+ * every column family opened for this database", which no longer describes
2728
+ * a live change scoped to a single column family — leaving it set would
2729
+ * restamp every other family with this algorithm on the next
2730
+ * same-instance `close()` + `open()`.
2731
+ *
2732
+ * The flag clear is unconditional (a plain assignment, so it cannot itself
2733
+ * fail) and comes first: a concurrent close racing the live-state read
2734
+ * below throws "Database not open" from the getter, and that read failing
2735
+ * must not also skip the flag clear -- otherwise the same restamp this
2736
+ * method exists to prevent survives on the next open().
2737
+ */
2738
+ syncStoreCompressionFromLive() {
2739
+ this.store.compressionForAllColumnFamilies = false;
2740
+ try {
2741
+ this.store.compression = this.compression;
2742
+ } catch {}
2743
+ }
2744
+ /**
2651
2745
  * The informational-log settings currently in effect for this database,
2652
2746
  * read live from RocksDB, as `{ maxLogFileSize, infoLogLevel }`. These are
2653
2747
  * database-wide settings (not per-column-family): `maxLogFileSize` bounds
@@ -3651,18 +3745,11 @@ function findResyncPosition(dataView, from, dataEnd) {
3651
3745
  }
3652
3746
  }
3653
3747
  /**
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.
3748
+ * Builds the report for a broken frame, with the offset iteration should continue from.
3657
3749
  */
3658
3750
  function corruptFrame(message, transactionLog, dataView, logBuffer, position, limit, size, readUncommitted) {
3659
3751
  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
- }
3752
+ if (readUncommitted) dataEnd = Math.min(logBuffer.length, logBuffer.size ?? readableExtent(logBuffer));
3666
3753
  const resyncPosition = findResyncPosition(dataView, position + 1, dataEnd);
3667
3754
  return {
3668
3755
  error: new CorruptFrameError(message, logBuffer.logId, position, resyncPosition),
@@ -3692,7 +3779,7 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
3692
3779
  let logId = latestLogId;
3693
3780
  let position = 0;
3694
3781
  let dataView;
3695
- let logBuffer = this._currentLogBuffer;
3782
+ let logBuffer = this._currentLogBuffer?.deref();
3696
3783
  let foundExactStart = false;
3697
3784
  if (start === void 0 && !startFromLastFlushed) {
3698
3785
  position = size;
@@ -3710,7 +3797,7 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
3710
3797
  }
3711
3798
  if (logBuffer === void 0 || logBuffer.logId !== logId) {
3712
3799
  logBuffer = getLogMemoryMap(this, logId);
3713
- if (logBuffer && latestLogId === logId && !readUncommitted) this._currentLogBuffer = logBuffer;
3800
+ if (logBuffer && latestLogId === logId && !readUncommitted && this._currentLogBuffer?.deref() !== logBuffer) this._currentLogBuffer = new WeakRef(logBuffer);
3714
3801
  if (logBuffer === void 0) {
3715
3802
  logBuffer = Buffer.alloc(0);
3716
3803
  logBuffer.logId = 0;
@@ -3720,9 +3807,11 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
3720
3807
  }
3721
3808
  }
3722
3809
  dataView = logBuffer.dataView;
3723
- if (latestLogId !== logId) {
3724
- size = logBuffer.size;
3725
- if (size === void 0) size = logBuffer.size = this.getLogFileSize(logId);
3810
+ if (latestLogId < logId) size = 0;
3811
+ else if (latestLogId > logId) {
3812
+ const cachedSize = logBuffer.size;
3813
+ if (cachedSize === void 0) size = logBuffer.size = readableExtent(logBuffer);
3814
+ else size = cachedSize;
3726
3815
  }
3727
3816
  return {
3728
3817
  [Symbol.iterator]() {
@@ -3732,15 +3821,15 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
3732
3821
  let timestamp;
3733
3822
  if (position >= size) {
3734
3823
  const { logId: latestLogId, size: latestSize } = loadLastPosition(transactionLog, !!readUncommitted);
3735
- size = latestSize;
3824
+ size = latestLogId < logBuffer.logId ? 0 : latestSize;
3736
3825
  if (latestLogId > logBuffer.logId) {
3737
- size = logBuffer.size ?? (logBuffer.size = transactionLog.getLogFileSize(logBuffer.logId));
3826
+ size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
3738
3827
  if (position >= size) {
3739
- const nextLogBuffer = getLogMemoryMap(transactionLog, logBuffer.logId + 1);
3828
+ const nextLogBuffer = nextReadableLogBuffer(transactionLog, logBuffer.logId, latestLogId);
3740
3829
  if (nextLogBuffer) {
3741
3830
  dataView = nextLogBuffer.dataView;
3742
3831
  logBuffer = nextLogBuffer;
3743
- if (latestLogId > logBuffer.logId) size = logBuffer.size ?? (logBuffer.size = transactionLog.getLogFileSize(logBuffer.logId));
3832
+ if (latestLogId > logBuffer.logId) size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
3744
3833
  else size = latestSize;
3745
3834
  position = TRANSACTION_LOG_FILE_HEADER_SIZE;
3746
3835
  }
@@ -3791,19 +3880,25 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
3791
3880
  };
3792
3881
  if (position >= size) {
3793
3882
  const { logId: latestLogId, size: latestSize } = loadLastPosition(transactionLog, !!readUncommitted);
3794
- size = latestSize;
3883
+ size = latestLogId < logBuffer.logId ? 0 : latestSize;
3795
3884
  if (latestLogId > logBuffer.logId) {
3796
- const nextLogBuffer = getLogMemoryMap(transactionLog, logBuffer.logId + 1);
3885
+ size = logBuffer.size ?? readableExtent(logBuffer);
3886
+ if (!readUncommitted) logBuffer.size = size;
3887
+ if (position < size) continue;
3888
+ const nextLogBuffer = nextReadableLogBuffer(transactionLog, logBuffer.logId, latestLogId);
3797
3889
  if (!nextLogBuffer) return {
3798
3890
  done: true,
3799
3891
  value: void 0
3800
3892
  };
3801
3893
  logBuffer = nextLogBuffer;
3802
3894
  dataView = logBuffer.dataView;
3803
- size = logBuffer.size;
3804
- if (size == void 0) {
3805
- size = transactionLog.getLogFileSize(logBuffer.logId);
3806
- if (!readUncommitted) logBuffer.size = size;
3895
+ if (latestLogId === logBuffer.logId) size = latestSize;
3896
+ else {
3897
+ const cachedSize = logBuffer.size;
3898
+ if (cachedSize === void 0) {
3899
+ size = readableExtent(logBuffer);
3900
+ if (!readUncommitted) logBuffer.size = size;
3901
+ } else size = cachedSize;
3807
3902
  }
3808
3903
  position = TRANSACTION_LOG_FILE_HEADER_SIZE;
3809
3904
  }
@@ -3816,6 +3911,35 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
3816
3911
  }
3817
3912
  };
3818
3913
  } });
3914
+ /**
3915
+ * Maps the next readable segment after `fromLogId`, skipping a run retention has deleted —
3916
+ * stopping at the hole wedges the iterator there for every later poll (invariant 30).
3917
+ *
3918
+ * The jump target is `_nextLogId()`, the successor over the store's registered segments — *not*
3919
+ * `_findPosition(0)`, which names the bottom of the contiguous run ending at the current segment
3920
+ * and so lands *past* a survivor sitting between two holes.
3921
+ */
3922
+ function nextReadableLogBuffer(transactionLog, fromLogId, latestLogId) {
3923
+ let candidateLogId = fromLogId + 1;
3924
+ while (candidateLogId <= latestLogId) {
3925
+ const logBuffer = getLogMemoryMap(transactionLog, candidateLogId);
3926
+ if (logBuffer) {
3927
+ if (candidateLogId === latestLogId || readableExtent(logBuffer) > TRANSACTION_LOG_FILE_HEADER_SIZE) return logBuffer;
3928
+ } else if (transactionLog.getLogFileSize(candidateLogId) > TRANSACTION_LOG_FILE_HEADER_SIZE) return;
3929
+ candidateLogId = transactionLog._nextLogId(candidateLogId);
3930
+ if (candidateLogId === 0) return;
3931
+ }
3932
+ }
3933
+ /**
3934
+ * The bound for reading `logBuffer`. Native advances this append-owned extent only after a
3935
+ * successful write. It remains attached to the mapping after purge forgets the file object, so
3936
+ * readers retain committed history without interpreting a partial failed append as another frame.
3937
+ */
3938
+ function readableExtent(logBuffer) {
3939
+ const extent = logBuffer.readableExtent;
3940
+ if (extent === void 0) throw new Error(`Transaction log buffer ${logBuffer.logId} has no readableExtent`);
3941
+ return Math.min(logBuffer.length, extent);
3942
+ }
3819
3943
  function getLogMemoryMap(transactionLog, logId) {
3820
3944
  if (logId <= 0) return;
3821
3945
  let logBuffer = transactionLog._logBuffers.get(logId)?.deref();
@@ -3861,7 +3985,7 @@ function loadLastPosition(transactionLog, readUncommitted) {
3861
3985
  //#region src/index.ts
3862
3986
  const versions = {
3863
3987
  rocksdb: version,
3864
- "rocksdb-js": "2.10.0"
3988
+ "rocksdb-js": "2.12.0"
3865
3989
  };
3866
3990
 
3867
3991
  //#endregion
@@ -3879,6 +4003,7 @@ exports.constants = constants;
3879
4003
  exports.coolTransactionLogs = coolTransactionLogs;
3880
4004
  exports.currentThreadId = currentThreadId;
3881
4005
  exports.fileLockRelease = fileLockRelease;
4006
+ exports.getRegistryStatus = getRegistryStatus;
3882
4007
  exports.getWriteBufferManagerStats = getWriteBufferManagerStats;
3883
4008
  exports.parseTransactionLog = parseTransactionLog;
3884
4009
  exports.registryStatus = registryStatus;