@harperfast/rocksdb-js 2.13.0 → 2.14.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
@@ -30,6 +30,7 @@ let node_fs = require("node:fs");
30
30
  let node_module = require("node:module");
31
31
  let node_path = require("node:path");
32
32
  let node_url = require("node:url");
33
+ let node_worker_threads = require("node:worker_threads");
33
34
  let node_fs_promises = require("node:fs/promises");
34
35
  let ordered_binary = require("ordered-binary");
35
36
  ordered_binary = __toESM(ordered_binary, 1);
@@ -79,6 +80,18 @@ function locateBinding() {
79
80
  throw new Error("Unable to locate rocksdb-js native binding");
80
81
  }
81
82
  const binding = req(locateBinding());
83
+ if (node_worker_threads.isMainThread) {
84
+ let exitShutdownRan = false;
85
+ const shutdownOnExit = () => {
86
+ if (exitShutdownRan) return;
87
+ exitShutdownRan = true;
88
+ if (!binding.shutdownOnExit() && !process.exitCode) process.exitCode = 1;
89
+ };
90
+ process.on("exit", shutdownOnExit);
91
+ process.on("uncaughtExceptionMonitor", () => {
92
+ if (process._exiting) shutdownOnExit();
93
+ });
94
+ }
82
95
  /**
83
96
  * The native `BackgroundError` constructor (a real `Error` subclass). Exported
84
97
  * as both a value — for `err instanceof BackgroundError` — and, via declaration
@@ -141,7 +154,7 @@ const getRegistryStatus = binding.registryStatus;
141
154
  */
142
155
  const registryStatus = getRegistryStatus;
143
156
  /**
144
- * Flushes every open database and waits for outstanding compactions, then
157
+ * Flushes every open database and cancels its outstanding compactions, then
145
158
  * releases the registry.
146
159
  *
147
160
  * **Throws** the first failure it encounters: a close whose native teardown
@@ -151,19 +164,10 @@ const registryStatus = getRegistryStatus;
151
164
  * `database:closeFailed`; a timeout has no event, so the throw is its only
152
165
  * signal.
153
166
  *
154
- * A `process.on('exit')` listener must therefore wrap it: an exception thrown
155
- * from an `exit` listener skips every `exit` listener registered after it (and
156
- * sets exit code 1 unless an `uncaughtException` handler is installed).
157
- *
158
- * ```typescript
159
- * process.on('exit', () => {
160
- * try {
161
- * shutdown();
162
- * } catch (error) {
163
- * console.error('rocksdb-js shutdown failed', error);
164
- * }
165
- * });
166
- * ```
167
+ * You do not need to call it at exit: when rocksdb-js loads on the main
168
+ * thread it registers a `process.on('exit')` listener that does the same and
169
+ * reports failures to stderr and the exit code instead of throwing (see the
170
+ * `shutdownOnExit` config option). Calling it yourself as well is safe.
167
171
  */
168
172
  const shutdown = binding.shutdown;
169
173
  const currentThreadId = binding.currentThreadId;
@@ -183,6 +187,30 @@ const currentThreadId = binding.currentThreadId;
183
187
  */
184
188
  const steadyClockNow = binding.steadyClockNow;
185
189
  /**
190
+ * Raises the process-wide monotonic timestamp clock — the one behind
191
+ * `db.getMonotonicTimestamp()`, every transaction's initial timestamp and so
192
+ * every transaction-log batch key — to at least `timestamp` (Unix epoch
193
+ * milliseconds). Every timestamp the process issues after the call is strictly
194
+ * greater than `timestamp`, across every database and `worker_threads` worker.
195
+ * Timestamps already issued and timestamps adopted with `txn.setTimestamp()`
196
+ * are unaffected. No open database is needed.
197
+ *
198
+ * Use it to carry a floor the caller persisted across a restart, which a
199
+ * backward wall-clock step would otherwise let the new process reissue. The
200
+ * floor is in memory only and is not a fence: restore it before the process
201
+ * starts any transaction it originates, and pass only floors this node
202
+ * persisted itself, since the clock stays that far ahead until wall time
203
+ * catches up.
204
+ *
205
+ * @returns `true` if the clock was raised: `timestamp` was ahead of both the
206
+ * wall clock and every timestamp already issued. `false` if the clock was
207
+ * already at or above it.
208
+ * @throws {TypeError} If `timestamp` is not a number.
209
+ * @throws {RangeError} If `timestamp` is not finite, is not greater than 0, is
210
+ * not less than 8.64e15, or is more than ten years ahead of the wall clock.
211
+ */
212
+ const raiseTimestampFloor = binding.raiseTimestampFloor;
213
+ /**
186
214
  * Advises the kernel that the file-backed pages of every mapped transaction log
187
215
  * are cold (Linux MADV_COLD), so they are reclaimed first under memory pressure
188
216
  * without being freed — useful during replication catch-up, where a full read of
@@ -1821,6 +1849,10 @@ var Store = class {
1821
1849
  */
1822
1850
  arenaBlockSize;
1823
1851
  /**
1852
+ * Bits per key of the whole-key filter in each SST; `0` disables filters.
1853
+ */
1854
+ bloomFilterBitsPerKey;
1855
+ /**
1824
1856
  * The function used to encode keys using the shared `keyBuffer`.
1825
1857
  */
1826
1858
  writeKey;
@@ -1874,6 +1906,7 @@ var Store = class {
1874
1906
  this.verificationTable = options?.verificationTable;
1875
1907
  this.writeBufferSize = options?.writeBufferSize;
1876
1908
  this.arenaBlockSize = options?.arenaBlockSize;
1909
+ this.bloomFilterBitsPerKey = options?.bloomFilterBitsPerKey;
1877
1910
  this.writeKey = writeKey;
1878
1911
  this.#columnFamilyOptions = options ? { ...options } : void 0;
1879
1912
  }
@@ -2033,7 +2066,7 @@ var Store = class {
2033
2066
  const result = context.getSync(keyParam, flags | ONLY_IF_IN_MEMORY_CACHE_FLAG, txnId, expectedVersion);
2034
2067
  if (typeof result === "number") {
2035
2068
  if (result === NOT_IN_MEMORY_CACHE_FLAG) return new Promise((resolve, reject) => {
2036
- context.get(keyParam, resolve, reject, txnId, expectedVersion);
2069
+ context.get(keyParam, resolve, reject, txnId, expectedVersion, flags);
2037
2070
  });
2038
2071
  if (result === FRESH_VERSION_FLAG) return result;
2039
2072
  VALUE_BUFFER.end = result;
@@ -2241,7 +2274,8 @@ var Store = class {
2241
2274
  timestampFloorLog: this.timestampFloorLog,
2242
2275
  verificationTable: this.verificationTable,
2243
2276
  writeBufferSize: this.writeBufferSize,
2244
- arenaBlockSize: this.arenaBlockSize
2277
+ arenaBlockSize: this.arenaBlockSize,
2278
+ bloomFilterBitsPerKey: this.bloomFilterBitsPerKey
2245
2279
  });
2246
2280
  this.identityPath = this.db.identityPath;
2247
2281
  return false;
@@ -3797,6 +3831,40 @@ function parseTransactionLog(path, options = {}) {
3797
3831
  const FLOAT_TO_UINT32 = /* @__PURE__ */ new Float64Array(1);
3798
3832
  const UINT32_FROM_FLOAT = new Uint32Array(FLOAT_TO_UINT32.buffer);
3799
3833
  const EXACT_START_SPAN = /* @__PURE__ */ new Float64Array(3);
3834
+ const ITERATOR_TOKEN = Symbol("transactionLogIterator");
3835
+ const logEquals = TransactionLog.prototype.equals;
3836
+ var LogIterator = class {
3837
+ #sourceLog;
3838
+ #control;
3839
+ next;
3840
+ constructor(token, sourceLog, next, control) {
3841
+ if (token !== ITERATOR_TOKEN) throw new TypeError("Transaction log iterators require query()");
3842
+ this.#sourceLog = sourceLog;
3843
+ this.next = next;
3844
+ this.#control = control;
3845
+ }
3846
+ static cursor(value) {
3847
+ if (typeof value === "object" && value !== null && #control in value) return value;
3848
+ }
3849
+ static isFrom(value, sourceLog) {
3850
+ return logEquals.call(sourceLog, value.#sourceLog);
3851
+ }
3852
+ [Symbol.iterator]() {
3853
+ return this;
3854
+ }
3855
+ peek() {
3856
+ return this.#control("peek");
3857
+ }
3858
+ close() {
3859
+ this.#control("close");
3860
+ }
3861
+ static reached(value, logId, position, atSegmentEnd) {
3862
+ return value.#control("reached", logId, position, atSegmentEnd);
3863
+ }
3864
+ static scanEnd(value, logId, size) {
3865
+ return value.#control("scanEnd", logId, size);
3866
+ }
3867
+ };
3800
3868
  const { TRANSACTION_LOG_FILE_HEADER_SIZE, TRANSACTION_LOG_ENTRY_HEADER_SIZE, TRANSACTION_LOG_ENTRY_LAST_FLAG, TRANSACTION_LOG_ENTRY_ABORTED_FLAG } = constants;
3801
3869
  /**
3802
3870
  * Number of consecutive well-formed frames that, on their own, signal that real log data has
@@ -3858,6 +3926,17 @@ var TransactionLogGapError = class extends Error {
3858
3926
  this.unavailableLogCount = unavailableLogCount;
3859
3927
  }
3860
3928
  };
3929
+ /** Readable input ended before the iterator's physical `until` bound. Retry after input is available. */
3930
+ var TransactionLogUntilUnreachedError = class extends Error {
3931
+ logId;
3932
+ position;
3933
+ constructor(logId, position) {
3934
+ super(`Transaction log reader stopped at position ${position.toString(16)} of log ${logId}, short of the iterator it reads until`);
3935
+ this.name = "TransactionLogUntilUnreachedError";
3936
+ this.logId = logId;
3937
+ this.position = position;
3938
+ }
3939
+ };
3861
3940
  /**
3862
3941
  * Whether a complete, in-bounds frame begins at `pos`. The only sane bound on an entry's length is
3863
3942
  * the written extent: a single entry can legitimately exceed the rotation threshold (the first
@@ -3920,7 +3999,9 @@ function corruptFrame(message, transactionLog, dataView, logBuffer, position, li
3920
3999
  * exactly matches the start timestamp, and then return all subsequent transactions in the log
3921
4000
  * regardless of whether their timestamp is before or after the start
3922
4001
  */
3923
- Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, exactStart, startFromLastFlushed, readUncommitted, exclusiveStart } = {}) {
4002
+ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, exactStart, startFromLastFlushed, readUncommitted, exclusiveStart, until } = {}) {
4003
+ let untilCursor = LogIterator.cursor(until);
4004
+ if (until !== void 0 && (!untilCursor || !LogIterator.isFrom(untilCursor, this))) throw new TypeError("until must be an iterator from query() on the same transaction log");
3924
4005
  if (!this._lastCommittedPosition) {
3925
4006
  const lastCommittedPosition = this._getLastCommittedPosition();
3926
4007
  this._lastCommittedPosition = new Float64Array(lastCommittedPosition.buffer);
@@ -3967,7 +4048,7 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
3967
4048
  if (position === 0) position = TRANSACTION_LOG_FILE_HEADER_SIZE;
3968
4049
  }
3969
4050
  const positionedAt = position;
3970
- if (stopLogId !== 0 && pastStop(logId, position, stopLogId, stopPosition)) {
4051
+ if (stopLogId !== 0 && pastStop(logId, position, stopLogId, stopPosition) && mayJumpTo(untilCursor, tailLogId, tailPosition)) {
3971
4052
  stopLogId = 0;
3972
4053
  if (skipToTail(this, logId, position, tailLogId, tailPosition, segmentRemovals)) {
3973
4054
  logId = tailLogId;
@@ -3977,95 +4058,144 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
3977
4058
  tailLogBuffer = void 0;
3978
4059
  }
3979
4060
  let gapFloor = logId;
4061
+ let startUnmapped = false;
4062
+ let startMissing = false;
3980
4063
  if (logBuffer === void 0 || logBuffer.logId !== logId) {
3981
4064
  logBuffer = getLogMemoryMap(this, logId);
3982
4065
  if (logBuffer && latestLogId === logId && !readUncommitted && this._currentLogBuffer?.deref() !== logBuffer) this._currentLogBuffer = new WeakRef(logBuffer);
3983
4066
  if (logBuffer === void 0) {
3984
4067
  logBuffer = Buffer.alloc(0);
3985
- logBuffer.logId = 0;
4068
+ logBuffer.logId = logId;
3986
4069
  logBuffer.size = 0;
3987
4070
  logBuffer.dataView = new DataView(logBuffer.buffer);
3988
4071
  size = 0;
4072
+ startUnmapped = logId > 0;
3989
4073
  if (positionedAt <= TRANSACTION_LOG_FILE_HEADER_SIZE || logId > 0 && this.getLogFileSize(logId) > 0) gapFloor = logId - 1;
3990
4074
  }
3991
4075
  }
3992
4076
  dataView = logBuffer.dataView;
3993
4077
  if (latestLogId < logId) size = 0;
3994
- else if (latestLogId > logId) {
4078
+ else if (latestLogId > logId && !startUnmapped) {
3995
4079
  const cachedSize = logBuffer.size;
3996
4080
  if (cachedSize === void 0) size = logBuffer.size = readableExtent(logBuffer);
3997
4081
  else size = cachedSize;
3998
4082
  }
3999
- const iterator = {
4000
- [Symbol.iterator]() {
4001
- return this;
4002
- },
4003
- next() {
4004
- let timestamp;
4005
- if (position >= size) {
4006
- const { logId: latestLogId, size: latestSize } = loadLastPosition(transactionLog, !!readUncommitted);
4007
- size = latestLogId < logBuffer.logId ? 0 : latestSize;
4008
- if (latestLogId > logBuffer.logId) {
4009
- size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
4010
- if (position >= size) {
4011
- const fromLogId = logBuffer.logId;
4012
- const nextLogBuffer = nextReadableLogBuffer(transactionLog, fromLogId, latestLogId, gapFloor);
4013
- if (nextLogBuffer) {
4014
- const unavailable = unavailableLogCount;
4015
- dataView = nextLogBuffer.dataView;
4016
- logBuffer = nextLogBuffer;
4017
- if (latestLogId > logBuffer.logId) size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
4018
- else size = latestSize;
4019
- position = TRANSACTION_LOG_FILE_HEADER_SIZE;
4020
- previousEndedTransaction = true;
4021
- if (unavailable > 0) throw new TransactionLogGapError(Math.max(fromLogId, gapFloor), logBuffer.logId, unavailable);
4022
- }
4023
- }
4083
+ let peeked;
4084
+ let peekedLogId = 0;
4085
+ let peekedPosition = 0;
4086
+ let closed = false;
4087
+ let needsCursorCheck = untilCursor !== void 0 || startUnmapped;
4088
+ const iterator = new LogIterator(ITERATOR_TOKEN, transactionLog, function next() {
4089
+ let scanEnd = size;
4090
+ if (needsCursorCheck) {
4091
+ if (peeked) {
4092
+ const result = peeked;
4093
+ if (!closed) {
4094
+ peeked = void 0;
4095
+ needsCursorCheck = untilCursor !== void 0;
4024
4096
  }
4097
+ return result;
4025
4098
  }
4026
- while (position < size) {
4027
- try {
4028
- timestamp = dataView.getFloat64(position);
4029
- } catch (error) {
4030
- error.message += ` at position ${position.toString(16)} of log ${logBuffer.logId} (size=${size}, log buffer length=${logBuffer.length})`;
4031
- throw error;
4099
+ if (untilCursor) {
4100
+ const limit = LogIterator.scanEnd(untilCursor, logBuffer.logId, size);
4101
+ if (limit === void 0) {
4102
+ untilCursor = void 0;
4103
+ needsCursorCheck = startUnmapped;
4104
+ } else {
4105
+ if (position >= limit && LogIterator.reached(untilCursor, logBuffer.logId, position, position >= size && logBuffer.length > 0 && position >= readableExtent(logBuffer))) return {
4106
+ done: true,
4107
+ value: void 0
4108
+ };
4109
+ scanEnd = limit;
4032
4110
  }
4033
- if (!timestamp) return {
4034
- done: true,
4035
- value: void 0
4036
- };
4037
- const limit = readUncommitted ? logBuffer.length : Math.min(size, logBuffer.length);
4038
- if (position + TRANSACTION_LOG_ENTRY_HEADER_SIZE > limit) {
4039
- const broken = corruptFrame(`Corrupt transaction log: truncated entry header at position ${position.toString(16)} of log ${logBuffer.logId} (available=${limit - position})`, transactionLog, dataView, logBuffer, position, limit, size, !!readUncommitted);
4040
- position = broken.nextPosition;
4041
- previousEndedTransaction = true;
4042
- throw broken.error;
4043
- }
4044
- const length = dataView.getUint32(position + 8);
4045
- if (length === 0 || position + TRANSACTION_LOG_ENTRY_HEADER_SIZE + length > limit) {
4046
- const broken = corruptFrame(length === 0 ? `Corrupt transaction log entry at position ${position.toString(16)} of log ${logBuffer.logId}: zero-length entry` : `Corrupt transaction log entry at position ${position.toString(16)} of log ${logBuffer.logId}: declared length ${length} overruns the log (limit=${limit})`, transactionLog, dataView, logBuffer, position, limit, size, !!readUncommitted);
4047
- position = broken.nextPosition;
4048
- previousEndedTransaction = true;
4049
- throw broken.error;
4111
+ }
4112
+ if (startUnmapped) {
4113
+ const mapped = getLogMemoryMap(transactionLog, logBuffer.logId);
4114
+ if (mapped) {
4115
+ logBuffer = mapped;
4116
+ dataView = mapped.dataView;
4117
+ } else {
4118
+ const latestLogId = loadLastPosition(transactionLog, !!readUncommitted).logId;
4119
+ const fileSize = transactionLog.getLogFileSize(logBuffer.logId);
4120
+ if (logBuffer.logId >= latestLogId || fileSize > position) return endOfReadable(untilCursor, logBuffer, position);
4121
+ if (fileSize === 0) startMissing = true;
4050
4122
  }
4051
- position += TRANSACTION_LOG_ENTRY_HEADER_SIZE;
4052
- const flags = logBuffer[position - 1];
4053
- const aborted = (flags & TRANSACTION_LOG_ENTRY_ABORTED_FLAG) !== 0;
4054
- if (previousEndedTransaction || timestamp !== previousTimestamp || aborted) skippingAborted = aborted;
4055
- previousEndedTransaction = (flags & TRANSACTION_LOG_ENTRY_LAST_FLAG) !== 0;
4056
- previousTimestamp = timestamp;
4057
- let matchesRange;
4058
- if (skippingAborted) matchesRange = false;
4059
- else if (foundExactStart) matchesRange = (!exclusiveStart || timestamp !== start) && timestamp < end;
4060
- else if (exactStart) {
4061
- matchesRange = false;
4062
- if (timestamp === start) {
4063
- matchesRange = !exclusiveStart;
4064
- foundExactStart = true;
4065
- stopLogId = 0;
4123
+ startUnmapped = false;
4124
+ needsCursorCheck = untilCursor !== void 0;
4125
+ size = 0;
4126
+ }
4127
+ }
4128
+ let timestamp;
4129
+ if (position >= size) {
4130
+ const { logId: latestLogId, size: latestSize } = loadLastPosition(transactionLog, !!readUncommitted);
4131
+ size = latestLogId < logBuffer.logId ? 0 : latestSize;
4132
+ if (latestLogId > logBuffer.logId) {
4133
+ size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
4134
+ if (position >= size) {
4135
+ if (untilCursor && LogIterator.reached(untilCursor, logBuffer.logId, position, logBuffer.length > 0 && position >= readableExtent(logBuffer))) return {
4136
+ done: true,
4137
+ value: void 0
4138
+ };
4139
+ const fromLogId = logBuffer.logId - (startMissing ? 1 : 0);
4140
+ const nextLogBuffer = nextReadableLogBuffer(transactionLog, fromLogId, latestLogId, gapFloor);
4141
+ if (nextLogBuffer) {
4142
+ startMissing = false;
4143
+ const unavailable = unavailableLogCount;
4144
+ dataView = nextLogBuffer.dataView;
4145
+ logBuffer = nextLogBuffer;
4146
+ if (latestLogId > logBuffer.logId) size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
4147
+ else size = latestSize;
4148
+ position = TRANSACTION_LOG_FILE_HEADER_SIZE;
4149
+ previousEndedTransaction = true;
4150
+ if (unavailable > 0) throw new TransactionLogGapError(Math.max(fromLogId, gapFloor), logBuffer.logId, unavailable);
4066
4151
  }
4067
- } else matchesRange = (exclusiveStart ? timestamp > start : timestamp >= start) && timestamp < end;
4068
- if (!matchesRange && stopLogId !== 0 && pastStop(logBuffer.logId, position + length, stopLogId, stopPosition)) {
4152
+ }
4153
+ }
4154
+ scanEnd = untilCursor ? LogIterator.scanEnd(untilCursor, logBuffer.logId, size) ?? size : size;
4155
+ }
4156
+ if (position >= scanEnd) return endOfReadable(untilCursor, logBuffer, position);
4157
+ let jumpRefused = false;
4158
+ let limit = Math.min(size, logBuffer.length);
4159
+ do {
4160
+ try {
4161
+ timestamp = dataView.getFloat64(position);
4162
+ } catch (error) {
4163
+ error.message += ` at position ${position.toString(16)} of log ${logBuffer.logId} (size=${size}, log buffer length=${logBuffer.length})`;
4164
+ throw error;
4165
+ }
4166
+ if (!timestamp) return endOfReadable(untilCursor, logBuffer, position);
4167
+ if (position + TRANSACTION_LOG_ENTRY_HEADER_SIZE > limit) {
4168
+ const broken = corruptFrame(`Corrupt transaction log: truncated entry header at position ${position.toString(16)} of log ${logBuffer.logId} (available=${limit - position})`, transactionLog, dataView, logBuffer, position, limit, size, !!readUncommitted);
4169
+ position = broken.nextPosition;
4170
+ previousEndedTransaction = true;
4171
+ throw broken.error;
4172
+ }
4173
+ const length = dataView.getUint32(position + 8);
4174
+ if (length === 0 || position + TRANSACTION_LOG_ENTRY_HEADER_SIZE + length > limit) {
4175
+ const broken = corruptFrame(length === 0 ? `Corrupt transaction log entry at position ${position.toString(16)} of log ${logBuffer.logId}: zero-length entry` : `Corrupt transaction log entry at position ${position.toString(16)} of log ${logBuffer.logId}: declared length ${length} overruns the log (limit=${limit})`, transactionLog, dataView, logBuffer, position, limit, size, !!readUncommitted);
4176
+ position = broken.nextPosition;
4177
+ previousEndedTransaction = true;
4178
+ throw broken.error;
4179
+ }
4180
+ position += TRANSACTION_LOG_ENTRY_HEADER_SIZE;
4181
+ const flags = logBuffer[position - 1];
4182
+ const aborted = (flags & TRANSACTION_LOG_ENTRY_ABORTED_FLAG) !== 0;
4183
+ if (previousEndedTransaction || timestamp !== previousTimestamp || aborted) skippingAborted = aborted;
4184
+ previousEndedTransaction = (flags & TRANSACTION_LOG_ENTRY_LAST_FLAG) !== 0;
4185
+ previousTimestamp = timestamp;
4186
+ let matchesRange;
4187
+ if (skippingAborted) matchesRange = false;
4188
+ else if (foundExactStart) matchesRange = (!exclusiveStart || timestamp !== start) && timestamp < end;
4189
+ else if (exactStart) {
4190
+ matchesRange = false;
4191
+ if (timestamp === start) {
4192
+ matchesRange = !exclusiveStart;
4193
+ foundExactStart = true;
4194
+ stopLogId = 0;
4195
+ }
4196
+ } else matchesRange = (exclusiveStart ? timestamp > start : timestamp >= start) && timestamp < end;
4197
+ if (!matchesRange && stopLogId !== 0 && !jumpRefused && pastStop(logBuffer.logId, position + length, stopLogId, stopPosition)) {
4198
+ if (mayJumpTo(untilCursor, tailLogId, tailPosition)) {
4069
4199
  stopLogId = 0;
4070
4200
  position += length;
4071
4201
  if (skipToTail(transactionLog, logBuffer.logId, position, tailLogId, tailPosition, segmentRemovals)) {
@@ -4080,57 +4210,108 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
4080
4210
  tailLogBuffer = void 0;
4081
4211
  return iterator.next();
4082
4212
  }
4083
- const entryStart = position;
4084
- position += length;
4085
- if (matchesRange) return {
4086
- done: false,
4087
- value: {
4088
- timestamp,
4089
- endTxn: previousEndedTransaction,
4090
- data: logBuffer.subarray(entryStart, position)
4091
- }
4213
+ jumpRefused = true;
4214
+ }
4215
+ const entryStart = position;
4216
+ position += length;
4217
+ if (matchesRange) return {
4218
+ done: false,
4219
+ value: {
4220
+ timestamp,
4221
+ endTxn: previousEndedTransaction,
4222
+ data: logBuffer.subarray(entryStart, position)
4223
+ }
4224
+ };
4225
+ if (position >= scanEnd) {
4226
+ if (untilCursor && LogIterator.reached(untilCursor, logBuffer.logId, position)) return {
4227
+ done: true,
4228
+ value: void 0
4092
4229
  };
4093
- if (position >= size) {
4094
- const { logId: latestLogId, size: latestSize } = loadLastPosition(transactionLog, !!readUncommitted);
4095
- size = latestLogId < logBuffer.logId ? 0 : latestSize;
4096
- if (latestLogId > logBuffer.logId) {
4097
- size = logBuffer.size ?? readableExtent(logBuffer);
4098
- if (!readUncommitted) logBuffer.size = size;
4099
- if (position < size) continue;
4100
- const fromLogId = logBuffer.logId;
4101
- const nextLogBuffer = nextReadableLogBuffer(transactionLog, fromLogId, latestLogId, gapFloor);
4102
- if (!nextLogBuffer) return {
4103
- done: true,
4104
- value: void 0
4105
- };
4106
- const unavailable = unavailableLogCount;
4107
- logBuffer = nextLogBuffer;
4108
- dataView = logBuffer.dataView;
4109
- if (latestLogId === logBuffer.logId) size = latestSize;
4110
- else {
4111
- const cachedSize = logBuffer.size;
4112
- if (cachedSize === void 0) {
4113
- size = readableExtent(logBuffer);
4114
- if (!readUncommitted) logBuffer.size = size;
4115
- } else size = cachedSize;
4116
- }
4117
- position = TRANSACTION_LOG_FILE_HEADER_SIZE;
4118
- previousEndedTransaction = true;
4119
- if (unavailable > 0) throw new TransactionLogGapError(Math.max(fromLogId, gapFloor), logBuffer.logId, unavailable);
4230
+ const { logId: latestLogId, size: latestSize } = loadLastPosition(transactionLog, !!readUncommitted);
4231
+ size = latestLogId < logBuffer.logId ? 0 : latestSize;
4232
+ if (latestLogId > logBuffer.logId) {
4233
+ size = logBuffer.size ?? readableExtent(logBuffer);
4234
+ if (!readUncommitted) logBuffer.size = size;
4235
+ if (position < size) {
4236
+ scanEnd = untilCursor ? LogIterator.scanEnd(untilCursor, logBuffer.logId, size) ?? size : size;
4237
+ limit = Math.min(size, logBuffer.length);
4238
+ continue;
4239
+ }
4240
+ if (untilCursor && LogIterator.reached(untilCursor, logBuffer.logId, position, logBuffer.length > 0 && position >= readableExtent(logBuffer))) return {
4241
+ done: true,
4242
+ value: void 0
4243
+ };
4244
+ const fromLogId = logBuffer.logId;
4245
+ const nextLogBuffer = nextReadableLogBuffer(transactionLog, fromLogId, latestLogId, gapFloor);
4246
+ if (!nextLogBuffer) return endOfReadable(untilCursor, logBuffer, position);
4247
+ const unavailable = unavailableLogCount;
4248
+ logBuffer = nextLogBuffer;
4249
+ dataView = logBuffer.dataView;
4250
+ if (latestLogId === logBuffer.logId) size = latestSize;
4251
+ else {
4252
+ const cachedSize = logBuffer.size;
4253
+ if (cachedSize === void 0) {
4254
+ size = readableExtent(logBuffer);
4255
+ if (!readUncommitted) logBuffer.size = size;
4256
+ } else size = cachedSize;
4120
4257
  }
4258
+ position = TRANSACTION_LOG_FILE_HEADER_SIZE;
4259
+ previousEndedTransaction = true;
4260
+ if (unavailable > 0) throw new TransactionLogGapError(Math.max(fromLogId, gapFloor), logBuffer.logId, unavailable);
4121
4261
  }
4262
+ scanEnd = untilCursor ? LogIterator.scanEnd(untilCursor, logBuffer.logId, size) ?? size : size;
4263
+ limit = Math.min(size, logBuffer.length);
4264
+ }
4265
+ } while (position < scanEnd);
4266
+ return endOfReadable(untilCursor, logBuffer, position);
4267
+ }, function control(operation, fromLogId, fromPosition, atSegmentEnd) {
4268
+ if (operation === "peek") {
4269
+ if (peeked) return peeked;
4270
+ const result = iterator.next();
4271
+ if (!result.done) {
4272
+ peeked = result;
4273
+ needsCursorCheck = true;
4274
+ peekedLogId = logBuffer.logId;
4275
+ peekedPosition = position - result.value.data.length - TRANSACTION_LOG_ENTRY_HEADER_SIZE;
4122
4276
  }
4123
- return {
4277
+ return result;
4278
+ }
4279
+ if (operation === "close") {
4280
+ closed = true;
4281
+ needsCursorCheck = true;
4282
+ peeked = {
4124
4283
  done: true,
4125
4284
  value: void 0
4126
4285
  };
4286
+ logBuffer = void 0;
4287
+ dataView = void 0;
4288
+ untilCursor = void 0;
4289
+ return;
4127
4290
  }
4128
- };
4291
+ if (closed) return;
4292
+ const boundLogId = peeked ? peekedLogId : logBuffer.logId;
4293
+ const boundPosition = peeked ? peekedPosition : position;
4294
+ if (operation === "scanEnd") {
4295
+ if (boundLogId !== fromLogId) return boundLogId > fromLogId ? fromPosition : 0;
4296
+ return boundPosition < fromPosition ? boundPosition : fromPosition;
4297
+ }
4298
+ return pastStop(fromLogId, fromPosition, boundLogId, boundPosition) || !!atSegmentEnd && boundLogId === fromLogId + 1 && boundPosition === TRANSACTION_LOG_FILE_HEADER_SIZE;
4299
+ });
4129
4300
  return iterator;
4130
4301
  } });
4302
+ function endOfReadable(untilCursor, logBuffer, position) {
4303
+ if (untilCursor && LogIterator.reached(untilCursor, logBuffer.logId, position, logBuffer.length > 0 && position >= readableExtent(logBuffer)) === false) throw new TransactionLogUntilUnreachedError(logBuffer.logId, position);
4304
+ return {
4305
+ done: true,
4306
+ value: void 0
4307
+ };
4308
+ }
4131
4309
  function pastStop(logId, position, stopLogId, stopPosition) {
4132
4310
  return logId > stopLogId || logId === stopLogId && position >= stopPosition;
4133
4311
  }
4312
+ function mayJumpTo(untilCursor, logId, position) {
4313
+ return !untilCursor || LogIterator.reached(untilCursor, logId, position - 1) !== true;
4314
+ }
4134
4315
  let tailLogBuffer;
4135
4316
  /**
4136
4317
  * Whether an exact-start lookup at `position` of `fromLogId` may take its one-shot jump to the tail.
@@ -4229,7 +4410,7 @@ function loadLastPosition(transactionLog, readUncommitted) {
4229
4410
  // src/index.ts
4230
4411
  const versions = {
4231
4412
  rocksdb: version,
4232
- "rocksdb-js": "2.13.0"
4413
+ "rocksdb-js": "2.14.0"
4233
4414
  };
4234
4415
 
4235
4416
  exports.BackgroundError = BackgroundError;
@@ -4244,6 +4425,7 @@ exports.Transaction = Transaction;
4244
4425
  exports.TransactionAbortUnresolvedError = TransactionAbortUnresolvedError;
4245
4426
  exports.TransactionLog = TransactionLog;
4246
4427
  exports.TransactionLogGapError = TransactionLogGapError;
4428
+ exports.TransactionLogUntilUnreachedError = TransactionLogUntilUnreachedError;
4247
4429
  exports.backups = backups;
4248
4430
  exports.constants = constants;
4249
4431
  exports.coolTransactionLogs = coolTransactionLogs;
@@ -4252,6 +4434,7 @@ exports.fileLockRelease = fileLockRelease;
4252
4434
  exports.getRegistryStatus = getRegistryStatus;
4253
4435
  exports.getWriteBufferManagerStats = getWriteBufferManagerStats;
4254
4436
  exports.parseTransactionLog = parseTransactionLog;
4437
+ exports.raiseTimestampFloor = raiseTimestampFloor;
4255
4438
  exports.registryStatus = registryStatus;
4256
4439
  exports.shutdown = shutdown;
4257
4440
  exports.stats = stats;