@frostpillar/frostpillar-btree 0.2.4 → 0.2.6
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-JA.md +39 -6
- package/README.md +39 -6
- package/dist/InMemoryBTree.d.ts +33 -0
- package/dist/btree/autoScale.d.ts +1 -0
- package/dist/btree/node-ops.d.ts +15 -0
- package/dist/btree/rangeQuery.d.ts +1 -0
- package/dist/btree/serialization.d.ts +0 -4
- package/dist/btree/types.d.ts +12 -26
- package/dist/{chunk-ZA3EQNDI.js → chunk-OWHENPGJ.js} +259 -212
- package/dist/concurrency/ConcurrentInMemoryBTree.d.ts +20 -16
- package/dist/concurrency/helpers.d.ts +9 -2
- package/dist/concurrency/types.d.ts +14 -1
- package/dist/concurrency/writeOps.d.ts +48 -0
- package/dist/core.cjs +259 -212
- package/dist/core.js +1 -1
- package/dist/errors.d.ts +3 -0
- package/dist/frostpillar-btree-core.min.js +1 -1
- package/dist/frostpillar-btree.min.js +1 -1
- package/dist/index.cjs +543 -278
- package/dist/index.js +285 -67
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -51,54 +51,10 @@ var BTreeConcurrencyError = class extends Error {
|
|
|
51
51
|
}
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
-
// src/btree/
|
|
55
|
-
var DEFAULT_MAX_LEAF_ENTRIES = 64;
|
|
56
|
-
var DEFAULT_MAX_BRANCH_CHILDREN = 64;
|
|
57
|
-
var MIN_NODE_CAPACITY = 3;
|
|
58
|
-
var MAX_NODE_CAPACITY = 16384;
|
|
59
|
-
var NODE_LEAF = 0;
|
|
60
|
-
var NODE_BRANCH = 1;
|
|
61
|
-
var normalizeDuplicateKeyPolicy = (value) => {
|
|
62
|
-
if (value === void 0) {
|
|
63
|
-
return "replace";
|
|
64
|
-
}
|
|
65
|
-
if (value !== "allow" && value !== "reject" && value !== "replace") {
|
|
66
|
-
throw new BTreeValidationError(
|
|
67
|
-
`Invalid duplicateKeys option.`
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
return value;
|
|
71
|
-
};
|
|
72
|
-
var isLeafNode = (node) => {
|
|
73
|
-
return node.kind === NODE_LEAF;
|
|
74
|
-
};
|
|
75
|
-
var writeMinKeyTo = (node, target) => {
|
|
76
|
-
if (node.kind === NODE_LEAF) {
|
|
77
|
-
if (node.entryOffset >= node.entries.length) return false;
|
|
78
|
-
const e = node.entries[node.entryOffset];
|
|
79
|
-
target.key = e.key;
|
|
80
|
-
target.sequence = e.entryId;
|
|
81
|
-
return true;
|
|
82
|
-
}
|
|
83
|
-
if (node.childOffset >= node.keys.length) return false;
|
|
84
|
-
target.key = node.keys[node.childOffset].key;
|
|
85
|
-
target.sequence = node.keys[node.childOffset].sequence;
|
|
86
|
-
return true;
|
|
87
|
-
};
|
|
88
|
-
var normalizeNodeCapacity = (value, field, defaultValue) => {
|
|
89
|
-
if (value === void 0) {
|
|
90
|
-
return defaultValue;
|
|
91
|
-
}
|
|
92
|
-
if (!Number.isInteger(value) || value < MIN_NODE_CAPACITY || value > MAX_NODE_CAPACITY) {
|
|
93
|
-
throw new BTreeValidationError(
|
|
94
|
-
`${field}: integer ${MIN_NODE_CAPACITY}\u2013${MAX_NODE_CAPACITY} required.`
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
return value;
|
|
98
|
-
};
|
|
54
|
+
// src/btree/node-ops.ts
|
|
99
55
|
var createLeafNode = (entries, parent) => {
|
|
100
56
|
return {
|
|
101
|
-
kind:
|
|
57
|
+
kind: 0,
|
|
102
58
|
entries,
|
|
103
59
|
entryOffset: 0,
|
|
104
60
|
parent,
|
|
@@ -110,7 +66,7 @@ var createLeafNode = (entries, parent) => {
|
|
|
110
66
|
var createBranchNode = (children, parent) => {
|
|
111
67
|
const keys = [];
|
|
112
68
|
const branch = {
|
|
113
|
-
kind:
|
|
69
|
+
kind: 1,
|
|
114
70
|
children,
|
|
115
71
|
keys,
|
|
116
72
|
childOffset: 0,
|
|
@@ -249,6 +205,56 @@ var branchRemoveAt = (branch, physIndex) => {
|
|
|
249
205
|
}
|
|
250
206
|
};
|
|
251
207
|
|
|
208
|
+
// src/btree/types.ts
|
|
209
|
+
var DEFAULT_MAX_LEAF_ENTRIES = 64;
|
|
210
|
+
var DEFAULT_MAX_BRANCH_CHILDREN = 64;
|
|
211
|
+
var MIN_NODE_CAPACITY = 3;
|
|
212
|
+
var MAX_NODE_CAPACITY = 16384;
|
|
213
|
+
var NODE_LEAF = 0;
|
|
214
|
+
var normalizeDuplicateKeyPolicy = (value) => {
|
|
215
|
+
if (value === void 0) {
|
|
216
|
+
return "replace";
|
|
217
|
+
}
|
|
218
|
+
if (value !== "allow" && value !== "reject" && value !== "replace") {
|
|
219
|
+
throw new BTreeValidationError(
|
|
220
|
+
`Invalid duplicateKeys option.`
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
return value;
|
|
224
|
+
};
|
|
225
|
+
var toPublicEntry = (entry) => ({
|
|
226
|
+
entryId: entry.entryId,
|
|
227
|
+
key: entry.key,
|
|
228
|
+
value: entry.value
|
|
229
|
+
});
|
|
230
|
+
var isLeafNode = (node) => {
|
|
231
|
+
return node.kind === NODE_LEAF;
|
|
232
|
+
};
|
|
233
|
+
var writeMinKeyTo = (node, target) => {
|
|
234
|
+
if (node.kind === NODE_LEAF) {
|
|
235
|
+
if (node.entryOffset >= node.entries.length) return false;
|
|
236
|
+
const e = node.entries[node.entryOffset];
|
|
237
|
+
target.key = e.key;
|
|
238
|
+
target.sequence = e.entryId;
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
if (node.childOffset >= node.keys.length) return false;
|
|
242
|
+
target.key = node.keys[node.childOffset].key;
|
|
243
|
+
target.sequence = node.keys[node.childOffset].sequence;
|
|
244
|
+
return true;
|
|
245
|
+
};
|
|
246
|
+
var normalizeNodeCapacity = (value, field, defaultValue) => {
|
|
247
|
+
if (value === void 0) {
|
|
248
|
+
return defaultValue;
|
|
249
|
+
}
|
|
250
|
+
if (!Number.isInteger(value) || value < MIN_NODE_CAPACITY || value > MAX_NODE_CAPACITY) {
|
|
251
|
+
throw new BTreeValidationError(
|
|
252
|
+
`${field}: integer ${MIN_NODE_CAPACITY}\u2013${MAX_NODE_CAPACITY} required.`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
return value;
|
|
256
|
+
};
|
|
257
|
+
|
|
252
258
|
// src/btree/navigation.ts
|
|
253
259
|
var selectBranchChild = (compare, branch, userKey, sequence) => {
|
|
254
260
|
const off = branch.childOffset;
|
|
@@ -262,7 +268,7 @@ var selectBranchChild = (compare, branch, userKey, sequence) => {
|
|
|
262
268
|
const mid = lower + upper >>> 1;
|
|
263
269
|
const k = branch.keys[mid];
|
|
264
270
|
const cmp = compare(k.key, userKey);
|
|
265
|
-
if ((cmp !== 0 ? cmp : k.sequence - sequence) <= 0) {
|
|
271
|
+
if ((cmp !== 0 ? cmp : k.sequence < sequence ? -1 : k.sequence > sequence ? 1 : 0) <= 0) {
|
|
266
272
|
selectedIndex = mid;
|
|
267
273
|
lower = mid + 1;
|
|
268
274
|
} else {
|
|
@@ -287,7 +293,7 @@ var lowerBoundInLeaf = (state, leaf, userKey, sequence) => {
|
|
|
287
293
|
const mid = lower + upper >>> 1;
|
|
288
294
|
const e = leaf.entries[mid];
|
|
289
295
|
const cmp = compare(e.key, userKey);
|
|
290
|
-
if ((cmp !== 0 ? cmp : e.entryId - sequence) < 0) {
|
|
296
|
+
if ((cmp !== 0 ? cmp : e.entryId < sequence ? -1 : e.entryId > sequence ? 1 : 0) < 0) {
|
|
291
297
|
lower = mid + 1;
|
|
292
298
|
} else {
|
|
293
299
|
upper = mid;
|
|
@@ -303,7 +309,7 @@ var upperBoundInLeaf = (state, leaf, userKey, sequence) => {
|
|
|
303
309
|
const mid = lower + upper >>> 1;
|
|
304
310
|
const e = leaf.entries[mid];
|
|
305
311
|
const cmp = compare(e.key, userKey);
|
|
306
|
-
if ((cmp !== 0 ? cmp : e.entryId - sequence) <= 0) {
|
|
312
|
+
if ((cmp !== 0 ? cmp : e.entryId < sequence ? -1 : e.entryId > sequence ? 1 : 0) <= 0) {
|
|
307
313
|
lower = mid + 1;
|
|
308
314
|
} else {
|
|
309
315
|
upper = mid;
|
|
@@ -440,6 +446,114 @@ var findPairOrNextLower = (state, key) => {
|
|
|
440
446
|
return null;
|
|
441
447
|
};
|
|
442
448
|
|
|
449
|
+
// src/btree/rangeQuery.ts
|
|
450
|
+
function isEmptyRange(compare, startKey, endKey, options) {
|
|
451
|
+
const cmp = compare(startKey, endKey);
|
|
452
|
+
if (cmp > 0) return true;
|
|
453
|
+
const lowerExclusive = options?.lowerBound === "exclusive";
|
|
454
|
+
const upperExclusive = options?.upperBound === "exclusive";
|
|
455
|
+
return lowerExclusive && upperExclusive && cmp === 0;
|
|
456
|
+
}
|
|
457
|
+
var initRangeCursor = (state, startKey, endKey, options) => {
|
|
458
|
+
if (state.entryCount === 0) return null;
|
|
459
|
+
const compare = state.compareKeys;
|
|
460
|
+
if (isEmptyRange(compare, startKey, endKey, options)) return null;
|
|
461
|
+
const lowerExclusive = options?.lowerBound === "exclusive";
|
|
462
|
+
const upperExclusive = options?.upperBound === "exclusive";
|
|
463
|
+
const startSeq = lowerExclusive ? Number.MAX_SAFE_INTEGER : 0;
|
|
464
|
+
const leaf = findLeafForKey(state, startKey, startSeq);
|
|
465
|
+
const index = lowerExclusive ? upperBoundInLeaf(state, leaf, startKey, Number.MAX_SAFE_INTEGER) : lowerBoundInLeaf(state, leaf, startKey, 0);
|
|
466
|
+
return { leaf, index, compare, upperExclusive };
|
|
467
|
+
};
|
|
468
|
+
var countRangeEntries = (state, startKey, endKey, options) => {
|
|
469
|
+
const cursor = initRangeCursor(state, startKey, endKey, options);
|
|
470
|
+
if (cursor === null) return 0;
|
|
471
|
+
let cursorLeaf = cursor.leaf;
|
|
472
|
+
let cursorIndex = cursor.index;
|
|
473
|
+
const { compare, upperExclusive } = cursor;
|
|
474
|
+
let count = 0;
|
|
475
|
+
while (cursorLeaf !== null) {
|
|
476
|
+
const leafCount = leafEntryCount(cursorLeaf);
|
|
477
|
+
if (cursorIndex >= leafCount) {
|
|
478
|
+
cursorLeaf = cursorLeaf.next;
|
|
479
|
+
cursorIndex = 0;
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
const lastEntry = leafEntryAt(cursorLeaf, leafCount - 1);
|
|
483
|
+
const cmpLast = compare(lastEntry.key, endKey);
|
|
484
|
+
if (upperExclusive ? cmpLast < 0 : cmpLast <= 0) {
|
|
485
|
+
count += leafCount - cursorIndex;
|
|
486
|
+
cursorLeaf = cursorLeaf.next;
|
|
487
|
+
cursorIndex = 0;
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
const endSeq = upperExclusive ? 0 : Number.MAX_SAFE_INTEGER;
|
|
491
|
+
const endBound = upperExclusive ? lowerBoundInLeaf(state, cursorLeaf, endKey, endSeq) : upperBoundInLeaf(state, cursorLeaf, endKey, endSeq);
|
|
492
|
+
const limit = endBound < leafCount ? endBound : leafCount;
|
|
493
|
+
count += limit - cursorIndex;
|
|
494
|
+
return count;
|
|
495
|
+
}
|
|
496
|
+
return count;
|
|
497
|
+
};
|
|
498
|
+
var RANGE_PREALLOC_THRESHOLD = 200;
|
|
499
|
+
var allocateRangeOutput = (state, cursorLeaf, cursorIndex, compare, upperExclusive, startKey, endKey, options) => {
|
|
500
|
+
const firstLeafCount = leafEntryCount(cursorLeaf);
|
|
501
|
+
const firstLeafRemainder = firstLeafCount - cursorIndex;
|
|
502
|
+
if (firstLeafRemainder >= RANGE_PREALLOC_THRESHOLD && cursorLeaf.next !== null) {
|
|
503
|
+
const lastEntry = leafEntryAt(cursorLeaf, firstLeafCount - 1);
|
|
504
|
+
const cmpLast = compare(lastEntry.key, endKey);
|
|
505
|
+
if (upperExclusive ? cmpLast < 0 : cmpLast <= 0) {
|
|
506
|
+
const total = countRangeEntries(state, startKey, endKey, options);
|
|
507
|
+
return new Array(total);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return [];
|
|
511
|
+
};
|
|
512
|
+
var appendLeafSlice = (leaf, from, to, output, useIndexed, writeIdx) => {
|
|
513
|
+
if (useIndexed) {
|
|
514
|
+
for (let i = from; i < to; i += 1) {
|
|
515
|
+
output[writeIdx++] = leafEntryAt(leaf, i);
|
|
516
|
+
}
|
|
517
|
+
} else {
|
|
518
|
+
for (let i = from; i < to; i += 1) {
|
|
519
|
+
output.push(leafEntryAt(leaf, i));
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return writeIdx;
|
|
523
|
+
};
|
|
524
|
+
var rangeQueryEntries = (state, startKey, endKey, options) => {
|
|
525
|
+
const cursor = initRangeCursor(state, startKey, endKey, options);
|
|
526
|
+
if (cursor === null) return [];
|
|
527
|
+
let cursorLeaf = cursor.leaf;
|
|
528
|
+
let cursorIndex = cursor.index;
|
|
529
|
+
const { compare, upperExclusive } = cursor;
|
|
530
|
+
const output = allocateRangeOutput(state, cursorLeaf, cursorIndex, compare, upperExclusive, startKey, endKey, options);
|
|
531
|
+
let writeIdx = 0;
|
|
532
|
+
const useIndexed = output.length > 0;
|
|
533
|
+
while (cursorLeaf !== null) {
|
|
534
|
+
const leafCount = leafEntryCount(cursorLeaf);
|
|
535
|
+
if (cursorIndex >= leafCount) {
|
|
536
|
+
cursorLeaf = cursorLeaf.next;
|
|
537
|
+
cursorIndex = 0;
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
const lastEntry = leafEntryAt(cursorLeaf, leafCount - 1);
|
|
541
|
+
const cmpLast = compare(lastEntry.key, endKey);
|
|
542
|
+
if (upperExclusive ? cmpLast < 0 : cmpLast <= 0) {
|
|
543
|
+
writeIdx = appendLeafSlice(cursorLeaf, cursorIndex, leafCount, output, useIndexed, writeIdx);
|
|
544
|
+
cursorLeaf = cursorLeaf.next;
|
|
545
|
+
cursorIndex = 0;
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
const endSeq = upperExclusive ? 0 : Number.MAX_SAFE_INTEGER;
|
|
549
|
+
const endBound = upperExclusive ? lowerBoundInLeaf(state, cursorLeaf, endKey, endSeq) : upperBoundInLeaf(state, cursorLeaf, endKey, endSeq);
|
|
550
|
+
const limit = endBound < leafCount ? endBound : leafCount;
|
|
551
|
+
appendLeafSlice(cursorLeaf, cursorIndex, limit, output, useIndexed, writeIdx);
|
|
552
|
+
return output;
|
|
553
|
+
}
|
|
554
|
+
return output;
|
|
555
|
+
};
|
|
556
|
+
|
|
443
557
|
// src/btree/rebalance.ts
|
|
444
558
|
var updateMinKeyInAncestors = (node) => {
|
|
445
559
|
let current = node;
|
|
@@ -665,16 +779,7 @@ var findRemoveEnd = (state, leaf, idx, endKey, upperExclusive) => {
|
|
|
665
779
|
}
|
|
666
780
|
return removeEnd;
|
|
667
781
|
};
|
|
668
|
-
var
|
|
669
|
-
let h = 0;
|
|
670
|
-
let n = state.root;
|
|
671
|
-
while (!isLeafNode(n)) {
|
|
672
|
-
n = n.children[n.childOffset];
|
|
673
|
-
h += 1;
|
|
674
|
-
}
|
|
675
|
-
return h;
|
|
676
|
-
};
|
|
677
|
-
var spliceLeafAndRebalance = (state, leaf, idx, removeCount, maxRebalanceDepth) => {
|
|
782
|
+
var spliceLeafAndRebalance = (state, leaf, idx, removeCount) => {
|
|
678
783
|
if (state.entryKeys !== null) {
|
|
679
784
|
for (let i = idx; i < idx + removeCount; i += 1) {
|
|
680
785
|
state.entryKeys.delete(leafEntryAt(leaf, i).entryId);
|
|
@@ -689,11 +794,11 @@ var spliceLeafAndRebalance = (state, leaf, idx, removeCount, maxRebalanceDepth)
|
|
|
689
794
|
updateMinKeyInAncestors(leaf);
|
|
690
795
|
}
|
|
691
796
|
const countAfterSplice = leafEntryCount(leaf);
|
|
692
|
-
let
|
|
693
|
-
while (
|
|
797
|
+
let safetyGuard = state.minLeafEntries + 4;
|
|
798
|
+
while (safetyGuard > 0 && leaf !== state.root && leafEntryCount(leaf) < state.minLeafEntries) {
|
|
694
799
|
rebalanceAfterLeafRemoval(state, leaf);
|
|
695
800
|
if (leaf.parent !== null && leaf.parent.children[leaf.indexInParent] !== leaf) break;
|
|
696
|
-
|
|
801
|
+
safetyGuard -= 1;
|
|
697
802
|
}
|
|
698
803
|
if (leafEmptied && leafEntryCount(leaf) > 0 && leaf.parent !== null && leaf.parent.children[leaf.indexInParent] === leaf) {
|
|
699
804
|
updateMinKeyInAncestors(leaf);
|
|
@@ -703,12 +808,9 @@ var spliceLeafAndRebalance = (state, leaf, idx, removeCount, maxRebalanceDepth)
|
|
|
703
808
|
var isLeafStillValid = (state, leaf) => leaf.parent === null ? leaf === state.root : leaf.parent.children[leaf.indexInParent] === leaf;
|
|
704
809
|
var deleteRangeEntries = (state, startKey, endKey, options) => {
|
|
705
810
|
if (state.entryCount === 0) return 0;
|
|
706
|
-
|
|
707
|
-
if (boundCompared > 0) return 0;
|
|
811
|
+
if (isEmptyRange(state.compareKeys, startKey, endKey, options)) return 0;
|
|
708
812
|
const lowerExclusive = options?.lowerBound === "exclusive";
|
|
709
813
|
const upperExclusive = options?.upperBound === "exclusive";
|
|
710
|
-
if (lowerExclusive && upperExclusive && boundCompared === 0) return 0;
|
|
711
|
-
const treeHeight = computeTreeHeight(state);
|
|
712
814
|
let deleted = 0;
|
|
713
815
|
let needsNavigate = true;
|
|
714
816
|
let leaf = null;
|
|
@@ -726,7 +828,7 @@ var deleteRangeEntries = (state, startKey, endKey, options) => {
|
|
|
726
828
|
const removeEnd = findRemoveEnd(state, leaf, idx, endKey, upperExclusive);
|
|
727
829
|
const removeCount = removeEnd - idx;
|
|
728
830
|
if (removeCount === 0) break;
|
|
729
|
-
const countAfterSplice = spliceLeafAndRebalance(state, leaf, idx, removeCount
|
|
831
|
+
const countAfterSplice = spliceLeafAndRebalance(state, leaf, idx, removeCount);
|
|
730
832
|
deleted += removeCount;
|
|
731
833
|
if (removeEnd < count) break;
|
|
732
834
|
if (!isLeafStillValid(state, leaf)) {
|
|
@@ -745,6 +847,7 @@ var deleteRangeEntries = (state, startKey, endKey, options) => {
|
|
|
745
847
|
};
|
|
746
848
|
|
|
747
849
|
// src/btree/autoScale.ts
|
|
850
|
+
var minOccupancy = (max) => Math.ceil(max / 2);
|
|
748
851
|
var AUTO_SCALE_TIERS = [
|
|
749
852
|
{ threshold: 0, maxLeaf: 32, maxBranch: 32 },
|
|
750
853
|
{ threshold: 1e3, maxLeaf: 64, maxBranch: 64 },
|
|
@@ -796,8 +899,8 @@ var createInitialState = (config) => {
|
|
|
796
899
|
maxLeafEntries,
|
|
797
900
|
maxBranchChildren,
|
|
798
901
|
duplicateKeys,
|
|
799
|
-
minLeafEntries:
|
|
800
|
-
minBranchChildren:
|
|
902
|
+
minLeafEntries: minOccupancy(maxLeafEntries),
|
|
903
|
+
minBranchChildren: minOccupancy(maxBranchChildren),
|
|
801
904
|
root: emptyLeaf,
|
|
802
905
|
leftmostLeaf: emptyLeaf,
|
|
803
906
|
rightmostLeaf: emptyLeaf,
|
|
@@ -814,11 +917,11 @@ var maybeAutoScale = (state) => {
|
|
|
814
917
|
const { maxLeaf, maxBranch } = computeAutoScaleTier(state.entryCount);
|
|
815
918
|
if (maxLeaf > state.maxLeafEntries) {
|
|
816
919
|
state.maxLeafEntries = maxLeaf;
|
|
817
|
-
state.minLeafEntries =
|
|
920
|
+
state.minLeafEntries = minOccupancy(maxLeaf);
|
|
818
921
|
}
|
|
819
922
|
if (maxBranch > state.maxBranchChildren) {
|
|
820
923
|
state.maxBranchChildren = maxBranch;
|
|
821
|
-
state.minBranchChildren =
|
|
924
|
+
state.minBranchChildren = minOccupancy(maxBranch);
|
|
822
925
|
}
|
|
823
926
|
state._nextAutoScaleThreshold = computeNextAutoScaleThreshold(state.entryCount);
|
|
824
927
|
};
|
|
@@ -844,8 +947,8 @@ var applyAutoScaleCapacitySnapshot = (state, maxLeafEntries, maxBranchChildren)
|
|
|
844
947
|
}
|
|
845
948
|
state.maxLeafEntries = normalizedLeaf;
|
|
846
949
|
state.maxBranchChildren = normalizedBranch;
|
|
847
|
-
state.minLeafEntries =
|
|
848
|
-
state.minBranchChildren =
|
|
950
|
+
state.minLeafEntries = minOccupancy(normalizedLeaf);
|
|
951
|
+
state.minBranchChildren = minOccupancy(normalizedBranch);
|
|
849
952
|
};
|
|
850
953
|
|
|
851
954
|
// src/btree/bulkLoad.ts
|
|
@@ -1070,6 +1173,9 @@ var findLeafEntryBySequence = (state, userKey, sequence) => {
|
|
|
1070
1173
|
return { leaf: targetLeaf, index };
|
|
1071
1174
|
};
|
|
1072
1175
|
var removeEntryById = (state, entryId) => {
|
|
1176
|
+
if (state.entryKeys === null) {
|
|
1177
|
+
throw new BTreeInvariantError("entryKeys lookup map is not enabled on this tree.");
|
|
1178
|
+
}
|
|
1073
1179
|
const userKey = state.entryKeys.get(entryId);
|
|
1074
1180
|
if (userKey === void 0) return null;
|
|
1075
1181
|
const found = findLeafEntryBySequence(state, userKey, entryId);
|
|
@@ -1087,6 +1193,9 @@ var removeEntryById = (state, entryId) => {
|
|
|
1087
1193
|
return entry;
|
|
1088
1194
|
};
|
|
1089
1195
|
var peekEntryById = (state, entryId) => {
|
|
1196
|
+
if (state.entryKeys === null) {
|
|
1197
|
+
throw new BTreeInvariantError("entryKeys lookup map is not enabled on this tree.");
|
|
1198
|
+
}
|
|
1090
1199
|
const userKey = state.entryKeys.get(entryId);
|
|
1091
1200
|
if (userKey === void 0) return null;
|
|
1092
1201
|
const found = findLeafEntryBySequence(state, userKey, entryId);
|
|
@@ -1095,6 +1204,9 @@ var peekEntryById = (state, entryId) => {
|
|
|
1095
1204
|
return entry;
|
|
1096
1205
|
};
|
|
1097
1206
|
var updateEntryById = (state, entryId, newValue) => {
|
|
1207
|
+
if (state.entryKeys === null) {
|
|
1208
|
+
throw new BTreeInvariantError("entryKeys lookup map is not enabled on this tree.");
|
|
1209
|
+
}
|
|
1098
1210
|
const userKey = state.entryKeys.get(entryId);
|
|
1099
1211
|
if (userKey === void 0) return null;
|
|
1100
1212
|
const found = findLeafEntryBySequence(state, userKey, entryId);
|
|
@@ -1108,9 +1220,12 @@ var putManyEntries = (state, entries) => {
|
|
|
1108
1220
|
const strictlyAscending = state.duplicateKeys !== "allow";
|
|
1109
1221
|
for (let i = 1; i < entries.length; i += 1) {
|
|
1110
1222
|
const cmp = state.compareKeys(entries[i - 1].key, entries[i].key);
|
|
1111
|
-
if (
|
|
1223
|
+
if (cmp > 0) {
|
|
1224
|
+
throw new BTreeValidationError("putMany: entries not in ascending order.");
|
|
1225
|
+
}
|
|
1226
|
+
if (strictlyAscending && cmp === 0) {
|
|
1112
1227
|
throw new BTreeValidationError(
|
|
1113
|
-
|
|
1228
|
+
state.duplicateKeys === "reject" ? "putMany: duplicate key rejected." : "putMany: equal keys not allowed in strict mode."
|
|
1114
1229
|
);
|
|
1115
1230
|
}
|
|
1116
1231
|
}
|
|
@@ -1128,109 +1243,6 @@ var putManyEntries = (state, entries) => {
|
|
|
1128
1243
|
return bulkLoadEntries(state, entries);
|
|
1129
1244
|
};
|
|
1130
1245
|
|
|
1131
|
-
// src/btree/rangeQuery.ts
|
|
1132
|
-
var initRangeCursor = (state, startKey, endKey, options) => {
|
|
1133
|
-
if (state.entryCount === 0) return null;
|
|
1134
|
-
const compare = state.compareKeys;
|
|
1135
|
-
const boundCompared = compare(startKey, endKey);
|
|
1136
|
-
if (boundCompared > 0) return null;
|
|
1137
|
-
const lowerExclusive = options?.lowerBound === "exclusive";
|
|
1138
|
-
const upperExclusive = options?.upperBound === "exclusive";
|
|
1139
|
-
if (lowerExclusive && upperExclusive && boundCompared === 0) return null;
|
|
1140
|
-
const startSeq = lowerExclusive ? Number.MAX_SAFE_INTEGER : 0;
|
|
1141
|
-
const leaf = findLeafForKey(state, startKey, startSeq);
|
|
1142
|
-
const index = lowerExclusive ? upperBoundInLeaf(state, leaf, startKey, Number.MAX_SAFE_INTEGER) : lowerBoundInLeaf(state, leaf, startKey, 0);
|
|
1143
|
-
return { leaf, index, compare, upperExclusive };
|
|
1144
|
-
};
|
|
1145
|
-
var countRangeEntries = (state, startKey, endKey, options) => {
|
|
1146
|
-
const cursor = initRangeCursor(state, startKey, endKey, options);
|
|
1147
|
-
if (cursor === null) return 0;
|
|
1148
|
-
let cursorLeaf = cursor.leaf;
|
|
1149
|
-
let cursorIndex = cursor.index;
|
|
1150
|
-
const { compare, upperExclusive } = cursor;
|
|
1151
|
-
let count = 0;
|
|
1152
|
-
while (cursorLeaf !== null) {
|
|
1153
|
-
const leafCount = leafEntryCount(cursorLeaf);
|
|
1154
|
-
if (cursorIndex >= leafCount) {
|
|
1155
|
-
cursorLeaf = cursorLeaf.next;
|
|
1156
|
-
cursorIndex = 0;
|
|
1157
|
-
continue;
|
|
1158
|
-
}
|
|
1159
|
-
const lastEntry = leafEntryAt(cursorLeaf, leafCount - 1);
|
|
1160
|
-
const cmpLast = compare(lastEntry.key, endKey);
|
|
1161
|
-
if (upperExclusive ? cmpLast < 0 : cmpLast <= 0) {
|
|
1162
|
-
count += leafCount - cursorIndex;
|
|
1163
|
-
cursorLeaf = cursorLeaf.next;
|
|
1164
|
-
cursorIndex = 0;
|
|
1165
|
-
continue;
|
|
1166
|
-
}
|
|
1167
|
-
const endSeq = upperExclusive ? 0 : Number.MAX_SAFE_INTEGER;
|
|
1168
|
-
const endBound = upperExclusive ? lowerBoundInLeaf(state, cursorLeaf, endKey, endSeq) : upperBoundInLeaf(state, cursorLeaf, endKey, endSeq);
|
|
1169
|
-
const limit = endBound < leafCount ? endBound : leafCount;
|
|
1170
|
-
count += limit - cursorIndex;
|
|
1171
|
-
return count;
|
|
1172
|
-
}
|
|
1173
|
-
return count;
|
|
1174
|
-
};
|
|
1175
|
-
var RANGE_PREALLOC_THRESHOLD = 200;
|
|
1176
|
-
var allocateRangeOutput = (state, cursorLeaf, cursorIndex, compare, upperExclusive, startKey, endKey, options) => {
|
|
1177
|
-
const firstLeafCount = leafEntryCount(cursorLeaf);
|
|
1178
|
-
const firstLeafRemainder = firstLeafCount - cursorIndex;
|
|
1179
|
-
if (firstLeafRemainder >= RANGE_PREALLOC_THRESHOLD && cursorLeaf.next !== null) {
|
|
1180
|
-
const lastEntry = leafEntryAt(cursorLeaf, firstLeafCount - 1);
|
|
1181
|
-
const cmpLast = compare(lastEntry.key, endKey);
|
|
1182
|
-
if (upperExclusive ? cmpLast < 0 : cmpLast <= 0) {
|
|
1183
|
-
const total = countRangeEntries(state, startKey, endKey, options);
|
|
1184
|
-
return new Array(total);
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
return [];
|
|
1188
|
-
};
|
|
1189
|
-
var appendLeafSlice = (leaf, from, to, output, useIndexed, writeIdx) => {
|
|
1190
|
-
if (useIndexed) {
|
|
1191
|
-
for (let i = from; i < to; i += 1) {
|
|
1192
|
-
output[writeIdx++] = leafEntryAt(leaf, i);
|
|
1193
|
-
}
|
|
1194
|
-
} else {
|
|
1195
|
-
for (let i = from; i < to; i += 1) {
|
|
1196
|
-
output.push(leafEntryAt(leaf, i));
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
return writeIdx;
|
|
1200
|
-
};
|
|
1201
|
-
var rangeQueryEntries = (state, startKey, endKey, options) => {
|
|
1202
|
-
const cursor = initRangeCursor(state, startKey, endKey, options);
|
|
1203
|
-
if (cursor === null) return [];
|
|
1204
|
-
let cursorLeaf = cursor.leaf;
|
|
1205
|
-
let cursorIndex = cursor.index;
|
|
1206
|
-
const { compare, upperExclusive } = cursor;
|
|
1207
|
-
const output = allocateRangeOutput(state, cursorLeaf, cursorIndex, compare, upperExclusive, startKey, endKey, options);
|
|
1208
|
-
let writeIdx = 0;
|
|
1209
|
-
const useIndexed = output.length > 0;
|
|
1210
|
-
while (cursorLeaf !== null) {
|
|
1211
|
-
const leafCount = leafEntryCount(cursorLeaf);
|
|
1212
|
-
if (cursorIndex >= leafCount) {
|
|
1213
|
-
cursorLeaf = cursorLeaf.next;
|
|
1214
|
-
cursorIndex = 0;
|
|
1215
|
-
continue;
|
|
1216
|
-
}
|
|
1217
|
-
const lastEntry = leafEntryAt(cursorLeaf, leafCount - 1);
|
|
1218
|
-
const cmpLast = compare(lastEntry.key, endKey);
|
|
1219
|
-
if (upperExclusive ? cmpLast < 0 : cmpLast <= 0) {
|
|
1220
|
-
writeIdx = appendLeafSlice(cursorLeaf, cursorIndex, leafCount, output, useIndexed, writeIdx);
|
|
1221
|
-
cursorLeaf = cursorLeaf.next;
|
|
1222
|
-
cursorIndex = 0;
|
|
1223
|
-
continue;
|
|
1224
|
-
}
|
|
1225
|
-
const endSeq = upperExclusive ? 0 : Number.MAX_SAFE_INTEGER;
|
|
1226
|
-
const endBound = upperExclusive ? lowerBoundInLeaf(state, cursorLeaf, endKey, endSeq) : upperBoundInLeaf(state, cursorLeaf, endKey, endSeq);
|
|
1227
|
-
const limit = endBound < leafCount ? endBound : leafCount;
|
|
1228
|
-
appendLeafSlice(cursorLeaf, cursorIndex, limit, output, useIndexed, writeIdx);
|
|
1229
|
-
return output;
|
|
1230
|
-
}
|
|
1231
|
-
return output;
|
|
1232
|
-
};
|
|
1233
|
-
|
|
1234
1246
|
// src/btree/serialization.ts
|
|
1235
1247
|
var buildConfigFromState = (state) => {
|
|
1236
1248
|
const config = {
|
|
@@ -1360,7 +1372,7 @@ var compareNodeKeys = (comparator, leftKey, leftSeq, rightKey, rightSeq) => {
|
|
|
1360
1372
|
if (cmp !== 0) {
|
|
1361
1373
|
return cmp;
|
|
1362
1374
|
}
|
|
1363
|
-
return leftSeq - rightSeq;
|
|
1375
|
+
return leftSeq < rightSeq ? -1 : leftSeq > rightSeq ? 1 : 0;
|
|
1364
1376
|
};
|
|
1365
1377
|
var getNodeMaxKey = (node) => {
|
|
1366
1378
|
if (isLeafNode(node)) {
|
|
@@ -1693,44 +1705,56 @@ var InMemoryBTree = class _InMemoryBTree {
|
|
|
1693
1705
|
return putManyEntries(this.state, entries);
|
|
1694
1706
|
}
|
|
1695
1707
|
remove(key) {
|
|
1696
|
-
|
|
1708
|
+
const entry = removeFirstMatchingEntry(this.state, key);
|
|
1709
|
+
if (entry === null) return null;
|
|
1710
|
+
return toPublicEntry(entry);
|
|
1697
1711
|
}
|
|
1698
1712
|
removeById(entryId) {
|
|
1699
1713
|
if (this.state.entryKeys === null) {
|
|
1700
1714
|
throw new BTreeValidationError("Requires enableEntryIdLookup: true.");
|
|
1701
1715
|
}
|
|
1702
|
-
|
|
1716
|
+
const entry = removeEntryById(this.state, entryId);
|
|
1717
|
+
if (entry === null) return null;
|
|
1718
|
+
return toPublicEntry(entry);
|
|
1703
1719
|
}
|
|
1704
1720
|
peekById(entryId) {
|
|
1705
1721
|
if (this.state.entryKeys === null) {
|
|
1706
1722
|
throw new BTreeValidationError("Requires enableEntryIdLookup: true.");
|
|
1707
1723
|
}
|
|
1708
|
-
|
|
1724
|
+
const entry = peekEntryById(this.state, entryId);
|
|
1725
|
+
if (entry === null) return null;
|
|
1726
|
+
return toPublicEntry(entry);
|
|
1709
1727
|
}
|
|
1710
1728
|
updateById(entryId, value) {
|
|
1711
1729
|
if (this.state.entryKeys === null) {
|
|
1712
1730
|
throw new BTreeValidationError("Requires enableEntryIdLookup: true.");
|
|
1713
1731
|
}
|
|
1714
|
-
|
|
1732
|
+
const entry = updateEntryById(this.state, entryId, value);
|
|
1733
|
+
if (entry === null) return null;
|
|
1734
|
+
return toPublicEntry(entry);
|
|
1715
1735
|
}
|
|
1716
1736
|
popFirst() {
|
|
1717
|
-
|
|
1737
|
+
const entry = popFirstEntry(this.state);
|
|
1738
|
+
if (entry === null) return null;
|
|
1739
|
+
return toPublicEntry(entry);
|
|
1718
1740
|
}
|
|
1719
1741
|
peekFirst() {
|
|
1720
1742
|
if (this.state.entryCount === 0) {
|
|
1721
1743
|
return null;
|
|
1722
1744
|
}
|
|
1723
|
-
return leafEntryAt(this.state.leftmostLeaf, 0);
|
|
1745
|
+
return toPublicEntry(leafEntryAt(this.state.leftmostLeaf, 0));
|
|
1724
1746
|
}
|
|
1725
1747
|
peekLast() {
|
|
1726
1748
|
if (this.state.entryCount === 0) {
|
|
1727
1749
|
return null;
|
|
1728
1750
|
}
|
|
1729
1751
|
const leaf = this.state.rightmostLeaf;
|
|
1730
|
-
return leafEntryAt(leaf, leafEntryCount(leaf) - 1);
|
|
1752
|
+
return toPublicEntry(leafEntryAt(leaf, leafEntryCount(leaf) - 1));
|
|
1731
1753
|
}
|
|
1732
1754
|
popLast() {
|
|
1733
|
-
|
|
1755
|
+
const entry = popLastEntry(this.state);
|
|
1756
|
+
if (entry === null) return null;
|
|
1757
|
+
return toPublicEntry(entry);
|
|
1734
1758
|
}
|
|
1735
1759
|
clear() {
|
|
1736
1760
|
const emptyLeaf = createLeafNode([], null);
|
|
@@ -1747,8 +1771,8 @@ var InMemoryBTree = class _InMemoryBTree {
|
|
|
1747
1771
|
const tier = computeAutoScaleTier(0);
|
|
1748
1772
|
this.state.maxLeafEntries = tier.maxLeaf;
|
|
1749
1773
|
this.state.maxBranchChildren = tier.maxBranch;
|
|
1750
|
-
this.state.minLeafEntries =
|
|
1751
|
-
this.state.minBranchChildren =
|
|
1774
|
+
this.state.minLeafEntries = minOccupancy(tier.maxLeaf);
|
|
1775
|
+
this.state.minBranchChildren = minOccupancy(tier.maxBranch);
|
|
1752
1776
|
this.state._nextAutoScaleThreshold = computeNextAutoScaleThreshold(0);
|
|
1753
1777
|
}
|
|
1754
1778
|
}
|
|
@@ -1763,39 +1787,65 @@ var InMemoryBTree = class _InMemoryBTree {
|
|
|
1763
1787
|
findFirst(key) {
|
|
1764
1788
|
const found = findFirstMatchingUserKey(this.state, key);
|
|
1765
1789
|
if (found === null) return null;
|
|
1766
|
-
return leafEntryAt(found.leaf, found.index);
|
|
1790
|
+
return toPublicEntry(leafEntryAt(found.leaf, found.index));
|
|
1767
1791
|
}
|
|
1792
|
+
/**
|
|
1793
|
+
* Returns the last entry whose key matches `key`, or `null` if not found.
|
|
1794
|
+
* Useful when `duplicateKeys` is `'allow'` and multiple entries share the same key.
|
|
1795
|
+
*/
|
|
1768
1796
|
findLast(key) {
|
|
1769
1797
|
const found = findLastMatchingUserKey(this.state, key);
|
|
1770
1798
|
if (found === null) return null;
|
|
1771
|
-
return leafEntryAt(found.leaf, found.index);
|
|
1799
|
+
return toPublicEntry(leafEntryAt(found.leaf, found.index));
|
|
1772
1800
|
}
|
|
1801
|
+
/**
|
|
1802
|
+
* Returns the smallest key in the tree that is strictly greater than `key`,
|
|
1803
|
+
* or `null` if no such key exists.
|
|
1804
|
+
*/
|
|
1773
1805
|
nextHigherKey(key) {
|
|
1774
1806
|
return findNextHigherKey(this.state, key);
|
|
1775
1807
|
}
|
|
1808
|
+
/**
|
|
1809
|
+
* Returns the largest key in the tree that is strictly less than `key`,
|
|
1810
|
+
* or `null` if no such key exists.
|
|
1811
|
+
*/
|
|
1776
1812
|
nextLowerKey(key) {
|
|
1777
1813
|
return findNextLowerKey(this.state, key);
|
|
1778
1814
|
}
|
|
1815
|
+
/**
|
|
1816
|
+
* Returns the entry for `key` if it exists; otherwise returns the entry with
|
|
1817
|
+
* the largest key strictly less than `key`. Returns `null` when the tree is
|
|
1818
|
+
* empty or every key is greater than `key`.
|
|
1819
|
+
*/
|
|
1779
1820
|
getPairOrNextLower(key) {
|
|
1780
1821
|
const found = findPairOrNextLower(this.state, key);
|
|
1781
1822
|
if (found === null) return null;
|
|
1782
|
-
return leafEntryAt(found.leaf, found.index);
|
|
1823
|
+
return toPublicEntry(leafEntryAt(found.leaf, found.index));
|
|
1783
1824
|
}
|
|
1825
|
+
/**
|
|
1826
|
+
* Returns the number of entries whose keys fall within [`startKey`, `endKey`].
|
|
1827
|
+
* Pass `options` to make either bound exclusive.
|
|
1828
|
+
*/
|
|
1784
1829
|
count(startKey, endKey, options) {
|
|
1785
1830
|
return countRangeEntries(this.state, startKey, endKey, options);
|
|
1786
1831
|
}
|
|
1832
|
+
/**
|
|
1833
|
+
* Deletes all entries whose keys fall within [`startKey`, `endKey`].
|
|
1834
|
+
* Pass `options` to make either bound exclusive.
|
|
1835
|
+
* @returns The number of entries deleted.
|
|
1836
|
+
*/
|
|
1787
1837
|
deleteRange(startKey, endKey, options) {
|
|
1788
1838
|
return deleteRangeEntries(this.state, startKey, endKey, options);
|
|
1789
1839
|
}
|
|
1790
1840
|
range(startKey, endKey, options) {
|
|
1791
|
-
return rangeQueryEntries(this.state, startKey, endKey, options);
|
|
1841
|
+
return rangeQueryEntries(this.state, startKey, endKey, options).map(toPublicEntry);
|
|
1792
1842
|
}
|
|
1793
1843
|
*entries() {
|
|
1794
1844
|
let leaf = this.state.leftmostLeaf;
|
|
1795
1845
|
while (leaf !== null) {
|
|
1796
1846
|
const count = leafEntryCount(leaf);
|
|
1797
1847
|
for (let i = 0; i < count; i += 1) {
|
|
1798
|
-
yield leafEntryAt(leaf, i);
|
|
1848
|
+
yield toPublicEntry(leafEntryAt(leaf, i));
|
|
1799
1849
|
}
|
|
1800
1850
|
leaf = leaf.next;
|
|
1801
1851
|
}
|
|
@@ -1805,29 +1855,19 @@ var InMemoryBTree = class _InMemoryBTree {
|
|
|
1805
1855
|
while (leaf !== null) {
|
|
1806
1856
|
const count = leafEntryCount(leaf);
|
|
1807
1857
|
for (let i = count - 1; i >= 0; i -= 1) {
|
|
1808
|
-
yield leafEntryAt(leaf, i);
|
|
1858
|
+
yield toPublicEntry(leafEntryAt(leaf, i));
|
|
1809
1859
|
}
|
|
1810
1860
|
leaf = leaf.prev;
|
|
1811
1861
|
}
|
|
1812
1862
|
}
|
|
1813
1863
|
*keys() {
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
const count = leafEntryCount(leaf);
|
|
1817
|
-
for (let i = 0; i < count; i += 1) {
|
|
1818
|
-
yield leafEntryAt(leaf, i).key;
|
|
1819
|
-
}
|
|
1820
|
-
leaf = leaf.next;
|
|
1864
|
+
for (const entry of this.entries()) {
|
|
1865
|
+
yield entry.key;
|
|
1821
1866
|
}
|
|
1822
1867
|
}
|
|
1823
1868
|
*values() {
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
const count = leafEntryCount(leaf);
|
|
1827
|
-
for (let i = 0; i < count; i += 1) {
|
|
1828
|
-
yield leafEntryAt(leaf, i).value;
|
|
1829
|
-
}
|
|
1830
|
-
leaf = leaf.next;
|
|
1869
|
+
for (const entry of this.entries()) {
|
|
1870
|
+
yield entry.value;
|
|
1831
1871
|
}
|
|
1832
1872
|
}
|
|
1833
1873
|
[Symbol.iterator]() {
|
|
@@ -1838,7 +1878,7 @@ var InMemoryBTree = class _InMemoryBTree {
|
|
|
1838
1878
|
while (leaf !== null) {
|
|
1839
1879
|
const count = leafEntryCount(leaf);
|
|
1840
1880
|
for (let i = 0; i < count; i += 1) {
|
|
1841
|
-
callback.call(thisArg, leafEntryAt(leaf, i));
|
|
1881
|
+
callback.call(thisArg, toPublicEntry(leafEntryAt(leaf, i)));
|
|
1842
1882
|
}
|
|
1843
1883
|
leaf = leaf.next;
|
|
1844
1884
|
}
|
|
@@ -1850,12 +1890,19 @@ var InMemoryBTree = class _InMemoryBTree {
|
|
|
1850
1890
|
while (leaf !== null) {
|
|
1851
1891
|
const count = leafEntryCount(leaf);
|
|
1852
1892
|
for (let i = 0; i < count; i += 1) {
|
|
1853
|
-
result[writeIdx++] = leafEntryAt(leaf, i);
|
|
1893
|
+
result[writeIdx++] = toPublicEntry(leafEntryAt(leaf, i));
|
|
1854
1894
|
}
|
|
1855
1895
|
leaf = leaf.next;
|
|
1856
1896
|
}
|
|
1857
1897
|
return result;
|
|
1858
1898
|
}
|
|
1899
|
+
/**
|
|
1900
|
+
* Returns a structurally independent `InMemoryBTree` with identical
|
|
1901
|
+
* configuration and entries. The tree structure (nodes, links, entry IDs)
|
|
1902
|
+
* is fully independent, but stored key and value references are shared
|
|
1903
|
+
* with the source tree.
|
|
1904
|
+
* Note: `EntryId` values are reassigned in the clone — IDs from the source tree are not valid for the clone.
|
|
1905
|
+
*/
|
|
1859
1906
|
clone() {
|
|
1860
1907
|
const cloned = new _InMemoryBTree(buildConfigFromState(this.state));
|
|
1861
1908
|
applyAutoScaleCapacitySnapshot(
|
|
@@ -1943,6 +1990,81 @@ var assertNeverMutation = (mutation) => {
|
|
|
1943
1990
|
`Unsupported mutation type from shared store: ${String(unknownMutation.type)}`
|
|
1944
1991
|
);
|
|
1945
1992
|
};
|
|
1993
|
+
var validatePutManyEntries = (entries) => {
|
|
1994
|
+
for (const entry of entries) {
|
|
1995
|
+
if (typeof entry !== "object" || entry === null || !("key" in entry) || !("value" in entry)) {
|
|
1996
|
+
throw new BTreeConcurrencyError("Malformed putMany mutation: each entry must have key and value.");
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
};
|
|
2000
|
+
var validateInitMutation = (m, expectedConfigFingerprint) => {
|
|
2001
|
+
if (typeof m.configFingerprint !== "string") {
|
|
2002
|
+
throw new BTreeConcurrencyError("Malformed init mutation: missing configFingerprint.");
|
|
2003
|
+
}
|
|
2004
|
+
if (expectedConfigFingerprint !== void 0 && m.configFingerprint !== expectedConfigFingerprint) {
|
|
2005
|
+
throw new BTreeConcurrencyError(
|
|
2006
|
+
"Config mismatch: store peers must share identical tree config."
|
|
2007
|
+
);
|
|
2008
|
+
}
|
|
2009
|
+
};
|
|
2010
|
+
var validateSingleMutation = (m, expectedConfigFingerprint) => {
|
|
2011
|
+
switch (m.type) {
|
|
2012
|
+
case "init":
|
|
2013
|
+
validateInitMutation(m, expectedConfigFingerprint);
|
|
2014
|
+
break;
|
|
2015
|
+
case "put":
|
|
2016
|
+
if (!("key" in m) || !("value" in m)) {
|
|
2017
|
+
throw new BTreeConcurrencyError("Malformed put mutation: missing key or value.");
|
|
2018
|
+
}
|
|
2019
|
+
break;
|
|
2020
|
+
case "remove":
|
|
2021
|
+
if (!("key" in m)) {
|
|
2022
|
+
throw new BTreeConcurrencyError("Malformed remove mutation: missing key.");
|
|
2023
|
+
}
|
|
2024
|
+
break;
|
|
2025
|
+
case "removeById":
|
|
2026
|
+
if (!("entryId" in m)) {
|
|
2027
|
+
throw new BTreeConcurrencyError("Malformed removeById mutation: missing entryId.");
|
|
2028
|
+
}
|
|
2029
|
+
break;
|
|
2030
|
+
case "updateById":
|
|
2031
|
+
if (!("entryId" in m) || !("value" in m)) {
|
|
2032
|
+
throw new BTreeConcurrencyError("Malformed updateById mutation: missing entryId or value.");
|
|
2033
|
+
}
|
|
2034
|
+
break;
|
|
2035
|
+
case "popFirst":
|
|
2036
|
+
case "popLast":
|
|
2037
|
+
break;
|
|
2038
|
+
case "putMany":
|
|
2039
|
+
if (!("entries" in m) || !Array.isArray(m.entries)) {
|
|
2040
|
+
throw new BTreeConcurrencyError("Malformed putMany mutation: missing entries array.");
|
|
2041
|
+
}
|
|
2042
|
+
validatePutManyEntries(m.entries);
|
|
2043
|
+
break;
|
|
2044
|
+
case "deleteRange":
|
|
2045
|
+
if (!("startKey" in m) || !("endKey" in m)) {
|
|
2046
|
+
throw new BTreeConcurrencyError("Malformed deleteRange mutation: missing startKey or endKey.");
|
|
2047
|
+
}
|
|
2048
|
+
break;
|
|
2049
|
+
case "clear":
|
|
2050
|
+
break;
|
|
2051
|
+
default:
|
|
2052
|
+
throw new BTreeConcurrencyError(
|
|
2053
|
+
`Unsupported mutation type from shared store: ${String(m.type)}`
|
|
2054
|
+
);
|
|
2055
|
+
}
|
|
2056
|
+
};
|
|
2057
|
+
var validateMutationBatch = (mutations, expectedConfigFingerprint) => {
|
|
2058
|
+
for (const mutation of mutations) {
|
|
2059
|
+
if (typeof mutation !== "object" || mutation === null) {
|
|
2060
|
+
throw new BTreeConcurrencyError("Malformed mutation: expected an object.");
|
|
2061
|
+
}
|
|
2062
|
+
validateSingleMutation(
|
|
2063
|
+
mutation,
|
|
2064
|
+
expectedConfigFingerprint
|
|
2065
|
+
);
|
|
2066
|
+
}
|
|
2067
|
+
};
|
|
1946
2068
|
var normalizeMaxRetries = (value) => {
|
|
1947
2069
|
if (value === void 0) {
|
|
1948
2070
|
return DEFAULT_MAX_RETRIES;
|
|
@@ -2001,10 +2123,110 @@ function assertAppendVersionContract(expectedVersion, appendResult) {
|
|
|
2001
2123
|
}
|
|
2002
2124
|
}
|
|
2003
2125
|
|
|
2126
|
+
// src/concurrency/writeOps.ts
|
|
2127
|
+
var applyMutationLocal = (tree, mutation, onInit) => {
|
|
2128
|
+
switch (mutation.type) {
|
|
2129
|
+
case "init":
|
|
2130
|
+
onInit();
|
|
2131
|
+
return null;
|
|
2132
|
+
case "put":
|
|
2133
|
+
return tree.put(mutation.key, mutation.value);
|
|
2134
|
+
case "putMany":
|
|
2135
|
+
return tree.putMany(mutation.entries);
|
|
2136
|
+
case "remove":
|
|
2137
|
+
return tree.remove(mutation.key);
|
|
2138
|
+
case "removeById":
|
|
2139
|
+
return tree.removeById(mutation.entryId);
|
|
2140
|
+
case "updateById":
|
|
2141
|
+
return tree.updateById(mutation.entryId, mutation.value);
|
|
2142
|
+
case "popFirst":
|
|
2143
|
+
return tree.popFirst();
|
|
2144
|
+
case "popLast":
|
|
2145
|
+
return tree.popLast();
|
|
2146
|
+
case "deleteRange":
|
|
2147
|
+
return tree.deleteRange(mutation.startKey, mutation.endKey, mutation.options);
|
|
2148
|
+
case "clear":
|
|
2149
|
+
tree.clear();
|
|
2150
|
+
return null;
|
|
2151
|
+
default:
|
|
2152
|
+
return assertNeverMutation(mutation);
|
|
2153
|
+
}
|
|
2154
|
+
};
|
|
2155
|
+
var createPutEvaluator = (duplicateKeys, key, value) => {
|
|
2156
|
+
return (tree) => {
|
|
2157
|
+
if (duplicateKeys === "reject" && tree.hasKey(key)) {
|
|
2158
|
+
throw new BTreeValidationError("Duplicate key rejected.");
|
|
2159
|
+
}
|
|
2160
|
+
return { type: "put", key, value };
|
|
2161
|
+
};
|
|
2162
|
+
};
|
|
2163
|
+
var createRemoveEvaluator = (key) => {
|
|
2164
|
+
return (tree) => {
|
|
2165
|
+
return tree.hasKey(key) ? { type: "remove", key } : null;
|
|
2166
|
+
};
|
|
2167
|
+
};
|
|
2168
|
+
var createRemoveByIdEvaluator = (entryId) => {
|
|
2169
|
+
return (tree) => {
|
|
2170
|
+
return tree.peekById(entryId) !== null ? { type: "removeById", entryId } : null;
|
|
2171
|
+
};
|
|
2172
|
+
};
|
|
2173
|
+
var createUpdateByIdEvaluator = (entryId, value) => {
|
|
2174
|
+
return (tree) => {
|
|
2175
|
+
return tree.peekById(entryId) !== null ? { type: "updateById", entryId, value } : null;
|
|
2176
|
+
};
|
|
2177
|
+
};
|
|
2178
|
+
var createPopFirstEvaluator = () => {
|
|
2179
|
+
return (tree) => {
|
|
2180
|
+
return tree.peekFirst() !== null ? { type: "popFirst" } : null;
|
|
2181
|
+
};
|
|
2182
|
+
};
|
|
2183
|
+
var createPopLastEvaluator = () => {
|
|
2184
|
+
return (tree) => {
|
|
2185
|
+
return tree.peekLast() !== null ? { type: "popLast" } : null;
|
|
2186
|
+
};
|
|
2187
|
+
};
|
|
2188
|
+
var createPutManyEvaluator = (entries, duplicateKeys, compareKeys) => {
|
|
2189
|
+
return (tree) => {
|
|
2190
|
+
const strictlyAscending = duplicateKeys !== "allow";
|
|
2191
|
+
for (let i = 1; i < entries.length; i += 1) {
|
|
2192
|
+
const cmp = compareKeys(entries[i - 1].key, entries[i].key);
|
|
2193
|
+
if (cmp > 0) {
|
|
2194
|
+
throw new BTreeValidationError("putMany: entries not in ascending order.");
|
|
2195
|
+
}
|
|
2196
|
+
if (strictlyAscending && cmp === 0) {
|
|
2197
|
+
throw new BTreeValidationError(
|
|
2198
|
+
duplicateKeys === "reject" ? "putMany: duplicate key rejected." : "putMany: equal keys not allowed in strict mode."
|
|
2199
|
+
);
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
if (duplicateKeys === "reject") {
|
|
2203
|
+
for (const entry of entries) {
|
|
2204
|
+
if (tree.hasKey(entry.key)) {
|
|
2205
|
+
throw new BTreeValidationError("Duplicate key rejected.");
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
return { type: "putMany", entries };
|
|
2210
|
+
};
|
|
2211
|
+
};
|
|
2212
|
+
var createDeleteRangeEvaluator = (startKey, endKey, options) => {
|
|
2213
|
+
return (tree) => {
|
|
2214
|
+
const count = tree.count(startKey, endKey, options);
|
|
2215
|
+
if (count === 0) {
|
|
2216
|
+
return null;
|
|
2217
|
+
}
|
|
2218
|
+
return { type: "deleteRange", startKey, endKey, options };
|
|
2219
|
+
};
|
|
2220
|
+
};
|
|
2221
|
+
var createClearEvaluator = () => {
|
|
2222
|
+
return () => ({ type: "clear" });
|
|
2223
|
+
};
|
|
2224
|
+
|
|
2004
2225
|
// src/concurrency/ConcurrentInMemoryBTree.ts
|
|
2005
2226
|
var ConcurrentInMemoryBTree = class {
|
|
2006
2227
|
constructor(config) {
|
|
2007
2228
|
this.store = config.store;
|
|
2229
|
+
this.compareKeys = config.compareKeys;
|
|
2008
2230
|
this.maxRetries = normalizeMaxRetries(config.maxRetries);
|
|
2009
2231
|
this.maxSyncMutationsPerBatch = normalizeMaxSyncMutationsPerBatch(
|
|
2010
2232
|
config.maxSyncMutationsPerBatch
|
|
@@ -2023,6 +2245,7 @@ var ConcurrentInMemoryBTree = class {
|
|
|
2023
2245
|
this.currentVersion = 0n;
|
|
2024
2246
|
this.operationQueue = Promise.resolve();
|
|
2025
2247
|
this.initSeen = false;
|
|
2248
|
+
this.corrupted = false;
|
|
2026
2249
|
}
|
|
2027
2250
|
async sync() {
|
|
2028
2251
|
await this.runExclusive(async () => {
|
|
@@ -2045,39 +2268,31 @@ var ConcurrentInMemoryBTree = class {
|
|
|
2045
2268
|
if (log.version <= this.currentVersion) {
|
|
2046
2269
|
return;
|
|
2047
2270
|
}
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
}
|
|
2061
|
-
|
|
2062
|
-
return null;
|
|
2063
|
-
case "put":
|
|
2064
|
-
return this.tree.put(mutation.key, mutation.value);
|
|
2065
|
-
case "remove":
|
|
2066
|
-
return this.tree.remove(mutation.key);
|
|
2067
|
-
case "removeById":
|
|
2068
|
-
return this.tree.removeById(mutation.entryId);
|
|
2069
|
-
case "updateById":
|
|
2070
|
-
return this.tree.updateById(mutation.entryId, mutation.value);
|
|
2071
|
-
case "popFirst":
|
|
2072
|
-
return this.tree.popFirst();
|
|
2073
|
-
case "popLast":
|
|
2074
|
-
return this.tree.popLast();
|
|
2075
|
-
default:
|
|
2076
|
-
return assertNeverMutation(mutation);
|
|
2271
|
+
validateMutationBatch(log.mutations, this.configFingerprint);
|
|
2272
|
+
try {
|
|
2273
|
+
for (const mutation of log.mutations) {
|
|
2274
|
+
applyMutationLocal(this.tree, mutation, () => {
|
|
2275
|
+
this.initSeen = true;
|
|
2276
|
+
});
|
|
2277
|
+
}
|
|
2278
|
+
this.currentVersion = log.version;
|
|
2279
|
+
} catch (error) {
|
|
2280
|
+
this.corrupted = true;
|
|
2281
|
+
const cause = error instanceof Error ? error.message : String(error);
|
|
2282
|
+
throw new BTreeConcurrencyError(
|
|
2283
|
+
`Replay failure: instance is permanently corrupted. Discard and create a new instance. Cause: ${cause}`
|
|
2284
|
+
);
|
|
2077
2285
|
}
|
|
2078
2286
|
}
|
|
2079
2287
|
runExclusive(operation) {
|
|
2080
|
-
const run = async () =>
|
|
2288
|
+
const run = async () => {
|
|
2289
|
+
if (this.corrupted) {
|
|
2290
|
+
throw new BTreeConcurrencyError(
|
|
2291
|
+
"Instance is permanently corrupted. Discard and create a new instance."
|
|
2292
|
+
);
|
|
2293
|
+
}
|
|
2294
|
+
return operation();
|
|
2295
|
+
};
|
|
2081
2296
|
const result = this.operationQueue.then(run, run);
|
|
2082
2297
|
this.operationQueue = result.then(
|
|
2083
2298
|
() => void 0,
|
|
@@ -2105,13 +2320,29 @@ var ConcurrentInMemoryBTree = class {
|
|
|
2105
2320
|
const appendResult = await this.store.append(expectedVersion, mutations);
|
|
2106
2321
|
assertAppendVersionContract(expectedVersion, appendResult);
|
|
2107
2322
|
if (appendResult.applied) {
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2323
|
+
try {
|
|
2324
|
+
for (const m of mutations) {
|
|
2325
|
+
if (m === mutation) break;
|
|
2326
|
+
applyMutationLocal(this.tree, m, () => {
|
|
2327
|
+
this.initSeen = true;
|
|
2328
|
+
});
|
|
2329
|
+
}
|
|
2330
|
+
const localResult = applyMutationLocal(
|
|
2331
|
+
this.tree,
|
|
2332
|
+
mutation,
|
|
2333
|
+
() => {
|
|
2334
|
+
this.initSeen = true;
|
|
2335
|
+
}
|
|
2336
|
+
);
|
|
2337
|
+
this.currentVersion = appendResult.version;
|
|
2338
|
+
return localResult;
|
|
2339
|
+
} catch (error) {
|
|
2340
|
+
this.corrupted = true;
|
|
2341
|
+
const cause = error instanceof Error ? error.message : String(error);
|
|
2342
|
+
throw new BTreeConcurrencyError(
|
|
2343
|
+
`Local apply failure after successful append: instance is permanently corrupted. Discard and create a new instance. Cause: ${cause}`
|
|
2344
|
+
);
|
|
2111
2345
|
}
|
|
2112
|
-
const localResult = this.applyMutationLocal(mutation);
|
|
2113
|
-
this.currentVersion = appendResult.version;
|
|
2114
|
-
return localResult;
|
|
2115
2346
|
}
|
|
2116
2347
|
}
|
|
2117
2348
|
throw new BTreeConcurrencyError(
|
|
@@ -2121,47 +2352,56 @@ var ConcurrentInMemoryBTree = class {
|
|
|
2121
2352
|
async put(key, value) {
|
|
2122
2353
|
return this.runExclusive(async () => {
|
|
2123
2354
|
return this.appendMutationAndApplyUnlocked(
|
|
2124
|
-
(
|
|
2125
|
-
if (this.duplicateKeys === "reject" && tree.hasKey(key)) {
|
|
2126
|
-
throw new BTreeValidationError("Duplicate key rejected.");
|
|
2127
|
-
}
|
|
2128
|
-
return { type: "put", key, value };
|
|
2129
|
-
}
|
|
2355
|
+
createPutEvaluator(this.duplicateKeys, key, value)
|
|
2130
2356
|
);
|
|
2131
2357
|
});
|
|
2132
2358
|
}
|
|
2133
2359
|
async remove(key) {
|
|
2134
2360
|
return this.runExclusive(async () => {
|
|
2135
|
-
return this.appendMutationAndApplyUnlocked(
|
|
2136
|
-
(tree) => {
|
|
2137
|
-
return tree.hasKey(key) ? { type: "remove", key } : null;
|
|
2138
|
-
}
|
|
2139
|
-
);
|
|
2361
|
+
return this.appendMutationAndApplyUnlocked(createRemoveEvaluator(key));
|
|
2140
2362
|
});
|
|
2141
2363
|
}
|
|
2142
2364
|
async removeById(entryId) {
|
|
2143
2365
|
return this.runExclusive(async () => {
|
|
2144
|
-
return this.appendMutationAndApplyUnlocked(
|
|
2145
|
-
(tree) => {
|
|
2146
|
-
return tree.peekById(entryId) !== null ? { type: "removeById", entryId } : null;
|
|
2147
|
-
}
|
|
2148
|
-
);
|
|
2366
|
+
return this.appendMutationAndApplyUnlocked(createRemoveByIdEvaluator(entryId));
|
|
2149
2367
|
});
|
|
2150
2368
|
}
|
|
2151
2369
|
async updateById(entryId, value) {
|
|
2370
|
+
return this.runExclusive(async () => {
|
|
2371
|
+
return this.appendMutationAndApplyUnlocked(createUpdateByIdEvaluator(entryId, value));
|
|
2372
|
+
});
|
|
2373
|
+
}
|
|
2374
|
+
async popFirst() {
|
|
2375
|
+
return this.runExclusive(async () => {
|
|
2376
|
+
return this.appendMutationAndApplyUnlocked(createPopFirstEvaluator());
|
|
2377
|
+
});
|
|
2378
|
+
}
|
|
2379
|
+
async popLast() {
|
|
2380
|
+
return this.runExclusive(async () => {
|
|
2381
|
+
return this.appendMutationAndApplyUnlocked(createPopLastEvaluator());
|
|
2382
|
+
});
|
|
2383
|
+
}
|
|
2384
|
+
async putMany(entries) {
|
|
2385
|
+
if (entries.length === 0) {
|
|
2386
|
+
return [];
|
|
2387
|
+
}
|
|
2152
2388
|
return this.runExclusive(async () => {
|
|
2153
2389
|
return this.appendMutationAndApplyUnlocked(
|
|
2154
|
-
(
|
|
2155
|
-
return tree.peekById(entryId) !== null ? { type: "updateById", entryId, value } : null;
|
|
2156
|
-
}
|
|
2390
|
+
createPutManyEvaluator(entries, this.duplicateKeys, this.compareKeys)
|
|
2157
2391
|
);
|
|
2158
2392
|
});
|
|
2159
2393
|
}
|
|
2160
|
-
async
|
|
2394
|
+
async deleteRange(startKey, endKey, options) {
|
|
2161
2395
|
return this.runExclusive(async () => {
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2396
|
+
const result = await this.appendMutationAndApplyUnlocked(
|
|
2397
|
+
createDeleteRangeEvaluator(startKey, endKey, options)
|
|
2398
|
+
);
|
|
2399
|
+
return result ?? 0;
|
|
2400
|
+
});
|
|
2401
|
+
}
|
|
2402
|
+
async clear() {
|
|
2403
|
+
await this.runExclusive(async () => {
|
|
2404
|
+
await this.appendMutationAndApplyUnlocked(createClearEvaluator());
|
|
2165
2405
|
});
|
|
2166
2406
|
}
|
|
2167
2407
|
async get(key) {
|
|
@@ -2197,13 +2437,6 @@ var ConcurrentInMemoryBTree = class {
|
|
|
2197
2437
|
async peekLast() {
|
|
2198
2438
|
return this.readOp((tree) => tree.peekLast());
|
|
2199
2439
|
}
|
|
2200
|
-
async popLast() {
|
|
2201
|
-
return this.runExclusive(async () => {
|
|
2202
|
-
return this.appendMutationAndApplyUnlocked((tree) => {
|
|
2203
|
-
return tree.peekLast() !== null ? { type: "popLast" } : null;
|
|
2204
|
-
});
|
|
2205
|
-
});
|
|
2206
|
-
}
|
|
2207
2440
|
async peekById(entryId) {
|
|
2208
2441
|
return this.readOp((tree) => tree.peekById(entryId));
|
|
2209
2442
|
}
|
|
@@ -2219,6 +2452,38 @@ var ConcurrentInMemoryBTree = class {
|
|
|
2219
2452
|
async getPairOrNextLower(key) {
|
|
2220
2453
|
return this.readOp((tree) => tree.getPairOrNextLower(key));
|
|
2221
2454
|
}
|
|
2455
|
+
async entries() {
|
|
2456
|
+
return this.readOp((tree) => Array.from(tree.entries()));
|
|
2457
|
+
}
|
|
2458
|
+
async entriesReversed() {
|
|
2459
|
+
return this.readOp((tree) => Array.from(tree.entriesReversed()));
|
|
2460
|
+
}
|
|
2461
|
+
async keys() {
|
|
2462
|
+
return this.readOp((tree) => Array.from(tree.keys()));
|
|
2463
|
+
}
|
|
2464
|
+
async values() {
|
|
2465
|
+
return this.readOp((tree) => Array.from(tree.values()));
|
|
2466
|
+
}
|
|
2467
|
+
async forEach(callback) {
|
|
2468
|
+
await this.readOp((tree) => {
|
|
2469
|
+
tree.forEach(callback);
|
|
2470
|
+
});
|
|
2471
|
+
}
|
|
2472
|
+
async *[Symbol.asyncIterator]() {
|
|
2473
|
+
const all = await this.entries();
|
|
2474
|
+
for (const entry of all) {
|
|
2475
|
+
yield entry;
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
async clone() {
|
|
2479
|
+
return this.readOp((tree) => tree.clone());
|
|
2480
|
+
}
|
|
2481
|
+
async toJSON() {
|
|
2482
|
+
return this.readOp((tree) => tree.toJSON());
|
|
2483
|
+
}
|
|
2484
|
+
static fromJSON(json, compareKeys) {
|
|
2485
|
+
return InMemoryBTree.fromJSON(json, compareKeys);
|
|
2486
|
+
}
|
|
2222
2487
|
};
|
|
2223
2488
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2224
2489
|
0 && (module.exports = {
|