@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.mjs CHANGED
@@ -105,7 +105,17 @@ const NativeDatabase = binding.Database;
105
105
  const NativeIterator = binding.Iterator;
106
106
  const NativeTransaction = binding.Transaction;
107
107
  const TransactionLog = binding.TransactionLog;
108
- const registryStatus = binding.registryStatus;
108
+ /**
109
+ * Returns the status of every database currently held by the native registry.
110
+ * Walks every registered descriptor, so it samples live state rather than
111
+ * reading a stored property.
112
+ */
113
+ const getRegistryStatus = binding.registryStatus;
114
+ /**
115
+ * @deprecated Use {@link getRegistryStatus} instead. This alias will be removed
116
+ * in v3.0.0.
117
+ */
118
+ const registryStatus = getRegistryStatus;
109
119
  /**
110
120
  * Flushes every open database and waits for outstanding compactions, then
111
121
  * releases the registry.
@@ -169,6 +179,12 @@ const coolTransactionLogs = binding.coolTransactionLogs;
169
179
  */
170
180
  const transactionLogMapCount = binding.transactionLogMapCount;
171
181
  /**
182
+ * Number of coordinated-retry wake callbacks registered on verification-table
183
+ * locks across the process. Internal — used by tests to verify that a park
184
+ * ending by timeout, env teardown, or close leaves no registration behind.
185
+ */
186
+ const lockWakeCallbackCount = binding.lockWakeCallbackCount;
187
+ /**
172
188
  * Test-only: force the next `count` transaction commits to fail with TryAgain (rolled back, so
173
189
  * no data is committed), reproducing a stranded-snapshot conflict deterministically. Pass 0 to
174
190
  * disarm. Used by the ERR_TRY_AGAIN retry regression test.
@@ -509,6 +525,14 @@ const backups = {
509
525
  * Verifies a backup's file sizes, and optionally their checksums (which
510
526
  * requires reading all backed-up data).
511
527
  *
528
+ * Set `verifyTables: true` to also prove this build can read the backup
529
+ * before restoring it: every table file is opened, one at a time, and its
530
+ * footer (including `format_version`) and properties must be readable, and
531
+ * every compression algorithm it records must be supported by this build.
532
+ * A checksum-valid table written by a newer or differently built RocksDB
533
+ * otherwise passes and fails on the first read after a restore. Blob files
534
+ * are not checked.
535
+ *
512
536
  * When the backup was created with `transactionLogs: true`, the backup's
513
537
  * transaction log snapshot (`<backupDir>/transaction_logs/<backupId>/`) is
514
538
  * also validated — every log file's header and entry framing must be intact
@@ -516,7 +540,9 @@ const backups = {
516
540
  * is a verification failure). Set `verifyTransactionLogs: false` to skip.
517
541
  */
518
542
  async verify(backupDir, backupId, options) {
519
- await new Promise((resolve, reject) => nativeBackupVerify(resolve, reject, backupDir, backupId, options?.verifyWithChecksum ?? false));
543
+ const verifyTables = options?.verifyTables;
544
+ if (verifyTables !== void 0 && typeof verifyTables !== "boolean") throw new TypeError(`verifyTables must be a boolean, received ${verifyTables === null ? "null" : typeof verifyTables}`);
545
+ await new Promise((resolve, reject) => nativeBackupVerify(resolve, reject, backupDir, backupId, options?.verifyWithChecksum ?? false, verifyTables === true));
520
546
  if (options?.verifyTransactionLogs === false) return;
521
547
  const logsDir = join(backupDir, TRANSACTION_LOGS_DIRNAME, String(backupId));
522
548
  if (!await exists(logsDir)) return;
@@ -1706,6 +1732,7 @@ var Store = class {
1706
1732
  /**
1707
1733
  * The target maximum size of a transaction log before rotation. Transactions
1708
1734
  * are never split across files, so one transaction may exceed this target.
1735
+ * Defaults to 16 MiB. `0` (formerly unlimited) is rejected at open.
1709
1736
  */
1710
1737
  transactionLogMaxSize;
1711
1738
  /**
@@ -1720,6 +1747,11 @@ var Store = class {
1720
1747
  */
1721
1748
  transactionLogsPath;
1722
1749
  /**
1750
+ * The transaction log whose batch keys this process originates; seeds the
1751
+ * monotonic timestamp floor at open. See `StoreOptions.timestampFloorLog`.
1752
+ */
1753
+ timestampFloorLog;
1754
+ /**
1723
1755
  * Whether this store's column family participates in the VerificationTable.
1724
1756
  */
1725
1757
  verificationTable;
@@ -1778,6 +1810,7 @@ var Store = class {
1778
1810
  this.transactionLogMaxSize = options?.transactionLogMaxSize;
1779
1811
  this.transactionLogRetention = options?.transactionLogRetention;
1780
1812
  this.transactionLogsPath = options?.transactionLogsPath;
1813
+ this.timestampFloorLog = options?.timestampFloorLog;
1781
1814
  this.verificationTable = options?.verificationTable;
1782
1815
  this.writeBufferSize = options?.writeBufferSize;
1783
1816
  this.writeKey = writeKey;
@@ -2142,6 +2175,7 @@ var Store = class {
2142
2175
  transactionLogMaxSize: this.transactionLogMaxSize,
2143
2176
  transactionLogRetentionMs: this.transactionLogRetention ? parseDuration(this.transactionLogRetention) : void 0,
2144
2177
  transactionLogsPath: this.transactionLogsPath,
2178
+ timestampFloorLog: this.timestampFloorLog,
2145
2179
  verificationTable: this.verificationTable,
2146
2180
  writeBufferSize: this.writeBufferSize
2147
2181
  });
@@ -2623,6 +2657,66 @@ var RocksDatabase = class RocksDatabase extends DBI {
2623
2657
  return this.store.db.getCompression();
2624
2658
  }
2625
2659
  /**
2660
+ * Dynamically changes the compression algorithm (and optional level) for
2661
+ * this database's column family on an already-open database — no close, no
2662
+ * reopen, and it does not conflict with another handle that already has
2663
+ * this column family open (unlike an explicit open-time `compression`
2664
+ * mismatch). That other handle's live options change too, silently,
2665
+ * without notice; a later explicit reopen of it is compared against the
2666
+ * new value, so a stale request it still holds can then conflict. Backed
2667
+ * by RocksDB's `DB::SetOptions()`, which documents both `compression` and
2668
+ * `blob_compression_type` as mutable column family options.
2669
+ *
2670
+ * This governs only *newly written* files (the next flush and any future
2671
+ * compaction output) going forward; SST and blob files already on disk
2672
+ * keep their existing compression until they are rewritten by a later
2673
+ * compaction. `compact()`'s default options can skip already-bottommost
2674
+ * files; use `compact({ bottommost: true })` to rewrite them under the new
2675
+ * codec. This is the live-mutation counterpart to the open-time
2676
+ * `compression` option — see the README's Compression section for when to
2677
+ * use each.
2678
+ *
2679
+ * @example
2680
+ * ```typescript
2681
+ * const db = RocksDatabase.open('/path/to/db', { compression: 'none' });
2682
+ * db.setCompression({ algorithm: 'zstd', level: 19 });
2683
+ * // new writes are now compressed with zstd; existing SSTs are untouched
2684
+ * // until compacted.
2685
+ * ```
2686
+ */
2687
+ setCompression(compression) {
2688
+ const { compression: algorithm, compressionLevel } = normalizeCompression(compression);
2689
+ if (algorithm === void 0) throw new TypeError("setCompression requires a compression algorithm");
2690
+ try {
2691
+ this.store.db.setCompression(algorithm, compressionLevel);
2692
+ } catch (error) {
2693
+ if (error?.appliedInMemoryOnly) this.syncStoreCompressionFromLive();
2694
+ throw error;
2695
+ }
2696
+ this.syncStoreCompressionFromLive();
2697
+ }
2698
+ /**
2699
+ * Resyncs the store's cached open-time compression option from the live
2700
+ * database state after `setCompression()`. Also clears
2701
+ * `compressionForAllColumnFamilies`: that flag means "apply this option to
2702
+ * every column family opened for this database", which no longer describes
2703
+ * a live change scoped to a single column family — leaving it set would
2704
+ * restamp every other family with this algorithm on the next
2705
+ * same-instance `close()` + `open()`.
2706
+ *
2707
+ * The flag clear is unconditional (a plain assignment, so it cannot itself
2708
+ * fail) and comes first: a concurrent close racing the live-state read
2709
+ * below throws "Database not open" from the getter, and that read failing
2710
+ * must not also skip the flag clear -- otherwise the same restamp this
2711
+ * method exists to prevent survives on the next open().
2712
+ */
2713
+ syncStoreCompressionFromLive() {
2714
+ this.store.compressionForAllColumnFamilies = false;
2715
+ try {
2716
+ this.store.compression = this.compression;
2717
+ } catch {}
2718
+ }
2719
+ /**
2626
2720
  * The informational-log settings currently in effect for this database,
2627
2721
  * read live from RocksDB, as `{ maxLogFileSize, infoLogLevel }`. These are
2628
2722
  * database-wide settings (not per-column-family): `maxLogFileSize` bounds
@@ -3626,18 +3720,11 @@ function findResyncPosition(dataView, from, dataEnd) {
3626
3720
  }
3627
3721
  }
3628
3722
  /**
3629
- * Builds the report for a broken frame, with the offset iteration should continue from. Resolves
3630
- * the written extent here rather than per frame: `getLogFileSize` crosses into native and takes
3631
- * the store mutex, which a healthy read must never pay.
3723
+ * Builds the report for a broken frame, with the offset iteration should continue from.
3632
3724
  */
3633
3725
  function corruptFrame(message, transactionLog, dataView, logBuffer, position, limit, size, readUncommitted) {
3634
3726
  let dataEnd = limit;
3635
- if (readUncommitted) try {
3636
- const writtenExtent = transactionLog.getLogFileSize(logBuffer.logId);
3637
- dataEnd = writtenExtent > 0 ? Math.min(logBuffer.length, writtenExtent) : 0;
3638
- } catch {
3639
- dataEnd = 0;
3640
- }
3727
+ if (readUncommitted) dataEnd = Math.min(logBuffer.length, logBuffer.size ?? readableExtent(logBuffer));
3641
3728
  const resyncPosition = findResyncPosition(dataView, position + 1, dataEnd);
3642
3729
  return {
3643
3730
  error: new CorruptFrameError(message, logBuffer.logId, position, resyncPosition),
@@ -3667,7 +3754,7 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
3667
3754
  let logId = latestLogId;
3668
3755
  let position = 0;
3669
3756
  let dataView;
3670
- let logBuffer = this._currentLogBuffer;
3757
+ let logBuffer = this._currentLogBuffer?.deref();
3671
3758
  let foundExactStart = false;
3672
3759
  if (start === void 0 && !startFromLastFlushed) {
3673
3760
  position = size;
@@ -3685,7 +3772,7 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
3685
3772
  }
3686
3773
  if (logBuffer === void 0 || logBuffer.logId !== logId) {
3687
3774
  logBuffer = getLogMemoryMap(this, logId);
3688
- if (logBuffer && latestLogId === logId && !readUncommitted) this._currentLogBuffer = logBuffer;
3775
+ if (logBuffer && latestLogId === logId && !readUncommitted && this._currentLogBuffer?.deref() !== logBuffer) this._currentLogBuffer = new WeakRef(logBuffer);
3689
3776
  if (logBuffer === void 0) {
3690
3777
  logBuffer = Buffer.alloc(0);
3691
3778
  logBuffer.logId = 0;
@@ -3695,9 +3782,11 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
3695
3782
  }
3696
3783
  }
3697
3784
  dataView = logBuffer.dataView;
3698
- if (latestLogId !== logId) {
3699
- size = logBuffer.size;
3700
- if (size === void 0) size = logBuffer.size = this.getLogFileSize(logId);
3785
+ if (latestLogId < logId) size = 0;
3786
+ else if (latestLogId > logId) {
3787
+ const cachedSize = logBuffer.size;
3788
+ if (cachedSize === void 0) size = logBuffer.size = readableExtent(logBuffer);
3789
+ else size = cachedSize;
3701
3790
  }
3702
3791
  return {
3703
3792
  [Symbol.iterator]() {
@@ -3707,15 +3796,15 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
3707
3796
  let timestamp;
3708
3797
  if (position >= size) {
3709
3798
  const { logId: latestLogId, size: latestSize } = loadLastPosition(transactionLog, !!readUncommitted);
3710
- size = latestSize;
3799
+ size = latestLogId < logBuffer.logId ? 0 : latestSize;
3711
3800
  if (latestLogId > logBuffer.logId) {
3712
- size = logBuffer.size ?? (logBuffer.size = transactionLog.getLogFileSize(logBuffer.logId));
3801
+ size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
3713
3802
  if (position >= size) {
3714
- const nextLogBuffer = getLogMemoryMap(transactionLog, logBuffer.logId + 1);
3803
+ const nextLogBuffer = nextReadableLogBuffer(transactionLog, logBuffer.logId, latestLogId);
3715
3804
  if (nextLogBuffer) {
3716
3805
  dataView = nextLogBuffer.dataView;
3717
3806
  logBuffer = nextLogBuffer;
3718
- if (latestLogId > logBuffer.logId) size = logBuffer.size ?? (logBuffer.size = transactionLog.getLogFileSize(logBuffer.logId));
3807
+ if (latestLogId > logBuffer.logId) size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
3719
3808
  else size = latestSize;
3720
3809
  position = TRANSACTION_LOG_FILE_HEADER_SIZE;
3721
3810
  }
@@ -3766,19 +3855,25 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
3766
3855
  };
3767
3856
  if (position >= size) {
3768
3857
  const { logId: latestLogId, size: latestSize } = loadLastPosition(transactionLog, !!readUncommitted);
3769
- size = latestSize;
3858
+ size = latestLogId < logBuffer.logId ? 0 : latestSize;
3770
3859
  if (latestLogId > logBuffer.logId) {
3771
- const nextLogBuffer = getLogMemoryMap(transactionLog, logBuffer.logId + 1);
3860
+ size = logBuffer.size ?? readableExtent(logBuffer);
3861
+ if (!readUncommitted) logBuffer.size = size;
3862
+ if (position < size) continue;
3863
+ const nextLogBuffer = nextReadableLogBuffer(transactionLog, logBuffer.logId, latestLogId);
3772
3864
  if (!nextLogBuffer) return {
3773
3865
  done: true,
3774
3866
  value: void 0
3775
3867
  };
3776
3868
  logBuffer = nextLogBuffer;
3777
3869
  dataView = logBuffer.dataView;
3778
- size = logBuffer.size;
3779
- if (size == void 0) {
3780
- size = transactionLog.getLogFileSize(logBuffer.logId);
3781
- if (!readUncommitted) logBuffer.size = size;
3870
+ if (latestLogId === logBuffer.logId) size = latestSize;
3871
+ else {
3872
+ const cachedSize = logBuffer.size;
3873
+ if (cachedSize === void 0) {
3874
+ size = readableExtent(logBuffer);
3875
+ if (!readUncommitted) logBuffer.size = size;
3876
+ } else size = cachedSize;
3782
3877
  }
3783
3878
  position = TRANSACTION_LOG_FILE_HEADER_SIZE;
3784
3879
  }
@@ -3791,6 +3886,35 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
3791
3886
  }
3792
3887
  };
3793
3888
  } });
3889
+ /**
3890
+ * Maps the next readable segment after `fromLogId`, skipping a run retention has deleted —
3891
+ * stopping at the hole wedges the iterator there for every later poll (invariant 30).
3892
+ *
3893
+ * The jump target is `_nextLogId()`, the successor over the store's registered segments — *not*
3894
+ * `_findPosition(0)`, which names the bottom of the contiguous run ending at the current segment
3895
+ * and so lands *past* a survivor sitting between two holes.
3896
+ */
3897
+ function nextReadableLogBuffer(transactionLog, fromLogId, latestLogId) {
3898
+ let candidateLogId = fromLogId + 1;
3899
+ while (candidateLogId <= latestLogId) {
3900
+ const logBuffer = getLogMemoryMap(transactionLog, candidateLogId);
3901
+ if (logBuffer) {
3902
+ if (candidateLogId === latestLogId || readableExtent(logBuffer) > TRANSACTION_LOG_FILE_HEADER_SIZE) return logBuffer;
3903
+ } else if (transactionLog.getLogFileSize(candidateLogId) > TRANSACTION_LOG_FILE_HEADER_SIZE) return;
3904
+ candidateLogId = transactionLog._nextLogId(candidateLogId);
3905
+ if (candidateLogId === 0) return;
3906
+ }
3907
+ }
3908
+ /**
3909
+ * The bound for reading `logBuffer`. Native advances this append-owned extent only after a
3910
+ * successful write. It remains attached to the mapping after purge forgets the file object, so
3911
+ * readers retain committed history without interpreting a partial failed append as another frame.
3912
+ */
3913
+ function readableExtent(logBuffer) {
3914
+ const extent = logBuffer.readableExtent;
3915
+ if (extent === void 0) throw new Error(`Transaction log buffer ${logBuffer.logId} has no readableExtent`);
3916
+ return Math.min(logBuffer.length, extent);
3917
+ }
3794
3918
  function getLogMemoryMap(transactionLog, logId) {
3795
3919
  if (logId <= 0) return;
3796
3920
  let logBuffer = transactionLog._logBuffers.get(logId)?.deref();
@@ -3836,9 +3960,9 @@ function loadLastPosition(transactionLog, readUncommitted) {
3836
3960
  //#region src/index.ts
3837
3961
  const versions = {
3838
3962
  rocksdb: version,
3839
- "rocksdb-js": "2.10.0"
3963
+ "rocksdb-js": "2.12.0"
3840
3964
  };
3841
3965
 
3842
3966
  //#endregion
3843
- export { BackgroundError, CorruptFrameError, CountEstimator, DBI, DBIterator, RocksDatabase, Store, Transaction, TransactionLog, backups, constants, coolTransactionLogs, currentThreadId, fileLockRelease, getWriteBufferManagerStats, parseTransactionLog, registryStatus, shutdown, stats, steadyClockNow, supportedCompression, tryFileLock, validateTransactionLogStore, versions };
3967
+ export { BackgroundError, CorruptFrameError, CountEstimator, DBI, DBIterator, RocksDatabase, Store, Transaction, TransactionLog, backups, constants, coolTransactionLogs, currentThreadId, fileLockRelease, getRegistryStatus, getWriteBufferManagerStats, parseTransactionLog, registryStatus, shutdown, stats, steadyClockNow, supportedCompression, tryFileLock, validateTransactionLogStore, versions };
3844
3968
  //# sourceMappingURL=index.mjs.map