@harperfast/rocksdb-js 2.7.1 → 2.9.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 +624 -45
- package/dist/index.cjs +730 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +752 -41
- package/dist/index.d.mts +752 -41
- package/dist/index.mjs +726 -47
- package/dist/index.mjs.map +1 -1
- package/docs/stats.md +355 -269
- package/docs/transaction-log.md +18 -5
- package/docs/transaction-timestamp-integrity-design.md +123 -0
- package/package.json +25 -26
package/dist/index.cjs
CHANGED
|
@@ -20,7 +20,7 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
20
20
|
}
|
|
21
21
|
return to;
|
|
22
22
|
};
|
|
23
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
24
24
|
value: mod,
|
|
25
25
|
enumerable: true
|
|
26
26
|
}) : target, mod));
|
|
@@ -80,7 +80,37 @@ function locateBinding() {
|
|
|
80
80
|
throw new Error("Unable to locate rocksdb-js native binding");
|
|
81
81
|
}
|
|
82
82
|
const binding = req(locateBinding());
|
|
83
|
+
/**
|
|
84
|
+
* The native `BackgroundError` constructor (a real `Error` subclass). Exported
|
|
85
|
+
* as both a value — for `err instanceof BackgroundError` — and, via declaration
|
|
86
|
+
* merging with the interface above, a type. Instances are produced by the
|
|
87
|
+
* `'error'` event and `db.getLastError()`; consumers rarely construct their own.
|
|
88
|
+
* The `details` param is required and typed to the instance fields so a bare
|
|
89
|
+
* `new BackgroundError()` (which would omit `severity` / `writesDisabled` / …)
|
|
90
|
+
* is a type error.
|
|
91
|
+
*/
|
|
92
|
+
const BackgroundError = binding.BackgroundError;
|
|
83
93
|
const config = binding.config;
|
|
94
|
+
/**
|
|
95
|
+
* Reads the live state of the process-wide `WriteBufferManager`: its budget,
|
|
96
|
+
* charged memory, mutable share, stall state, and attached column-family
|
|
97
|
+
* inventory. The manager is shared by every database and worker thread in the
|
|
98
|
+
* process.
|
|
99
|
+
*
|
|
100
|
+
* A WriteBufferManager stall does not pass through RocksDB's `WriteController`,
|
|
101
|
+
* so it is not reflected by `rocksdb.stall.micros`, `db.isWriteStalled()`, or
|
|
102
|
+
* the `'writeStall'` event. Use `stallActive` to distinguish this condition from
|
|
103
|
+
* an idle database.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```typescript
|
|
107
|
+
* const wbm = getWriteBufferManagerStats();
|
|
108
|
+
* if (wbm.stallActive) {
|
|
109
|
+
* log.warn(`writes stalled for ${wbm.stallActiveMs}ms`);
|
|
110
|
+
* }
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
const getWriteBufferManagerStats = binding.getWriteBufferManagerStats;
|
|
84
114
|
const FRESH_VERSION_FLAG$1 = binding.constants.FRESH_VERSION_FLAG;
|
|
85
115
|
const addGlobalListener = binding.addListener;
|
|
86
116
|
const removeGlobalListener = binding.removeListener;
|
|
@@ -129,6 +159,8 @@ const transactionLogMapCount = binding.transactionLogMapCount;
|
|
|
129
159
|
* disarm. Used by the ERR_TRY_AGAIN retry regression test.
|
|
130
160
|
*/
|
|
131
161
|
const forceTryAgainForTesting = binding.forceTryAgainForTesting;
|
|
162
|
+
/** Delays one selected watchdog join in the concurrent-shutdown regression test. */
|
|
163
|
+
const setWriteBufferManagerJoinDelayForTesting = binding.setWriteBufferManagerJoinDelayForTesting;
|
|
132
164
|
/**
|
|
133
165
|
* Creates a native file lock using the specified file path (`flock` on POSIX,
|
|
134
166
|
* `LockFileEx` on Windows), creating the file and any missing parent
|
|
@@ -471,6 +503,150 @@ const backups = {
|
|
|
471
503
|
}
|
|
472
504
|
};
|
|
473
505
|
|
|
506
|
+
//#endregion
|
|
507
|
+
//#region src/count-estimator.ts
|
|
508
|
+
/**
|
|
509
|
+
* Below this many traversed entries the observed density is too noisy to
|
|
510
|
+
* calibrate with, so `estimate()` returns the raw statistical estimate.
|
|
511
|
+
*/
|
|
512
|
+
const CALIBRATION_MIN_TRAVERSED = 16;
|
|
513
|
+
const CALIBRATION_MIN_CONFIDENCE = .8;
|
|
514
|
+
/**
|
|
515
|
+
* Bounds on how far the observed-vs-estimated ratio may scale the remainder;
|
|
516
|
+
* the statistical estimate can be arbitrarily wrong at data-block granularity
|
|
517
|
+
* and an unclamped ratio would let one bad sample dominate.
|
|
518
|
+
*/
|
|
519
|
+
const CALIBRATION_MAX = 8;
|
|
520
|
+
/**
|
|
521
|
+
* Progressively refines a range key-count estimate as an iterator traverses
|
|
522
|
+
* the range. The estimate starts as the pure statistical estimate
|
|
523
|
+
* (`estimateCount`) and converges toward the exact count: the traversed
|
|
524
|
+
* portion is exact, and the statistical estimate of the untraversed remainder
|
|
525
|
+
* is calibrated by the observed ratio of actual-to-estimated entries over the
|
|
526
|
+
* portion already traversed.
|
|
527
|
+
*
|
|
528
|
+
* The estimator never touches the iterator itself — the caller reports
|
|
529
|
+
* progress with `advance(lastKey, count)` whenever it wants a checkpoint
|
|
530
|
+
* (e.g. once per page), then reads `estimate()`. The caller owns the
|
|
531
|
+
* progress contract: cursors must move monotonically through the range and
|
|
532
|
+
* each entry must be reported once (a re-reported page inflates the count
|
|
533
|
+
* undetectably). When traversal completes, call `finish()` so `estimate()`
|
|
534
|
+
* returns the exact total instead of adding a block-granular remainder.
|
|
535
|
+
*
|
|
536
|
+
* Each `estimate()` checkpoint queries RocksDB statistics for the two range
|
|
537
|
+
* segments (cost scales with the SSTs overlapping them, not with key count);
|
|
538
|
+
* results are memoized per checkpoint, so repeated reads between `advance()`
|
|
539
|
+
* calls are free.
|
|
540
|
+
*/
|
|
541
|
+
var CountEstimator = class {
|
|
542
|
+
#store;
|
|
543
|
+
#start;
|
|
544
|
+
#end;
|
|
545
|
+
#exclusiveStart;
|
|
546
|
+
#inclusiveEnd;
|
|
547
|
+
#reverse;
|
|
548
|
+
#cursor;
|
|
549
|
+
#traversed = 0;
|
|
550
|
+
#finished = false;
|
|
551
|
+
#memoized;
|
|
552
|
+
constructor(store, options) {
|
|
553
|
+
this.#store = store;
|
|
554
|
+
this.#reverse = options?.reverse ?? false;
|
|
555
|
+
this.#start = this.#reverse ? options?.end : options?.start;
|
|
556
|
+
this.#end = this.#reverse ? options?.start : options?.end;
|
|
557
|
+
this.#exclusiveStart = options?.exclusiveStart ?? this.#reverse;
|
|
558
|
+
this.#inclusiveEnd = options?.inclusiveEnd ?? this.#reverse;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* The number of entries reported traversed so far.
|
|
562
|
+
*/
|
|
563
|
+
get traversed() {
|
|
564
|
+
return this.#traversed;
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Records that iteration has advanced through `count` more entries, ending
|
|
568
|
+
* at `lastKey`. Pass `count: 0` when an empty page has no last key.
|
|
569
|
+
*/
|
|
570
|
+
advance(lastKey, count = 1) {
|
|
571
|
+
if (lastKey === void 0) {
|
|
572
|
+
if (count !== 0) throw new Error("CountEstimator.advance requires lastKey when count is nonzero");
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
this.#cursor = lastKey;
|
|
576
|
+
this.#traversed += count;
|
|
577
|
+
this.#memoized = void 0;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Marks traversal of the range as complete: `estimate()` becomes the exact
|
|
581
|
+
* traversed count.
|
|
582
|
+
*/
|
|
583
|
+
finish() {
|
|
584
|
+
this.#finished = true;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Estimates the total number of entries in the full range: the exact
|
|
588
|
+
* traversed count plus a calibrated statistical estimate of the remainder.
|
|
589
|
+
* `confidence` is the exactness-weighted blend of the traversed portion
|
|
590
|
+
* (exact) and the remainder's statistical confidence, so it approaches 1
|
|
591
|
+
* as the exact portion grows (though a checkpoint can decrease when
|
|
592
|
+
* calibration makes a large correction), and is exactly 1 after `finish()`.
|
|
593
|
+
*/
|
|
594
|
+
estimate() {
|
|
595
|
+
if (this.#finished) return {
|
|
596
|
+
count: this.#traversed,
|
|
597
|
+
confidence: 1
|
|
598
|
+
};
|
|
599
|
+
if (this.#memoized !== void 0) return { ...this.#memoized };
|
|
600
|
+
if (this.#cursor === void 0) {
|
|
601
|
+
this.#memoized = this.#store.estimateCount({
|
|
602
|
+
start: this.#start,
|
|
603
|
+
end: this.#end,
|
|
604
|
+
exclusiveStart: this.#exclusiveStart,
|
|
605
|
+
inclusiveEnd: this.#inclusiveEnd
|
|
606
|
+
});
|
|
607
|
+
return { ...this.#memoized };
|
|
608
|
+
}
|
|
609
|
+
const traversedRange = this.#reverse ? {
|
|
610
|
+
start: this.#cursor,
|
|
611
|
+
end: this.#end,
|
|
612
|
+
inclusiveEnd: this.#inclusiveEnd
|
|
613
|
+
} : {
|
|
614
|
+
start: this.#start,
|
|
615
|
+
exclusiveStart: this.#exclusiveStart,
|
|
616
|
+
end: this.#cursor,
|
|
617
|
+
inclusiveEnd: true
|
|
618
|
+
};
|
|
619
|
+
const remainingRange = this.#reverse ? {
|
|
620
|
+
start: this.#start,
|
|
621
|
+
exclusiveStart: this.#exclusiveStart,
|
|
622
|
+
end: this.#cursor
|
|
623
|
+
} : {
|
|
624
|
+
start: this.#cursor,
|
|
625
|
+
exclusiveStart: true,
|
|
626
|
+
end: this.#end,
|
|
627
|
+
inclusiveEnd: this.#inclusiveEnd
|
|
628
|
+
};
|
|
629
|
+
const remainingEstimate = this.#store.estimateCount(remainingRange);
|
|
630
|
+
let remaining = remainingEstimate.count;
|
|
631
|
+
let calibrationConfidence = 1;
|
|
632
|
+
if (this.#traversed >= CALIBRATION_MIN_TRAVERSED) {
|
|
633
|
+
const traversedEstimate = this.#store.estimateCount(traversedRange);
|
|
634
|
+
if (traversedEstimate.count >= CALIBRATION_MIN_TRAVERSED && traversedEstimate.confidence >= CALIBRATION_MIN_CONFIDENCE) {
|
|
635
|
+
const calibration = Math.min(CALIBRATION_MAX, Math.max(1 / CALIBRATION_MAX, this.#traversed / traversedEstimate.count));
|
|
636
|
+
remaining *= calibration;
|
|
637
|
+
calibrationConfidence = Math.min(calibration, 1 / calibration);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
const count = Math.round(this.#traversed + remaining);
|
|
641
|
+
const confidence = count > 0 ? Math.min(.999, (this.#traversed + calibrationConfidence * remainingEstimate.confidence * remaining) / count) : Math.min(.999, calibrationConfidence * remainingEstimate.confidence);
|
|
642
|
+
this.#memoized = {
|
|
643
|
+
count,
|
|
644
|
+
confidence
|
|
645
|
+
};
|
|
646
|
+
return { ...this.#memoized };
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
|
|
474
650
|
//#endregion
|
|
475
651
|
//#region src/util.ts
|
|
476
652
|
/**
|
|
@@ -513,14 +689,12 @@ function parseDuration(duration) {
|
|
|
513
689
|
num *= 1e3;
|
|
514
690
|
break;
|
|
515
691
|
case "m":
|
|
516
|
-
num *=
|
|
692
|
+
num *= 6e4;
|
|
517
693
|
break;
|
|
518
694
|
case "h":
|
|
519
|
-
num *=
|
|
520
|
-
break;
|
|
521
|
-
case "d":
|
|
522
|
-
num *= 1e3 * 60 * 60 * 24;
|
|
695
|
+
num *= 36e5;
|
|
523
696
|
break;
|
|
697
|
+
case "d": num *= 864e5;
|
|
524
698
|
}
|
|
525
699
|
result += num;
|
|
526
700
|
}
|
|
@@ -1223,10 +1397,10 @@ function createFixedBuffer(size) {
|
|
|
1223
1397
|
|
|
1224
1398
|
//#endregion
|
|
1225
1399
|
//#region src/store.ts
|
|
1226
|
-
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 } = constants;
|
|
1400
|
+
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;
|
|
1227
1401
|
const KEY_BUFFER_SIZE = 4096;
|
|
1228
1402
|
const KEY_BUFFER = createFixedBuffer(KEY_BUFFER_SIZE);
|
|
1229
|
-
const VALUE_BUFFER = createFixedBuffer(
|
|
1403
|
+
const VALUE_BUFFER = createFixedBuffer(65536);
|
|
1230
1404
|
/**
|
|
1231
1405
|
* Backing buffer for the shared iterator state. Layout (Uint32Array view):
|
|
1232
1406
|
* [0] = key length written into KEY_BUFFER by the most recent iterator step
|
|
@@ -1234,7 +1408,7 @@ const VALUE_BUFFER = createFixedBuffer(64 * 1024);
|
|
|
1234
1408
|
*/
|
|
1235
1409
|
const ITERATOR_STATE_BUFFER = Buffer.allocUnsafeSlow(8);
|
|
1236
1410
|
const ITERATOR_STATE = new Uint32Array(ITERATOR_STATE_BUFFER.buffer, ITERATOR_STATE_BUFFER.byteOffset, 2);
|
|
1237
|
-
const MAX_KEY_SIZE =
|
|
1411
|
+
const MAX_KEY_SIZE = 1048576;
|
|
1238
1412
|
const SAVE_BUFFER_SIZE = 8192;
|
|
1239
1413
|
/**
|
|
1240
1414
|
* Normalizes the public `compression` option into the primitive fields the
|
|
@@ -1269,6 +1443,15 @@ function normalizeCompression(option) {
|
|
|
1269
1443
|
};
|
|
1270
1444
|
}
|
|
1271
1445
|
/**
|
|
1446
|
+
* Whether `codec` is a pre-constructed encoder/decoder instance that `open()`
|
|
1447
|
+
* would reuse as-is (rather than instantiate fresh from an `Encoder` factory).
|
|
1448
|
+
* Such an instance carries per-column-family mutable state (its `name`, msgpack
|
|
1449
|
+
* `structures`), so it cannot be shared across column families.
|
|
1450
|
+
*/
|
|
1451
|
+
function sharesMutableCodec(codec) {
|
|
1452
|
+
return codec != null && typeof codec === "object" && typeof codec.Encoder !== "function" && (typeof codec.encode === "function" || typeof codec.decode === "function");
|
|
1453
|
+
}
|
|
1454
|
+
/**
|
|
1272
1455
|
* A store wraps the `NativeDatabase` binding and database settings so that a
|
|
1273
1456
|
* single database instance can be shared between the main `RocksDatabase`
|
|
1274
1457
|
* instance and the `Transaction` instance.
|
|
@@ -1287,6 +1470,13 @@ var Store = class {
|
|
|
1287
1470
|
*/
|
|
1288
1471
|
#claimed = false;
|
|
1289
1472
|
/**
|
|
1473
|
+
* Shallow snapshot of the options this store was constructed with, used by
|
|
1474
|
+
* `createColumnFamilyStore()` to derive sibling column-family stores. Snapshot
|
|
1475
|
+
* (not the caller's object) so later mutations of the caller's options don't
|
|
1476
|
+
* leak into derived views.
|
|
1477
|
+
*/
|
|
1478
|
+
#columnFamilyOptions;
|
|
1479
|
+
/**
|
|
1290
1480
|
* The database instance.
|
|
1291
1481
|
*/
|
|
1292
1482
|
db;
|
|
@@ -1422,6 +1612,11 @@ var Store = class {
|
|
|
1422
1612
|
*/
|
|
1423
1613
|
path;
|
|
1424
1614
|
/**
|
|
1615
|
+
* The open database's resolved filesystem identity, read once from
|
|
1616
|
+
* `NativeDatabase.identityPath`. `undefined` until opened.
|
|
1617
|
+
*/
|
|
1618
|
+
identityPath;
|
|
1619
|
+
/**
|
|
1425
1620
|
* Whether to use pessimistic locking for transactions. When `true`,
|
|
1426
1621
|
* transactions will fail as soon as a conflict is detected. When `false`,
|
|
1427
1622
|
* transactions will only fail when `commit()` is called.
|
|
@@ -1429,10 +1624,19 @@ var Store = class {
|
|
|
1429
1624
|
pessimistic;
|
|
1430
1625
|
/**
|
|
1431
1626
|
* Whether the database is open in readonly mode. When `true`, write
|
|
1432
|
-
* operations will throw an error with code `ERR_DATABASE_READONLY
|
|
1627
|
+
* operations will throw an error with code `ERR_DATABASE_READONLY`, except
|
|
1628
|
+
* for `flush()`/`flushSync()`/`compact()`/`compactSync()`, which succeed as
|
|
1629
|
+
* no-ops.
|
|
1433
1630
|
*/
|
|
1434
1631
|
readOnly;
|
|
1435
1632
|
/**
|
|
1633
|
+
* The secondary instance's workspace directory when the database is opened
|
|
1634
|
+
* as a secondary (see `RocksDatabaseOptions.secondaryPath`); `undefined`
|
|
1635
|
+
* otherwise. A secondary is read-only, so `readOnly` is `true` whenever
|
|
1636
|
+
* this is set.
|
|
1637
|
+
*/
|
|
1638
|
+
secondaryPath;
|
|
1639
|
+
/**
|
|
1436
1640
|
* Encoder specific flag used to signal that the encoder should use a random
|
|
1437
1641
|
* access structure.
|
|
1438
1642
|
*/
|
|
@@ -1456,8 +1660,8 @@ var Store = class {
|
|
|
1456
1660
|
*/
|
|
1457
1661
|
transactionLogMaxAgeThreshold;
|
|
1458
1662
|
/**
|
|
1459
|
-
* The maximum size of a transaction log before
|
|
1460
|
-
*
|
|
1663
|
+
* The target maximum size of a transaction log before rotation. Transactions
|
|
1664
|
+
* are never split across files, so one transaction may exceed this target.
|
|
1461
1665
|
*/
|
|
1462
1666
|
transactionLogMaxSize;
|
|
1463
1667
|
/**
|
|
@@ -1518,7 +1722,10 @@ var Store = class {
|
|
|
1518
1722
|
this.parallelismThreads = options?.parallelismThreads;
|
|
1519
1723
|
this.path = path;
|
|
1520
1724
|
this.pessimistic = options?.pessimistic ?? false;
|
|
1521
|
-
|
|
1725
|
+
const secondaryPath = options?.secondaryPath ?? void 0;
|
|
1726
|
+
if (secondaryPath !== void 0 && options?.readOnly === false) throw new Error("A secondary open is read-only; secondaryPath cannot be combined with readOnly: false");
|
|
1727
|
+
this.readOnly = options?.readOnly ?? secondaryPath !== void 0;
|
|
1728
|
+
this.secondaryPath = secondaryPath;
|
|
1522
1729
|
this.randomAccessStructure = options?.randomAccessStructure ?? false;
|
|
1523
1730
|
this.readKey = readKey;
|
|
1524
1731
|
this.sharedStructuresKey = options?.sharedStructuresKey;
|
|
@@ -1530,6 +1737,36 @@ var Store = class {
|
|
|
1530
1737
|
this.verificationTable = options?.verificationTable;
|
|
1531
1738
|
this.writeBufferSize = options?.writeBufferSize;
|
|
1532
1739
|
this.writeKey = writeKey;
|
|
1740
|
+
this.#columnFamilyOptions = options ? { ...options } : void 0;
|
|
1741
|
+
}
|
|
1742
|
+
/**
|
|
1743
|
+
* Creates an independent `Store` for another column family of the same
|
|
1744
|
+
* database, used by {@link RocksDatabase#use}. The returned store shares the
|
|
1745
|
+
* underlying database but must have its own codec state: a mutable
|
|
1746
|
+
* encoder/decoder instance cannot be shared across column families (opening a
|
|
1747
|
+
* view mutates the encoder's `name`, and clearing a view resets its
|
|
1748
|
+
* `structures`), so a pre-constructed instance is rejected here — supply an
|
|
1749
|
+
* encoder factory (`{ Encoder }`) or a named `encoding` instead.
|
|
1750
|
+
*
|
|
1751
|
+
* Override this in a `Store` subclass whose constructor cannot be recreated
|
|
1752
|
+
* from `(path, options)` (e.g. it takes injected dependencies) or that needs
|
|
1753
|
+
* to control how a view's codec state is built.
|
|
1754
|
+
*
|
|
1755
|
+
* @param name - The column family name for the derived store.
|
|
1756
|
+
* @param options - Options overriding this store's, for the derived store.
|
|
1757
|
+
* @returns A new, independent `Store` bound to `name`.
|
|
1758
|
+
*/
|
|
1759
|
+
createColumnFamilyStore(name, options) {
|
|
1760
|
+
const derived = {
|
|
1761
|
+
...this.#columnFamilyOptions,
|
|
1762
|
+
...options,
|
|
1763
|
+
name
|
|
1764
|
+
};
|
|
1765
|
+
if (sharesMutableCodec(derived.encoder) || sharesMutableCodec(derived.decoder)) throw new Error("Cannot derive a column family view from a Store configured with a pre-constructed encoder/decoder instance — its mutable state would be shared across column families. Provide an encoder factory (`{ Encoder }`) or a named `encoding`, or override `Store#createColumnFamilyStore`.");
|
|
1766
|
+
const StoreClass = this.constructor;
|
|
1767
|
+
const store = new StoreClass(this.path, derived);
|
|
1768
|
+
if (store.name !== name) throw new Error(`Store#createColumnFamilyStore produced a store bound to "${store.name}" instead of "${name}"; a custom Store must forward options to super() or override this method.`);
|
|
1769
|
+
return store;
|
|
1533
1770
|
}
|
|
1534
1771
|
/**
|
|
1535
1772
|
* Closes the database.
|
|
@@ -1666,6 +1903,24 @@ var Store = class {
|
|
|
1666
1903
|
}
|
|
1667
1904
|
return result;
|
|
1668
1905
|
}
|
|
1906
|
+
/**
|
|
1907
|
+
* Estimates the number of keys in a range from RocksDB statistics
|
|
1908
|
+
* (memtable stats + approximate SST sizes with range-local entry density)
|
|
1909
|
+
* without iterating. Estimates always reflect committed state, so there is
|
|
1910
|
+
* no transactional variant.
|
|
1911
|
+
*/
|
|
1912
|
+
estimateCount(options) {
|
|
1913
|
+
let startBuffer;
|
|
1914
|
+
let endBuffer;
|
|
1915
|
+
const reverse = options?.reverse ?? false;
|
|
1916
|
+
const start = reverse ? options?.end : options?.start;
|
|
1917
|
+
const end = reverse ? options?.start : options?.end;
|
|
1918
|
+
const exclusiveStart = options?.exclusiveStart ?? reverse;
|
|
1919
|
+
const inclusiveEnd = options?.inclusiveEnd ?? reverse;
|
|
1920
|
+
if (start !== void 0) startBuffer = copyEncodedKey(this.encodeKey(start), exclusiveStart);
|
|
1921
|
+
if (end !== void 0) endBuffer = copyEncodedKey(this.encodeKey(end), inclusiveEnd);
|
|
1922
|
+
return this.db.estimateCount(startBuffer, endBuffer);
|
|
1923
|
+
}
|
|
1669
1924
|
getCount(context, options) {
|
|
1670
1925
|
options = { ...options };
|
|
1671
1926
|
if (options?.start !== void 0) {
|
|
@@ -1693,6 +1948,7 @@ var Store = class {
|
|
|
1693
1948
|
let endUnencoded = options?.key ?? options?.end;
|
|
1694
1949
|
const includeValues = options?.values ?? true;
|
|
1695
1950
|
const reverse = options?.reverse ?? false;
|
|
1951
|
+
const txnId = this.getTxnId(options);
|
|
1696
1952
|
let exclusiveStart = options?.exclusiveStart ?? false;
|
|
1697
1953
|
let inclusiveEnd = options?.inclusiveEnd ?? false;
|
|
1698
1954
|
if (options?.key !== void 0) inclusiveEnd = true;
|
|
@@ -1711,13 +1967,15 @@ var Store = class {
|
|
|
1711
1967
|
startKeyEnd = this.writeKey(startUnencoded, keyBuffer, 0);
|
|
1712
1968
|
if (startKeyEnd === 0) throw new Error("Zero length key is not allowed");
|
|
1713
1969
|
}
|
|
1714
|
-
if (endUnencoded !== void 0)
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1970
|
+
if (endUnencoded !== void 0) {
|
|
1971
|
+
if (endUnencoded === startUnencoded) {
|
|
1972
|
+
endKeyStart = 0;
|
|
1973
|
+
endKeyEnd = startKeyEnd;
|
|
1974
|
+
} else {
|
|
1975
|
+
endKeyStart = startKeyEnd;
|
|
1976
|
+
endKeyEnd = this.writeKey(endUnencoded, keyBuffer, endKeyStart);
|
|
1977
|
+
if (endKeyEnd === endKeyStart) throw new Error("Zero length key is not allowed");
|
|
1978
|
+
}
|
|
1721
1979
|
}
|
|
1722
1980
|
let flags = 0;
|
|
1723
1981
|
if (reverse) flags |= ITERATOR_REVERSE_FLAG;
|
|
@@ -1726,8 +1984,9 @@ var Store = class {
|
|
|
1726
1984
|
if (includeValues) flags |= ITERATOR_INCLUDE_VALUES_FLAG;
|
|
1727
1985
|
if (includeValues && !this.decoderCopies) flags |= ITERATOR_NEEDS_STABLE_VALUE_BUFFER_FLAG;
|
|
1728
1986
|
if (context !== this.db) flags |= ITERATOR_CONTEXT_IS_TRANSACTION_FLAG;
|
|
1987
|
+
if (txnId !== void 0) flags |= ITERATOR_HAS_TRANSACTION_ID_FLAG;
|
|
1729
1988
|
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;
|
|
1730
|
-
return new _harperfast_extended_iterable.ExtendedIterable(new DBIterator(new NativeIterator(context, flags, startKeyEnd, endKeyStart, endKeyEnd, advancedOptions), this, includeValues, options?.limit));
|
|
1989
|
+
return new _harperfast_extended_iterable.ExtendedIterable(new DBIterator(new NativeIterator(context, flags, startKeyEnd, endKeyStart, endKeyEnd, advancedOptions, txnId), this, includeValues, options?.limit));
|
|
1731
1990
|
}
|
|
1732
1991
|
getSync(context, key, alwaysCreateNewBuffer = false, options) {
|
|
1733
1992
|
const keyParam = getKeyParam(this.encodeKey(key));
|
|
@@ -1748,9 +2007,12 @@ var Store = class {
|
|
|
1748
2007
|
*/
|
|
1749
2008
|
getTxnId(options) {
|
|
1750
2009
|
let txnId;
|
|
1751
|
-
|
|
1752
|
-
|
|
2010
|
+
const transaction = options?.transaction;
|
|
2011
|
+
if (!this.readOnly && transaction) {
|
|
2012
|
+
txnId = transaction.id;
|
|
1753
2013
|
if (txnId === void 0) throw new TypeError("Invalid transaction");
|
|
2014
|
+
if (transaction.store === void 0) throw new TypeError("Invalid transaction");
|
|
2015
|
+
if (this.identityPath !== void 0 && transaction.store.identityPath !== this.identityPath) throw new TypeError("Transaction belongs to a different database");
|
|
1754
2016
|
}
|
|
1755
2017
|
return txnId;
|
|
1756
2018
|
}
|
|
@@ -1808,7 +2070,10 @@ var Store = class {
|
|
|
1808
2070
|
* are performed.
|
|
1809
2071
|
*/
|
|
1810
2072
|
open() {
|
|
1811
|
-
if (this.db.opened)
|
|
2073
|
+
if (this.db.opened) {
|
|
2074
|
+
this.identityPath = this.db.identityPath;
|
|
2075
|
+
return true;
|
|
2076
|
+
}
|
|
1812
2077
|
const { compression, compressionLevel } = normalizeCompression(this.compression);
|
|
1813
2078
|
this.db.open(this.path, {
|
|
1814
2079
|
compression,
|
|
@@ -1827,6 +2092,7 @@ var Store = class {
|
|
|
1827
2092
|
noBlockCache: this.noBlockCache,
|
|
1828
2093
|
parallelismThreads: this.parallelismThreads,
|
|
1829
2094
|
readOnly: this.readOnly,
|
|
2095
|
+
secondaryPath: this.secondaryPath,
|
|
1830
2096
|
statsLevel: this.statsLevel,
|
|
1831
2097
|
transactionLogMaxAgeThreshold: this.transactionLogMaxAgeThreshold,
|
|
1832
2098
|
transactionLogMaxSize: this.transactionLogMaxSize,
|
|
@@ -1835,6 +2101,7 @@ var Store = class {
|
|
|
1835
2101
|
verificationTable: this.verificationTable,
|
|
1836
2102
|
writeBufferSize: this.writeBufferSize
|
|
1837
2103
|
});
|
|
2104
|
+
this.identityPath = this.db.identityPath;
|
|
1838
2105
|
return false;
|
|
1839
2106
|
}
|
|
1840
2107
|
putSync(context, key, value, options) {
|
|
@@ -1879,7 +2146,7 @@ var Store = class {
|
|
|
1879
2146
|
useLog(context, name) {
|
|
1880
2147
|
if (typeof name !== "string" && typeof name !== "number") throw new TypeError("Log name must be a string or number");
|
|
1881
2148
|
if (typeof name === "string" && /[\t\n\r\\/]/.test(name)) throw new Error(`Invalid transaction log name "${name}"`);
|
|
1882
|
-
return context.useLog(String(name));
|
|
2149
|
+
return context.useLog(String(name), this.db);
|
|
1883
2150
|
}
|
|
1884
2151
|
/**
|
|
1885
2152
|
* Checks the process-global verification table for a fresh version match
|
|
@@ -1913,6 +2180,18 @@ var Store = class {
|
|
|
1913
2180
|
}
|
|
1914
2181
|
};
|
|
1915
2182
|
/**
|
|
2183
|
+
* Copies an encoded key out of the shared key buffer (which the next
|
|
2184
|
+
* `encodeKey` call would clobber), optionally appending a zero byte to
|
|
2185
|
+
* produce the key's bytewise successor.
|
|
2186
|
+
*/
|
|
2187
|
+
function copyEncodedKey(encoded, appendSuccessorByte) {
|
|
2188
|
+
const length = encoded.end - encoded.start;
|
|
2189
|
+
const copy = Buffer.allocUnsafe(length + (appendSuccessorByte ? 1 : 0));
|
|
2190
|
+
copy.set(encoded.subarray(encoded.start, encoded.end));
|
|
2191
|
+
if (appendSuccessorByte) copy[length] = 0;
|
|
2192
|
+
return copy;
|
|
2193
|
+
}
|
|
2194
|
+
/**
|
|
1916
2195
|
* Ensure that they key has been copied into our shared buffer, and return the ending position
|
|
1917
2196
|
* @param keyBuffer
|
|
1918
2197
|
*/
|
|
@@ -2083,10 +2362,12 @@ var Transaction = class extends DBI {
|
|
|
2083
2362
|
return err;
|
|
2084
2363
|
}
|
|
2085
2364
|
/**
|
|
2086
|
-
* Returns the transaction
|
|
2087
|
-
* the transaction
|
|
2365
|
+
* Returns the transaction timestamp in milliseconds since the Unix epoch. It
|
|
2366
|
+
* defaults to a process-wide monotonic value assigned when the transaction
|
|
2367
|
+
* was created. A producer may use this value as its write identity; the
|
|
2368
|
+
* transaction log uses it as the batch key.
|
|
2088
2369
|
*
|
|
2089
|
-
* @returns The transaction
|
|
2370
|
+
* @returns The transaction timestamp in milliseconds.
|
|
2090
2371
|
*/
|
|
2091
2372
|
getTimestamp() {
|
|
2092
2373
|
return this.#txn.getTimestamp();
|
|
@@ -2098,9 +2379,14 @@ var Transaction = class extends DBI {
|
|
|
2098
2379
|
return this.#txn.id;
|
|
2099
2380
|
}
|
|
2100
2381
|
/**
|
|
2101
|
-
*
|
|
2382
|
+
* Overrides the transaction timestamp in milliseconds since the Unix epoch.
|
|
2383
|
+
* Call this before reading the timestamp or staging writes when adopting an
|
|
2384
|
+
* origin timestamp for replication or replay. Once a write or log entry is
|
|
2385
|
+
* staged, the timestamp is frozen, but reapplying the same timestamp remains
|
|
2386
|
+
* idempotent while the transaction is pending. The value must be finite,
|
|
2387
|
+
* positive, and below 8640000000000000.
|
|
2102
2388
|
*
|
|
2103
|
-
* @param timestamp - The timestamp to set in
|
|
2389
|
+
* @param timestamp - The timestamp to set in milliseconds.
|
|
2104
2390
|
*/
|
|
2105
2391
|
setTimestamp(timestamp) {
|
|
2106
2392
|
this.#txn.setTimestamp(timestamp);
|
|
@@ -2109,6 +2395,16 @@ var Transaction = class extends DBI {
|
|
|
2109
2395
|
|
|
2110
2396
|
//#endregion
|
|
2111
2397
|
//#region src/database.ts
|
|
2398
|
+
const BACKGROUND_ERROR_SEVERITY_NAMES = [
|
|
2399
|
+
"none",
|
|
2400
|
+
"soft",
|
|
2401
|
+
"hard",
|
|
2402
|
+
"fatal",
|
|
2403
|
+
"unrecoverable"
|
|
2404
|
+
];
|
|
2405
|
+
function backgroundErrorSeverityName(severity) {
|
|
2406
|
+
return BACKGROUND_ERROR_SEVERITY_NAMES[severity] ?? "unknown";
|
|
2407
|
+
}
|
|
2112
2408
|
/**
|
|
2113
2409
|
* The main class for interacting with a RocksDB database.
|
|
2114
2410
|
*
|
|
@@ -2124,9 +2420,23 @@ var Transaction = class extends DBI {
|
|
|
2124
2420
|
*/
|
|
2125
2421
|
var RocksDatabase = class RocksDatabase extends DBI {
|
|
2126
2422
|
/**
|
|
2127
|
-
* The name of the database.
|
|
2423
|
+
* The name of the database's column family.
|
|
2128
2424
|
*/
|
|
2129
2425
|
#name;
|
|
2426
|
+
/**
|
|
2427
|
+
* Weak cache of column-family views opened via `use()`, keyed by column
|
|
2428
|
+
* family name — mirroring `useLog`'s get-or-create semantics. The reference
|
|
2429
|
+
* is weak so a view is never pinned for this database's lifetime: an
|
|
2430
|
+
* unreferenced view can be collected (closing its own `DBHandle`), and the
|
|
2431
|
+
* next `use()` transparently recreates it. `#columnFamilyRegistry` deletes an
|
|
2432
|
+
* entry once its view is collected so a churn of distinct names (e.g.
|
|
2433
|
+
* `use(uuid())`) does not grow the map without bound.
|
|
2434
|
+
*/
|
|
2435
|
+
#columnFamilies = /* @__PURE__ */ new Map();
|
|
2436
|
+
#columnFamilyRegistry = new FinalizationRegistry((name) => {
|
|
2437
|
+
const ref = this.#columnFamilies.get(name);
|
|
2438
|
+
if (ref !== void 0 && ref.deref() === void 0) this.#columnFamilies.delete(name);
|
|
2439
|
+
});
|
|
2130
2440
|
constructor(pathOrStore, options) {
|
|
2131
2441
|
let store;
|
|
2132
2442
|
if (typeof pathOrStore === "string") store = new Store(pathOrStore, options);
|
|
@@ -2134,7 +2444,7 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
2134
2444
|
else throw new TypeError("Invalid database path or store");
|
|
2135
2445
|
store.claim();
|
|
2136
2446
|
super(store);
|
|
2137
|
-
this.#name =
|
|
2447
|
+
this.#name = this.store.name;
|
|
2138
2448
|
}
|
|
2139
2449
|
/**
|
|
2140
2450
|
* Removes all data from the database asynchronously.
|
|
@@ -2180,6 +2490,9 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
2180
2490
|
* Compacts the entire key range of the database asynchronously.
|
|
2181
2491
|
* This triggers manual compaction which removes tombstones and reclaims space.
|
|
2182
2492
|
*
|
|
2493
|
+
* On a read-only database this is a no-op: arguments are still validated, but
|
|
2494
|
+
* the returned promise resolves without compacting rather than rejecting.
|
|
2495
|
+
*
|
|
2183
2496
|
* @example
|
|
2184
2497
|
* ```typescript
|
|
2185
2498
|
* const db = RocksDatabase.open('/path/to/database');
|
|
@@ -2231,6 +2544,9 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
2231
2544
|
* Compacts the entire key range of the database synchronously.
|
|
2232
2545
|
* This triggers manual compaction which removes tombstones and reclaims space.
|
|
2233
2546
|
*
|
|
2547
|
+
* On a read-only database this validates its arguments and then returns
|
|
2548
|
+
* without compacting, rather than throwing.
|
|
2549
|
+
*
|
|
2234
2550
|
* @example
|
|
2235
2551
|
* ```typescript
|
|
2236
2552
|
* const db = RocksDatabase.open('/path/to/database');
|
|
@@ -2347,6 +2663,40 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
2347
2663
|
static notify(event, ...args) {
|
|
2348
2664
|
return globalNotify(event, args);
|
|
2349
2665
|
}
|
|
2666
|
+
/**
|
|
2667
|
+
* Advances a secondary instance to the primary's current state by tailing
|
|
2668
|
+
* and replaying the primary's MANIFEST and WAL. A secondary does not see
|
|
2669
|
+
* the primary's writes until this (or {@link catchUpWithPrimarySync}) is
|
|
2670
|
+
* called. Concurrent reads through this handle remain safe while the
|
|
2671
|
+
* catch-up runs.
|
|
2672
|
+
*
|
|
2673
|
+
* Only valid on a database opened with the `secondaryPath` option; throws
|
|
2674
|
+
* with code `ERR_NOT_SECONDARY` otherwise.
|
|
2675
|
+
*
|
|
2676
|
+
* Advances the **database** view only. Against a cross-process primary,
|
|
2677
|
+
* transaction-log reads keep serving the stores discovered at open (a store
|
|
2678
|
+
* the primary creates later, and its appends, need a reopen); a primary in
|
|
2679
|
+
* the same process shares the store registry, so its stores and appends are
|
|
2680
|
+
* visible immediately. Either way a log entry can describe data this
|
|
2681
|
+
* handle's database view does not have yet — the log write completes before
|
|
2682
|
+
* the RocksDB commit for every writer, so consumers must tolerate the log
|
|
2683
|
+
* leading the database.
|
|
2684
|
+
*
|
|
2685
|
+
* Await one call before starting the next: the replay holds a libuv worker
|
|
2686
|
+
* for its whole duration and concurrent calls queue on a per-database mutex
|
|
2687
|
+
* while holding theirs, so overlapping catch-ups on a backlogged follower
|
|
2688
|
+
* can exhaust the default four-thread pool and stall unrelated I/O.
|
|
2689
|
+
*/
|
|
2690
|
+
catchUpWithPrimary() {
|
|
2691
|
+
return new Promise((resolve, reject) => this.store.db.catchUpWithPrimary(resolve, reject));
|
|
2692
|
+
}
|
|
2693
|
+
/**
|
|
2694
|
+
* Synchronous version of {@link catchUpWithPrimary}. The replay runs on the
|
|
2695
|
+
* JS thread, so prefer the async form unless the caller is already blocking.
|
|
2696
|
+
*/
|
|
2697
|
+
catchUpWithPrimarySync() {
|
|
2698
|
+
this.store.db.catchUpWithPrimarySync();
|
|
2699
|
+
}
|
|
2350
2700
|
destroy() {
|
|
2351
2701
|
this.store.db.destroy();
|
|
2352
2702
|
}
|
|
@@ -2364,18 +2714,107 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
2364
2714
|
/**
|
|
2365
2715
|
* Flushes the underlying database by performing a commit or clearing any buffered operations.
|
|
2366
2716
|
*
|
|
2717
|
+
* On a read-only database this is a no-op: the options bag is still validated, but the
|
|
2718
|
+
* returned promise resolves without flushing rather than rejecting.
|
|
2719
|
+
*
|
|
2720
|
+
* @param options - Flush options; see {@link FlushOptions.allowWriteStall} before relying on
|
|
2721
|
+
* this resolving promptly under write pressure.
|
|
2367
2722
|
* @return {void} Does not return a value.
|
|
2368
2723
|
*/
|
|
2369
|
-
flush() {
|
|
2370
|
-
return new Promise((resolve, reject) => this.store.db.flush(resolve, reject));
|
|
2724
|
+
flush(options) {
|
|
2725
|
+
return new Promise((resolve, reject) => this.store.db.flush(resolve, reject, options));
|
|
2371
2726
|
}
|
|
2372
2727
|
/**
|
|
2373
2728
|
* Synchronously flushes the underlying database by performing a commit or clearing any buffered operations.
|
|
2374
2729
|
*
|
|
2730
|
+
* The default `allowWriteStall: false` wait is taken on the JS thread here, so it freezes the
|
|
2731
|
+
* event loop rather than parking a libuv worker — a larger blast radius than {@link flush}, not
|
|
2732
|
+
* a smaller one.
|
|
2733
|
+
*
|
|
2734
|
+
* On a read-only database this validates its options and then returns without flushing, rather
|
|
2735
|
+
* than throwing.
|
|
2736
|
+
*
|
|
2737
|
+
* @param options - Flush options; see {@link FlushOptions.allowWriteStall}.
|
|
2375
2738
|
* @return {void} Does not return a value.
|
|
2376
2739
|
*/
|
|
2377
|
-
flushSync() {
|
|
2378
|
-
return this.store.db.flushSync();
|
|
2740
|
+
flushSync(options) {
|
|
2741
|
+
return this.store.db.flushSync(options);
|
|
2742
|
+
}
|
|
2743
|
+
/**
|
|
2744
|
+
* Returns the most recent {@link BackgroundError} observed on this database, or
|
|
2745
|
+
* `null` when none has occurred. The `'error'` event is the push counterpart;
|
|
2746
|
+
* this is the pull equivalent for on-demand checks (e.g. a health probe) and
|
|
2747
|
+
* for catching an error that fired after open but before a listener was
|
|
2748
|
+
* attached. (A background error that occurs *during* the initial open is a
|
|
2749
|
+
* narrow startup window not yet captured, addressed separately.)
|
|
2750
|
+
*
|
|
2751
|
+
* Purely historical — it is not cleared by a successful {@link RocksDatabase.resume}.
|
|
2752
|
+
* When the returned error's `writesDisabled` is `true`, writes are stopped
|
|
2753
|
+
* until recovery.
|
|
2754
|
+
*
|
|
2755
|
+
* @example
|
|
2756
|
+
* ```typescript
|
|
2757
|
+
* const err = db.getLastError();
|
|
2758
|
+
* if (err?.writesDisabled) {
|
|
2759
|
+
* // ...free disk space...
|
|
2760
|
+
* db.resume();
|
|
2761
|
+
* }
|
|
2762
|
+
* ```
|
|
2763
|
+
*/
|
|
2764
|
+
getLastError() {
|
|
2765
|
+
return this.store.db.getLastError();
|
|
2766
|
+
}
|
|
2767
|
+
/**
|
|
2768
|
+
* Sets or clears the last background error, mirroring the Win32
|
|
2769
|
+
* `SetLastError`/`GetLastError` pair. Pass `null` (or no argument) to **reset**
|
|
2770
|
+
* it — {@link RocksDatabase.getLastError} then returns `null` and no event
|
|
2771
|
+
* fires; this is the intended way to clear the error after handling or
|
|
2772
|
+
* recovering it (e.g. after {@link RocksDatabase.resume}). Pass an object to
|
|
2773
|
+
* set it: it is stored and the `'error'` event fires with the reconstructed
|
|
2774
|
+
* {@link BackgroundError}. `type` defaults to `'background'`.
|
|
2775
|
+
*
|
|
2776
|
+
* @example
|
|
2777
|
+
* ```typescript
|
|
2778
|
+
* db.resume();
|
|
2779
|
+
* db.setLastError(null); // recovered — clear the historical error
|
|
2780
|
+
* ```
|
|
2781
|
+
*/
|
|
2782
|
+
setLastError(error) {
|
|
2783
|
+
if (error == null) return this.store.db.setLastError(null);
|
|
2784
|
+
const severity = error.severity ?? 0;
|
|
2785
|
+
return this.store.db.setLastError({
|
|
2786
|
+
...error,
|
|
2787
|
+
type: error.type ?? "background",
|
|
2788
|
+
severity,
|
|
2789
|
+
severityName: error.severityName ?? backgroundErrorSeverityName(severity),
|
|
2790
|
+
writesDisabled: error.writesDisabled ?? severity >= 2
|
|
2791
|
+
});
|
|
2792
|
+
}
|
|
2793
|
+
/**
|
|
2794
|
+
* Attempts to recover the database from a background error by calling
|
|
2795
|
+
* RocksDB's `DB::Resume()`. When a write fails at the filesystem level (e.g. a
|
|
2796
|
+
* full disk), RocksDB stops accepting writes and this database emits an
|
|
2797
|
+
* {@link BackgroundError} via the `'error'` event. Call `resume()` after the
|
|
2798
|
+
* underlying condition has cleared — e.g. once disk space has been freed. On
|
|
2799
|
+
* success writes are accepted again; on failure (the condition has not
|
|
2800
|
+
* actually cleared) it throws and the database stays read-only. A no-op on a
|
|
2801
|
+
* healthy database.
|
|
2802
|
+
*
|
|
2803
|
+
* Runs synchronously and may briefly block, since recovery can re-flush
|
|
2804
|
+
* memtables.
|
|
2805
|
+
*
|
|
2806
|
+
* @example
|
|
2807
|
+
* ```typescript
|
|
2808
|
+
* db.on('error', (err) => {
|
|
2809
|
+
* if (err.writesDisabled) {
|
|
2810
|
+
* // ...free disk space...
|
|
2811
|
+
* db.resume();
|
|
2812
|
+
* }
|
|
2813
|
+
* });
|
|
2814
|
+
* ```
|
|
2815
|
+
*/
|
|
2816
|
+
resume() {
|
|
2817
|
+
return this.store.db.resume();
|
|
2379
2818
|
}
|
|
2380
2819
|
/**
|
|
2381
2820
|
* Gets a RocksDB database property as an integer.
|
|
@@ -2410,7 +2849,32 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
2410
2849
|
return this.store.db.getDBProperty(propertyName);
|
|
2411
2850
|
}
|
|
2412
2851
|
/**
|
|
2413
|
-
*
|
|
2852
|
+
* Whether RocksDB is currently applying write backpressure to this database —
|
|
2853
|
+
* either delaying (rate-limiting) or fully stopping writes. This is the
|
|
2854
|
+
* authoritative, live pull counterpart to the `'writeStall'`
|
|
2855
|
+
* [event](../README.md#event-writestall): the event pushes per-column-family
|
|
2856
|
+
* transitions (and is rate-limited), while this reads the current state on
|
|
2857
|
+
* demand and can never be stale.
|
|
2858
|
+
*
|
|
2859
|
+
* The write controller is database-wide (shared across every column family),
|
|
2860
|
+
* so this reflects the database as a whole, not a single column family — a
|
|
2861
|
+
* stall triggered by any column family stops writes for all of them. Reads
|
|
2862
|
+
* `rocksdb.is-write-stopped` and `rocksdb.actual-delayed-write-rate`.
|
|
2863
|
+
*
|
|
2864
|
+
* @example
|
|
2865
|
+
* ```typescript
|
|
2866
|
+
* db.on('writeStall', (cf) => {
|
|
2867
|
+
* if (db.isWriteStalled()) log.warn(`writes throttled/blocked (triggered by ${cf})`);
|
|
2868
|
+
* });
|
|
2869
|
+
* ```
|
|
2870
|
+
*/
|
|
2871
|
+
isWriteStalled() {
|
|
2872
|
+
return this.getDBIntProperty("rocksdb.is-write-stopped") === 1 || (this.getDBIntProperty("rocksdb.actual-delayed-write-rate") ?? 0) > 0;
|
|
2873
|
+
}
|
|
2874
|
+
/**
|
|
2875
|
+
* Retrieves the estimated number of keys in the database. This is an alias
|
|
2876
|
+
* for `db.estimateCount().count`; use `estimateCount()` for range support
|
|
2877
|
+
* and a confidence indicator.
|
|
2414
2878
|
*
|
|
2415
2879
|
* @example
|
|
2416
2880
|
* ```typescript
|
|
@@ -2423,6 +2887,58 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
2423
2887
|
return this.getDBIntProperty("rocksdb.estimate-num-keys") ?? 0;
|
|
2424
2888
|
}
|
|
2425
2889
|
/**
|
|
2890
|
+
* Estimates the number of keys in the database, or within a key range,
|
|
2891
|
+
* returning `{ count, confidence }`. Unlike `getKeysCount()`, this never
|
|
2892
|
+
* iterates: the estimate is derived from RocksDB statistics (memtable
|
|
2893
|
+
* stats plus approximate SST sizes converted through the entry density of
|
|
2894
|
+
* the SSTs overlapping the range), so its cost scales with the number of
|
|
2895
|
+
* SSTs overlapping the range rather than the number of keys — though
|
|
2896
|
+
* reading table properties for cold files can do I/O through the table
|
|
2897
|
+
* cache, and a start-only range does the work of its complement below
|
|
2898
|
+
* `start`. Accuracy improves with range size. Resolution is bounded by SST
|
|
2899
|
+
* data-block granularity, so tiny ranges may over-report or report zero for
|
|
2900
|
+
* present keys; low `confidence` is the signal. Recently deleted or
|
|
2901
|
+
* overwritten entries may be counted until compaction. An inverted range
|
|
2902
|
+
* (`start` >= `end`) returns `{ count: 0, confidence: 1 }`.
|
|
2903
|
+
*
|
|
2904
|
+
* `confidence` is a heuristic 0–1 trust indicator (1 only when exact) —
|
|
2905
|
+
* see `CountEstimate`. Estimates always reflect committed state; writes
|
|
2906
|
+
* pending in a transaction are not included.
|
|
2907
|
+
*
|
|
2908
|
+
* @example
|
|
2909
|
+
* ```typescript
|
|
2910
|
+
* const db = RocksDatabase.open('/path/to/database');
|
|
2911
|
+
* const { count, confidence } = db.estimateCount({ start: 'a', end: 'z' });
|
|
2912
|
+
* ```
|
|
2913
|
+
*/
|
|
2914
|
+
estimateCount(options) {
|
|
2915
|
+
return this.store.estimateCount(options);
|
|
2916
|
+
}
|
|
2917
|
+
/**
|
|
2918
|
+
* Creates a `CountEstimator` for progressively refining a range count
|
|
2919
|
+
* estimate while iterating the range: report progress with
|
|
2920
|
+
* `advance(lastKey, count)` (e.g. once per page of results) and
|
|
2921
|
+
* `estimate()` returns the exact traversed count plus a calibrated
|
|
2922
|
+
* estimate of the remainder, converging toward the exact total. Call
|
|
2923
|
+
* `finish()` when traversal completes to make `estimate()` exact.
|
|
2924
|
+
*
|
|
2925
|
+
* @example
|
|
2926
|
+
* ```typescript
|
|
2927
|
+
* const estimator = db.createCountEstimator({ start: 'a', end: 'z' });
|
|
2928
|
+
* let lastKey;
|
|
2929
|
+
* let pageSize = 0;
|
|
2930
|
+
* for (const { key } of db.getRange({ start: 'a', end: 'z', limit: 25 })) {
|
|
2931
|
+
* lastKey = key;
|
|
2932
|
+
* pageSize++;
|
|
2933
|
+
* }
|
|
2934
|
+
* estimator.advance(lastKey, pageSize);
|
|
2935
|
+
* const { count, confidence } = estimator.estimate();
|
|
2936
|
+
* ```
|
|
2937
|
+
*/
|
|
2938
|
+
createCountEstimator(options) {
|
|
2939
|
+
return new CountEstimator(this.store, options);
|
|
2940
|
+
}
|
|
2941
|
+
/**
|
|
2426
2942
|
* Returns the current timestamp as a monotonically increasing timestamp in
|
|
2427
2943
|
* milliseconds represented as a decimal number.
|
|
2428
2944
|
*
|
|
@@ -2533,18 +3049,29 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
2533
3049
|
return this.store.listLogs();
|
|
2534
3050
|
}
|
|
2535
3051
|
/**
|
|
2536
|
-
* The name of the database.
|
|
3052
|
+
* The name of the database's column family.
|
|
2537
3053
|
*/
|
|
2538
3054
|
get name() {
|
|
2539
3055
|
return this.#name;
|
|
2540
3056
|
}
|
|
2541
3057
|
/**
|
|
2542
|
-
* Whether the database is open in readonly mode.
|
|
3058
|
+
* Whether the database is open in readonly mode. Also `true` for a
|
|
3059
|
+
* secondary instance (see {@link secondaryPath}), which is read-only by
|
|
3060
|
+
* construction.
|
|
2543
3061
|
*/
|
|
2544
3062
|
get readOnly() {
|
|
2545
3063
|
return this.store.readOnly;
|
|
2546
3064
|
}
|
|
2547
3065
|
/**
|
|
3066
|
+
* The secondary instance's workspace directory when the database was opened
|
|
3067
|
+
* as a secondary (a read-only follower of a live primary — see the
|
|
3068
|
+
* `secondaryPath` open option), or `undefined` for a regular or plain
|
|
3069
|
+
* read-only open.
|
|
3070
|
+
*/
|
|
3071
|
+
get secondaryPath() {
|
|
3072
|
+
return this.store.secondaryPath;
|
|
3073
|
+
}
|
|
3074
|
+
/**
|
|
2548
3075
|
* Sugar method for opening a database.
|
|
2549
3076
|
*
|
|
2550
3077
|
* @param pathOrStore - The filesystem path to the database or a custom store.
|
|
@@ -2817,6 +3344,57 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
2817
3344
|
return this.store.unlock(key);
|
|
2818
3345
|
}
|
|
2819
3346
|
/**
|
|
3347
|
+
* Returns a `RocksDatabase` bound to the `name` column family of this same
|
|
3348
|
+
* database, opening (and creating, if missing) the column family on first
|
|
3349
|
+
* use. This is a factory — create-and-open sugar — not a stateful switch: the
|
|
3350
|
+
* returned view is an independent instance whose own reads/writes target its
|
|
3351
|
+
* column family, while sharing the underlying database (so a transaction,
|
|
3352
|
+
* backup, or checkpoint still spans every column family). Because the view
|
|
3353
|
+
* opens its own handle, this database need not itself be open — a discarded
|
|
3354
|
+
* parent (`new RocksDatabase(path).use('events')`) still yields a valid,
|
|
3355
|
+
* opened view.
|
|
3356
|
+
*
|
|
3357
|
+
* Get-or-create, backed by a weak cache: `db.use('events') === db.use('events')`
|
|
3358
|
+
* while the view is still referenced **and open**, and calling with this
|
|
3359
|
+
* database's own column-family name returns `this`. A view that has been
|
|
3360
|
+
* closed (or garbage-collected — the cache does not pin it) is transparently
|
|
3361
|
+
* recreated on the next `use()`. Views are independent handles: closing this
|
|
3362
|
+
* database does not close them, and vice versa — the underlying database
|
|
3363
|
+
* stays open until every handle is closed or collected.
|
|
3364
|
+
*
|
|
3365
|
+
* The view's store is derived by {@link Store#createColumnFamilyStore}, which
|
|
3366
|
+
* builds an independent store of the same class with its own codec state (so
|
|
3367
|
+
* views never share a mutable encoder/decoder) and inherits the options this
|
|
3368
|
+
* store was constructed with; pass `options` to override them for this column
|
|
3369
|
+
* family. A database configured with a pre-constructed encoder/decoder
|
|
3370
|
+
* instance cannot derive views (its state would be shared) — use an encoder
|
|
3371
|
+
* factory (`{ Encoder }`) or a named `encoding`, or override
|
|
3372
|
+
* `createColumnFamilyStore` on a custom `Store`.
|
|
3373
|
+
*
|
|
3374
|
+
* @param name - The column family name.
|
|
3375
|
+
* @param options - Options for the column family, overriding inherited ones.
|
|
3376
|
+
* @returns A `RocksDatabase` bound to the column family.
|
|
3377
|
+
*
|
|
3378
|
+
* @example
|
|
3379
|
+
* ```typescript
|
|
3380
|
+
* const db = RocksDatabase.open('/path/to/database');
|
|
3381
|
+
* const events = db.use('events');
|
|
3382
|
+
* await events.put('e1', payload);
|
|
3383
|
+
* await db.put('k', 'v'); // default column family, unaffected
|
|
3384
|
+
* ```
|
|
3385
|
+
*/
|
|
3386
|
+
use(name, options) {
|
|
3387
|
+
if (typeof name !== "string" || name === "") throw new TypeError("Column family name must be a non-empty string");
|
|
3388
|
+
if (name === this.#name) return this.open();
|
|
3389
|
+
const cached = this.#columnFamilies.get(name)?.deref();
|
|
3390
|
+
if (cached !== void 0 && cached.isOpen()) return cached;
|
|
3391
|
+
const columnFamily = new RocksDatabase(this.store.createColumnFamilyStore(name, options));
|
|
3392
|
+
columnFamily.open();
|
|
3393
|
+
this.#columnFamilies.set(name, new WeakRef(columnFamily));
|
|
3394
|
+
this.#columnFamilyRegistry.register(columnFamily, name);
|
|
3395
|
+
return columnFamily;
|
|
3396
|
+
}
|
|
3397
|
+
/**
|
|
2820
3398
|
* Excecutes a function using a thread-safe lock to ensure mutual
|
|
2821
3399
|
* exclusion.
|
|
2822
3400
|
*
|
|
@@ -2928,6 +3506,98 @@ const FLOAT_TO_UINT32 = /* @__PURE__ */ new Float64Array(1);
|
|
|
2928
3506
|
const UINT32_FROM_FLOAT = new Uint32Array(FLOAT_TO_UINT32.buffer);
|
|
2929
3507
|
const { TRANSACTION_LOG_FILE_HEADER_SIZE, TRANSACTION_LOG_ENTRY_HEADER_SIZE } = constants;
|
|
2930
3508
|
/**
|
|
3509
|
+
* Number of consecutive well-formed frames that, on their own, signal that real log data has
|
|
3510
|
+
* resumed after a framing break. Kept in step with `RESYNC_MIN_FRAMES` in
|
|
3511
|
+
* `src/binding/transaction_log/transaction_log_recovery.cpp`, which uses the same heuristic to
|
|
3512
|
+
* classify a break as mid-file corruption (valid entries follow) rather than a torn tail.
|
|
3513
|
+
*/
|
|
3514
|
+
const RESYNC_MIN_FRAMES = 8;
|
|
3515
|
+
/**
|
|
3516
|
+
* Thrown when an entry's framing is broken. `resyncPosition` is the offset where valid framing
|
|
3517
|
+
* resumes, when one was found: the frames after a mid-log break are intact and reachable, and
|
|
3518
|
+
* only the broken span between `position` and `resyncPosition` is unreadable. It is `undefined`
|
|
3519
|
+
* for a torn tail, where nothing follows the break.
|
|
3520
|
+
*
|
|
3521
|
+
* The iterator is left positioned at `resyncPosition` (or at end-of-log when there is none), so a
|
|
3522
|
+
* caller that chooses to recover the entries past the break calls `next()` again.
|
|
3523
|
+
*/
|
|
3524
|
+
var CorruptFrameError = class extends RangeError {
|
|
3525
|
+
/** Sequence number of the log file the break is in. */
|
|
3526
|
+
logId;
|
|
3527
|
+
/** Offset of the broken frame. */
|
|
3528
|
+
position;
|
|
3529
|
+
/** Offset where valid framing resumes, or `undefined` when nothing valid follows. */
|
|
3530
|
+
resyncPosition;
|
|
3531
|
+
/** Bytes between the break and the resume point that cannot be read. */
|
|
3532
|
+
unreadableBytes;
|
|
3533
|
+
constructor(message, logId, position, resyncPosition) {
|
|
3534
|
+
super(resyncPosition === void 0 ? message : `${message}; valid framing resumes at ${resyncPosition.toString(16)}, ${resyncPosition - position} byte(s) unreadable`);
|
|
3535
|
+
this.name = "CorruptFrameError";
|
|
3536
|
+
this.logId = logId;
|
|
3537
|
+
this.position = position;
|
|
3538
|
+
this.resyncPosition = resyncPosition;
|
|
3539
|
+
this.unreadableBytes = resyncPosition === void 0 ? 0 : resyncPosition - position;
|
|
3540
|
+
}
|
|
3541
|
+
};
|
|
3542
|
+
/**
|
|
3543
|
+
* Whether a complete, in-bounds frame begins at `pos`. The only sane bound on an entry's length is
|
|
3544
|
+
* the written extent: a single entry can legitimately exceed the rotation threshold (the first
|
|
3545
|
+
* entry written to a fresh file is always written in full). A zero timestamp is the
|
|
3546
|
+
* end-of-entries marker, not a frame.
|
|
3547
|
+
*/
|
|
3548
|
+
function frameFits(dataView, pos, dataEnd) {
|
|
3549
|
+
if (pos + TRANSACTION_LOG_ENTRY_HEADER_SIZE > dataEnd) return false;
|
|
3550
|
+
if (dataView.getFloat64(pos) === 0) return false;
|
|
3551
|
+
const length = dataView.getUint32(pos + 8);
|
|
3552
|
+
if (length === 0) return false;
|
|
3553
|
+
return pos + TRANSACTION_LOG_ENTRY_HEADER_SIZE + length <= dataEnd;
|
|
3554
|
+
}
|
|
3555
|
+
/**
|
|
3556
|
+
* Finds the offset in `[from, dataEnd)` where valid log framing resumes: a run of at least
|
|
3557
|
+
* `RESYNC_MIN_FRAMES` well-formed frames, or a run that lands exactly on `dataEnd`. Returns
|
|
3558
|
+
* `undefined` when nothing resumes (a torn tail).
|
|
3559
|
+
*
|
|
3560
|
+
* `dataEnd` must be the **written extent**, not the mapped capacity. The log's memory map is
|
|
3561
|
+
* pre-extended well past the data, and every offset inside that zero padding reads as an
|
|
3562
|
+
* end-of-entries marker — so scanning against the map would both lose the exact-end signal (the
|
|
3563
|
+
* chain can never land on a capacity that data does not reach) and, if a zero were accepted as a
|
|
3564
|
+
* terminator, let a chain "end" anywhere in megabytes of padding. Against the written extent the
|
|
3565
|
+
* end is a single offset, so a chain landing on it is ~1/2^32 to be coincidence.
|
|
3566
|
+
*
|
|
3567
|
+
* The JS counterpart of `validFramingResumes()` in `transaction_log_recovery.cpp`, which answers
|
|
3568
|
+
* the same question at open time but keeps only the yes/no, discarding the offset.
|
|
3569
|
+
*/
|
|
3570
|
+
function findResyncPosition(dataView, from, dataEnd) {
|
|
3571
|
+
const lastStart = dataEnd - TRANSACTION_LOG_ENTRY_HEADER_SIZE;
|
|
3572
|
+
for (let start = from; start <= lastStart; start++) {
|
|
3573
|
+
let pos = start;
|
|
3574
|
+
let frames = 0;
|
|
3575
|
+
while (frames < RESYNC_MIN_FRAMES && frameFits(dataView, pos, dataEnd)) {
|
|
3576
|
+
pos += TRANSACTION_LOG_ENTRY_HEADER_SIZE + dataView.getUint32(pos + 8);
|
|
3577
|
+
if (++frames >= RESYNC_MIN_FRAMES || pos === dataEnd) return start;
|
|
3578
|
+
}
|
|
3579
|
+
}
|
|
3580
|
+
}
|
|
3581
|
+
/**
|
|
3582
|
+
* Builds the report for a broken frame, with the offset iteration should continue from. Resolves
|
|
3583
|
+
* the written extent here rather than per frame: `getLogFileSize` crosses into native and takes
|
|
3584
|
+
* the store mutex, which a healthy read must never pay.
|
|
3585
|
+
*/
|
|
3586
|
+
function corruptFrame(message, transactionLog, dataView, logBuffer, position, limit, size, readUncommitted) {
|
|
3587
|
+
let dataEnd = limit;
|
|
3588
|
+
if (readUncommitted) try {
|
|
3589
|
+
const writtenExtent = transactionLog.getLogFileSize(logBuffer.logId);
|
|
3590
|
+
dataEnd = writtenExtent > 0 ? Math.min(logBuffer.length, writtenExtent) : 0;
|
|
3591
|
+
} catch {
|
|
3592
|
+
dataEnd = 0;
|
|
3593
|
+
}
|
|
3594
|
+
const resyncPosition = findResyncPosition(dataView, position + 1, dataEnd);
|
|
3595
|
+
return {
|
|
3596
|
+
error: new CorruptFrameError(message, logBuffer.logId, position, resyncPosition),
|
|
3597
|
+
nextPosition: resyncPosition ?? Math.max(dataEnd || limit, size, position + 1)
|
|
3598
|
+
};
|
|
3599
|
+
}
|
|
3600
|
+
/**
|
|
2931
3601
|
* Returns an iterable for transaction entries within the specified range of timestamps
|
|
2932
3602
|
* This iterable can be iterated over multiple times, and subsequent iterations will continue
|
|
2933
3603
|
* from where the last iteration left off, allowing for iteration through the log file
|
|
@@ -3017,17 +3687,26 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3017
3687
|
value: void 0
|
|
3018
3688
|
};
|
|
3019
3689
|
const limit = readUncommitted ? logBuffer.length : Math.min(size, logBuffer.length);
|
|
3020
|
-
if (position + TRANSACTION_LOG_ENTRY_HEADER_SIZE > limit)
|
|
3690
|
+
if (position + TRANSACTION_LOG_ENTRY_HEADER_SIZE > limit) {
|
|
3691
|
+
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);
|
|
3692
|
+
position = broken.nextPosition;
|
|
3693
|
+
throw broken.error;
|
|
3694
|
+
}
|
|
3021
3695
|
const length = dataView.getUint32(position + 8);
|
|
3022
|
-
if (position + TRANSACTION_LOG_ENTRY_HEADER_SIZE + length > limit)
|
|
3696
|
+
if (length === 0 || position + TRANSACTION_LOG_ENTRY_HEADER_SIZE + length > limit) {
|
|
3697
|
+
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);
|
|
3698
|
+
position = broken.nextPosition;
|
|
3699
|
+
throw broken.error;
|
|
3700
|
+
}
|
|
3023
3701
|
position += TRANSACTION_LOG_ENTRY_HEADER_SIZE;
|
|
3024
3702
|
let matchesRange;
|
|
3025
3703
|
if (foundExactStart) matchesRange = (!exclusiveStart || timestamp !== start) && timestamp < end;
|
|
3026
|
-
else if (exactStart)
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3704
|
+
else if (exactStart) {
|
|
3705
|
+
if (timestamp === start) {
|
|
3706
|
+
matchesRange = !exclusiveStart;
|
|
3707
|
+
foundExactStart = true;
|
|
3708
|
+
} else matchesRange = false;
|
|
3709
|
+
} else matchesRange = (exclusiveStart ? timestamp > start : timestamp >= start) && timestamp < end;
|
|
3031
3710
|
const entryStart = position;
|
|
3032
3711
|
position += length;
|
|
3033
3712
|
if (matchesRange) return {
|
|
@@ -3110,10 +3789,13 @@ function loadLastPosition(transactionLog, readUncommitted) {
|
|
|
3110
3789
|
//#region src/index.ts
|
|
3111
3790
|
const versions = {
|
|
3112
3791
|
rocksdb: version,
|
|
3113
|
-
"rocksdb-js": "2.
|
|
3792
|
+
"rocksdb-js": "2.9.0"
|
|
3114
3793
|
};
|
|
3115
3794
|
|
|
3116
3795
|
//#endregion
|
|
3796
|
+
exports.BackgroundError = BackgroundError;
|
|
3797
|
+
exports.CorruptFrameError = CorruptFrameError;
|
|
3798
|
+
exports.CountEstimator = CountEstimator;
|
|
3117
3799
|
exports.DBI = DBI;
|
|
3118
3800
|
exports.DBIterator = DBIterator;
|
|
3119
3801
|
exports.RocksDatabase = RocksDatabase;
|
|
@@ -3125,6 +3807,7 @@ exports.constants = constants;
|
|
|
3125
3807
|
exports.coolTransactionLogs = coolTransactionLogs;
|
|
3126
3808
|
exports.currentThreadId = currentThreadId;
|
|
3127
3809
|
exports.fileLockRelease = fileLockRelease;
|
|
3810
|
+
exports.getWriteBufferManagerStats = getWriteBufferManagerStats;
|
|
3128
3811
|
exports.parseTransactionLog = parseTransactionLog;
|
|
3129
3812
|
exports.registryStatus = registryStatus;
|
|
3130
3813
|
exports.shutdown = shutdown;
|