@harperfast/rocksdb-js 2.11.0 → 2.13.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/README.md +158 -27
- package/dist/index.cjs +322 -66
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +178 -39
- package/dist/index.d.mts +178 -39
- package/dist/index.mjs +320 -67
- package/dist/index.mjs.map +1 -1
- package/docs/backups.md +27 -0
- package/docs/performance/mutex-contention.md +4 -3
- package/docs/stats.md +11 -4
- package/docs/steady-clock-design.md +1 -1
- package/docs/transaction-log.md +27 -7
- package/package.json +19 -18
package/dist/index.mjs
CHANGED
|
@@ -8,11 +8,10 @@ import * as orderedBinary from "ordered-binary";
|
|
|
8
8
|
import { ExtendedIterable } from "@harperfast/extended-iterable";
|
|
9
9
|
import { Encoder } from "msgpackr";
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
// \0rolldown/runtime.js
|
|
12
12
|
var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
//#region src/load-binding.ts
|
|
14
|
+
// src/load-binding.ts
|
|
16
15
|
const nativeExtRE = /\.node$/;
|
|
17
16
|
const req = createRequire(import.meta.url);
|
|
18
17
|
/**
|
|
@@ -147,7 +146,7 @@ const currentThreadId = binding.currentThreadId;
|
|
|
147
146
|
* Reads the process-wide steady clock: `std::chrono::steady_clock` as
|
|
148
147
|
* fractional milliseconds from an unspecified origin that is fixed for the life
|
|
149
148
|
* of the process. Every sample in the process — main thread and every
|
|
150
|
-
* `worker_threads` worker, whenever started or restarted, on Node
|
|
149
|
+
* `worker_threads` worker, whenever started or restarted, on Node and Bun
|
|
151
150
|
* — is in one domain, and the difference between two samples is real elapsed
|
|
152
151
|
* time unaffected by wall-clock steps. Non-decreasing but not unique (two
|
|
153
152
|
* samples may be equal; treat `==` as "not later"). Windows QPC samples from
|
|
@@ -185,12 +184,30 @@ const transactionLogMapCount = binding.transactionLogMapCount;
|
|
|
185
184
|
*/
|
|
186
185
|
const lockWakeCallbackCount = binding.lockWakeCallbackCount;
|
|
187
186
|
/**
|
|
187
|
+
* The calling thread's counters for the per-transaction iterator pool: pooled
|
|
188
|
+
* iterators created, and idle ones reused by a later range. Internal — used by
|
|
189
|
+
* tests to prove a range was served by a reused iterator.
|
|
190
|
+
*/
|
|
191
|
+
const iteratorPoolStats = binding.iteratorPoolStats;
|
|
192
|
+
/**
|
|
193
|
+
* Whether transaction `getRange()` reuses pooled native iterators (off with
|
|
194
|
+
* `ROCKSDB_JS_ITERATOR_POOL=0`). Internal.
|
|
195
|
+
*/
|
|
196
|
+
const iteratorPoolEnabled = binding.iteratorPoolEnabled;
|
|
197
|
+
/**
|
|
188
198
|
* Test-only: force the next `count` transaction commits to fail with TryAgain (rolled back, so
|
|
189
199
|
* no data is committed), reproducing a stranded-snapshot conflict deterministically. Pass 0 to
|
|
190
200
|
* disarm. Used by the ERR_TRY_AGAIN retry regression test.
|
|
191
201
|
*/
|
|
192
202
|
const forceTryAgainForTesting = binding.forceTryAgainForTesting;
|
|
193
203
|
/**
|
|
204
|
+
* Test-only: turn the RocksDB conflict check before the transaction-log write off (`true`) or back
|
|
205
|
+
* on, so a test can make a logged batch conflict at commit from a stale snapshot.
|
|
206
|
+
*/
|
|
207
|
+
const disablePrelogConflictCheckForTesting = binding.disablePrelogConflictCheckForTesting;
|
|
208
|
+
/** Test-only: run the pre-log conflict check on retries alone (`true`, the default) or on every attempt. */
|
|
209
|
+
const setPrelogConflictCheckRetriesOnlyForTesting = binding.setPrelogConflictCheckRetriesOnlyForTesting;
|
|
210
|
+
/**
|
|
194
211
|
* Test-only seam: makes the physical column-family drop report failure.
|
|
195
212
|
* `0` = inert, `1` = fail without dropping (the retry performs the drop),
|
|
196
213
|
* `2` = drop for real and then report failure (the retry resolves as
|
|
@@ -233,8 +250,7 @@ const nativeValidateTransactionLog = binding.validateTransactionLog;
|
|
|
233
250
|
const stats = binding.stats;
|
|
234
251
|
const version = binding.version;
|
|
235
252
|
|
|
236
|
-
|
|
237
|
-
//#region src/validate-transaction-log.ts
|
|
253
|
+
// src/validate-transaction-log.ts
|
|
238
254
|
/**
|
|
239
255
|
* Validates a transaction log store directory: every `<sequence>.txnlog` file
|
|
240
256
|
* is checked for a valid header (token, version) and intact v1 entry framing
|
|
@@ -274,8 +290,7 @@ function validateTransactionLogStore(path, options) {
|
|
|
274
290
|
return new Promise((resolve, reject) => nativeValidateTransactionLog(resolve, reject, path, options?.strict ?? false));
|
|
275
291
|
}
|
|
276
292
|
|
|
277
|
-
|
|
278
|
-
//#region src/backup.ts
|
|
293
|
+
// src/backup.ts
|
|
279
294
|
/** Subdirectory (under a backup directory) holding per-backup transaction log snapshots. */
|
|
280
295
|
const TRANSACTION_LOGS_DIRNAME = "transaction_logs";
|
|
281
296
|
/** Non-blocking existence check (`fs.existsSync` would block the event loop). */
|
|
@@ -525,6 +540,14 @@ const backups = {
|
|
|
525
540
|
* Verifies a backup's file sizes, and optionally their checksums (which
|
|
526
541
|
* requires reading all backed-up data).
|
|
527
542
|
*
|
|
543
|
+
* Set `verifyTables: true` to also prove this build can read the backup
|
|
544
|
+
* before restoring it: every table file is opened, one at a time, and its
|
|
545
|
+
* footer (including `format_version`) and properties must be readable, and
|
|
546
|
+
* every compression algorithm it records must be supported by this build.
|
|
547
|
+
* A checksum-valid table written by a newer or differently built RocksDB
|
|
548
|
+
* otherwise passes and fails on the first read after a restore. Blob files
|
|
549
|
+
* are not checked.
|
|
550
|
+
*
|
|
528
551
|
* When the backup was created with `transactionLogs: true`, the backup's
|
|
529
552
|
* transaction log snapshot (`<backupDir>/transaction_logs/<backupId>/`) is
|
|
530
553
|
* also validated — every log file's header and entry framing must be intact
|
|
@@ -532,7 +555,9 @@ const backups = {
|
|
|
532
555
|
* is a verification failure). Set `verifyTransactionLogs: false` to skip.
|
|
533
556
|
*/
|
|
534
557
|
async verify(backupDir, backupId, options) {
|
|
535
|
-
|
|
558
|
+
const verifyTables = options?.verifyTables;
|
|
559
|
+
if (verifyTables !== void 0 && typeof verifyTables !== "boolean") throw new TypeError(`verifyTables must be a boolean, received ${verifyTables === null ? "null" : typeof verifyTables}`);
|
|
560
|
+
await new Promise((resolve, reject) => nativeBackupVerify(resolve, reject, backupDir, backupId, options?.verifyWithChecksum ?? false, verifyTables === true));
|
|
536
561
|
if (options?.verifyTransactionLogs === false) return;
|
|
537
562
|
const logsDir = join(backupDir, TRANSACTION_LOGS_DIRNAME, String(backupId));
|
|
538
563
|
if (!await exists(logsDir)) return;
|
|
@@ -549,8 +574,7 @@ const backups = {
|
|
|
549
574
|
}
|
|
550
575
|
};
|
|
551
576
|
|
|
552
|
-
|
|
553
|
-
//#region src/count-estimator.ts
|
|
577
|
+
// src/count-estimator.ts
|
|
554
578
|
/**
|
|
555
579
|
* Below this many traversed entries the observed density is too noisy to
|
|
556
580
|
* calibrate with, so `estimate()` returns the raw statistical estimate.
|
|
@@ -693,8 +717,7 @@ var CountEstimator = class {
|
|
|
693
717
|
}
|
|
694
718
|
};
|
|
695
719
|
|
|
696
|
-
|
|
697
|
-
//#region src/util.ts
|
|
720
|
+
// src/util.ts
|
|
698
721
|
/**
|
|
699
722
|
* Parses a duration string into milliseconds.
|
|
700
723
|
*
|
|
@@ -765,8 +788,7 @@ function when(subject, callback, errback) {
|
|
|
765
788
|
}
|
|
766
789
|
}
|
|
767
790
|
|
|
768
|
-
|
|
769
|
-
//#region src/dbi.ts
|
|
791
|
+
// src/dbi.ts
|
|
770
792
|
/**
|
|
771
793
|
* The base class for all database operations. This base class is shared by
|
|
772
794
|
* `RocksDatabase` and `Transaction`.
|
|
@@ -1051,8 +1073,7 @@ var DBI = class DBI {
|
|
|
1051
1073
|
}
|
|
1052
1074
|
};
|
|
1053
1075
|
|
|
1054
|
-
|
|
1055
|
-
//#region src/tar.ts
|
|
1076
|
+
// src/tar.ts
|
|
1056
1077
|
/**
|
|
1057
1078
|
* A minimal, dependency-free streaming USTAR (POSIX tar) encoder.
|
|
1058
1079
|
*
|
|
@@ -1258,8 +1279,7 @@ var TarEncoder = class {
|
|
|
1258
1279
|
}
|
|
1259
1280
|
};
|
|
1260
1281
|
|
|
1261
|
-
|
|
1262
|
-
//#region src/backup-stream.ts
|
|
1282
|
+
// src/backup-stream.ts
|
|
1263
1283
|
/** Native event discriminator: a new file header (vs. a payload chunk). */
|
|
1264
1284
|
const EVENT_FILE = 0;
|
|
1265
1285
|
/**
|
|
@@ -1303,9 +1323,22 @@ async function backupToStream(db, stream, options) {
|
|
|
1303
1323
|
}
|
|
1304
1324
|
}
|
|
1305
1325
|
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
const
|
|
1326
|
+
// src/dbi-iterator.ts
|
|
1327
|
+
const { ITERATOR_RESULT_DONE, ITERATOR_RESULT_FAST, ITERATOR_RESULT_RELEASED } = constants;
|
|
1328
|
+
const transactionsToTrim = /* @__PURE__ */ new Set();
|
|
1329
|
+
function trimIdleIterators() {
|
|
1330
|
+
try {
|
|
1331
|
+
for (const transaction of transactionsToTrim) transaction.trimIterators();
|
|
1332
|
+
} finally {
|
|
1333
|
+
transactionsToTrim.clear();
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
function scheduleTrim(transaction) {
|
|
1337
|
+
if (transaction !== void 0) {
|
|
1338
|
+
if (transactionsToTrim.size === 0) setImmediate(trimIdleIterators);
|
|
1339
|
+
transactionsToTrim.add(transaction);
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1309
1342
|
const DONE_RESULT = Object.freeze({
|
|
1310
1343
|
done: true,
|
|
1311
1344
|
value: void 0
|
|
@@ -1325,10 +1358,12 @@ var DBIterator = class {
|
|
|
1325
1358
|
#includeValues;
|
|
1326
1359
|
#limit;
|
|
1327
1360
|
#yielded = 0;
|
|
1328
|
-
|
|
1361
|
+
#transaction;
|
|
1362
|
+
constructor(iterator, store, includeValues, limit, transaction) {
|
|
1329
1363
|
this.iterator = iterator;
|
|
1330
1364
|
this.store = store;
|
|
1331
1365
|
this.#includeValues = includeValues;
|
|
1366
|
+
this.#transaction = transaction;
|
|
1332
1367
|
this.#limit = limit ?? void 0;
|
|
1333
1368
|
}
|
|
1334
1369
|
[Symbol.iterator]() {
|
|
@@ -1336,7 +1371,7 @@ var DBIterator = class {
|
|
|
1336
1371
|
}
|
|
1337
1372
|
next() {
|
|
1338
1373
|
if (this.#limit !== void 0 && this.#yielded >= this.#limit) {
|
|
1339
|
-
this.iterator.return
|
|
1374
|
+
this.#release(this.iterator.return());
|
|
1340
1375
|
return DONE_RESULT;
|
|
1341
1376
|
}
|
|
1342
1377
|
let result;
|
|
@@ -1344,11 +1379,15 @@ var DBIterator = class {
|
|
|
1344
1379
|
result = this.iterator.next();
|
|
1345
1380
|
} catch (err) {
|
|
1346
1381
|
try {
|
|
1347
|
-
this.iterator.return
|
|
1382
|
+
this.#release(this.iterator.return());
|
|
1348
1383
|
} catch {}
|
|
1349
1384
|
throw err;
|
|
1350
1385
|
}
|
|
1351
1386
|
if (result === ITERATOR_RESULT_DONE) return DONE_RESULT;
|
|
1387
|
+
if (result === ITERATOR_RESULT_RELEASED) {
|
|
1388
|
+
scheduleTrim(this.#transaction);
|
|
1389
|
+
return DONE_RESULT;
|
|
1390
|
+
}
|
|
1352
1391
|
this.#yielded++;
|
|
1353
1392
|
const includeValues = this.#includeValues;
|
|
1354
1393
|
const value = {};
|
|
@@ -1369,20 +1408,23 @@ var DBIterator = class {
|
|
|
1369
1408
|
};
|
|
1370
1409
|
}
|
|
1371
1410
|
return(value) {
|
|
1372
|
-
this.iterator.return
|
|
1411
|
+
this.#release(this.iterator.return());
|
|
1373
1412
|
return {
|
|
1374
1413
|
done: true,
|
|
1375
1414
|
value
|
|
1376
1415
|
};
|
|
1377
1416
|
}
|
|
1378
1417
|
throw(err) {
|
|
1379
|
-
this.iterator.throw
|
|
1418
|
+
this.#release(this.iterator.throw(err));
|
|
1380
1419
|
throw err;
|
|
1381
1420
|
}
|
|
1421
|
+
#release(pooled) {
|
|
1422
|
+
if (pooled) scheduleTrim(this.#transaction);
|
|
1423
|
+
this.#transaction = void 0;
|
|
1424
|
+
}
|
|
1382
1425
|
};
|
|
1383
1426
|
|
|
1384
|
-
|
|
1385
|
-
//#region src/encoding.ts
|
|
1427
|
+
// src/encoding.ts
|
|
1386
1428
|
/**
|
|
1387
1429
|
* Initializes the key encoder functions.
|
|
1388
1430
|
*
|
|
@@ -1449,8 +1491,7 @@ function createFixedBuffer(size) {
|
|
|
1449
1491
|
return buffer;
|
|
1450
1492
|
}
|
|
1451
1493
|
|
|
1452
|
-
|
|
1453
|
-
//#region src/store.ts
|
|
1494
|
+
// src/store.ts
|
|
1454
1495
|
const { ONLY_IF_IN_MEMORY_CACHE_FLAG, NOT_IN_MEMORY_CACHE_FLAG, ALWAYS_CREATE_NEW_BUFFER_FLAG, FRESH_VERSION_FLAG, POPULATE_VERSION_FLAG, ITERATOR_REVERSE_FLAG, ITERATOR_INCLUSIVE_END_FLAG, ITERATOR_EXCLUSIVE_START_FLAG, ITERATOR_INCLUDE_VALUES_FLAG, ITERATOR_NEEDS_STABLE_VALUE_BUFFER_FLAG, ITERATOR_CONTEXT_IS_TRANSACTION_FLAG, ITERATOR_HAS_TRANSACTION_ID_FLAG } = constants;
|
|
1455
1496
|
const KEY_BUFFER_SIZE = 4096;
|
|
1456
1497
|
const KEY_BUFFER = createFixedBuffer(KEY_BUFFER_SIZE);
|
|
@@ -1751,6 +1792,10 @@ var Store = class {
|
|
|
1751
1792
|
*/
|
|
1752
1793
|
writeBufferSize;
|
|
1753
1794
|
/**
|
|
1795
|
+
* The memtable arena block size in bytes; `0` or unset is automatic.
|
|
1796
|
+
*/
|
|
1797
|
+
arenaBlockSize;
|
|
1798
|
+
/**
|
|
1754
1799
|
* The function used to encode keys using the shared `keyBuffer`.
|
|
1755
1800
|
*/
|
|
1756
1801
|
writeKey;
|
|
@@ -1803,6 +1848,7 @@ var Store = class {
|
|
|
1803
1848
|
this.timestampFloorLog = options?.timestampFloorLog;
|
|
1804
1849
|
this.verificationTable = options?.verificationTable;
|
|
1805
1850
|
this.writeBufferSize = options?.writeBufferSize;
|
|
1851
|
+
this.arenaBlockSize = options?.arenaBlockSize;
|
|
1806
1852
|
this.writeKey = writeKey;
|
|
1807
1853
|
this.#columnFamilyOptions = options ? { ...options } : void 0;
|
|
1808
1854
|
}
|
|
@@ -2053,7 +2099,9 @@ var Store = class {
|
|
|
2053
2099
|
if (context !== this.db) flags |= ITERATOR_CONTEXT_IS_TRANSACTION_FLAG;
|
|
2054
2100
|
if (txnId !== void 0) flags |= ITERATOR_HAS_TRANSACTION_ID_FLAG;
|
|
2055
2101
|
const advancedOptions = options !== void 0 && (options.adaptiveReadahead !== void 0 || options.asyncIO !== void 0 || options.autoReadaheadSize !== void 0 || options.backgroundPurgeOnIteratorCleanup !== void 0 || options.fillCache !== void 0 || options.readaheadSize !== void 0 || options.tailing !== void 0) ? options : void 0;
|
|
2056
|
-
|
|
2102
|
+
const nativeIterator = new NativeIterator(context, flags, startKeyEnd, endKeyStart, endKeyEnd, advancedOptions, txnId);
|
|
2103
|
+
const transaction = context !== this.db ? context : options?.transaction?._context;
|
|
2104
|
+
return new ExtendedIterable(new DBIterator(nativeIterator, this, includeValues, options?.limit, transaction));
|
|
2057
2105
|
}
|
|
2058
2106
|
getSync(context, key, alwaysCreateNewBuffer = false, options) {
|
|
2059
2107
|
const keyParam = getKeyParam(this.encodeKey(key));
|
|
@@ -2167,7 +2215,8 @@ var Store = class {
|
|
|
2167
2215
|
transactionLogsPath: this.transactionLogsPath,
|
|
2168
2216
|
timestampFloorLog: this.timestampFloorLog,
|
|
2169
2217
|
verificationTable: this.verificationTable,
|
|
2170
|
-
writeBufferSize: this.writeBufferSize
|
|
2218
|
+
writeBufferSize: this.writeBufferSize,
|
|
2219
|
+
arenaBlockSize: this.arenaBlockSize
|
|
2171
2220
|
});
|
|
2172
2221
|
this.identityPath = this.db.identityPath;
|
|
2173
2222
|
return false;
|
|
@@ -2273,8 +2322,7 @@ function getKeyParam(keyBuffer) {
|
|
|
2273
2322
|
return keyBuffer.length;
|
|
2274
2323
|
}
|
|
2275
2324
|
|
|
2276
|
-
|
|
2277
|
-
//#region src/transaction.ts
|
|
2325
|
+
// src/transaction.ts
|
|
2278
2326
|
/**
|
|
2279
2327
|
* Sentinel value returned by `commit()` when `coordinatedRetry: true` and the
|
|
2280
2328
|
* transaction encountered an IsBusy conflict. The native layer parks on VT
|
|
@@ -2298,10 +2346,18 @@ var TransactionAlreadyAbortedError = class extends Error {
|
|
|
2298
2346
|
*/
|
|
2299
2347
|
var TransactionRetryableError = class extends Error {
|
|
2300
2348
|
hasLog;
|
|
2349
|
+
/**
|
|
2350
|
+
* The attempt was refused before writing its transaction-log batch (`transactionLogAbortMarkers`):
|
|
2351
|
+
* an older transaction held a write intent on one of its keys (`transactionLogWriteIntents`), or, on a retry, RocksDB's conflict
|
|
2352
|
+
* check found a write that already committed (see `lastConflictKind()`). Nothing of it is in the
|
|
2353
|
+
* log, so a retry may stage its batch afresh.
|
|
2354
|
+
*/
|
|
2355
|
+
refusedBeforeLog;
|
|
2301
2356
|
txn;
|
|
2302
2357
|
constructor(error, txn) {
|
|
2303
2358
|
super(error.message);
|
|
2304
2359
|
this.hasLog = error.hasLog ?? false;
|
|
2360
|
+
this.refusedBeforeLog = error.refusedBeforeLog ?? false;
|
|
2305
2361
|
this.txn = txn;
|
|
2306
2362
|
}
|
|
2307
2363
|
};
|
|
@@ -2331,6 +2387,38 @@ var TransactionAbandonedError = class extends Error {
|
|
|
2331
2387
|
}
|
|
2332
2388
|
};
|
|
2333
2389
|
/**
|
|
2390
|
+
* `abort()` of a transaction whose transaction-log batch could not be marked aborted
|
|
2391
|
+
* (`transactionLogAbortMarkers`). The batch stays unresolved, so committed readers do not pass it and
|
|
2392
|
+
* the log store refuses appends, until a retry on the next append, database flush or store close
|
|
2393
|
+
* marks it.
|
|
2394
|
+
*/
|
|
2395
|
+
var TransactionAbortUnresolvedError = class extends Error {
|
|
2396
|
+
code = "ERR_TRANSACTION_ABORT_UNRESOLVED";
|
|
2397
|
+
txn;
|
|
2398
|
+
constructor(error, txn) {
|
|
2399
|
+
super(error.message);
|
|
2400
|
+
this.txn = txn;
|
|
2401
|
+
}
|
|
2402
|
+
};
|
|
2403
|
+
/**
|
|
2404
|
+
* Why a transaction's most recent attempt was refused or retried, from {@link Transaction.lastConflictKind}.
|
|
2405
|
+
*/
|
|
2406
|
+
const ConflictKind = {
|
|
2407
|
+
none: 0,
|
|
2408
|
+
/** Refused on an older transaction's write intent before writing its log batch. */
|
|
2409
|
+
intentBeforeLog: 1,
|
|
2410
|
+
/** Refused on an older transaction's write intent after its log batch was written. */
|
|
2411
|
+
intentAfterLog: 2,
|
|
2412
|
+
/** RocksDB found a conflict at commit. */
|
|
2413
|
+
atCommit: 3,
|
|
2414
|
+
/**
|
|
2415
|
+
* RocksDB's conflict check, run before the log write, found a write that already committed, or,
|
|
2416
|
+
* as at commit, could not validate because the memtable history it needs was flushed (needs a
|
|
2417
|
+
* RocksDB build that exports the check).
|
|
2418
|
+
*/
|
|
2419
|
+
checkBeforeLog: 4
|
|
2420
|
+
};
|
|
2421
|
+
/**
|
|
2334
2422
|
* Provides transaction level operations to a transaction callback.
|
|
2335
2423
|
*/
|
|
2336
2424
|
var Transaction = class extends DBI {
|
|
@@ -2345,9 +2433,10 @@ var Transaction = class extends DBI {
|
|
|
2345
2433
|
if (store.readOnly) {
|
|
2346
2434
|
super(store);
|
|
2347
2435
|
this.#txn = { id: 0 };
|
|
2348
|
-
this.abandonWrites = this.abort = this.commitSync = this.setTimestamp = () => {};
|
|
2436
|
+
this.abandonWrites = this.abort = this.commitSync = this.setTimestamp = this.setParkDeadline = () => {};
|
|
2349
2437
|
this.commit = async () => {};
|
|
2350
|
-
this.getTimestamp = () => 0;
|
|
2438
|
+
this.getTimestamp = this.lastConflictKind = () => 0;
|
|
2439
|
+
this.logWritten = () => false;
|
|
2351
2440
|
} else {
|
|
2352
2441
|
const txn = new NativeTransaction(store.db, options);
|
|
2353
2442
|
super(store, txn);
|
|
@@ -2373,6 +2462,7 @@ var Transaction = class extends DBI {
|
|
|
2373
2462
|
this.#txn.abort();
|
|
2374
2463
|
} catch (err) {
|
|
2375
2464
|
if (err instanceof Error && "code" in err && err.code === "ERR_TRANSACTION_ABANDONED") throw new TransactionAbandonedError(err, this);
|
|
2465
|
+
if (err instanceof Error && "code" in err && err.code === "ERR_TRANSACTION_ABORT_UNRESOLVED") throw new TransactionAbortUnresolvedError(err, this);
|
|
2376
2466
|
throw err;
|
|
2377
2467
|
}
|
|
2378
2468
|
}
|
|
@@ -2441,6 +2531,28 @@ var Transaction = class extends DBI {
|
|
|
2441
2531
|
return this.#txn.getTimestamp();
|
|
2442
2532
|
}
|
|
2443
2533
|
/**
|
|
2534
|
+
* Whether this transaction's batch is in the transaction log. A commit conflict after the batch
|
|
2535
|
+
* was written keeps it for the retry, which must not stage it again; an attempt refused before
|
|
2536
|
+
* writing it (`refusedBeforeLog`) leaves nothing.
|
|
2537
|
+
*/
|
|
2538
|
+
logWritten() {
|
|
2539
|
+
return this.#txn.logWritten();
|
|
2540
|
+
}
|
|
2541
|
+
/**
|
|
2542
|
+
* Why the most recent attempt was refused or retried; one of {@link ConflictKind}.
|
|
2543
|
+
*/
|
|
2544
|
+
lastConflictKind() {
|
|
2545
|
+
return this.#txn.lastConflictKind();
|
|
2546
|
+
}
|
|
2547
|
+
/**
|
|
2548
|
+
* Bounds coordinated-retry parks: past `deadline` (epoch milliseconds) a parked commit resolves
|
|
2549
|
+
* `RETRY_NOW` at once, so a caller enforcing its own deadline can abort instead of waiting out
|
|
2550
|
+
* the default park bound. 0 clears it.
|
|
2551
|
+
*/
|
|
2552
|
+
setParkDeadline(deadline) {
|
|
2553
|
+
this.#txn.setParkDeadline(deadline);
|
|
2554
|
+
}
|
|
2555
|
+
/**
|
|
2444
2556
|
* Get the transaction id.
|
|
2445
2557
|
*/
|
|
2446
2558
|
get id() {
|
|
@@ -2461,8 +2573,8 @@ var Transaction = class extends DBI {
|
|
|
2461
2573
|
}
|
|
2462
2574
|
};
|
|
2463
2575
|
|
|
2464
|
-
|
|
2465
|
-
|
|
2576
|
+
// src/database.ts
|
|
2577
|
+
const backoffCell = new Int32Array(new SharedArrayBuffer(4));
|
|
2466
2578
|
const BACKGROUND_ERROR_SEVERITY_NAMES = [
|
|
2467
2579
|
"none",
|
|
2468
2580
|
"soft",
|
|
@@ -2872,6 +2984,19 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
2872
2984
|
return this.store.db.flushSync(options);
|
|
2873
2985
|
}
|
|
2874
2986
|
/**
|
|
2987
|
+
* Syncs the write-ahead log to stable storage (`DB::SyncWAL`), so every WAL-backed write
|
|
2988
|
+
* committed before the call survives a power loss afterwards. Unlike {@link flush} it writes
|
|
2989
|
+
* no SST file and never stalls writers. Not covered: writes that skipped the WAL
|
|
2990
|
+
* (`disableWAL`), and transaction-log files, which keep their own durability. Runs on the
|
|
2991
|
+
* libuv threadpool; RocksDB serializes concurrent syncs of one WAL, so keep one in flight
|
|
2992
|
+
* per database rather than parking pool workers on a queue of them.
|
|
2993
|
+
*
|
|
2994
|
+
* On a read-only database the returned promise resolves without syncing.
|
|
2995
|
+
*/
|
|
2996
|
+
syncWal() {
|
|
2997
|
+
return new Promise((resolve, reject) => this.store.db.syncWal(resolve, reject));
|
|
2998
|
+
}
|
|
2999
|
+
/**
|
|
2875
3000
|
* Returns the most recent {@link BackgroundError} observed on this database, or
|
|
2876
3001
|
* `null` when none has occurred. The `'error'` event is the push counterpart;
|
|
2877
3002
|
* this is the pull equivalent for on-demand checks (e.g. a health probe) and
|
|
@@ -3340,7 +3465,10 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
3340
3465
|
async transaction(callback, options) {
|
|
3341
3466
|
if (typeof callback !== "function") throw new TypeError("Callback must be a function");
|
|
3342
3467
|
const maxRetries = options?.maxRetries ?? 3;
|
|
3343
|
-
const txn = new Transaction(this.store, options
|
|
3468
|
+
const txn = new Transaction(this.store, options?.retryOnBusy === false && options.coordinatedRetry ? {
|
|
3469
|
+
...options,
|
|
3470
|
+
coordinatedRetry: false
|
|
3471
|
+
} : options);
|
|
3344
3472
|
let result;
|
|
3345
3473
|
this.notify("begin-transaction");
|
|
3346
3474
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
@@ -3354,7 +3482,7 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
3354
3482
|
commitResult = await txn.commit();
|
|
3355
3483
|
} catch (commitErr) {
|
|
3356
3484
|
if (commitErr instanceof TransactionAlreadyAbortedError) return;
|
|
3357
|
-
if (commitErr instanceof TransactionRetryableError && (options?.retryOnBusy ?? commitErr.hasLog) && attempt < maxRetries) continue;
|
|
3485
|
+
if (commitErr instanceof TransactionRetryableError && (options?.retryOnBusy ?? (commitErr.hasLog || commitErr.refusedBeforeLog)) && attempt < maxRetries) continue;
|
|
3358
3486
|
this.#abandonTransaction(txn, commitErr);
|
|
3359
3487
|
return;
|
|
3360
3488
|
}
|
|
@@ -3386,10 +3514,13 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
3386
3514
|
if (typeof callback !== "function") throw new TypeError("Callback must be a function");
|
|
3387
3515
|
const maxRetries = options?.maxRetries ?? 3;
|
|
3388
3516
|
const isRetryable = (err, attempt) => {
|
|
3389
|
-
|
|
3517
|
+
if (!(err instanceof TransactionRetryableError && (options?.retryOnBusy ?? (err.hasLog || err.refusedBeforeLog)) && attempt <= maxRetries)) return false;
|
|
3518
|
+
const kind = txn.lastConflictKind();
|
|
3519
|
+
if (kind === ConflictKind.intentBeforeLog || kind === ConflictKind.intentAfterLog) Atomics.wait(backoffCell, 0, 0, Math.min(2 ** attempt, 50));
|
|
3520
|
+
return true;
|
|
3390
3521
|
};
|
|
3522
|
+
const txn = new Transaction(this.store, options);
|
|
3391
3523
|
const runAttempt = (attempt) => {
|
|
3392
|
-
const txn = new Transaction(this.store, options);
|
|
3393
3524
|
let result;
|
|
3394
3525
|
try {
|
|
3395
3526
|
result = callback(txn, attempt);
|
|
@@ -3405,7 +3536,7 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
3405
3536
|
if (isRetryable(commitErr, attempt)) return runAttempt(attempt + 1);
|
|
3406
3537
|
this.#abandonTransaction(txn, commitErr);
|
|
3407
3538
|
}
|
|
3408
|
-
});
|
|
3539
|
+
}, (callbackErr) => this.#abortTransaction(txn, callbackErr));
|
|
3409
3540
|
try {
|
|
3410
3541
|
txn.commitSync();
|
|
3411
3542
|
return result;
|
|
@@ -3430,7 +3561,7 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
3430
3561
|
try {
|
|
3431
3562
|
txn.abort();
|
|
3432
3563
|
} catch (abortErr) {
|
|
3433
|
-
if (abortErr instanceof TransactionAbandonedError) throw abortErr;
|
|
3564
|
+
if (abortErr instanceof TransactionAbandonedError || abortErr instanceof TransactionAbortUnresolvedError) throw abortErr;
|
|
3434
3565
|
}
|
|
3435
3566
|
throw commitErr;
|
|
3436
3567
|
}
|
|
@@ -3545,10 +3676,10 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
3545
3676
|
}
|
|
3546
3677
|
};
|
|
3547
3678
|
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
const { TRANSACTION_LOG_TOKEN, TRANSACTION_LOG_ENTRY_HEADER_SIZE: TRANSACTION_LOG_ENTRY_HEADER_SIZE$1 } = constants;
|
|
3679
|
+
// src/parse-transaction-log.ts
|
|
3680
|
+
const { TRANSACTION_LOG_TOKEN, TRANSACTION_LOG_ENTRY_HEADER_SIZE: TRANSACTION_LOG_ENTRY_HEADER_SIZE$1, TRANSACTION_LOG_ENTRY_LAST_FLAG: TRANSACTION_LOG_ENTRY_LAST_FLAG$1, TRANSACTION_LOG_ENTRY_ABORTED_FLAG: TRANSACTION_LOG_ENTRY_ABORTED_FLAG$1 } = constants;
|
|
3551
3681
|
const MIN_VALID_TIMESTAMP = Date.UTC(2026, 0, 27);
|
|
3682
|
+
const VALID_FLAGS_MASK = TRANSACTION_LOG_ENTRY_LAST_FLAG$1 | TRANSACTION_LOG_ENTRY_ABORTED_FLAG$1;
|
|
3552
3683
|
/**
|
|
3553
3684
|
* Loads an entire transaction log file into memory.
|
|
3554
3685
|
* @param path - The path to the transaction log file.
|
|
@@ -3586,6 +3717,8 @@ function parseTransactionLog(path, options = {}) {
|
|
|
3586
3717
|
if (!Number.isFinite(timestamp) || timestamp < MIN_VALID_TIMESTAMP) anomalies.push(`Header timestamp ${timestamp} predates 2026-01-27 (possible corruption)`);
|
|
3587
3718
|
const entries = [];
|
|
3588
3719
|
let entryAnomalyCount = 0;
|
|
3720
|
+
let previousEndedTransaction = true;
|
|
3721
|
+
let previousTimestamp = NaN;
|
|
3589
3722
|
while (fileOffset < size) {
|
|
3590
3723
|
const timestamp = read(8).readDoubleBE(0);
|
|
3591
3724
|
if (timestamp === 0) {
|
|
@@ -3602,7 +3735,11 @@ function parseTransactionLog(path, options = {}) {
|
|
|
3602
3735
|
const data = read(length);
|
|
3603
3736
|
const entryAnomalies = [];
|
|
3604
3737
|
if (!Number.isFinite(timestamp) || timestamp < MIN_VALID_TIMESTAMP) entryAnomalies.push(`timestamp ${timestamp} predates 2026-01-27 (possible corruption)`);
|
|
3605
|
-
if ((flags &
|
|
3738
|
+
if ((flags & ~VALID_FLAGS_MASK) !== 0) entryAnomalies.push(`flags 0x${flags.toString(16).padStart(2, "0")} contains undefined bits (expected 0x00-0x03)`);
|
|
3739
|
+
const startsTransaction = previousEndedTransaction || timestamp !== previousTimestamp;
|
|
3740
|
+
if ((flags & TRANSACTION_LOG_ENTRY_ABORTED_FLAG$1) !== 0 && !startsTransaction) entryAnomalies.push("abort flag on an entry that does not start a transaction");
|
|
3741
|
+
previousEndedTransaction = (flags & TRANSACTION_LOG_ENTRY_LAST_FLAG$1) !== 0;
|
|
3742
|
+
previousTimestamp = timestamp;
|
|
3606
3743
|
const entry = {
|
|
3607
3744
|
timestamp,
|
|
3608
3745
|
length,
|
|
@@ -3631,11 +3768,11 @@ function parseTransactionLog(path, options = {}) {
|
|
|
3631
3768
|
}
|
|
3632
3769
|
}
|
|
3633
3770
|
|
|
3634
|
-
|
|
3635
|
-
//#region src/transaction-log-reader.ts
|
|
3771
|
+
// src/transaction-log-reader.ts
|
|
3636
3772
|
const FLOAT_TO_UINT32 = /* @__PURE__ */ new Float64Array(1);
|
|
3637
3773
|
const UINT32_FROM_FLOAT = new Uint32Array(FLOAT_TO_UINT32.buffer);
|
|
3638
|
-
const
|
|
3774
|
+
const EXACT_START_SPAN = /* @__PURE__ */ new Float64Array(3);
|
|
3775
|
+
const { TRANSACTION_LOG_FILE_HEADER_SIZE, TRANSACTION_LOG_ENTRY_HEADER_SIZE, TRANSACTION_LOG_ENTRY_LAST_FLAG, TRANSACTION_LOG_ENTRY_ABORTED_FLAG } = constants;
|
|
3639
3776
|
/**
|
|
3640
3777
|
* Number of consecutive well-formed frames that, on their own, signal that real log data has
|
|
3641
3778
|
* resumed after a framing break. Kept in step with `RESYNC_MIN_FRAMES` in
|
|
@@ -3671,6 +3808,32 @@ var CorruptFrameError = class extends RangeError {
|
|
|
3671
3808
|
}
|
|
3672
3809
|
};
|
|
3673
3810
|
/**
|
|
3811
|
+
* Thrown when an iterator advances past segments it never read and that can no longer be read —
|
|
3812
|
+
* typically ones retention purged while the reader lagged. The entries they held cannot be
|
|
3813
|
+
* recovered from this log, so a consumer replicating it must not treat the stream as contiguous.
|
|
3814
|
+
* Missing sequence numbers prove only that continuity is unknown, not that each held entries.
|
|
3815
|
+
*
|
|
3816
|
+
* The iterator is left positioned at the start of `resumeLogId`, so a caller that accepts the
|
|
3817
|
+
* discontinuity calls `next()` again to continue from there.
|
|
3818
|
+
*
|
|
3819
|
+
* Not a `RangeError`: this is not broken framing, and consumers classify those by class.
|
|
3820
|
+
*/
|
|
3821
|
+
var TransactionLogGapError = class extends Error {
|
|
3822
|
+
/** Sequence number of the last segment this iterator read up to. */
|
|
3823
|
+
logId;
|
|
3824
|
+
/** Sequence number of the segment iteration resumes in. */
|
|
3825
|
+
resumeLogId;
|
|
3826
|
+
/** Segments between `logId` and `resumeLogId` that could not be read. Empty segments are not counted. */
|
|
3827
|
+
unavailableLogCount;
|
|
3828
|
+
constructor(logId, resumeLogId, unavailableLogCount) {
|
|
3829
|
+
super(`Transaction log iterator skipped ${unavailableLogCount} unavailable segment(s) between log ${logId} and log ${resumeLogId}; entries in them were not read`);
|
|
3830
|
+
this.name = "TransactionLogGapError";
|
|
3831
|
+
this.logId = logId;
|
|
3832
|
+
this.resumeLogId = resumeLogId;
|
|
3833
|
+
this.unavailableLogCount = unavailableLogCount;
|
|
3834
|
+
}
|
|
3835
|
+
};
|
|
3836
|
+
/**
|
|
3674
3837
|
* Whether a complete, in-bounds frame begins at `pos`. The only sane bound on an entry's length is
|
|
3675
3838
|
* the written extent: a single entry can legitimately exceed the rotation threshold (the first
|
|
3676
3839
|
* entry written to a fresh file is always written in full). A zero timestamp is the
|
|
@@ -3746,6 +3909,14 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3746
3909
|
let dataView;
|
|
3747
3910
|
let logBuffer = this._currentLogBuffer?.deref();
|
|
3748
3911
|
let foundExactStart = false;
|
|
3912
|
+
let stopLogId = 0;
|
|
3913
|
+
let stopPosition = 0;
|
|
3914
|
+
let tailLogId = 0;
|
|
3915
|
+
let tailPosition = 0;
|
|
3916
|
+
let segmentRemovals = 0;
|
|
3917
|
+
let previousEndedTransaction = true;
|
|
3918
|
+
let previousTimestamp = NaN;
|
|
3919
|
+
let skippingAborted = false;
|
|
3749
3920
|
if (start === void 0 && !startFromLastFlushed) {
|
|
3750
3921
|
position = size;
|
|
3751
3922
|
if (position === 0) position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
@@ -3755,11 +3926,32 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3755
3926
|
FLOAT_TO_UINT32[0] = this._getLastFlushed();
|
|
3756
3927
|
if (FLOAT_TO_UINT32[0] === 0) FLOAT_TO_UINT32[0] = this._findPosition(0);
|
|
3757
3928
|
start ??= 0;
|
|
3929
|
+
} else if (exactStart) {
|
|
3930
|
+
const startPosition = this._findExactStart(start, EXACT_START_SPAN);
|
|
3931
|
+
FLOAT_TO_UINT32[0] = EXACT_START_SPAN[0];
|
|
3932
|
+
stopLogId = UINT32_FROM_FLOAT[1];
|
|
3933
|
+
stopPosition = UINT32_FROM_FLOAT[0];
|
|
3934
|
+
FLOAT_TO_UINT32[0] = EXACT_START_SPAN[1];
|
|
3935
|
+
tailLogId = UINT32_FROM_FLOAT[1];
|
|
3936
|
+
tailPosition = UINT32_FROM_FLOAT[0];
|
|
3937
|
+
segmentRemovals = EXACT_START_SPAN[2];
|
|
3938
|
+
FLOAT_TO_UINT32[0] = startPosition;
|
|
3758
3939
|
} else FLOAT_TO_UINT32[0] = this._findPosition(start);
|
|
3759
3940
|
logId = UINT32_FROM_FLOAT[1];
|
|
3760
3941
|
position = UINT32_FROM_FLOAT[0];
|
|
3761
3942
|
if (position === 0) position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
3762
3943
|
}
|
|
3944
|
+
const positionedAt = position;
|
|
3945
|
+
if (stopLogId !== 0 && pastStop(logId, position, stopLogId, stopPosition)) {
|
|
3946
|
+
stopLogId = 0;
|
|
3947
|
+
if (skipToTail(this, logId, position, tailLogId, tailPosition, segmentRemovals)) {
|
|
3948
|
+
logId = tailLogId;
|
|
3949
|
+
position = tailPosition;
|
|
3950
|
+
logBuffer = tailLogBuffer ?? logBuffer;
|
|
3951
|
+
}
|
|
3952
|
+
tailLogBuffer = void 0;
|
|
3953
|
+
}
|
|
3954
|
+
let gapFloor = logId;
|
|
3763
3955
|
if (logBuffer === void 0 || logBuffer.logId !== logId) {
|
|
3764
3956
|
logBuffer = getLogMemoryMap(this, logId);
|
|
3765
3957
|
if (logBuffer && latestLogId === logId && !readUncommitted && this._currentLogBuffer?.deref() !== logBuffer) this._currentLogBuffer = new WeakRef(logBuffer);
|
|
@@ -3769,6 +3961,7 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3769
3961
|
logBuffer.size = 0;
|
|
3770
3962
|
logBuffer.dataView = new DataView(logBuffer.buffer);
|
|
3771
3963
|
size = 0;
|
|
3964
|
+
if (positionedAt <= TRANSACTION_LOG_FILE_HEADER_SIZE || logId > 0 && this.getLogFileSize(logId) > 0) gapFloor = logId - 1;
|
|
3772
3965
|
}
|
|
3773
3966
|
}
|
|
3774
3967
|
dataView = logBuffer.dataView;
|
|
@@ -3778,7 +3971,7 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3778
3971
|
if (cachedSize === void 0) size = logBuffer.size = readableExtent(logBuffer);
|
|
3779
3972
|
else size = cachedSize;
|
|
3780
3973
|
}
|
|
3781
|
-
|
|
3974
|
+
const iterator = {
|
|
3782
3975
|
[Symbol.iterator]() {
|
|
3783
3976
|
return this;
|
|
3784
3977
|
},
|
|
@@ -3790,13 +3983,17 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3790
3983
|
if (latestLogId > logBuffer.logId) {
|
|
3791
3984
|
size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
|
|
3792
3985
|
if (position >= size) {
|
|
3793
|
-
const
|
|
3986
|
+
const fromLogId = logBuffer.logId;
|
|
3987
|
+
const nextLogBuffer = nextReadableLogBuffer(transactionLog, fromLogId, latestLogId, gapFloor);
|
|
3794
3988
|
if (nextLogBuffer) {
|
|
3989
|
+
const unavailable = unavailableLogCount;
|
|
3795
3990
|
dataView = nextLogBuffer.dataView;
|
|
3796
3991
|
logBuffer = nextLogBuffer;
|
|
3797
3992
|
if (latestLogId > logBuffer.logId) size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
|
|
3798
3993
|
else size = latestSize;
|
|
3799
3994
|
position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
3995
|
+
previousEndedTransaction = true;
|
|
3996
|
+
if (unavailable > 0) throw new TransactionLogGapError(Math.max(fromLogId, gapFloor), logBuffer.logId, unavailable);
|
|
3800
3997
|
}
|
|
3801
3998
|
}
|
|
3802
3999
|
}
|
|
@@ -3816,30 +4013,55 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3816
4013
|
if (position + TRANSACTION_LOG_ENTRY_HEADER_SIZE > limit) {
|
|
3817
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);
|
|
3818
4015
|
position = broken.nextPosition;
|
|
4016
|
+
previousEndedTransaction = true;
|
|
3819
4017
|
throw broken.error;
|
|
3820
4018
|
}
|
|
3821
4019
|
const length = dataView.getUint32(position + 8);
|
|
3822
4020
|
if (length === 0 || position + TRANSACTION_LOG_ENTRY_HEADER_SIZE + length > limit) {
|
|
3823
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);
|
|
3824
4022
|
position = broken.nextPosition;
|
|
4023
|
+
previousEndedTransaction = true;
|
|
3825
4024
|
throw broken.error;
|
|
3826
4025
|
}
|
|
3827
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;
|
|
3828
4032
|
let matchesRange;
|
|
3829
|
-
if (
|
|
4033
|
+
if (skippingAborted) matchesRange = false;
|
|
4034
|
+
else if (foundExactStart) matchesRange = (!exclusiveStart || timestamp !== start) && timestamp < end;
|
|
3830
4035
|
else if (exactStart) {
|
|
4036
|
+
matchesRange = false;
|
|
3831
4037
|
if (timestamp === start) {
|
|
3832
4038
|
matchesRange = !exclusiveStart;
|
|
3833
4039
|
foundExactStart = true;
|
|
3834
|
-
|
|
4040
|
+
stopLogId = 0;
|
|
4041
|
+
}
|
|
3835
4042
|
} else matchesRange = (exclusiveStart ? timestamp > start : timestamp >= start) && timestamp < end;
|
|
4043
|
+
if (!matchesRange && stopLogId !== 0 && pastStop(logBuffer.logId, position + length, stopLogId, stopPosition)) {
|
|
4044
|
+
stopLogId = 0;
|
|
4045
|
+
position += length;
|
|
4046
|
+
if (skipToTail(transactionLog, logBuffer.logId, position, tailLogId, tailPosition, segmentRemovals)) {
|
|
4047
|
+
if (tailLogBuffer) {
|
|
4048
|
+
logBuffer = tailLogBuffer;
|
|
4049
|
+
dataView = tailLogBuffer.dataView;
|
|
4050
|
+
}
|
|
4051
|
+
position = tailPosition;
|
|
4052
|
+
previousEndedTransaction = true;
|
|
4053
|
+
size = 0;
|
|
4054
|
+
}
|
|
4055
|
+
tailLogBuffer = void 0;
|
|
4056
|
+
return iterator.next();
|
|
4057
|
+
}
|
|
3836
4058
|
const entryStart = position;
|
|
3837
4059
|
position += length;
|
|
3838
4060
|
if (matchesRange) return {
|
|
3839
4061
|
done: false,
|
|
3840
4062
|
value: {
|
|
3841
4063
|
timestamp,
|
|
3842
|
-
endTxn:
|
|
4064
|
+
endTxn: previousEndedTransaction,
|
|
3843
4065
|
data: logBuffer.subarray(entryStart, position)
|
|
3844
4066
|
}
|
|
3845
4067
|
};
|
|
@@ -3850,11 +4072,13 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3850
4072
|
size = logBuffer.size ?? readableExtent(logBuffer);
|
|
3851
4073
|
if (!readUncommitted) logBuffer.size = size;
|
|
3852
4074
|
if (position < size) continue;
|
|
3853
|
-
const
|
|
4075
|
+
const fromLogId = logBuffer.logId;
|
|
4076
|
+
const nextLogBuffer = nextReadableLogBuffer(transactionLog, fromLogId, latestLogId, gapFloor);
|
|
3854
4077
|
if (!nextLogBuffer) return {
|
|
3855
4078
|
done: true,
|
|
3856
4079
|
value: void 0
|
|
3857
4080
|
};
|
|
4081
|
+
const unavailable = unavailableLogCount;
|
|
3858
4082
|
logBuffer = nextLogBuffer;
|
|
3859
4083
|
dataView = logBuffer.dataView;
|
|
3860
4084
|
if (latestLogId === logBuffer.logId) size = latestSize;
|
|
@@ -3866,6 +4090,8 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3866
4090
|
} else size = cachedSize;
|
|
3867
4091
|
}
|
|
3868
4092
|
position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
4093
|
+
previousEndedTransaction = true;
|
|
4094
|
+
if (unavailable > 0) throw new TransactionLogGapError(Math.max(fromLogId, gapFloor), logBuffer.logId, unavailable);
|
|
3869
4095
|
}
|
|
3870
4096
|
}
|
|
3871
4097
|
}
|
|
@@ -3875,7 +4101,29 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3875
4101
|
};
|
|
3876
4102
|
}
|
|
3877
4103
|
};
|
|
4104
|
+
return iterator;
|
|
3878
4105
|
} });
|
|
4106
|
+
function pastStop(logId, position, stopLogId, stopPosition) {
|
|
4107
|
+
return logId > stopLogId || logId === stopLogId && position >= stopPosition;
|
|
4108
|
+
}
|
|
4109
|
+
let tailLogBuffer;
|
|
4110
|
+
/**
|
|
4111
|
+
* Whether an exact-start lookup at `position` of `fromLogId` may take its one-shot jump to the tail.
|
|
4112
|
+
* next() calls it on a frame boundary, so a mapping error leaves a resumable cursor; from query()
|
|
4113
|
+
* the error escapes before any iterator exists. The removal
|
|
4114
|
+
* check follows the mapping and holds the store's lock, so a purge either declines the jump (the
|
|
4115
|
+
* scan then reports the gap) or follows it like a purge of segments already read.
|
|
4116
|
+
*/
|
|
4117
|
+
function skipToTail(transactionLog, fromLogId, position, tailLogId, tailPosition, segmentRemovals) {
|
|
4118
|
+
tailLogBuffer = void 0;
|
|
4119
|
+
if (tailLogId === fromLogId) return tailPosition > position;
|
|
4120
|
+
if (tailLogId < fromLogId) return false;
|
|
4121
|
+
const mapped = getLogMemoryMap(transactionLog, tailLogId);
|
|
4122
|
+
if (mapped === void 0 || transactionLog._segmentRemovals() !== segmentRemovals) return false;
|
|
4123
|
+
tailLogBuffer = mapped;
|
|
4124
|
+
return true;
|
|
4125
|
+
}
|
|
4126
|
+
let unavailableLogCount = 0;
|
|
3879
4127
|
/**
|
|
3880
4128
|
* Maps the next readable segment after `fromLogId`, skipping a run retention has deleted —
|
|
3881
4129
|
* stopping at the hole wedges the iterator there for every later poll (invariant 30).
|
|
@@ -3884,15 +4132,22 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3884
4132
|
* `_findPosition(0)`, which names the bottom of the contiguous run ending at the current segment
|
|
3885
4133
|
* and so lands *past* a survivor sitting between two holes.
|
|
3886
4134
|
*/
|
|
3887
|
-
function nextReadableLogBuffer(transactionLog, fromLogId, latestLogId) {
|
|
4135
|
+
function nextReadableLogBuffer(transactionLog, fromLogId, latestLogId, gapFloor) {
|
|
4136
|
+
unavailableLogCount = 0;
|
|
3888
4137
|
let candidateLogId = fromLogId + 1;
|
|
3889
4138
|
while (candidateLogId <= latestLogId) {
|
|
3890
4139
|
const logBuffer = getLogMemoryMap(transactionLog, candidateLogId);
|
|
3891
4140
|
if (logBuffer) {
|
|
3892
4141
|
if (candidateLogId === latestLogId || readableExtent(logBuffer) > TRANSACTION_LOG_FILE_HEADER_SIZE) return logBuffer;
|
|
3893
|
-
} else
|
|
3894
|
-
|
|
3895
|
-
|
|
4142
|
+
} else {
|
|
4143
|
+
const fileSize = transactionLog.getLogFileSize(candidateLogId);
|
|
4144
|
+
if (fileSize > TRANSACTION_LOG_FILE_HEADER_SIZE) return;
|
|
4145
|
+
if (fileSize === 0 && candidateLogId > gapFloor) unavailableLogCount++;
|
|
4146
|
+
}
|
|
4147
|
+
const nextLogId = transactionLog._nextLogId(candidateLogId);
|
|
4148
|
+
if (nextLogId === 0) return;
|
|
4149
|
+
unavailableLogCount += Math.max(0, nextLogId - 1 - Math.max(candidateLogId, gapFloor));
|
|
4150
|
+
candidateLogId = nextLogId;
|
|
3896
4151
|
}
|
|
3897
4152
|
}
|
|
3898
4153
|
/**
|
|
@@ -3946,13 +4201,11 @@ function loadLastPosition(transactionLog, readUncommitted) {
|
|
|
3946
4201
|
};
|
|
3947
4202
|
}
|
|
3948
4203
|
|
|
3949
|
-
|
|
3950
|
-
//#region src/index.ts
|
|
4204
|
+
// src/index.ts
|
|
3951
4205
|
const versions = {
|
|
3952
4206
|
rocksdb: version,
|
|
3953
|
-
"rocksdb-js": "2.
|
|
4207
|
+
"rocksdb-js": "2.13.0"
|
|
3954
4208
|
};
|
|
3955
4209
|
|
|
3956
|
-
|
|
3957
|
-
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 };
|
|
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 };
|
|
3958
4211
|
//# sourceMappingURL=index.mjs.map
|