@frostpillar/frostpillar-btree 0.2.5 → 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 +33 -2
- package/README.md +33 -2
- 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/types.d.ts +12 -26
- package/dist/{chunk-CZFRT2NN.js → chunk-OWHENPGJ.js} +255 -198
- package/dist/concurrency/ConcurrentInMemoryBTree.d.ts +20 -16
- package/dist/concurrency/helpers.d.ts +8 -2
- package/dist/concurrency/types.d.ts +14 -1
- package/dist/concurrency/writeOps.d.ts +48 -0
- package/dist/core.cjs +255 -198
- 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 +531 -299
- package/dist/index.js +277 -102
- 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,7 +1855,7 @@ 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
|
}
|
|
@@ -1828,7 +1878,7 @@ var InMemoryBTree = class _InMemoryBTree {
|
|
|
1828
1878
|
while (leaf !== null) {
|
|
1829
1879
|
const count = leafEntryCount(leaf);
|
|
1830
1880
|
for (let i = 0; i < count; i += 1) {
|
|
1831
|
-
callback.call(thisArg, leafEntryAt(leaf, i));
|
|
1881
|
+
callback.call(thisArg, toPublicEntry(leafEntryAt(leaf, i)));
|
|
1832
1882
|
}
|
|
1833
1883
|
leaf = leaf.next;
|
|
1834
1884
|
}
|
|
@@ -1840,12 +1890,19 @@ var InMemoryBTree = class _InMemoryBTree {
|
|
|
1840
1890
|
while (leaf !== null) {
|
|
1841
1891
|
const count = leafEntryCount(leaf);
|
|
1842
1892
|
for (let i = 0; i < count; i += 1) {
|
|
1843
|
-
result[writeIdx++] = leafEntryAt(leaf, i);
|
|
1893
|
+
result[writeIdx++] = toPublicEntry(leafEntryAt(leaf, i));
|
|
1844
1894
|
}
|
|
1845
1895
|
leaf = leaf.next;
|
|
1846
1896
|
}
|
|
1847
1897
|
return result;
|
|
1848
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
|
+
*/
|
|
1849
1906
|
clone() {
|
|
1850
1907
|
const cloned = new _InMemoryBTree(buildConfigFromState(this.state));
|
|
1851
1908
|
applyAutoScaleCapacitySnapshot(
|
|
@@ -1933,51 +1990,79 @@ var assertNeverMutation = (mutation) => {
|
|
|
1933
1990
|
`Unsupported mutation type from shared store: ${String(unknownMutation.type)}`
|
|
1934
1991
|
);
|
|
1935
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
|
+
};
|
|
1936
2057
|
var validateMutationBatch = (mutations, expectedConfigFingerprint) => {
|
|
1937
2058
|
for (const mutation of mutations) {
|
|
1938
2059
|
if (typeof mutation !== "object" || mutation === null) {
|
|
1939
2060
|
throw new BTreeConcurrencyError("Malformed mutation: expected an object.");
|
|
1940
2061
|
}
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
throw new BTreeConcurrencyError("Malformed init mutation: missing configFingerprint.");
|
|
1946
|
-
}
|
|
1947
|
-
if (expectedConfigFingerprint !== void 0 && m.configFingerprint !== expectedConfigFingerprint) {
|
|
1948
|
-
throw new BTreeConcurrencyError(
|
|
1949
|
-
"Config mismatch: store peers must share identical tree config."
|
|
1950
|
-
);
|
|
1951
|
-
}
|
|
1952
|
-
break;
|
|
1953
|
-
case "put":
|
|
1954
|
-
if (!("key" in m) || !("value" in m)) {
|
|
1955
|
-
throw new BTreeConcurrencyError("Malformed put mutation: missing key or value.");
|
|
1956
|
-
}
|
|
1957
|
-
break;
|
|
1958
|
-
case "remove":
|
|
1959
|
-
if (!("key" in m)) {
|
|
1960
|
-
throw new BTreeConcurrencyError("Malformed remove mutation: missing key.");
|
|
1961
|
-
}
|
|
1962
|
-
break;
|
|
1963
|
-
case "removeById":
|
|
1964
|
-
if (!("entryId" in m)) {
|
|
1965
|
-
throw new BTreeConcurrencyError("Malformed removeById mutation: missing entryId.");
|
|
1966
|
-
}
|
|
1967
|
-
break;
|
|
1968
|
-
case "updateById":
|
|
1969
|
-
if (!("entryId" in m) || !("value" in m)) {
|
|
1970
|
-
throw new BTreeConcurrencyError("Malformed updateById mutation: missing entryId or value.");
|
|
1971
|
-
}
|
|
1972
|
-
break;
|
|
1973
|
-
case "popFirst":
|
|
1974
|
-
case "popLast":
|
|
1975
|
-
break;
|
|
1976
|
-
default:
|
|
1977
|
-
throw new BTreeConcurrencyError(
|
|
1978
|
-
`Unsupported mutation type from shared store: ${String(m.type)}`
|
|
1979
|
-
);
|
|
1980
|
-
}
|
|
2062
|
+
validateSingleMutation(
|
|
2063
|
+
mutation,
|
|
2064
|
+
expectedConfigFingerprint
|
|
2065
|
+
);
|
|
1981
2066
|
}
|
|
1982
2067
|
};
|
|
1983
2068
|
var normalizeMaxRetries = (value) => {
|
|
@@ -2038,10 +2123,110 @@ function assertAppendVersionContract(expectedVersion, appendResult) {
|
|
|
2038
2123
|
}
|
|
2039
2124
|
}
|
|
2040
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
|
+
|
|
2041
2225
|
// src/concurrency/ConcurrentInMemoryBTree.ts
|
|
2042
2226
|
var ConcurrentInMemoryBTree = class {
|
|
2043
2227
|
constructor(config) {
|
|
2044
2228
|
this.store = config.store;
|
|
2229
|
+
this.compareKeys = config.compareKeys;
|
|
2045
2230
|
this.maxRetries = normalizeMaxRetries(config.maxRetries);
|
|
2046
2231
|
this.maxSyncMutationsPerBatch = normalizeMaxSyncMutationsPerBatch(
|
|
2047
2232
|
config.maxSyncMutationsPerBatch
|
|
@@ -2060,6 +2245,7 @@ var ConcurrentInMemoryBTree = class {
|
|
|
2060
2245
|
this.currentVersion = 0n;
|
|
2061
2246
|
this.operationQueue = Promise.resolve();
|
|
2062
2247
|
this.initSeen = false;
|
|
2248
|
+
this.corrupted = false;
|
|
2063
2249
|
}
|
|
2064
2250
|
async sync() {
|
|
2065
2251
|
await this.runExclusive(async () => {
|
|
@@ -2083,34 +2269,30 @@ var ConcurrentInMemoryBTree = class {
|
|
|
2083
2269
|
return;
|
|
2084
2270
|
}
|
|
2085
2271
|
validateMutationBatch(log.mutations, this.configFingerprint);
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
return this.tree.remove(mutation.key);
|
|
2100
|
-
case "removeById":
|
|
2101
|
-
return this.tree.removeById(mutation.entryId);
|
|
2102
|
-
case "updateById":
|
|
2103
|
-
return this.tree.updateById(mutation.entryId, mutation.value);
|
|
2104
|
-
case "popFirst":
|
|
2105
|
-
return this.tree.popFirst();
|
|
2106
|
-
case "popLast":
|
|
2107
|
-
return this.tree.popLast();
|
|
2108
|
-
default:
|
|
2109
|
-
return assertNeverMutation(mutation);
|
|
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
|
+
);
|
|
2110
2285
|
}
|
|
2111
2286
|
}
|
|
2112
2287
|
runExclusive(operation) {
|
|
2113
|
-
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
|
+
};
|
|
2114
2296
|
const result = this.operationQueue.then(run, run);
|
|
2115
2297
|
this.operationQueue = result.then(
|
|
2116
2298
|
() => void 0,
|
|
@@ -2138,13 +2320,29 @@ var ConcurrentInMemoryBTree = class {
|
|
|
2138
2320
|
const appendResult = await this.store.append(expectedVersion, mutations);
|
|
2139
2321
|
assertAppendVersionContract(expectedVersion, appendResult);
|
|
2140
2322
|
if (appendResult.applied) {
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
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
|
+
);
|
|
2144
2345
|
}
|
|
2145
|
-
const localResult = this.applyMutationLocal(mutation);
|
|
2146
|
-
this.currentVersion = appendResult.version;
|
|
2147
|
-
return localResult;
|
|
2148
2346
|
}
|
|
2149
2347
|
}
|
|
2150
2348
|
throw new BTreeConcurrencyError(
|
|
@@ -2154,47 +2352,56 @@ var ConcurrentInMemoryBTree = class {
|
|
|
2154
2352
|
async put(key, value) {
|
|
2155
2353
|
return this.runExclusive(async () => {
|
|
2156
2354
|
return this.appendMutationAndApplyUnlocked(
|
|
2157
|
-
(
|
|
2158
|
-
if (this.duplicateKeys === "reject" && tree.hasKey(key)) {
|
|
2159
|
-
throw new BTreeValidationError("Duplicate key rejected.");
|
|
2160
|
-
}
|
|
2161
|
-
return { type: "put", key, value };
|
|
2162
|
-
}
|
|
2355
|
+
createPutEvaluator(this.duplicateKeys, key, value)
|
|
2163
2356
|
);
|
|
2164
2357
|
});
|
|
2165
2358
|
}
|
|
2166
2359
|
async remove(key) {
|
|
2167
2360
|
return this.runExclusive(async () => {
|
|
2168
|
-
return this.appendMutationAndApplyUnlocked(
|
|
2169
|
-
(tree) => {
|
|
2170
|
-
return tree.hasKey(key) ? { type: "remove", key } : null;
|
|
2171
|
-
}
|
|
2172
|
-
);
|
|
2361
|
+
return this.appendMutationAndApplyUnlocked(createRemoveEvaluator(key));
|
|
2173
2362
|
});
|
|
2174
2363
|
}
|
|
2175
2364
|
async removeById(entryId) {
|
|
2176
2365
|
return this.runExclusive(async () => {
|
|
2177
|
-
return this.appendMutationAndApplyUnlocked(
|
|
2178
|
-
(tree) => {
|
|
2179
|
-
return tree.peekById(entryId) !== null ? { type: "removeById", entryId } : null;
|
|
2180
|
-
}
|
|
2181
|
-
);
|
|
2366
|
+
return this.appendMutationAndApplyUnlocked(createRemoveByIdEvaluator(entryId));
|
|
2182
2367
|
});
|
|
2183
2368
|
}
|
|
2184
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
|
+
}
|
|
2185
2388
|
return this.runExclusive(async () => {
|
|
2186
2389
|
return this.appendMutationAndApplyUnlocked(
|
|
2187
|
-
(
|
|
2188
|
-
return tree.peekById(entryId) !== null ? { type: "updateById", entryId, value } : null;
|
|
2189
|
-
}
|
|
2390
|
+
createPutManyEvaluator(entries, this.duplicateKeys, this.compareKeys)
|
|
2190
2391
|
);
|
|
2191
2392
|
});
|
|
2192
2393
|
}
|
|
2193
|
-
async
|
|
2394
|
+
async deleteRange(startKey, endKey, options) {
|
|
2194
2395
|
return this.runExclusive(async () => {
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
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());
|
|
2198
2405
|
});
|
|
2199
2406
|
}
|
|
2200
2407
|
async get(key) {
|
|
@@ -2230,13 +2437,6 @@ var ConcurrentInMemoryBTree = class {
|
|
|
2230
2437
|
async peekLast() {
|
|
2231
2438
|
return this.readOp((tree) => tree.peekLast());
|
|
2232
2439
|
}
|
|
2233
|
-
async popLast() {
|
|
2234
|
-
return this.runExclusive(async () => {
|
|
2235
|
-
return this.appendMutationAndApplyUnlocked((tree) => {
|
|
2236
|
-
return tree.peekLast() !== null ? { type: "popLast" } : null;
|
|
2237
|
-
});
|
|
2238
|
-
});
|
|
2239
|
-
}
|
|
2240
2440
|
async peekById(entryId) {
|
|
2241
2441
|
return this.readOp((tree) => tree.peekById(entryId));
|
|
2242
2442
|
}
|
|
@@ -2252,6 +2452,38 @@ var ConcurrentInMemoryBTree = class {
|
|
|
2252
2452
|
async getPairOrNextLower(key) {
|
|
2253
2453
|
return this.readOp((tree) => tree.getPairOrNextLower(key));
|
|
2254
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
|
+
}
|
|
2255
2487
|
};
|
|
2256
2488
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2257
2489
|
0 && (module.exports = {
|