@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.mjs CHANGED
@@ -3,6 +3,7 @@ import { execSync } from "node:child_process";
3
3
  import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, statSync } from "node:fs";
4
4
  import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
+ import { isMainThread } from "node:worker_threads";
6
7
  import { access, cp, mkdir, readdir, rm } from "node:fs/promises";
7
8
  import * as orderedBinary from "ordered-binary";
8
9
  import { ExtendedIterable } from "@harperfast/extended-iterable";
@@ -54,6 +55,18 @@ function locateBinding() {
54
55
  throw new Error("Unable to locate rocksdb-js native binding");
55
56
  }
56
57
  const binding = req(locateBinding());
58
+ if (isMainThread) {
59
+ let exitShutdownRan = false;
60
+ const shutdownOnExit = () => {
61
+ if (exitShutdownRan) return;
62
+ exitShutdownRan = true;
63
+ if (!binding.shutdownOnExit() && !process.exitCode) process.exitCode = 1;
64
+ };
65
+ process.on("exit", shutdownOnExit);
66
+ process.on("uncaughtExceptionMonitor", () => {
67
+ if (process._exiting) shutdownOnExit();
68
+ });
69
+ }
57
70
  /**
58
71
  * The native `BackgroundError` constructor (a real `Error` subclass). Exported
59
72
  * as both a value — for `err instanceof BackgroundError` — and, via declaration
@@ -116,7 +129,7 @@ const getRegistryStatus = binding.registryStatus;
116
129
  */
117
130
  const registryStatus = getRegistryStatus;
118
131
  /**
119
- * Flushes every open database and waits for outstanding compactions, then
132
+ * Flushes every open database and cancels its outstanding compactions, then
120
133
  * releases the registry.
121
134
  *
122
135
  * **Throws** the first failure it encounters: a close whose native teardown
@@ -126,19 +139,10 @@ const registryStatus = getRegistryStatus;
126
139
  * `database:closeFailed`; a timeout has no event, so the throw is its only
127
140
  * signal.
128
141
  *
129
- * A `process.on('exit')` listener must therefore wrap it: an exception thrown
130
- * from an `exit` listener skips every `exit` listener registered after it (and
131
- * sets exit code 1 unless an `uncaughtException` handler is installed).
132
- *
133
- * ```typescript
134
- * process.on('exit', () => {
135
- * try {
136
- * shutdown();
137
- * } catch (error) {
138
- * console.error('rocksdb-js shutdown failed', error);
139
- * }
140
- * });
141
- * ```
142
+ * You do not need to call it at exit: when rocksdb-js loads on the main
143
+ * thread it registers a `process.on('exit')` listener that does the same and
144
+ * reports failures to stderr and the exit code instead of throwing (see the
145
+ * `shutdownOnExit` config option). Calling it yourself as well is safe.
142
146
  */
143
147
  const shutdown = binding.shutdown;
144
148
  const currentThreadId = binding.currentThreadId;
@@ -158,6 +162,30 @@ const currentThreadId = binding.currentThreadId;
158
162
  */
159
163
  const steadyClockNow = binding.steadyClockNow;
160
164
  /**
165
+ * Raises the process-wide monotonic timestamp clock — the one behind
166
+ * `db.getMonotonicTimestamp()`, every transaction's initial timestamp and so
167
+ * every transaction-log batch key — to at least `timestamp` (Unix epoch
168
+ * milliseconds). Every timestamp the process issues after the call is strictly
169
+ * greater than `timestamp`, across every database and `worker_threads` worker.
170
+ * Timestamps already issued and timestamps adopted with `txn.setTimestamp()`
171
+ * are unaffected. No open database is needed.
172
+ *
173
+ * Use it to carry a floor the caller persisted across a restart, which a
174
+ * backward wall-clock step would otherwise let the new process reissue. The
175
+ * floor is in memory only and is not a fence: restore it before the process
176
+ * starts any transaction it originates, and pass only floors this node
177
+ * persisted itself, since the clock stays that far ahead until wall time
178
+ * catches up.
179
+ *
180
+ * @returns `true` if the clock was raised: `timestamp` was ahead of both the
181
+ * wall clock and every timestamp already issued. `false` if the clock was
182
+ * already at or above it.
183
+ * @throws {TypeError} If `timestamp` is not a number.
184
+ * @throws {RangeError} If `timestamp` is not finite, is not greater than 0, is
185
+ * not less than 8.64e15, or is more than ten years ahead of the wall clock.
186
+ */
187
+ const raiseTimestampFloor = binding.raiseTimestampFloor;
188
+ /**
161
189
  * Advises the kernel that the file-backed pages of every mapped transaction log
162
190
  * are cold (Linux MADV_COLD), so they are reclaimed first under memory pressure
163
191
  * without being freed — useful during replication catch-up, where a full read of
@@ -1796,6 +1824,10 @@ var Store = class {
1796
1824
  */
1797
1825
  arenaBlockSize;
1798
1826
  /**
1827
+ * Bits per key of the whole-key filter in each SST; `0` disables filters.
1828
+ */
1829
+ bloomFilterBitsPerKey;
1830
+ /**
1799
1831
  * The function used to encode keys using the shared `keyBuffer`.
1800
1832
  */
1801
1833
  writeKey;
@@ -1849,6 +1881,7 @@ var Store = class {
1849
1881
  this.verificationTable = options?.verificationTable;
1850
1882
  this.writeBufferSize = options?.writeBufferSize;
1851
1883
  this.arenaBlockSize = options?.arenaBlockSize;
1884
+ this.bloomFilterBitsPerKey = options?.bloomFilterBitsPerKey;
1852
1885
  this.writeKey = writeKey;
1853
1886
  this.#columnFamilyOptions = options ? { ...options } : void 0;
1854
1887
  }
@@ -2008,7 +2041,7 @@ var Store = class {
2008
2041
  const result = context.getSync(keyParam, flags | ONLY_IF_IN_MEMORY_CACHE_FLAG, txnId, expectedVersion);
2009
2042
  if (typeof result === "number") {
2010
2043
  if (result === NOT_IN_MEMORY_CACHE_FLAG) return new Promise((resolve, reject) => {
2011
- context.get(keyParam, resolve, reject, txnId, expectedVersion);
2044
+ context.get(keyParam, resolve, reject, txnId, expectedVersion, flags);
2012
2045
  });
2013
2046
  if (result === FRESH_VERSION_FLAG) return result;
2014
2047
  VALUE_BUFFER.end = result;
@@ -2216,7 +2249,8 @@ var Store = class {
2216
2249
  timestampFloorLog: this.timestampFloorLog,
2217
2250
  verificationTable: this.verificationTable,
2218
2251
  writeBufferSize: this.writeBufferSize,
2219
- arenaBlockSize: this.arenaBlockSize
2252
+ arenaBlockSize: this.arenaBlockSize,
2253
+ bloomFilterBitsPerKey: this.bloomFilterBitsPerKey
2220
2254
  });
2221
2255
  this.identityPath = this.db.identityPath;
2222
2256
  return false;
@@ -3772,6 +3806,40 @@ function parseTransactionLog(path, options = {}) {
3772
3806
  const FLOAT_TO_UINT32 = /* @__PURE__ */ new Float64Array(1);
3773
3807
  const UINT32_FROM_FLOAT = new Uint32Array(FLOAT_TO_UINT32.buffer);
3774
3808
  const EXACT_START_SPAN = /* @__PURE__ */ new Float64Array(3);
3809
+ const ITERATOR_TOKEN = Symbol("transactionLogIterator");
3810
+ const logEquals = TransactionLog.prototype.equals;
3811
+ var LogIterator = class {
3812
+ #sourceLog;
3813
+ #control;
3814
+ next;
3815
+ constructor(token, sourceLog, next, control) {
3816
+ if (token !== ITERATOR_TOKEN) throw new TypeError("Transaction log iterators require query()");
3817
+ this.#sourceLog = sourceLog;
3818
+ this.next = next;
3819
+ this.#control = control;
3820
+ }
3821
+ static cursor(value) {
3822
+ if (typeof value === "object" && value !== null && #control in value) return value;
3823
+ }
3824
+ static isFrom(value, sourceLog) {
3825
+ return logEquals.call(sourceLog, value.#sourceLog);
3826
+ }
3827
+ [Symbol.iterator]() {
3828
+ return this;
3829
+ }
3830
+ peek() {
3831
+ return this.#control("peek");
3832
+ }
3833
+ close() {
3834
+ this.#control("close");
3835
+ }
3836
+ static reached(value, logId, position, atSegmentEnd) {
3837
+ return value.#control("reached", logId, position, atSegmentEnd);
3838
+ }
3839
+ static scanEnd(value, logId, size) {
3840
+ return value.#control("scanEnd", logId, size);
3841
+ }
3842
+ };
3775
3843
  const { TRANSACTION_LOG_FILE_HEADER_SIZE, TRANSACTION_LOG_ENTRY_HEADER_SIZE, TRANSACTION_LOG_ENTRY_LAST_FLAG, TRANSACTION_LOG_ENTRY_ABORTED_FLAG } = constants;
3776
3844
  /**
3777
3845
  * Number of consecutive well-formed frames that, on their own, signal that real log data has
@@ -3833,6 +3901,17 @@ var TransactionLogGapError = class extends Error {
3833
3901
  this.unavailableLogCount = unavailableLogCount;
3834
3902
  }
3835
3903
  };
3904
+ /** Readable input ended before the iterator's physical `until` bound. Retry after input is available. */
3905
+ var TransactionLogUntilUnreachedError = class extends Error {
3906
+ logId;
3907
+ position;
3908
+ constructor(logId, position) {
3909
+ super(`Transaction log reader stopped at position ${position.toString(16)} of log ${logId}, short of the iterator it reads until`);
3910
+ this.name = "TransactionLogUntilUnreachedError";
3911
+ this.logId = logId;
3912
+ this.position = position;
3913
+ }
3914
+ };
3836
3915
  /**
3837
3916
  * Whether a complete, in-bounds frame begins at `pos`. The only sane bound on an entry's length is
3838
3917
  * the written extent: a single entry can legitimately exceed the rotation threshold (the first
@@ -3895,7 +3974,9 @@ function corruptFrame(message, transactionLog, dataView, logBuffer, position, li
3895
3974
  * exactly matches the start timestamp, and then return all subsequent transactions in the log
3896
3975
  * regardless of whether their timestamp is before or after the start
3897
3976
  */
3898
- Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, exactStart, startFromLastFlushed, readUncommitted, exclusiveStart } = {}) {
3977
+ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, exactStart, startFromLastFlushed, readUncommitted, exclusiveStart, until } = {}) {
3978
+ let untilCursor = LogIterator.cursor(until);
3979
+ if (until !== void 0 && (!untilCursor || !LogIterator.isFrom(untilCursor, this))) throw new TypeError("until must be an iterator from query() on the same transaction log");
3899
3980
  if (!this._lastCommittedPosition) {
3900
3981
  const lastCommittedPosition = this._getLastCommittedPosition();
3901
3982
  this._lastCommittedPosition = new Float64Array(lastCommittedPosition.buffer);
@@ -3942,7 +4023,7 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
3942
4023
  if (position === 0) position = TRANSACTION_LOG_FILE_HEADER_SIZE;
3943
4024
  }
3944
4025
  const positionedAt = position;
3945
- if (stopLogId !== 0 && pastStop(logId, position, stopLogId, stopPosition)) {
4026
+ if (stopLogId !== 0 && pastStop(logId, position, stopLogId, stopPosition) && mayJumpTo(untilCursor, tailLogId, tailPosition)) {
3946
4027
  stopLogId = 0;
3947
4028
  if (skipToTail(this, logId, position, tailLogId, tailPosition, segmentRemovals)) {
3948
4029
  logId = tailLogId;
@@ -3952,95 +4033,144 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
3952
4033
  tailLogBuffer = void 0;
3953
4034
  }
3954
4035
  let gapFloor = logId;
4036
+ let startUnmapped = false;
4037
+ let startMissing = false;
3955
4038
  if (logBuffer === void 0 || logBuffer.logId !== logId) {
3956
4039
  logBuffer = getLogMemoryMap(this, logId);
3957
4040
  if (logBuffer && latestLogId === logId && !readUncommitted && this._currentLogBuffer?.deref() !== logBuffer) this._currentLogBuffer = new WeakRef(logBuffer);
3958
4041
  if (logBuffer === void 0) {
3959
4042
  logBuffer = Buffer.alloc(0);
3960
- logBuffer.logId = 0;
4043
+ logBuffer.logId = logId;
3961
4044
  logBuffer.size = 0;
3962
4045
  logBuffer.dataView = new DataView(logBuffer.buffer);
3963
4046
  size = 0;
4047
+ startUnmapped = logId > 0;
3964
4048
  if (positionedAt <= TRANSACTION_LOG_FILE_HEADER_SIZE || logId > 0 && this.getLogFileSize(logId) > 0) gapFloor = logId - 1;
3965
4049
  }
3966
4050
  }
3967
4051
  dataView = logBuffer.dataView;
3968
4052
  if (latestLogId < logId) size = 0;
3969
- else if (latestLogId > logId) {
4053
+ else if (latestLogId > logId && !startUnmapped) {
3970
4054
  const cachedSize = logBuffer.size;
3971
4055
  if (cachedSize === void 0) size = logBuffer.size = readableExtent(logBuffer);
3972
4056
  else size = cachedSize;
3973
4057
  }
3974
- const iterator = {
3975
- [Symbol.iterator]() {
3976
- return this;
3977
- },
3978
- next() {
3979
- let timestamp;
3980
- if (position >= size) {
3981
- const { logId: latestLogId, size: latestSize } = loadLastPosition(transactionLog, !!readUncommitted);
3982
- size = latestLogId < logBuffer.logId ? 0 : latestSize;
3983
- if (latestLogId > logBuffer.logId) {
3984
- size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
3985
- if (position >= size) {
3986
- const fromLogId = logBuffer.logId;
3987
- const nextLogBuffer = nextReadableLogBuffer(transactionLog, fromLogId, latestLogId, gapFloor);
3988
- if (nextLogBuffer) {
3989
- const unavailable = unavailableLogCount;
3990
- dataView = nextLogBuffer.dataView;
3991
- logBuffer = nextLogBuffer;
3992
- if (latestLogId > logBuffer.logId) size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
3993
- else size = latestSize;
3994
- position = TRANSACTION_LOG_FILE_HEADER_SIZE;
3995
- previousEndedTransaction = true;
3996
- if (unavailable > 0) throw new TransactionLogGapError(Math.max(fromLogId, gapFloor), logBuffer.logId, unavailable);
3997
- }
3998
- }
4058
+ let peeked;
4059
+ let peekedLogId = 0;
4060
+ let peekedPosition = 0;
4061
+ let closed = false;
4062
+ let needsCursorCheck = untilCursor !== void 0 || startUnmapped;
4063
+ const iterator = new LogIterator(ITERATOR_TOKEN, transactionLog, function next() {
4064
+ let scanEnd = size;
4065
+ if (needsCursorCheck) {
4066
+ if (peeked) {
4067
+ const result = peeked;
4068
+ if (!closed) {
4069
+ peeked = void 0;
4070
+ needsCursorCheck = untilCursor !== void 0;
3999
4071
  }
4072
+ return result;
4000
4073
  }
4001
- while (position < size) {
4002
- try {
4003
- timestamp = dataView.getFloat64(position);
4004
- } catch (error) {
4005
- error.message += ` at position ${position.toString(16)} of log ${logBuffer.logId} (size=${size}, log buffer length=${logBuffer.length})`;
4006
- throw error;
4074
+ if (untilCursor) {
4075
+ const limit = LogIterator.scanEnd(untilCursor, logBuffer.logId, size);
4076
+ if (limit === void 0) {
4077
+ untilCursor = void 0;
4078
+ needsCursorCheck = startUnmapped;
4079
+ } else {
4080
+ if (position >= limit && LogIterator.reached(untilCursor, logBuffer.logId, position, position >= size && logBuffer.length > 0 && position >= readableExtent(logBuffer))) return {
4081
+ done: true,
4082
+ value: void 0
4083
+ };
4084
+ scanEnd = limit;
4007
4085
  }
4008
- if (!timestamp) return {
4009
- done: true,
4010
- value: void 0
4011
- };
4012
- const limit = readUncommitted ? logBuffer.length : Math.min(size, logBuffer.length);
4013
- if (position + TRANSACTION_LOG_ENTRY_HEADER_SIZE > limit) {
4014
- 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);
4015
- position = broken.nextPosition;
4016
- previousEndedTransaction = true;
4017
- throw broken.error;
4018
- }
4019
- const length = dataView.getUint32(position + 8);
4020
- if (length === 0 || position + TRANSACTION_LOG_ENTRY_HEADER_SIZE + length > limit) {
4021
- 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);
4022
- position = broken.nextPosition;
4023
- previousEndedTransaction = true;
4024
- throw broken.error;
4086
+ }
4087
+ if (startUnmapped) {
4088
+ const mapped = getLogMemoryMap(transactionLog, logBuffer.logId);
4089
+ if (mapped) {
4090
+ logBuffer = mapped;
4091
+ dataView = mapped.dataView;
4092
+ } else {
4093
+ const latestLogId = loadLastPosition(transactionLog, !!readUncommitted).logId;
4094
+ const fileSize = transactionLog.getLogFileSize(logBuffer.logId);
4095
+ if (logBuffer.logId >= latestLogId || fileSize > position) return endOfReadable(untilCursor, logBuffer, position);
4096
+ if (fileSize === 0) startMissing = true;
4025
4097
  }
4026
- position += TRANSACTION_LOG_ENTRY_HEADER_SIZE;
4027
- const flags = logBuffer[position - 1];
4028
- const aborted = (flags & TRANSACTION_LOG_ENTRY_ABORTED_FLAG) !== 0;
4029
- if (previousEndedTransaction || timestamp !== previousTimestamp || aborted) skippingAborted = aborted;
4030
- previousEndedTransaction = (flags & TRANSACTION_LOG_ENTRY_LAST_FLAG) !== 0;
4031
- previousTimestamp = timestamp;
4032
- let matchesRange;
4033
- if (skippingAborted) matchesRange = false;
4034
- else if (foundExactStart) matchesRange = (!exclusiveStart || timestamp !== start) && timestamp < end;
4035
- else if (exactStart) {
4036
- matchesRange = false;
4037
- if (timestamp === start) {
4038
- matchesRange = !exclusiveStart;
4039
- foundExactStart = true;
4040
- stopLogId = 0;
4098
+ startUnmapped = false;
4099
+ needsCursorCheck = untilCursor !== void 0;
4100
+ size = 0;
4101
+ }
4102
+ }
4103
+ let timestamp;
4104
+ if (position >= size) {
4105
+ const { logId: latestLogId, size: latestSize } = loadLastPosition(transactionLog, !!readUncommitted);
4106
+ size = latestLogId < logBuffer.logId ? 0 : latestSize;
4107
+ if (latestLogId > logBuffer.logId) {
4108
+ size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
4109
+ if (position >= size) {
4110
+ if (untilCursor && LogIterator.reached(untilCursor, logBuffer.logId, position, logBuffer.length > 0 && position >= readableExtent(logBuffer))) return {
4111
+ done: true,
4112
+ value: void 0
4113
+ };
4114
+ const fromLogId = logBuffer.logId - (startMissing ? 1 : 0);
4115
+ const nextLogBuffer = nextReadableLogBuffer(transactionLog, fromLogId, latestLogId, gapFloor);
4116
+ if (nextLogBuffer) {
4117
+ startMissing = false;
4118
+ const unavailable = unavailableLogCount;
4119
+ dataView = nextLogBuffer.dataView;
4120
+ logBuffer = nextLogBuffer;
4121
+ if (latestLogId > logBuffer.logId) size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
4122
+ else size = latestSize;
4123
+ position = TRANSACTION_LOG_FILE_HEADER_SIZE;
4124
+ previousEndedTransaction = true;
4125
+ if (unavailable > 0) throw new TransactionLogGapError(Math.max(fromLogId, gapFloor), logBuffer.logId, unavailable);
4041
4126
  }
4042
- } else matchesRange = (exclusiveStart ? timestamp > start : timestamp >= start) && timestamp < end;
4043
- if (!matchesRange && stopLogId !== 0 && pastStop(logBuffer.logId, position + length, stopLogId, stopPosition)) {
4127
+ }
4128
+ }
4129
+ scanEnd = untilCursor ? LogIterator.scanEnd(untilCursor, logBuffer.logId, size) ?? size : size;
4130
+ }
4131
+ if (position >= scanEnd) return endOfReadable(untilCursor, logBuffer, position);
4132
+ let jumpRefused = false;
4133
+ let limit = Math.min(size, logBuffer.length);
4134
+ do {
4135
+ try {
4136
+ timestamp = dataView.getFloat64(position);
4137
+ } catch (error) {
4138
+ error.message += ` at position ${position.toString(16)} of log ${logBuffer.logId} (size=${size}, log buffer length=${logBuffer.length})`;
4139
+ throw error;
4140
+ }
4141
+ if (!timestamp) return endOfReadable(untilCursor, logBuffer, position);
4142
+ if (position + TRANSACTION_LOG_ENTRY_HEADER_SIZE > limit) {
4143
+ 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);
4144
+ position = broken.nextPosition;
4145
+ previousEndedTransaction = true;
4146
+ throw broken.error;
4147
+ }
4148
+ const length = dataView.getUint32(position + 8);
4149
+ if (length === 0 || position + TRANSACTION_LOG_ENTRY_HEADER_SIZE + length > limit) {
4150
+ 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);
4151
+ position = broken.nextPosition;
4152
+ previousEndedTransaction = true;
4153
+ throw broken.error;
4154
+ }
4155
+ position += TRANSACTION_LOG_ENTRY_HEADER_SIZE;
4156
+ const flags = logBuffer[position - 1];
4157
+ const aborted = (flags & TRANSACTION_LOG_ENTRY_ABORTED_FLAG) !== 0;
4158
+ if (previousEndedTransaction || timestamp !== previousTimestamp || aborted) skippingAborted = aborted;
4159
+ previousEndedTransaction = (flags & TRANSACTION_LOG_ENTRY_LAST_FLAG) !== 0;
4160
+ previousTimestamp = timestamp;
4161
+ let matchesRange;
4162
+ if (skippingAborted) matchesRange = false;
4163
+ else if (foundExactStart) matchesRange = (!exclusiveStart || timestamp !== start) && timestamp < end;
4164
+ else if (exactStart) {
4165
+ matchesRange = false;
4166
+ if (timestamp === start) {
4167
+ matchesRange = !exclusiveStart;
4168
+ foundExactStart = true;
4169
+ stopLogId = 0;
4170
+ }
4171
+ } else matchesRange = (exclusiveStart ? timestamp > start : timestamp >= start) && timestamp < end;
4172
+ if (!matchesRange && stopLogId !== 0 && !jumpRefused && pastStop(logBuffer.logId, position + length, stopLogId, stopPosition)) {
4173
+ if (mayJumpTo(untilCursor, tailLogId, tailPosition)) {
4044
4174
  stopLogId = 0;
4045
4175
  position += length;
4046
4176
  if (skipToTail(transactionLog, logBuffer.logId, position, tailLogId, tailPosition, segmentRemovals)) {
@@ -4055,57 +4185,108 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
4055
4185
  tailLogBuffer = void 0;
4056
4186
  return iterator.next();
4057
4187
  }
4058
- const entryStart = position;
4059
- position += length;
4060
- if (matchesRange) return {
4061
- done: false,
4062
- value: {
4063
- timestamp,
4064
- endTxn: previousEndedTransaction,
4065
- data: logBuffer.subarray(entryStart, position)
4066
- }
4188
+ jumpRefused = true;
4189
+ }
4190
+ const entryStart = position;
4191
+ position += length;
4192
+ if (matchesRange) return {
4193
+ done: false,
4194
+ value: {
4195
+ timestamp,
4196
+ endTxn: previousEndedTransaction,
4197
+ data: logBuffer.subarray(entryStart, position)
4198
+ }
4199
+ };
4200
+ if (position >= scanEnd) {
4201
+ if (untilCursor && LogIterator.reached(untilCursor, logBuffer.logId, position)) return {
4202
+ done: true,
4203
+ value: void 0
4067
4204
  };
4068
- if (position >= size) {
4069
- const { logId: latestLogId, size: latestSize } = loadLastPosition(transactionLog, !!readUncommitted);
4070
- size = latestLogId < logBuffer.logId ? 0 : latestSize;
4071
- if (latestLogId > logBuffer.logId) {
4072
- size = logBuffer.size ?? readableExtent(logBuffer);
4073
- if (!readUncommitted) logBuffer.size = size;
4074
- if (position < size) continue;
4075
- const fromLogId = logBuffer.logId;
4076
- const nextLogBuffer = nextReadableLogBuffer(transactionLog, fromLogId, latestLogId, gapFloor);
4077
- if (!nextLogBuffer) return {
4078
- done: true,
4079
- value: void 0
4080
- };
4081
- const unavailable = unavailableLogCount;
4082
- logBuffer = nextLogBuffer;
4083
- dataView = logBuffer.dataView;
4084
- if (latestLogId === logBuffer.logId) size = latestSize;
4085
- else {
4086
- const cachedSize = logBuffer.size;
4087
- if (cachedSize === void 0) {
4088
- size = readableExtent(logBuffer);
4089
- if (!readUncommitted) logBuffer.size = size;
4090
- } else size = cachedSize;
4091
- }
4092
- position = TRANSACTION_LOG_FILE_HEADER_SIZE;
4093
- previousEndedTransaction = true;
4094
- if (unavailable > 0) throw new TransactionLogGapError(Math.max(fromLogId, gapFloor), logBuffer.logId, unavailable);
4205
+ const { logId: latestLogId, size: latestSize } = loadLastPosition(transactionLog, !!readUncommitted);
4206
+ size = latestLogId < logBuffer.logId ? 0 : latestSize;
4207
+ if (latestLogId > logBuffer.logId) {
4208
+ size = logBuffer.size ?? readableExtent(logBuffer);
4209
+ if (!readUncommitted) logBuffer.size = size;
4210
+ if (position < size) {
4211
+ scanEnd = untilCursor ? LogIterator.scanEnd(untilCursor, logBuffer.logId, size) ?? size : size;
4212
+ limit = Math.min(size, logBuffer.length);
4213
+ continue;
4214
+ }
4215
+ if (untilCursor && LogIterator.reached(untilCursor, logBuffer.logId, position, logBuffer.length > 0 && position >= readableExtent(logBuffer))) return {
4216
+ done: true,
4217
+ value: void 0
4218
+ };
4219
+ const fromLogId = logBuffer.logId;
4220
+ const nextLogBuffer = nextReadableLogBuffer(transactionLog, fromLogId, latestLogId, gapFloor);
4221
+ if (!nextLogBuffer) return endOfReadable(untilCursor, logBuffer, position);
4222
+ const unavailable = unavailableLogCount;
4223
+ logBuffer = nextLogBuffer;
4224
+ dataView = logBuffer.dataView;
4225
+ if (latestLogId === logBuffer.logId) size = latestSize;
4226
+ else {
4227
+ const cachedSize = logBuffer.size;
4228
+ if (cachedSize === void 0) {
4229
+ size = readableExtent(logBuffer);
4230
+ if (!readUncommitted) logBuffer.size = size;
4231
+ } else size = cachedSize;
4095
4232
  }
4233
+ position = TRANSACTION_LOG_FILE_HEADER_SIZE;
4234
+ previousEndedTransaction = true;
4235
+ if (unavailable > 0) throw new TransactionLogGapError(Math.max(fromLogId, gapFloor), logBuffer.logId, unavailable);
4096
4236
  }
4237
+ scanEnd = untilCursor ? LogIterator.scanEnd(untilCursor, logBuffer.logId, size) ?? size : size;
4238
+ limit = Math.min(size, logBuffer.length);
4239
+ }
4240
+ } while (position < scanEnd);
4241
+ return endOfReadable(untilCursor, logBuffer, position);
4242
+ }, function control(operation, fromLogId, fromPosition, atSegmentEnd) {
4243
+ if (operation === "peek") {
4244
+ if (peeked) return peeked;
4245
+ const result = iterator.next();
4246
+ if (!result.done) {
4247
+ peeked = result;
4248
+ needsCursorCheck = true;
4249
+ peekedLogId = logBuffer.logId;
4250
+ peekedPosition = position - result.value.data.length - TRANSACTION_LOG_ENTRY_HEADER_SIZE;
4097
4251
  }
4098
- return {
4252
+ return result;
4253
+ }
4254
+ if (operation === "close") {
4255
+ closed = true;
4256
+ needsCursorCheck = true;
4257
+ peeked = {
4099
4258
  done: true,
4100
4259
  value: void 0
4101
4260
  };
4261
+ logBuffer = void 0;
4262
+ dataView = void 0;
4263
+ untilCursor = void 0;
4264
+ return;
4102
4265
  }
4103
- };
4266
+ if (closed) return;
4267
+ const boundLogId = peeked ? peekedLogId : logBuffer.logId;
4268
+ const boundPosition = peeked ? peekedPosition : position;
4269
+ if (operation === "scanEnd") {
4270
+ if (boundLogId !== fromLogId) return boundLogId > fromLogId ? fromPosition : 0;
4271
+ return boundPosition < fromPosition ? boundPosition : fromPosition;
4272
+ }
4273
+ return pastStop(fromLogId, fromPosition, boundLogId, boundPosition) || !!atSegmentEnd && boundLogId === fromLogId + 1 && boundPosition === TRANSACTION_LOG_FILE_HEADER_SIZE;
4274
+ });
4104
4275
  return iterator;
4105
4276
  } });
4277
+ function endOfReadable(untilCursor, logBuffer, position) {
4278
+ if (untilCursor && LogIterator.reached(untilCursor, logBuffer.logId, position, logBuffer.length > 0 && position >= readableExtent(logBuffer)) === false) throw new TransactionLogUntilUnreachedError(logBuffer.logId, position);
4279
+ return {
4280
+ done: true,
4281
+ value: void 0
4282
+ };
4283
+ }
4106
4284
  function pastStop(logId, position, stopLogId, stopPosition) {
4107
4285
  return logId > stopLogId || logId === stopLogId && position >= stopPosition;
4108
4286
  }
4287
+ function mayJumpTo(untilCursor, logId, position) {
4288
+ return !untilCursor || LogIterator.reached(untilCursor, logId, position - 1) !== true;
4289
+ }
4109
4290
  let tailLogBuffer;
4110
4291
  /**
4111
4292
  * Whether an exact-start lookup at `position` of `fromLogId` may take its one-shot jump to the tail.
@@ -4204,8 +4385,8 @@ function loadLastPosition(transactionLog, readUncommitted) {
4204
4385
  // src/index.ts
4205
4386
  const versions = {
4206
4387
  rocksdb: version,
4207
- "rocksdb-js": "2.13.0"
4388
+ "rocksdb-js": "2.14.0"
4208
4389
  };
4209
4390
 
4210
- export { BackgroundError, ConflictKind, CorruptFrameError, CountEstimator, DBI, DBIterator, RocksDatabase, Store, Transaction, TransactionAbortUnresolvedError, TransactionLog, TransactionLogGapError, backups, constants, coolTransactionLogs, currentThreadId, fileLockRelease, getRegistryStatus, getWriteBufferManagerStats, parseTransactionLog, registryStatus, shutdown, stats, steadyClockNow, supportedCompression, tryFileLock, validateTransactionLogStore, versions };
4391
+ export { BackgroundError, ConflictKind, CorruptFrameError, CountEstimator, DBI, DBIterator, RocksDatabase, Store, Transaction, TransactionAbortUnresolvedError, TransactionLog, TransactionLogGapError, TransactionLogUntilUnreachedError, backups, constants, coolTransactionLogs, currentThreadId, fileLockRelease, getRegistryStatus, getWriteBufferManagerStats, parseTransactionLog, raiseTimestampFloor, registryStatus, shutdown, stats, steadyClockNow, supportedCompression, tryFileLock, validateTransactionLogStore, versions };
4211
4392
  //# sourceMappingURL=index.mjs.map