@frostpillar/frostpillar-btree 0.2.5 → 0.2.7
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 +117 -64
- package/README.md +117 -64
- package/dist/InMemoryBTree.d.ts +3 -3
- package/dist/btree/autoScale.d.ts +2 -0
- package/dist/btree/entry-lookup.d.ts +8 -0
- package/dist/btree/mutations.d.ts +1 -3
- package/dist/btree/node-ops.d.ts +15 -0
- package/dist/btree/rangeQuery.d.ts +5 -1
- package/dist/btree/rebalance-branch.d.ts +4 -0
- package/dist/btree/rebalance.d.ts +5 -2
- package/dist/btree/serialization.d.ts +3 -1
- package/dist/btree/split.d.ts +3 -0
- package/dist/btree/traversal.d.ts +7 -0
- package/dist/btree/types.d.ts +23 -26
- package/dist/{chunk-CZFRT2NN.js → chunk-UGGWGP4E.js} +823 -499
- package/dist/concurrency/ConcurrentInMemoryBTree.d.ts +22 -29
- package/dist/concurrency/coordinator.d.ts +21 -0
- package/dist/concurrency/helpers.d.ts +8 -2
- package/dist/concurrency/syncLogValidation.d.ts +2 -0
- package/dist/concurrency/types.d.ts +14 -1
- package/dist/concurrency/writeOps.d.ts +48 -0
- package/dist/core.cjs +823 -499
- package/dist/core.d.ts +2 -2
- 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 +1212 -670
- package/dist/index.d.ts +1 -1
- package/dist/index.js +397 -179
- 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,
|
|
@@ -121,11 +77,12 @@ var createBranchNode = (children, parent) => {
|
|
|
121
77
|
const child = children[i];
|
|
122
78
|
child.parent = branch;
|
|
123
79
|
child.indexInParent = i;
|
|
124
|
-
const target = {
|
|
80
|
+
const target = {
|
|
81
|
+
key: void 0,
|
|
82
|
+
sequence: 0
|
|
83
|
+
};
|
|
125
84
|
if (!writeMinKeyTo(child, target)) {
|
|
126
|
-
throw new BTreeInvariantError(
|
|
127
|
-
"branch child has no min key"
|
|
128
|
-
);
|
|
85
|
+
throw new BTreeInvariantError("branch child has no min key");
|
|
129
86
|
}
|
|
130
87
|
keys.push(target);
|
|
131
88
|
}
|
|
@@ -179,7 +136,14 @@ var leafInsertAt = (leaf, logicalIndex, entry) => {
|
|
|
179
136
|
leaf.entryOffset -= 1;
|
|
180
137
|
leaf.entries[phys - 1] = entry;
|
|
181
138
|
} else {
|
|
182
|
-
leaf.entries.
|
|
139
|
+
const len = leaf.entries.length;
|
|
140
|
+
if (phys >= len) {
|
|
141
|
+
leaf.entries.push(entry);
|
|
142
|
+
} else {
|
|
143
|
+
leaf.entries.push(leaf.entries[len - 1]);
|
|
144
|
+
leaf.entries.copyWithin(phys + 1, phys, len);
|
|
145
|
+
leaf.entries[phys] = entry;
|
|
146
|
+
}
|
|
183
147
|
}
|
|
184
148
|
};
|
|
185
149
|
var leafCompact = (leaf) => {
|
|
@@ -208,7 +172,11 @@ var branchInsertAt = (branch, logicalIndex, child, key) => {
|
|
|
208
172
|
const phys = branch.childOffset + logicalIndex;
|
|
209
173
|
const count = branch.children.length - branch.childOffset;
|
|
210
174
|
if (branch.childOffset > 0 && logicalIndex < count >>> 1) {
|
|
211
|
-
branch.children.copyWithin(
|
|
175
|
+
branch.children.copyWithin(
|
|
176
|
+
branch.childOffset - 1,
|
|
177
|
+
branch.childOffset,
|
|
178
|
+
phys
|
|
179
|
+
);
|
|
212
180
|
branch.keys.copyWithin(branch.childOffset - 1, branch.childOffset, phys);
|
|
213
181
|
branch.childOffset -= 1;
|
|
214
182
|
branch.children[phys - 1] = child;
|
|
@@ -229,8 +197,16 @@ var branchRemoveAt = (branch, physIndex) => {
|
|
|
229
197
|
const logicalIndex = physIndex - branch.childOffset;
|
|
230
198
|
const count = branch.children.length - branch.childOffset;
|
|
231
199
|
if (logicalIndex < count - 1 - logicalIndex) {
|
|
232
|
-
branch.children.copyWithin(
|
|
233
|
-
|
|
200
|
+
branch.children.copyWithin(
|
|
201
|
+
branch.childOffset + 1,
|
|
202
|
+
branch.childOffset,
|
|
203
|
+
physIndex
|
|
204
|
+
);
|
|
205
|
+
branch.keys.copyWithin(
|
|
206
|
+
branch.childOffset + 1,
|
|
207
|
+
branch.childOffset,
|
|
208
|
+
physIndex
|
|
209
|
+
);
|
|
234
210
|
branch.childOffset += 1;
|
|
235
211
|
for (let i = branch.childOffset; i <= physIndex; i += 1) {
|
|
236
212
|
branch.children[i].indexInParent = i;
|
|
@@ -249,6 +225,60 @@ var branchRemoveAt = (branch, physIndex) => {
|
|
|
249
225
|
}
|
|
250
226
|
};
|
|
251
227
|
|
|
228
|
+
// src/btree/types.ts
|
|
229
|
+
var DEFAULT_MAX_LEAF_ENTRIES = 64;
|
|
230
|
+
var DEFAULT_MAX_BRANCH_CHILDREN = 64;
|
|
231
|
+
var MIN_NODE_CAPACITY = 3;
|
|
232
|
+
var MAX_NODE_CAPACITY = 16384;
|
|
233
|
+
var NODE_LEAF = 0;
|
|
234
|
+
var normalizeDuplicateKeyPolicy = (value) => {
|
|
235
|
+
if (value === void 0) {
|
|
236
|
+
return "replace";
|
|
237
|
+
}
|
|
238
|
+
if (value !== "allow" && value !== "reject" && value !== "replace") {
|
|
239
|
+
throw new BTreeValidationError(`Invalid duplicateKeys option.`);
|
|
240
|
+
}
|
|
241
|
+
return value;
|
|
242
|
+
};
|
|
243
|
+
var normalizeDeleteRebalancePolicy = (value) => {
|
|
244
|
+
if (value === void 0) {
|
|
245
|
+
return "standard";
|
|
246
|
+
}
|
|
247
|
+
if (value !== "standard" && value !== "lazy") {
|
|
248
|
+
throw new BTreeValidationError(`Invalid deleteRebalancePolicy option.`);
|
|
249
|
+
}
|
|
250
|
+
return value;
|
|
251
|
+
};
|
|
252
|
+
var freezeEntry = (entry) => Object.freeze(entry);
|
|
253
|
+
var createEntry = (key, entryId, value) => Object.freeze({ key, entryId, value });
|
|
254
|
+
var isLeafNode = (node) => {
|
|
255
|
+
return node.kind === NODE_LEAF;
|
|
256
|
+
};
|
|
257
|
+
var writeMinKeyTo = (node, target) => {
|
|
258
|
+
if (node.kind === NODE_LEAF) {
|
|
259
|
+
if (node.entryOffset >= node.entries.length) return false;
|
|
260
|
+
const e = node.entries[node.entryOffset];
|
|
261
|
+
target.key = e.key;
|
|
262
|
+
target.sequence = e.entryId;
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
if (node.childOffset >= node.keys.length) return false;
|
|
266
|
+
target.key = node.keys[node.childOffset].key;
|
|
267
|
+
target.sequence = node.keys[node.childOffset].sequence;
|
|
268
|
+
return true;
|
|
269
|
+
};
|
|
270
|
+
var normalizeNodeCapacity = (value, field, defaultValue) => {
|
|
271
|
+
if (value === void 0) {
|
|
272
|
+
return defaultValue;
|
|
273
|
+
}
|
|
274
|
+
if (!Number.isInteger(value) || value < MIN_NODE_CAPACITY || value > MAX_NODE_CAPACITY) {
|
|
275
|
+
throw new BTreeValidationError(
|
|
276
|
+
`${field}: integer ${MIN_NODE_CAPACITY}\u2013${MAX_NODE_CAPACITY} required.`
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
return value;
|
|
280
|
+
};
|
|
281
|
+
|
|
252
282
|
// src/btree/navigation.ts
|
|
253
283
|
var selectBranchChild = (compare, branch, userKey, sequence) => {
|
|
254
284
|
const off = branch.childOffset;
|
|
@@ -262,7 +292,7 @@ var selectBranchChild = (compare, branch, userKey, sequence) => {
|
|
|
262
292
|
const mid = lower + upper >>> 1;
|
|
263
293
|
const k = branch.keys[mid];
|
|
264
294
|
const cmp = compare(k.key, userKey);
|
|
265
|
-
if ((cmp !== 0 ? cmp : k.sequence - sequence) <= 0) {
|
|
295
|
+
if ((cmp !== 0 ? cmp : k.sequence < sequence ? -1 : k.sequence > sequence ? 1 : 0) <= 0) {
|
|
266
296
|
selectedIndex = mid;
|
|
267
297
|
lower = mid + 1;
|
|
268
298
|
} else {
|
|
@@ -287,7 +317,7 @@ var lowerBoundInLeaf = (state, leaf, userKey, sequence) => {
|
|
|
287
317
|
const mid = lower + upper >>> 1;
|
|
288
318
|
const e = leaf.entries[mid];
|
|
289
319
|
const cmp = compare(e.key, userKey);
|
|
290
|
-
if ((cmp !== 0 ? cmp : e.entryId - sequence) < 0) {
|
|
320
|
+
if ((cmp !== 0 ? cmp : e.entryId < sequence ? -1 : e.entryId > sequence ? 1 : 0) < 0) {
|
|
291
321
|
lower = mid + 1;
|
|
292
322
|
} else {
|
|
293
323
|
upper = mid;
|
|
@@ -303,7 +333,7 @@ var upperBoundInLeaf = (state, leaf, userKey, sequence) => {
|
|
|
303
333
|
const mid = lower + upper >>> 1;
|
|
304
334
|
const e = leaf.entries[mid];
|
|
305
335
|
const cmp = compare(e.key, userKey);
|
|
306
|
-
if ((cmp !== 0 ? cmp : e.entryId - sequence) <= 0) {
|
|
336
|
+
if ((cmp !== 0 ? cmp : e.entryId < sequence ? -1 : e.entryId > sequence ? 1 : 0) <= 0) {
|
|
307
337
|
lower = mid + 1;
|
|
308
338
|
} else {
|
|
309
339
|
upper = mid;
|
|
@@ -370,7 +400,11 @@ var hasKeyEntry = (state, key) => {
|
|
|
370
400
|
var findNextHigherKey = (state, key) => {
|
|
371
401
|
if (state.entryCount === 0) return null;
|
|
372
402
|
const compare = state.compareKeys;
|
|
373
|
-
let leaf = findLeafForKey(
|
|
403
|
+
let leaf = findLeafForKey(
|
|
404
|
+
state,
|
|
405
|
+
key,
|
|
406
|
+
Number.MAX_SAFE_INTEGER
|
|
407
|
+
);
|
|
374
408
|
let idx = upperBoundInLeaf(state, leaf, key, Number.MAX_SAFE_INTEGER);
|
|
375
409
|
while (leaf !== null) {
|
|
376
410
|
if (idx < leafEntryCount(leaf)) {
|
|
@@ -440,7 +474,175 @@ var findPairOrNextLower = (state, key) => {
|
|
|
440
474
|
return null;
|
|
441
475
|
};
|
|
442
476
|
|
|
443
|
-
// src/btree/
|
|
477
|
+
// src/btree/rangeQuery.ts
|
|
478
|
+
function isEmptyRange(compare, startKey, endKey, options) {
|
|
479
|
+
const cmp = compare(startKey, endKey);
|
|
480
|
+
if (cmp > 0) return true;
|
|
481
|
+
const lowerExclusive = options?.lowerBound === "exclusive";
|
|
482
|
+
const upperExclusive = options?.upperBound === "exclusive";
|
|
483
|
+
return lowerExclusive && upperExclusive && cmp === 0;
|
|
484
|
+
}
|
|
485
|
+
var initRangeCursor = (state, startKey, endKey, options) => {
|
|
486
|
+
if (state.entryCount === 0) return null;
|
|
487
|
+
const compare = state.compareKeys;
|
|
488
|
+
if (isEmptyRange(compare, startKey, endKey, options)) return null;
|
|
489
|
+
const lowerExclusive = options?.lowerBound === "exclusive";
|
|
490
|
+
const upperExclusive = options?.upperBound === "exclusive";
|
|
491
|
+
const startSeq = lowerExclusive ? Number.MAX_SAFE_INTEGER : 0;
|
|
492
|
+
const leaf = findLeafForKey(state, startKey, startSeq);
|
|
493
|
+
const index = lowerExclusive ? upperBoundInLeaf(state, leaf, startKey, Number.MAX_SAFE_INTEGER) : lowerBoundInLeaf(state, leaf, startKey, 0);
|
|
494
|
+
return { leaf, index, compare, upperExclusive };
|
|
495
|
+
};
|
|
496
|
+
var countRangeEntries = (state, startKey, endKey, options) => {
|
|
497
|
+
const cursor = initRangeCursor(state, startKey, endKey, options);
|
|
498
|
+
if (cursor === null) return 0;
|
|
499
|
+
let cursorLeaf = cursor.leaf;
|
|
500
|
+
let cursorIndex = cursor.index;
|
|
501
|
+
const { compare, upperExclusive } = cursor;
|
|
502
|
+
let count = 0;
|
|
503
|
+
while (cursorLeaf !== null) {
|
|
504
|
+
const leafCount = leafEntryCount(cursorLeaf);
|
|
505
|
+
if (cursorIndex >= leafCount) {
|
|
506
|
+
cursorLeaf = cursorLeaf.next;
|
|
507
|
+
cursorIndex = 0;
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
const lastKey = leafEntryAt(cursorLeaf, leafCount - 1).key;
|
|
511
|
+
if (isLastEntryInRange(lastKey, endKey, compare, upperExclusive)) {
|
|
512
|
+
count += leafCount - cursorIndex;
|
|
513
|
+
cursorLeaf = cursorLeaf.next;
|
|
514
|
+
cursorIndex = 0;
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
count += findBoundaryEnd(state, cursorLeaf, endKey, upperExclusive, leafCount) - cursorIndex;
|
|
518
|
+
return count;
|
|
519
|
+
}
|
|
520
|
+
return count;
|
|
521
|
+
};
|
|
522
|
+
var isLastEntryInRange = (lastKey, endKey, compare, upperExclusive) => {
|
|
523
|
+
const cmp = compare(lastKey, endKey);
|
|
524
|
+
return upperExclusive ? cmp < 0 : cmp <= 0;
|
|
525
|
+
};
|
|
526
|
+
var findBoundaryEnd = (state, leaf, endKey, upperExclusive, leafCount) => {
|
|
527
|
+
const endSeq = upperExclusive ? 0 : Number.MAX_SAFE_INTEGER;
|
|
528
|
+
const endBound = upperExclusive ? lowerBoundInLeaf(state, leaf, endKey, endSeq) : upperBoundInLeaf(state, leaf, endKey, endSeq);
|
|
529
|
+
return endBound < leafCount ? endBound : leafCount;
|
|
530
|
+
};
|
|
531
|
+
var RANGE_PREALLOC_THRESHOLD = 200;
|
|
532
|
+
var allocateRangeOutput = (state, cursor, startKey, endKey, options) => {
|
|
533
|
+
const firstLeafCount = leafEntryCount(cursor.leaf);
|
|
534
|
+
const firstLeafRemainder = firstLeafCount - cursor.index;
|
|
535
|
+
if (firstLeafRemainder >= RANGE_PREALLOC_THRESHOLD && cursor.leaf.next !== null) {
|
|
536
|
+
const lastKey = leafEntryAt(cursor.leaf, firstLeafCount - 1).key;
|
|
537
|
+
if (isLastEntryInRange(lastKey, endKey, cursor.compare, cursor.upperExclusive)) {
|
|
538
|
+
return new Array(
|
|
539
|
+
countRangeEntries(state, startKey, endKey, options)
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return [];
|
|
544
|
+
};
|
|
545
|
+
var appendLeafSlicePublic = (leaf, from, to, output, useIndexed, writeIdx) => {
|
|
546
|
+
if (useIndexed) {
|
|
547
|
+
for (let i = from; i < to; i += 1) {
|
|
548
|
+
output[writeIdx++] = freezeEntry(leafEntryAt(leaf, i));
|
|
549
|
+
}
|
|
550
|
+
} else {
|
|
551
|
+
for (let i = from; i < to; i += 1) {
|
|
552
|
+
output.push(freezeEntry(leafEntryAt(leaf, i)));
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return writeIdx;
|
|
556
|
+
};
|
|
557
|
+
var collectPublicEntries = (state, cursor, endKey, output) => {
|
|
558
|
+
const { compare, upperExclusive } = cursor;
|
|
559
|
+
let cursorLeaf = cursor.leaf;
|
|
560
|
+
let cursorIndex = cursor.index;
|
|
561
|
+
let writeIdx = 0;
|
|
562
|
+
const useIndexed = output.length > 0;
|
|
563
|
+
while (cursorLeaf !== null) {
|
|
564
|
+
const leafCount = leafEntryCount(cursorLeaf);
|
|
565
|
+
if (cursorIndex >= leafCount) {
|
|
566
|
+
cursorLeaf = cursorLeaf.next;
|
|
567
|
+
cursorIndex = 0;
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
const lastKey = leafEntryAt(cursorLeaf, leafCount - 1).key;
|
|
571
|
+
if (isLastEntryInRange(lastKey, endKey, compare, upperExclusive)) {
|
|
572
|
+
writeIdx = appendLeafSlicePublic(
|
|
573
|
+
cursorLeaf,
|
|
574
|
+
cursorIndex,
|
|
575
|
+
leafCount,
|
|
576
|
+
output,
|
|
577
|
+
useIndexed,
|
|
578
|
+
writeIdx
|
|
579
|
+
);
|
|
580
|
+
cursorLeaf = cursorLeaf.next;
|
|
581
|
+
cursorIndex = 0;
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
const limit = findBoundaryEnd(
|
|
585
|
+
state,
|
|
586
|
+
cursorLeaf,
|
|
587
|
+
endKey,
|
|
588
|
+
upperExclusive,
|
|
589
|
+
leafCount
|
|
590
|
+
);
|
|
591
|
+
appendLeafSlicePublic(
|
|
592
|
+
cursorLeaf,
|
|
593
|
+
cursorIndex,
|
|
594
|
+
limit,
|
|
595
|
+
output,
|
|
596
|
+
useIndexed,
|
|
597
|
+
writeIdx
|
|
598
|
+
);
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
var rangeQueryPublicEntries = (state, startKey, endKey, options) => {
|
|
603
|
+
const cursor = initRangeCursor(state, startKey, endKey, options);
|
|
604
|
+
if (cursor === null) return [];
|
|
605
|
+
const output = allocateRangeOutput(state, cursor, startKey, endKey, options);
|
|
606
|
+
collectPublicEntries(state, cursor, endKey, output);
|
|
607
|
+
return output;
|
|
608
|
+
};
|
|
609
|
+
var forEachRangeEntries = (state, startKey, endKey, callback, options) => {
|
|
610
|
+
const cursor = initRangeCursor(state, startKey, endKey, options);
|
|
611
|
+
if (cursor === null) return;
|
|
612
|
+
let cursorLeaf = cursor.leaf;
|
|
613
|
+
let cursorIndex = cursor.index;
|
|
614
|
+
const { compare, upperExclusive } = cursor;
|
|
615
|
+
while (cursorLeaf !== null) {
|
|
616
|
+
const leafCount = leafEntryCount(cursorLeaf);
|
|
617
|
+
if (cursorIndex >= leafCount) {
|
|
618
|
+
cursorLeaf = cursorLeaf.next;
|
|
619
|
+
cursorIndex = 0;
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
const lastKey = leafEntryAt(cursorLeaf, leafCount - 1).key;
|
|
623
|
+
if (isLastEntryInRange(lastKey, endKey, compare, upperExclusive)) {
|
|
624
|
+
for (let i = cursorIndex; i < leafCount; i += 1) {
|
|
625
|
+
callback(freezeEntry(leafEntryAt(cursorLeaf, i)));
|
|
626
|
+
}
|
|
627
|
+
cursorLeaf = cursorLeaf.next;
|
|
628
|
+
cursorIndex = 0;
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
const limit = findBoundaryEnd(
|
|
632
|
+
state,
|
|
633
|
+
cursorLeaf,
|
|
634
|
+
endKey,
|
|
635
|
+
upperExclusive,
|
|
636
|
+
leafCount
|
|
637
|
+
);
|
|
638
|
+
for (let i = cursorIndex; i < limit; i += 1) {
|
|
639
|
+
callback(freezeEntry(leafEntryAt(cursorLeaf, i)));
|
|
640
|
+
}
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
// src/btree/rebalance-branch.ts
|
|
444
646
|
var updateMinKeyInAncestors = (node) => {
|
|
445
647
|
let current = node;
|
|
446
648
|
while (current.parent !== null) {
|
|
@@ -456,12 +658,9 @@ var requireParent = (node) => {
|
|
|
456
658
|
}
|
|
457
659
|
return node.parent;
|
|
458
660
|
};
|
|
459
|
-
var requireLeafNode = (node) => {
|
|
460
|
-
if (!isLeafNode(node)) throw new BTreeInvariantError("expected leaf, got branch");
|
|
461
|
-
return node;
|
|
462
|
-
};
|
|
463
661
|
var requireBranchNode = (node) => {
|
|
464
|
-
if (isLeafNode(node))
|
|
662
|
+
if (isLeafNode(node))
|
|
663
|
+
throw new BTreeInvariantError("expected branch, got leaf");
|
|
465
664
|
return node;
|
|
466
665
|
};
|
|
467
666
|
var removeChildFromBranch = (branch, childIndex) => {
|
|
@@ -470,27 +669,18 @@ var removeChildFromBranch = (branch, childIndex) => {
|
|
|
470
669
|
}
|
|
471
670
|
branchRemoveAt(branch, childIndex);
|
|
472
671
|
};
|
|
473
|
-
var detachLeafFromChain = (state, leaf) => {
|
|
474
|
-
if (leaf.prev !== null) {
|
|
475
|
-
leaf.prev.next = leaf.next;
|
|
476
|
-
} else if (leaf.next !== null) {
|
|
477
|
-
state.leftmostLeaf = leaf.next;
|
|
478
|
-
}
|
|
479
|
-
if (leaf.next !== null) {
|
|
480
|
-
leaf.next.prev = leaf.prev;
|
|
481
|
-
} else if (leaf.prev !== null) {
|
|
482
|
-
state.rightmostLeaf = leaf.prev;
|
|
483
|
-
}
|
|
484
|
-
leaf.prev = null;
|
|
485
|
-
leaf.next = null;
|
|
486
|
-
};
|
|
487
672
|
var borrowFromLeftBranch = (branch, leftSibling, branchIndex) => {
|
|
488
673
|
const borrowedChild = leftSibling.children.pop();
|
|
489
|
-
if (borrowedChild === void 0)
|
|
674
|
+
if (borrowedChild === void 0)
|
|
675
|
+
throw new BTreeInvariantError("left branch borrow failed");
|
|
490
676
|
leftSibling.keys.pop();
|
|
491
677
|
borrowedChild.parent = branch;
|
|
492
|
-
const borrowedMinKey = {
|
|
493
|
-
|
|
678
|
+
const borrowedMinKey = {
|
|
679
|
+
key: void 0,
|
|
680
|
+
sequence: 0
|
|
681
|
+
};
|
|
682
|
+
if (!writeMinKeyTo(borrowedChild, borrowedMinKey))
|
|
683
|
+
throw new BTreeInvariantError("borrowed child has no min key");
|
|
494
684
|
if (branch.childOffset > 0) {
|
|
495
685
|
branch.childOffset -= 1;
|
|
496
686
|
branch.children[branch.childOffset] = borrowedChild;
|
|
@@ -499,15 +689,20 @@ var borrowFromLeftBranch = (branch, leftSibling, branchIndex) => {
|
|
|
499
689
|
} else {
|
|
500
690
|
branch.children.unshift(borrowedChild);
|
|
501
691
|
branch.keys.unshift(borrowedMinKey);
|
|
502
|
-
for (let i = 0; i < branch.children.length; i += 1)
|
|
692
|
+
for (let i = 0; i < branch.children.length; i += 1)
|
|
693
|
+
branch.children[i].indexInParent = i;
|
|
503
694
|
}
|
|
504
695
|
const parent = requireParent(branch);
|
|
505
|
-
parent.keys[branchIndex] = {
|
|
696
|
+
parent.keys[branchIndex] = {
|
|
697
|
+
key: borrowedMinKey.key,
|
|
698
|
+
sequence: borrowedMinKey.sequence
|
|
699
|
+
};
|
|
506
700
|
updateMinKeyInAncestors(branch);
|
|
507
701
|
};
|
|
508
702
|
var borrowFromRightBranch = (branch, rightSibling, branchIndex) => {
|
|
509
703
|
const shiftIdx = rightSibling.childOffset;
|
|
510
|
-
if (shiftIdx >= rightSibling.children.length)
|
|
704
|
+
if (shiftIdx >= rightSibling.children.length)
|
|
705
|
+
throw new BTreeInvariantError("right branch borrow failed");
|
|
511
706
|
const borrowedChild = rightSibling.children[shiftIdx];
|
|
512
707
|
rightSibling.childOffset += 1;
|
|
513
708
|
if (rightSibling.childOffset >= rightSibling.children.length >>> 1) {
|
|
@@ -515,8 +710,12 @@ var borrowFromRightBranch = (branch, rightSibling, branchIndex) => {
|
|
|
515
710
|
}
|
|
516
711
|
branch.children.push(borrowedChild);
|
|
517
712
|
borrowedChild.parent = branch;
|
|
518
|
-
const borrowedMinKey = {
|
|
519
|
-
|
|
713
|
+
const borrowedMinKey = {
|
|
714
|
+
key: void 0,
|
|
715
|
+
sequence: 0
|
|
716
|
+
};
|
|
717
|
+
if (!writeMinKeyTo(borrowedChild, borrowedMinKey))
|
|
718
|
+
throw new BTreeInvariantError("borrowed child has no min key");
|
|
520
719
|
branch.keys.push(borrowedMinKey);
|
|
521
720
|
borrowedChild.indexInParent = branch.children.length - 1;
|
|
522
721
|
const parent = requireParent(branch);
|
|
@@ -569,7 +768,10 @@ var rebalanceAfterBranchRemoval = (state, branch) => {
|
|
|
569
768
|
const parent = branch.parent;
|
|
570
769
|
if (parent === null) throw new BTreeInvariantError("branch has no parent");
|
|
571
770
|
const branchIndex = branch.indexInParent;
|
|
572
|
-
const { left: leftSibling, right: rightSibling } = findBranchSiblings(
|
|
771
|
+
const { left: leftSibling, right: rightSibling } = findBranchSiblings(
|
|
772
|
+
parent,
|
|
773
|
+
branchIndex
|
|
774
|
+
);
|
|
573
775
|
if (rightSibling !== null && branchChildCount(rightSibling) > state.minBranchChildren) {
|
|
574
776
|
borrowFromRightBranch(branch, rightSibling, branchIndex);
|
|
575
777
|
return;
|
|
@@ -588,44 +790,68 @@ var rebalanceAfterBranchRemoval = (state, branch) => {
|
|
|
588
790
|
}
|
|
589
791
|
throw new BTreeInvariantError("no branch siblings to rebalance");
|
|
590
792
|
};
|
|
793
|
+
|
|
794
|
+
// src/btree/rebalance.ts
|
|
795
|
+
var requireLeafNode = (node) => {
|
|
796
|
+
if (!isLeafNode(node))
|
|
797
|
+
throw new BTreeInvariantError("expected leaf, got branch");
|
|
798
|
+
return node;
|
|
799
|
+
};
|
|
800
|
+
var detachLeafFromChain = (state, leaf) => {
|
|
801
|
+
if (leaf.prev !== null) {
|
|
802
|
+
leaf.prev.next = leaf.next;
|
|
803
|
+
} else if (leaf.next !== null) {
|
|
804
|
+
state.leftmostLeaf = leaf.next;
|
|
805
|
+
}
|
|
806
|
+
if (leaf.next !== null) {
|
|
807
|
+
leaf.next.prev = leaf.prev;
|
|
808
|
+
} else if (leaf.prev !== null) {
|
|
809
|
+
state.rightmostLeaf = leaf.prev;
|
|
810
|
+
}
|
|
811
|
+
leaf.prev = null;
|
|
812
|
+
leaf.next = null;
|
|
813
|
+
};
|
|
591
814
|
var mergeLeafEntries = (target, source) => {
|
|
592
815
|
if (target.entryOffset > 0) {
|
|
593
816
|
target.entries.copyWithin(0, target.entryOffset);
|
|
594
817
|
target.entries.length = target.entries.length - target.entryOffset;
|
|
595
818
|
target.entryOffset = 0;
|
|
596
819
|
}
|
|
597
|
-
|
|
598
|
-
for (let i = source.entryOffset; i < src.length; i += 1) target.entries.push(src[i]);
|
|
820
|
+
target.entries.push(...source.entries.slice(source.entryOffset));
|
|
599
821
|
};
|
|
600
|
-
var
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
state.rightmostLeaf = leaf;
|
|
605
|
-
}
|
|
606
|
-
return;
|
|
822
|
+
var applyLazyThreshold = (min) => Math.max(1, Math.ceil(min / 4));
|
|
823
|
+
var leafRebalanceThreshold = (state) => {
|
|
824
|
+
if (state.deleteRebalancePolicy === "lazy") {
|
|
825
|
+
return applyLazyThreshold(state.minLeafEntries);
|
|
607
826
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
const
|
|
612
|
-
const
|
|
613
|
-
|
|
827
|
+
return state.minLeafEntries;
|
|
828
|
+
};
|
|
829
|
+
var findLeafSiblings = (parent, leafIndex) => {
|
|
830
|
+
const left = leafIndex > parent.childOffset ? requireLeafNode(parent.children[leafIndex - 1]) : null;
|
|
831
|
+
const right = leafIndex + 1 < parent.children.length ? requireLeafNode(parent.children[leafIndex + 1]) : null;
|
|
832
|
+
return { left, right };
|
|
833
|
+
};
|
|
834
|
+
var tryBorrowFromLeafSibling = (state, leaf, parent, leafIndex, leftSibling, rightSibling) => {
|
|
614
835
|
if (rightSibling !== null && leafEntryCount(rightSibling) > state.minLeafEntries) {
|
|
615
836
|
const borrowed = leafShiftEntry(rightSibling);
|
|
616
|
-
if (borrowed === void 0)
|
|
837
|
+
if (borrowed === void 0)
|
|
838
|
+
throw new BTreeInvariantError("right leaf borrow failed");
|
|
617
839
|
leaf.entries.push(borrowed);
|
|
618
840
|
writeMinKeyTo(rightSibling, parent.keys[leafIndex + 1]);
|
|
619
|
-
return;
|
|
841
|
+
return true;
|
|
620
842
|
}
|
|
621
843
|
if (leftSibling !== null && leafEntryCount(leftSibling) > state.minLeafEntries) {
|
|
622
844
|
const borrowed = leftSibling.entries.pop();
|
|
623
|
-
if (borrowed === void 0)
|
|
845
|
+
if (borrowed === void 0)
|
|
846
|
+
throw new BTreeInvariantError("left leaf borrow failed");
|
|
624
847
|
leafUnshiftEntry(leaf, borrowed);
|
|
625
848
|
parent.keys[leafIndex] = { key: borrowed.key, sequence: borrowed.entryId };
|
|
626
849
|
updateMinKeyInAncestors(leaf);
|
|
627
|
-
return;
|
|
850
|
+
return true;
|
|
628
851
|
}
|
|
852
|
+
return false;
|
|
853
|
+
};
|
|
854
|
+
var mergeLeafWithSibling = (state, leaf, parent, leafIndex, leftSibling, rightSibling) => {
|
|
629
855
|
if (leftSibling !== null) {
|
|
630
856
|
mergeLeafEntries(leftSibling, leaf);
|
|
631
857
|
detachLeafFromChain(state, leaf);
|
|
@@ -642,6 +868,24 @@ var rebalanceAfterLeafRemoval = (state, leaf) => {
|
|
|
642
868
|
}
|
|
643
869
|
throw new BTreeInvariantError("no leaf siblings to rebalance");
|
|
644
870
|
};
|
|
871
|
+
var rebalanceAfterLeafRemoval = (state, leaf) => {
|
|
872
|
+
if (leaf === state.root) {
|
|
873
|
+
if (state.entryCount === 0) {
|
|
874
|
+
state.leftmostLeaf = leaf;
|
|
875
|
+
state.rightmostLeaf = leaf;
|
|
876
|
+
}
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
if (leafEntryCount(leaf) >= leafRebalanceThreshold(state)) return;
|
|
880
|
+
const parent = leaf.parent;
|
|
881
|
+
if (parent === null)
|
|
882
|
+
throw new BTreeInvariantError("Leaf node has no parent during rebalance.");
|
|
883
|
+
const leafIndex = leaf.indexInParent;
|
|
884
|
+
const { left, right } = findLeafSiblings(parent, leafIndex);
|
|
885
|
+
if (tryBorrowFromLeafSibling(state, leaf, parent, leafIndex, left, right))
|
|
886
|
+
return;
|
|
887
|
+
mergeLeafWithSibling(state, leaf, parent, leafIndex, left, right);
|
|
888
|
+
};
|
|
645
889
|
|
|
646
890
|
// src/btree/deleteRange.ts
|
|
647
891
|
var navigateToStart = (state, startKey, lowerExclusive) => {
|
|
@@ -654,27 +898,18 @@ var navigateToStart = (state, startKey, lowerExclusive) => {
|
|
|
654
898
|
}
|
|
655
899
|
return { leaf, idx };
|
|
656
900
|
};
|
|
657
|
-
var findRemoveEnd = (state, leaf,
|
|
901
|
+
var findRemoveEnd = (state, leaf, endKey, upperExclusive) => {
|
|
658
902
|
const count = leafEntryCount(leaf);
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
if (upperExclusive ? cmpEnd >= 0 : cmpEnd > 0) break;
|
|
664
|
-
removeEnd += 1;
|
|
665
|
-
}
|
|
666
|
-
return removeEnd;
|
|
667
|
-
};
|
|
668
|
-
var computeTreeHeight = (state) => {
|
|
669
|
-
let h = 0;
|
|
670
|
-
let n = state.root;
|
|
671
|
-
while (!isLeafNode(n)) {
|
|
672
|
-
n = n.children[n.childOffset];
|
|
673
|
-
h += 1;
|
|
903
|
+
const lastEntry = leafEntryAt(leaf, count - 1);
|
|
904
|
+
const cmpLast = state.compareKeys(lastEntry.key, endKey);
|
|
905
|
+
if (upperExclusive ? cmpLast < 0 : cmpLast <= 0) {
|
|
906
|
+
return count;
|
|
674
907
|
}
|
|
675
|
-
|
|
908
|
+
const endSeq = upperExclusive ? 0 : Number.MAX_SAFE_INTEGER;
|
|
909
|
+
const endBound = upperExclusive ? lowerBoundInLeaf(state, leaf, endKey, endSeq) : upperBoundInLeaf(state, leaf, endKey, endSeq);
|
|
910
|
+
return endBound < count ? endBound : count;
|
|
676
911
|
};
|
|
677
|
-
var spliceLeafAndRebalance = (state, leaf, idx, removeCount
|
|
912
|
+
var spliceLeafAndRebalance = (state, leaf, idx, removeCount) => {
|
|
678
913
|
if (state.entryKeys !== null) {
|
|
679
914
|
for (let i = idx; i < idx + removeCount; i += 1) {
|
|
680
915
|
state.entryKeys.delete(leafEntryAt(leaf, i).entryId);
|
|
@@ -689,11 +924,13 @@ var spliceLeafAndRebalance = (state, leaf, idx, removeCount, maxRebalanceDepth)
|
|
|
689
924
|
updateMinKeyInAncestors(leaf);
|
|
690
925
|
}
|
|
691
926
|
const countAfterSplice = leafEntryCount(leaf);
|
|
692
|
-
|
|
693
|
-
|
|
927
|
+
const rebalThreshold = leafRebalanceThreshold(state);
|
|
928
|
+
let safetyGuard = state.minLeafEntries + 4;
|
|
929
|
+
while (safetyGuard > 0 && leaf !== state.root && leafEntryCount(leaf) < rebalThreshold) {
|
|
694
930
|
rebalanceAfterLeafRemoval(state, leaf);
|
|
695
|
-
if (leaf.parent !== null && leaf.parent.children[leaf.indexInParent] !== leaf)
|
|
696
|
-
|
|
931
|
+
if (leaf.parent !== null && leaf.parent.children[leaf.indexInParent] !== leaf)
|
|
932
|
+
break;
|
|
933
|
+
safetyGuard -= 1;
|
|
697
934
|
}
|
|
698
935
|
if (leafEmptied && leafEntryCount(leaf) > 0 && leaf.parent !== null && leaf.parent.children[leaf.indexInParent] === leaf) {
|
|
699
936
|
updateMinKeyInAncestors(leaf);
|
|
@@ -703,12 +940,9 @@ var spliceLeafAndRebalance = (state, leaf, idx, removeCount, maxRebalanceDepth)
|
|
|
703
940
|
var isLeafStillValid = (state, leaf) => leaf.parent === null ? leaf === state.root : leaf.parent.children[leaf.indexInParent] === leaf;
|
|
704
941
|
var deleteRangeEntries = (state, startKey, endKey, options) => {
|
|
705
942
|
if (state.entryCount === 0) return 0;
|
|
706
|
-
|
|
707
|
-
if (boundCompared > 0) return 0;
|
|
943
|
+
if (isEmptyRange(state.compareKeys, startKey, endKey, options)) return 0;
|
|
708
944
|
const lowerExclusive = options?.lowerBound === "exclusive";
|
|
709
945
|
const upperExclusive = options?.upperBound === "exclusive";
|
|
710
|
-
if (lowerExclusive && upperExclusive && boundCompared === 0) return 0;
|
|
711
|
-
const treeHeight = computeTreeHeight(state);
|
|
712
946
|
let deleted = 0;
|
|
713
947
|
let needsNavigate = true;
|
|
714
948
|
let leaf = null;
|
|
@@ -723,10 +957,15 @@ var deleteRangeEntries = (state, startKey, endKey, options) => {
|
|
|
723
957
|
}
|
|
724
958
|
if (idx >= leafEntryCount(leaf)) break;
|
|
725
959
|
const count = leafEntryCount(leaf);
|
|
726
|
-
const removeEnd = findRemoveEnd(state, leaf,
|
|
960
|
+
const removeEnd = findRemoveEnd(state, leaf, endKey, upperExclusive);
|
|
727
961
|
const removeCount = removeEnd - idx;
|
|
728
962
|
if (removeCount === 0) break;
|
|
729
|
-
const countAfterSplice = spliceLeafAndRebalance(
|
|
963
|
+
const countAfterSplice = spliceLeafAndRebalance(
|
|
964
|
+
state,
|
|
965
|
+
leaf,
|
|
966
|
+
idx,
|
|
967
|
+
removeCount
|
|
968
|
+
);
|
|
730
969
|
deleted += removeCount;
|
|
731
970
|
if (removeEnd < count) break;
|
|
732
971
|
if (!isLeafStillValid(state, leaf)) {
|
|
@@ -745,6 +984,7 @@ var deleteRangeEntries = (state, startKey, endKey, options) => {
|
|
|
745
984
|
};
|
|
746
985
|
|
|
747
986
|
// src/btree/autoScale.ts
|
|
987
|
+
var minOccupancy = (max) => Math.ceil(max / 2);
|
|
748
988
|
var AUTO_SCALE_TIERS = [
|
|
749
989
|
{ threshold: 0, maxLeaf: 32, maxBranch: 32 },
|
|
750
990
|
{ threshold: 1e3, maxLeaf: 64, maxBranch: 64 },
|
|
@@ -771,33 +1011,50 @@ var computeNextAutoScaleThreshold = (entryCount) => {
|
|
|
771
1011
|
}
|
|
772
1012
|
return Number.MAX_SAFE_INTEGER;
|
|
773
1013
|
};
|
|
774
|
-
var
|
|
775
|
-
if (typeof config.compareKeys !== "function") {
|
|
776
|
-
throw new BTreeValidationError("compareKeys must be a function.");
|
|
777
|
-
}
|
|
778
|
-
const autoScale = config.autoScale === true;
|
|
1014
|
+
var resolveInitialCapacity = (config, autoScale) => {
|
|
779
1015
|
if (autoScale && (config.maxLeafEntries !== void 0 || config.maxBranchChildren !== void 0)) {
|
|
780
|
-
throw new BTreeValidationError(
|
|
1016
|
+
throw new BTreeValidationError(
|
|
1017
|
+
"autoScale conflicts with explicit capacity."
|
|
1018
|
+
);
|
|
781
1019
|
}
|
|
782
|
-
let maxLeafEntries;
|
|
783
|
-
let maxBranchChildren;
|
|
784
1020
|
if (autoScale) {
|
|
785
1021
|
const tier = computeAutoScaleTier(0);
|
|
786
|
-
maxLeafEntries
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
maxLeafEntries
|
|
790
|
-
|
|
1022
|
+
return { maxLeafEntries: tier.maxLeaf, maxBranchChildren: tier.maxBranch };
|
|
1023
|
+
}
|
|
1024
|
+
return {
|
|
1025
|
+
maxLeafEntries: normalizeNodeCapacity(
|
|
1026
|
+
config.maxLeafEntries,
|
|
1027
|
+
"maxLeafEntries",
|
|
1028
|
+
DEFAULT_MAX_LEAF_ENTRIES
|
|
1029
|
+
),
|
|
1030
|
+
maxBranchChildren: normalizeNodeCapacity(
|
|
1031
|
+
config.maxBranchChildren,
|
|
1032
|
+
"maxBranchChildren",
|
|
1033
|
+
DEFAULT_MAX_BRANCH_CHILDREN
|
|
1034
|
+
)
|
|
1035
|
+
};
|
|
1036
|
+
};
|
|
1037
|
+
var createInitialState = (config) => {
|
|
1038
|
+
if (typeof config.compareKeys !== "function") {
|
|
1039
|
+
throw new BTreeValidationError("compareKeys must be a function.");
|
|
791
1040
|
}
|
|
1041
|
+
const autoScale = config.autoScale === true;
|
|
1042
|
+
const { maxLeafEntries, maxBranchChildren } = resolveInitialCapacity(
|
|
1043
|
+
config,
|
|
1044
|
+
autoScale
|
|
1045
|
+
);
|
|
792
1046
|
const duplicateKeys = normalizeDuplicateKeyPolicy(config.duplicateKeys);
|
|
1047
|
+
const deleteRebalancePolicy = normalizeDeleteRebalancePolicy(
|
|
1048
|
+
config.deleteRebalancePolicy
|
|
1049
|
+
);
|
|
793
1050
|
const emptyLeaf = createLeafNode([], null);
|
|
794
1051
|
return {
|
|
795
1052
|
compareKeys: config.compareKeys,
|
|
796
1053
|
maxLeafEntries,
|
|
797
1054
|
maxBranchChildren,
|
|
798
1055
|
duplicateKeys,
|
|
799
|
-
minLeafEntries:
|
|
800
|
-
minBranchChildren:
|
|
1056
|
+
minLeafEntries: minOccupancy(maxLeafEntries),
|
|
1057
|
+
minBranchChildren: minOccupancy(maxBranchChildren),
|
|
801
1058
|
root: emptyLeaf,
|
|
802
1059
|
leftmostLeaf: emptyLeaf,
|
|
803
1060
|
rightmostLeaf: emptyLeaf,
|
|
@@ -805,6 +1062,7 @@ var createInitialState = (config) => {
|
|
|
805
1062
|
nextSequence: 0,
|
|
806
1063
|
entryKeys: config.enableEntryIdLookup === true ? /* @__PURE__ */ new Map() : null,
|
|
807
1064
|
autoScale,
|
|
1065
|
+
deleteRebalancePolicy,
|
|
808
1066
|
_nextAutoScaleThreshold: autoScale ? computeNextAutoScaleThreshold(0) : Number.MAX_SAFE_INTEGER,
|
|
809
1067
|
_cursor: { leaf: emptyLeaf, index: 0 }
|
|
810
1068
|
};
|
|
@@ -814,13 +1072,15 @@ var maybeAutoScale = (state) => {
|
|
|
814
1072
|
const { maxLeaf, maxBranch } = computeAutoScaleTier(state.entryCount);
|
|
815
1073
|
if (maxLeaf > state.maxLeafEntries) {
|
|
816
1074
|
state.maxLeafEntries = maxLeaf;
|
|
817
|
-
state.minLeafEntries =
|
|
1075
|
+
state.minLeafEntries = minOccupancy(maxLeaf);
|
|
818
1076
|
}
|
|
819
1077
|
if (maxBranch > state.maxBranchChildren) {
|
|
820
1078
|
state.maxBranchChildren = maxBranch;
|
|
821
|
-
state.minBranchChildren =
|
|
1079
|
+
state.minBranchChildren = minOccupancy(maxBranch);
|
|
822
1080
|
}
|
|
823
|
-
state._nextAutoScaleThreshold = computeNextAutoScaleThreshold(
|
|
1081
|
+
state._nextAutoScaleThreshold = computeNextAutoScaleThreshold(
|
|
1082
|
+
state.entryCount
|
|
1083
|
+
);
|
|
824
1084
|
};
|
|
825
1085
|
var applyAutoScaleCapacitySnapshot = (state, maxLeafEntries, maxBranchChildren) => {
|
|
826
1086
|
if (!state.autoScale) {
|
|
@@ -844,8 +1104,16 @@ var applyAutoScaleCapacitySnapshot = (state, maxLeafEntries, maxBranchChildren)
|
|
|
844
1104
|
}
|
|
845
1105
|
state.maxLeafEntries = normalizedLeaf;
|
|
846
1106
|
state.maxBranchChildren = normalizedBranch;
|
|
847
|
-
state.minLeafEntries =
|
|
848
|
-
state.minBranchChildren =
|
|
1107
|
+
state.minLeafEntries = minOccupancy(normalizedLeaf);
|
|
1108
|
+
state.minBranchChildren = minOccupancy(normalizedBranch);
|
|
1109
|
+
};
|
|
1110
|
+
var resetAutoScaleToTier0 = (state) => {
|
|
1111
|
+
const tier = computeAutoScaleTier(0);
|
|
1112
|
+
state.maxLeafEntries = tier.maxLeaf;
|
|
1113
|
+
state.maxBranchChildren = tier.maxBranch;
|
|
1114
|
+
state.minLeafEntries = minOccupancy(tier.maxLeaf);
|
|
1115
|
+
state.minBranchChildren = minOccupancy(tier.maxBranch);
|
|
1116
|
+
state._nextAutoScaleThreshold = computeNextAutoScaleThreshold(0);
|
|
849
1117
|
};
|
|
850
1118
|
|
|
851
1119
|
// src/btree/bulkLoad.ts
|
|
@@ -867,7 +1135,11 @@ var computeChunkBoundaries = (total, max, min) => {
|
|
|
867
1135
|
return boundaries;
|
|
868
1136
|
};
|
|
869
1137
|
var buildLeaves = (state, entries, ids, baseSequence) => {
|
|
870
|
-
const boundaries = computeChunkBoundaries(
|
|
1138
|
+
const boundaries = computeChunkBoundaries(
|
|
1139
|
+
entries.length,
|
|
1140
|
+
state.maxLeafEntries,
|
|
1141
|
+
state.minLeafEntries
|
|
1142
|
+
);
|
|
871
1143
|
const leaves = new Array(boundaries.length);
|
|
872
1144
|
let chunkStart = 0;
|
|
873
1145
|
for (let c = 0; c < boundaries.length; c += 1) {
|
|
@@ -875,7 +1147,11 @@ var buildLeaves = (state, entries, ids, baseSequence) => {
|
|
|
875
1147
|
const chunk = new Array(chunkEnd - chunkStart);
|
|
876
1148
|
for (let i = chunkStart; i < chunkEnd; i += 1) {
|
|
877
1149
|
const seq = baseSequence + i;
|
|
878
|
-
chunk[i - chunkStart] =
|
|
1150
|
+
chunk[i - chunkStart] = createEntry(
|
|
1151
|
+
entries[i].key,
|
|
1152
|
+
seq,
|
|
1153
|
+
entries[i].value
|
|
1154
|
+
);
|
|
879
1155
|
ids[i] = seq;
|
|
880
1156
|
if (state.entryKeys !== null) {
|
|
881
1157
|
state.entryKeys.set(seq, entries[i].key);
|
|
@@ -909,11 +1185,18 @@ var bulkLoadEntries = (state, entries) => {
|
|
|
909
1185
|
} else {
|
|
910
1186
|
let currentLevel = leaves;
|
|
911
1187
|
while (currentLevel.length > 1) {
|
|
912
|
-
const bounds = computeChunkBoundaries(
|
|
1188
|
+
const bounds = computeChunkBoundaries(
|
|
1189
|
+
currentLevel.length,
|
|
1190
|
+
state.maxBranchChildren,
|
|
1191
|
+
state.minBranchChildren
|
|
1192
|
+
);
|
|
913
1193
|
const nextLevel = new Array(bounds.length);
|
|
914
1194
|
let start = 0;
|
|
915
1195
|
for (let b = 0; b < bounds.length; b += 1) {
|
|
916
|
-
nextLevel[b] = createBranchNode(
|
|
1196
|
+
nextLevel[b] = createBranchNode(
|
|
1197
|
+
currentLevel.slice(start, bounds[b]),
|
|
1198
|
+
null
|
|
1199
|
+
);
|
|
917
1200
|
start = bounds[b];
|
|
918
1201
|
}
|
|
919
1202
|
currentLevel = nextLevel;
|
|
@@ -924,9 +1207,12 @@ var bulkLoadEntries = (state, entries) => {
|
|
|
924
1207
|
return ids;
|
|
925
1208
|
};
|
|
926
1209
|
|
|
927
|
-
// src/btree/
|
|
1210
|
+
// src/btree/split.ts
|
|
928
1211
|
var insertChildAfter = (state, parent, existingChild, childToInsert) => {
|
|
929
|
-
const newChildMinKey = {
|
|
1212
|
+
const newChildMinKey = {
|
|
1213
|
+
key: void 0,
|
|
1214
|
+
sequence: 0
|
|
1215
|
+
};
|
|
930
1216
|
if (!writeMinKeyTo(childToInsert, newChildMinKey)) {
|
|
931
1217
|
throw new BTreeInvariantError("inserted child has no min key");
|
|
932
1218
|
}
|
|
@@ -964,7 +1250,10 @@ var splitLeaf = (state, leaf) => {
|
|
|
964
1250
|
var splitBranch = (state, branch) => {
|
|
965
1251
|
branchCompact(branch);
|
|
966
1252
|
const splitAt = Math.ceil(branch.children.length / 2);
|
|
967
|
-
const sibling = createBranchNode(
|
|
1253
|
+
const sibling = createBranchNode(
|
|
1254
|
+
branch.children.splice(splitAt),
|
|
1255
|
+
branch.parent
|
|
1256
|
+
);
|
|
968
1257
|
branch.keys.splice(splitAt);
|
|
969
1258
|
if (branch.parent === null) {
|
|
970
1259
|
state.root = createBranchNode([branch, sibling], null);
|
|
@@ -972,42 +1261,125 @@ var splitBranch = (state, branch) => {
|
|
|
972
1261
|
}
|
|
973
1262
|
insertChildAfter(state, branch.parent, branch, sibling);
|
|
974
1263
|
};
|
|
1264
|
+
|
|
1265
|
+
// src/btree/entry-lookup.ts
|
|
1266
|
+
var findLeafEntryBySequence = (state, userKey, sequence) => {
|
|
1267
|
+
const targetLeaf = findLeafForKey(state, userKey, sequence);
|
|
1268
|
+
const index = lowerBoundInLeaf(state, targetLeaf, userKey, sequence);
|
|
1269
|
+
if (index >= leafEntryCount(targetLeaf)) return null;
|
|
1270
|
+
const entry = leafEntryAt(targetLeaf, index);
|
|
1271
|
+
if (entry.entryId !== sequence) return null;
|
|
1272
|
+
return { leaf: targetLeaf, index };
|
|
1273
|
+
};
|
|
1274
|
+
var peekEntryById = (state, entryId) => {
|
|
1275
|
+
if (state.entryKeys === null) {
|
|
1276
|
+
throw new BTreeInvariantError(
|
|
1277
|
+
"entryKeys lookup map is not enabled on this tree."
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1280
|
+
const userKey = state.entryKeys.get(entryId);
|
|
1281
|
+
if (userKey === void 0) return null;
|
|
1282
|
+
const found = findLeafEntryBySequence(state, userKey, entryId);
|
|
1283
|
+
if (found === null) return null;
|
|
1284
|
+
const entry = leafEntryAt(found.leaf, found.index);
|
|
1285
|
+
return entry;
|
|
1286
|
+
};
|
|
1287
|
+
var updateEntryById = (state, entryId, newValue) => {
|
|
1288
|
+
if (state.entryKeys === null) {
|
|
1289
|
+
throw new BTreeInvariantError(
|
|
1290
|
+
"entryKeys lookup map is not enabled on this tree."
|
|
1291
|
+
);
|
|
1292
|
+
}
|
|
1293
|
+
const userKey = state.entryKeys.get(entryId);
|
|
1294
|
+
if (userKey === void 0) return null;
|
|
1295
|
+
const found = findLeafEntryBySequence(state, userKey, entryId);
|
|
1296
|
+
if (found === null) return null;
|
|
1297
|
+
const entry = leafEntryAt(found.leaf, found.index);
|
|
1298
|
+
const updated = createEntry(entry.key, entry.entryId, newValue);
|
|
1299
|
+
found.leaf.entries[found.leaf.entryOffset + found.index] = updated;
|
|
1300
|
+
return updated;
|
|
1301
|
+
};
|
|
1302
|
+
var removeEntryById = (state, entryId) => {
|
|
1303
|
+
if (state.entryKeys === null) {
|
|
1304
|
+
throw new BTreeInvariantError(
|
|
1305
|
+
"entryKeys lookup map is not enabled on this tree."
|
|
1306
|
+
);
|
|
1307
|
+
}
|
|
1308
|
+
const userKey = state.entryKeys.get(entryId);
|
|
1309
|
+
if (userKey === void 0) return null;
|
|
1310
|
+
const found = findLeafEntryBySequence(state, userKey, entryId);
|
|
1311
|
+
if (found === null) return null;
|
|
1312
|
+
const entry = leafEntryAt(found.leaf, found.index);
|
|
1313
|
+
leafRemoveAt(found.leaf, found.index);
|
|
1314
|
+
state.entryCount -= 1;
|
|
1315
|
+
state.entryKeys.delete(entryId);
|
|
1316
|
+
if (found.index === 0 && leafEntryCount(found.leaf) > 0 && found.leaf.parent !== null) {
|
|
1317
|
+
updateMinKeyInAncestors(found.leaf);
|
|
1318
|
+
}
|
|
1319
|
+
if (found.leaf !== state.root && leafEntryCount(found.leaf) < state.minLeafEntries) {
|
|
1320
|
+
rebalanceAfterLeafRemoval(state, found.leaf);
|
|
1321
|
+
}
|
|
1322
|
+
return entry;
|
|
1323
|
+
};
|
|
1324
|
+
|
|
1325
|
+
// src/btree/mutations.ts
|
|
1326
|
+
var findDuplicateEntry = (state, targetLeaf, key, insertAt) => {
|
|
1327
|
+
if (state.duplicateKeys === "allow") return null;
|
|
1328
|
+
if (insertAt > 0) {
|
|
1329
|
+
const candidate = leafEntryAt(targetLeaf, insertAt - 1);
|
|
1330
|
+
if (state.compareKeys(candidate.key, key) === 0) {
|
|
1331
|
+
return {
|
|
1332
|
+
leaf: targetLeaf,
|
|
1333
|
+
physIndex: targetLeaf.entryOffset + insertAt - 1,
|
|
1334
|
+
entry: candidate
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
} else if (targetLeaf.prev !== null && leafEntryCount(targetLeaf.prev) > 0) {
|
|
1338
|
+
const prevLeaf = targetLeaf.prev;
|
|
1339
|
+
const prevCount = leafEntryCount(prevLeaf);
|
|
1340
|
+
const candidate = leafEntryAt(prevLeaf, prevCount - 1);
|
|
1341
|
+
if (state.compareKeys(candidate.key, key) === 0) {
|
|
1342
|
+
return {
|
|
1343
|
+
leaf: prevLeaf,
|
|
1344
|
+
physIndex: prevLeaf.entryOffset + prevCount - 1,
|
|
1345
|
+
entry: candidate
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
return null;
|
|
1350
|
+
};
|
|
975
1351
|
var putEntryIntoLeaf = (state, targetLeaf, key, value) => {
|
|
976
1352
|
const sequence = state.nextSequence;
|
|
977
1353
|
const insertAt = upperBoundInLeaf(state, targetLeaf, key, sequence);
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
if (
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
existingEntry = candidate;
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
if (existingEntry !== null) {
|
|
993
|
-
if (state.duplicateKeys === "reject") {
|
|
994
|
-
throw new BTreeValidationError("Duplicate key rejected.");
|
|
995
|
-
}
|
|
996
|
-
existingEntry.value = value;
|
|
997
|
-
return existingEntry.entryId;
|
|
998
|
-
}
|
|
1354
|
+
const dup = findDuplicateEntry(state, targetLeaf, key, insertAt);
|
|
1355
|
+
if (dup !== null) {
|
|
1356
|
+
if (state.duplicateKeys === "reject") {
|
|
1357
|
+
throw new BTreeValidationError("Duplicate key rejected.");
|
|
1358
|
+
}
|
|
1359
|
+
dup.leaf.entries[dup.physIndex] = createEntry(
|
|
1360
|
+
dup.entry.key,
|
|
1361
|
+
dup.entry.entryId,
|
|
1362
|
+
value
|
|
1363
|
+
);
|
|
1364
|
+
return dup.entry.entryId;
|
|
999
1365
|
}
|
|
1000
1366
|
if (state.nextSequence >= Number.MAX_SAFE_INTEGER) {
|
|
1001
1367
|
throw new BTreeValidationError("Sequence overflow.");
|
|
1002
1368
|
}
|
|
1003
1369
|
state.nextSequence += 1;
|
|
1004
|
-
leafInsertAt(
|
|
1370
|
+
leafInsertAt(
|
|
1371
|
+
targetLeaf,
|
|
1372
|
+
insertAt,
|
|
1373
|
+
createEntry(key, sequence, value)
|
|
1374
|
+
);
|
|
1005
1375
|
state.entryCount += 1;
|
|
1006
1376
|
if (state.entryKeys !== null) {
|
|
1007
1377
|
state.entryKeys.set(sequence, key);
|
|
1008
1378
|
}
|
|
1009
|
-
if (insertAt === 0 && targetLeaf.parent !== null)
|
|
1010
|
-
|
|
1379
|
+
if (insertAt === 0 && targetLeaf.parent !== null)
|
|
1380
|
+
updateMinKeyInAncestors(targetLeaf);
|
|
1381
|
+
if (leafEntryCount(targetLeaf) > state.maxLeafEntries)
|
|
1382
|
+
splitLeaf(state, targetLeaf);
|
|
1011
1383
|
maybeAutoScale(state);
|
|
1012
1384
|
return sequence;
|
|
1013
1385
|
};
|
|
@@ -1018,7 +1390,8 @@ var putEntry = (state, key, value) => {
|
|
|
1018
1390
|
var popFirstEntry = (state) => {
|
|
1019
1391
|
if (state.entryCount === 0) return null;
|
|
1020
1392
|
const firstEntry = leafShiftEntry(state.leftmostLeaf);
|
|
1021
|
-
if (firstEntry === void 0)
|
|
1393
|
+
if (firstEntry === void 0)
|
|
1394
|
+
throw new BTreeInvariantError("leftmost leaf empty but count > 0");
|
|
1022
1395
|
state.entryCount -= 1;
|
|
1023
1396
|
if (state.entryKeys !== null) {
|
|
1024
1397
|
state.entryKeys.delete(firstEntry.entryId);
|
|
@@ -1034,7 +1407,8 @@ var popFirstEntry = (state) => {
|
|
|
1034
1407
|
var popLastEntry = (state) => {
|
|
1035
1408
|
if (state.entryCount === 0) return null;
|
|
1036
1409
|
const lastEntry = leafPopEntry(state.rightmostLeaf);
|
|
1037
|
-
if (lastEntry === void 0)
|
|
1410
|
+
if (lastEntry === void 0)
|
|
1411
|
+
throw new BTreeInvariantError("rightmost leaf empty but count > 0");
|
|
1038
1412
|
state.entryCount -= 1;
|
|
1039
1413
|
if (state.entryKeys !== null) {
|
|
1040
1414
|
state.entryKeys.delete(lastEntry.entryId);
|
|
@@ -1055,62 +1429,26 @@ var removeFirstMatchingEntry = (state, key) => {
|
|
|
1055
1429
|
if (state.entryKeys !== null) {
|
|
1056
1430
|
state.entryKeys.delete(targetEntry.entryId);
|
|
1057
1431
|
}
|
|
1058
|
-
if (removeAt === 0 && leafEntryCount(targetLeaf) > 0 && targetLeaf.parent !== null)
|
|
1432
|
+
if (removeAt === 0 && leafEntryCount(targetLeaf) > 0 && targetLeaf.parent !== null)
|
|
1433
|
+
updateMinKeyInAncestors(targetLeaf);
|
|
1059
1434
|
if (targetLeaf !== state.root && leafEntryCount(targetLeaf) < state.minLeafEntries) {
|
|
1060
1435
|
rebalanceAfterLeafRemoval(state, targetLeaf);
|
|
1061
1436
|
}
|
|
1062
1437
|
return targetEntry;
|
|
1063
1438
|
};
|
|
1064
|
-
var findLeafEntryBySequence = (state, userKey, sequence) => {
|
|
1065
|
-
const targetLeaf = findLeafForKey(state, userKey, sequence);
|
|
1066
|
-
const index = lowerBoundInLeaf(state, targetLeaf, userKey, sequence);
|
|
1067
|
-
if (index >= leafEntryCount(targetLeaf)) return null;
|
|
1068
|
-
const entry = leafEntryAt(targetLeaf, index);
|
|
1069
|
-
if (entry.entryId !== sequence) return null;
|
|
1070
|
-
return { leaf: targetLeaf, index };
|
|
1071
|
-
};
|
|
1072
|
-
var removeEntryById = (state, entryId) => {
|
|
1073
|
-
const userKey = state.entryKeys.get(entryId);
|
|
1074
|
-
if (userKey === void 0) return null;
|
|
1075
|
-
const found = findLeafEntryBySequence(state, userKey, entryId);
|
|
1076
|
-
if (found === null) return null;
|
|
1077
|
-
const entry = leafEntryAt(found.leaf, found.index);
|
|
1078
|
-
leafRemoveAt(found.leaf, found.index);
|
|
1079
|
-
state.entryCount -= 1;
|
|
1080
|
-
state.entryKeys.delete(entryId);
|
|
1081
|
-
if (found.index === 0 && leafEntryCount(found.leaf) > 0 && found.leaf.parent !== null) {
|
|
1082
|
-
updateMinKeyInAncestors(found.leaf);
|
|
1083
|
-
}
|
|
1084
|
-
if (found.leaf !== state.root && leafEntryCount(found.leaf) < state.minLeafEntries) {
|
|
1085
|
-
rebalanceAfterLeafRemoval(state, found.leaf);
|
|
1086
|
-
}
|
|
1087
|
-
return entry;
|
|
1088
|
-
};
|
|
1089
|
-
var peekEntryById = (state, entryId) => {
|
|
1090
|
-
const userKey = state.entryKeys.get(entryId);
|
|
1091
|
-
if (userKey === void 0) return null;
|
|
1092
|
-
const found = findLeafEntryBySequence(state, userKey, entryId);
|
|
1093
|
-
if (found === null) return null;
|
|
1094
|
-
const entry = leafEntryAt(found.leaf, found.index);
|
|
1095
|
-
return entry;
|
|
1096
|
-
};
|
|
1097
|
-
var updateEntryById = (state, entryId, newValue) => {
|
|
1098
|
-
const userKey = state.entryKeys.get(entryId);
|
|
1099
|
-
if (userKey === void 0) return null;
|
|
1100
|
-
const found = findLeafEntryBySequence(state, userKey, entryId);
|
|
1101
|
-
if (found === null) return null;
|
|
1102
|
-
const entry = leafEntryAt(found.leaf, found.index);
|
|
1103
|
-
entry.value = newValue;
|
|
1104
|
-
return entry;
|
|
1105
|
-
};
|
|
1106
1439
|
var putManyEntries = (state, entries) => {
|
|
1107
1440
|
if (entries.length === 0) return [];
|
|
1108
1441
|
const strictlyAscending = state.duplicateKeys !== "allow";
|
|
1109
1442
|
for (let i = 1; i < entries.length; i += 1) {
|
|
1110
1443
|
const cmp = state.compareKeys(entries[i - 1].key, entries[i].key);
|
|
1111
|
-
if (
|
|
1444
|
+
if (cmp > 0) {
|
|
1112
1445
|
throw new BTreeValidationError(
|
|
1113
|
-
|
|
1446
|
+
"putMany: entries not in ascending order."
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
if (strictlyAscending && cmp === 0) {
|
|
1450
|
+
throw new BTreeValidationError(
|
|
1451
|
+
state.duplicateKeys === "reject" ? "putMany: duplicate key rejected." : "putMany: equal keys not allowed in strict mode."
|
|
1114
1452
|
);
|
|
1115
1453
|
}
|
|
1116
1454
|
}
|
|
@@ -1119,7 +1457,12 @@ var putManyEntries = (state, entries) => {
|
|
|
1119
1457
|
let hintLeaf = findLeafForKey(state, entries[0].key, state.nextSequence);
|
|
1120
1458
|
for (let i = 0; i < entries.length; i += 1) {
|
|
1121
1459
|
const entry = entries[i];
|
|
1122
|
-
const targetLeaf = findLeafFromHint(
|
|
1460
|
+
const targetLeaf = findLeafFromHint(
|
|
1461
|
+
state,
|
|
1462
|
+
hintLeaf,
|
|
1463
|
+
entry.key,
|
|
1464
|
+
state.nextSequence
|
|
1465
|
+
);
|
|
1123
1466
|
ids[i] = putEntryIntoLeaf(state, targetLeaf, entry.key, entry.value);
|
|
1124
1467
|
hintLeaf = targetLeaf;
|
|
1125
1468
|
}
|
|
@@ -1128,116 +1471,14 @@ var putManyEntries = (state, entries) => {
|
|
|
1128
1471
|
return bulkLoadEntries(state, entries);
|
|
1129
1472
|
};
|
|
1130
1473
|
|
|
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
1474
|
// src/btree/serialization.ts
|
|
1235
1475
|
var buildConfigFromState = (state) => {
|
|
1236
1476
|
const config = {
|
|
1237
1477
|
compareKeys: state.compareKeys,
|
|
1238
1478
|
duplicateKeys: state.duplicateKeys,
|
|
1239
1479
|
enableEntryIdLookup: state.entryKeys !== null,
|
|
1240
|
-
autoScale: state.autoScale
|
|
1480
|
+
autoScale: state.autoScale,
|
|
1481
|
+
deleteRebalancePolicy: state.deleteRebalancePolicy
|
|
1241
1482
|
};
|
|
1242
1483
|
if (!state.autoScale) {
|
|
1243
1484
|
config.maxLeafEntries = state.maxLeafEntries;
|
|
@@ -1257,17 +1498,17 @@ var serializeToJSON = (state) => {
|
|
|
1257
1498
|
}
|
|
1258
1499
|
leaf = leaf.next;
|
|
1259
1500
|
}
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
enableEntryIdLookup: state.entryKeys !== null,
|
|
1267
|
-
autoScale: state.autoScale
|
|
1268
|
-
},
|
|
1269
|
-
entries
|
|
1501
|
+
const config = {
|
|
1502
|
+
maxLeafEntries: state.maxLeafEntries,
|
|
1503
|
+
maxBranchChildren: state.maxBranchChildren,
|
|
1504
|
+
duplicateKeys: state.duplicateKeys,
|
|
1505
|
+
enableEntryIdLookup: state.entryKeys !== null,
|
|
1506
|
+
autoScale: state.autoScale
|
|
1270
1507
|
};
|
|
1508
|
+
if (state.deleteRebalancePolicy !== "standard") {
|
|
1509
|
+
config.deleteRebalancePolicy = state.deleteRebalancePolicy;
|
|
1510
|
+
}
|
|
1511
|
+
return { version: 1, config, entries };
|
|
1271
1512
|
};
|
|
1272
1513
|
var MAX_SERIALIZED_ENTRIES = 1e6;
|
|
1273
1514
|
var validateStructure = (json) => {
|
|
@@ -1330,13 +1571,28 @@ var validateBTreeJSON = (json) => {
|
|
|
1330
1571
|
validateStructure(json);
|
|
1331
1572
|
validateConfig(json.config);
|
|
1332
1573
|
};
|
|
1574
|
+
var validateBTreeJSONSortOrder = (json, compareKeys) => {
|
|
1575
|
+
const strict = json.config.duplicateKeys !== "allow";
|
|
1576
|
+
for (let i = 1; i < json.entries.length; i += 1) {
|
|
1577
|
+
const cmp = compareKeys(json.entries[i - 1][0], json.entries[i][0]);
|
|
1578
|
+
if (cmp > 0) {
|
|
1579
|
+
throw new BTreeValidationError("fromJSON: entries not sorted.");
|
|
1580
|
+
}
|
|
1581
|
+
if (strict && cmp === 0) {
|
|
1582
|
+
throw new BTreeValidationError(
|
|
1583
|
+
'fromJSON: duplicate keys require duplicateKeys "allow".'
|
|
1584
|
+
);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
};
|
|
1333
1588
|
var buildConfigFromJSON = (json, compareKeys) => {
|
|
1334
1589
|
const cfg = json.config;
|
|
1335
1590
|
const config = {
|
|
1336
1591
|
compareKeys,
|
|
1337
1592
|
duplicateKeys: cfg.duplicateKeys,
|
|
1338
1593
|
enableEntryIdLookup: cfg.enableEntryIdLookup,
|
|
1339
|
-
autoScale: cfg.autoScale
|
|
1594
|
+
autoScale: cfg.autoScale,
|
|
1595
|
+
deleteRebalancePolicy: cfg.deleteRebalancePolicy
|
|
1340
1596
|
};
|
|
1341
1597
|
if (!cfg.autoScale) {
|
|
1342
1598
|
config.maxLeafEntries = cfg.maxLeafEntries;
|
|
@@ -1345,22 +1601,63 @@ var buildConfigFromJSON = (json, compareKeys) => {
|
|
|
1345
1601
|
return config;
|
|
1346
1602
|
};
|
|
1347
1603
|
|
|
1348
|
-
// src/btree/
|
|
1349
|
-
var
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1604
|
+
// src/btree/traversal.ts
|
|
1605
|
+
var snapshotEntries = (state) => {
|
|
1606
|
+
const result = new Array(state.entryCount);
|
|
1607
|
+
let leaf = state.leftmostLeaf;
|
|
1608
|
+
let writeIdx = 0;
|
|
1609
|
+
while (leaf !== null) {
|
|
1610
|
+
const count = leafEntryCount(leaf);
|
|
1611
|
+
for (let i = 0; i < count; i += 1) {
|
|
1612
|
+
result[writeIdx++] = freezeEntry(leafEntryAt(leaf, i));
|
|
1613
|
+
}
|
|
1614
|
+
leaf = leaf.next;
|
|
1615
|
+
}
|
|
1616
|
+
return result;
|
|
1617
|
+
};
|
|
1618
|
+
var collectInternalEntries = (state) => {
|
|
1619
|
+
const result = new Array(state.entryCount);
|
|
1620
|
+
let leaf = state.leftmostLeaf;
|
|
1621
|
+
let writeIdx = 0;
|
|
1622
|
+
while (leaf !== null) {
|
|
1623
|
+
const count = leafEntryCount(leaf);
|
|
1624
|
+
for (let i = 0; i < count; i += 1) {
|
|
1625
|
+
result[writeIdx++] = leafEntryAt(leaf, i);
|
|
1626
|
+
}
|
|
1627
|
+
leaf = leaf.next;
|
|
1628
|
+
}
|
|
1629
|
+
return result;
|
|
1630
|
+
};
|
|
1631
|
+
var forEachEntry = (state, callback, thisArg) => {
|
|
1632
|
+
let leaf = state.leftmostLeaf;
|
|
1633
|
+
while (leaf !== null) {
|
|
1634
|
+
const count = leafEntryCount(leaf);
|
|
1635
|
+
for (let i = 0; i < count; i += 1) {
|
|
1636
|
+
callback.call(thisArg, freezeEntry(leafEntryAt(leaf, i)));
|
|
1637
|
+
}
|
|
1638
|
+
leaf = leaf.next;
|
|
1639
|
+
}
|
|
1640
|
+
};
|
|
1641
|
+
|
|
1642
|
+
// src/btree/integrity-helpers.ts
|
|
1643
|
+
var nodeMinKey = (node) => {
|
|
1644
|
+
if (isLeafNode(node)) {
|
|
1645
|
+
if (node.entryOffset >= node.entries.length) return null;
|
|
1646
|
+
const e = node.entries[node.entryOffset];
|
|
1647
|
+
return { key: e.key, sequence: e.entryId };
|
|
1354
1648
|
}
|
|
1355
1649
|
if (node.childOffset >= node.keys.length) return null;
|
|
1356
|
-
return {
|
|
1650
|
+
return {
|
|
1651
|
+
key: node.keys[node.childOffset].key,
|
|
1652
|
+
sequence: node.keys[node.childOffset].sequence
|
|
1653
|
+
};
|
|
1357
1654
|
};
|
|
1358
1655
|
var compareNodeKeys = (comparator, leftKey, leftSeq, rightKey, rightSeq) => {
|
|
1359
1656
|
const cmp = comparator(leftKey, rightKey);
|
|
1360
1657
|
if (cmp !== 0) {
|
|
1361
1658
|
return cmp;
|
|
1362
1659
|
}
|
|
1363
|
-
return leftSeq - rightSeq;
|
|
1660
|
+
return leftSeq < rightSeq ? -1 : leftSeq > rightSeq ? 1 : 0;
|
|
1364
1661
|
};
|
|
1365
1662
|
var getNodeMaxKey = (node) => {
|
|
1366
1663
|
if (isLeafNode(node)) {
|
|
@@ -1375,9 +1672,7 @@ var getNodeMaxKey = (node) => {
|
|
|
1375
1672
|
};
|
|
1376
1673
|
var validateComparatorResult = (result) => {
|
|
1377
1674
|
if (!Number.isFinite(result)) {
|
|
1378
|
-
throw new BTreeValidationError(
|
|
1379
|
-
"compareKeys must return a finite number."
|
|
1380
|
-
);
|
|
1675
|
+
throw new BTreeValidationError("compareKeys must return a finite number.");
|
|
1381
1676
|
}
|
|
1382
1677
|
return result;
|
|
1383
1678
|
};
|
|
@@ -1437,6 +1732,34 @@ var assertTransitivityAsInvariant = (state, first, second, third) => {
|
|
|
1437
1732
|
throw error;
|
|
1438
1733
|
}
|
|
1439
1734
|
};
|
|
1735
|
+
var validateAdjacentLeafOrdering = (state, previous, cursor) => {
|
|
1736
|
+
const prevMax = getNodeMaxKey(previous);
|
|
1737
|
+
const currentMin = nodeMinKey(cursor);
|
|
1738
|
+
if (prevMax === null || currentMin === null) {
|
|
1739
|
+
throw new BTreeInvariantError(
|
|
1740
|
+
"Non-empty tree leaf chain contains empty leaf node."
|
|
1741
|
+
);
|
|
1742
|
+
}
|
|
1743
|
+
if (compareNodeKeys(
|
|
1744
|
+
state.compareKeys,
|
|
1745
|
+
prevMax.key,
|
|
1746
|
+
prevMax.sequence,
|
|
1747
|
+
currentMin.key,
|
|
1748
|
+
currentMin.sequence
|
|
1749
|
+
) > 0) {
|
|
1750
|
+
throw new BTreeInvariantError("Adjacent leaf key ranges are out of order.");
|
|
1751
|
+
}
|
|
1752
|
+
const prevCount = leafEntryCount(previous);
|
|
1753
|
+
const curCount = leafEntryCount(cursor);
|
|
1754
|
+
if (state.duplicateKeys !== "allow" && prevCount > 0 && curCount > 0 && state.compareKeys(
|
|
1755
|
+
leafEntryAt(previous, prevCount - 1).key,
|
|
1756
|
+
leafEntryAt(cursor, 0).key
|
|
1757
|
+
) === 0) {
|
|
1758
|
+
throw new BTreeInvariantError(
|
|
1759
|
+
"Duplicate user key detected across adjacent leaves with uniqueness policy."
|
|
1760
|
+
);
|
|
1761
|
+
}
|
|
1762
|
+
};
|
|
1440
1763
|
var validateLeafChainStep = (state, cursor, previous, visited) => {
|
|
1441
1764
|
if (!isLeafNode(cursor)) {
|
|
1442
1765
|
throw new BTreeInvariantError("Leaf linkage cursor reached non-leaf node.");
|
|
@@ -1448,22 +1771,7 @@ var validateLeafChainStep = (state, cursor, previous, visited) => {
|
|
|
1448
1771
|
throw new BTreeInvariantError("Leaf prev pointer mismatch.");
|
|
1449
1772
|
}
|
|
1450
1773
|
if (previous !== null && isLeafNode(previous)) {
|
|
1451
|
-
|
|
1452
|
-
const currentMin = nodeMinKey(cursor);
|
|
1453
|
-
if (prevMax === null || currentMin === null) {
|
|
1454
|
-
throw new BTreeInvariantError("Non-empty tree leaf chain contains empty leaf node.");
|
|
1455
|
-
}
|
|
1456
|
-
if (compareNodeKeys(state.compareKeys, prevMax.key, prevMax.sequence, currentMin.key, currentMin.sequence) > 0) {
|
|
1457
|
-
throw new BTreeInvariantError("Adjacent leaf key ranges are out of order.");
|
|
1458
|
-
}
|
|
1459
|
-
const prevCount = leafEntryCount(previous);
|
|
1460
|
-
const curCount = leafEntryCount(cursor);
|
|
1461
|
-
if (state.duplicateKeys !== "allow" && prevCount > 0 && curCount > 0 && state.compareKeys(
|
|
1462
|
-
leafEntryAt(previous, prevCount - 1).key,
|
|
1463
|
-
leafEntryAt(cursor, 0).key
|
|
1464
|
-
) === 0) {
|
|
1465
|
-
throw new BTreeInvariantError("Duplicate user key detected across adjacent leaves with uniqueness policy.");
|
|
1466
|
-
}
|
|
1774
|
+
validateAdjacentLeafOrdering(state, previous, cursor);
|
|
1467
1775
|
}
|
|
1468
1776
|
};
|
|
1469
1777
|
var validateLeafLinks = (state, expectedLeafCount) => {
|
|
@@ -1472,7 +1780,9 @@ var validateLeafLinks = (state, expectedLeafCount) => {
|
|
|
1472
1780
|
throw new BTreeInvariantError("Empty tree root must be a leaf node.");
|
|
1473
1781
|
}
|
|
1474
1782
|
if (state.leftmostLeaf !== state.root || state.rightmostLeaf !== state.root) {
|
|
1475
|
-
throw new BTreeInvariantError(
|
|
1783
|
+
throw new BTreeInvariantError(
|
|
1784
|
+
"Empty tree leaf pointers must reference root leaf."
|
|
1785
|
+
);
|
|
1476
1786
|
}
|
|
1477
1787
|
return;
|
|
1478
1788
|
}
|
|
@@ -1497,7 +1807,9 @@ var validateLeafLinks = (state, expectedLeafCount) => {
|
|
|
1497
1807
|
throw new BTreeInvariantError("Rightmost leaf pointer mismatch.");
|
|
1498
1808
|
}
|
|
1499
1809
|
if (leafCount !== expectedLeafCount) {
|
|
1500
|
-
throw new BTreeInvariantError(
|
|
1810
|
+
throw new BTreeInvariantError(
|
|
1811
|
+
"Leaf chain count mismatch with tree traversal count."
|
|
1812
|
+
);
|
|
1501
1813
|
}
|
|
1502
1814
|
};
|
|
1503
1815
|
|
|
@@ -1524,7 +1836,9 @@ var validateLeafNodeOrdering = (state, node) => {
|
|
|
1524
1836
|
leafEntryAt(node, index - 1).key,
|
|
1525
1837
|
leafEntryAt(node, index).key
|
|
1526
1838
|
) === 0) {
|
|
1527
|
-
throw new BTreeInvariantError(
|
|
1839
|
+
throw new BTreeInvariantError(
|
|
1840
|
+
"Duplicate user key detected in tree with uniqueness policy."
|
|
1841
|
+
);
|
|
1528
1842
|
}
|
|
1529
1843
|
}
|
|
1530
1844
|
}
|
|
@@ -1532,12 +1846,7 @@ var validateLeafNodeOrdering = (state, node) => {
|
|
|
1532
1846
|
const first = leafEntryAt(node, index - 2);
|
|
1533
1847
|
const second = leafEntryAt(node, index - 1);
|
|
1534
1848
|
const third = leafEntryAt(node, index);
|
|
1535
|
-
assertTransitivityAsInvariant(
|
|
1536
|
-
state,
|
|
1537
|
-
first.key,
|
|
1538
|
-
second.key,
|
|
1539
|
-
third.key
|
|
1540
|
-
);
|
|
1849
|
+
assertTransitivityAsInvariant(state, first.key, second.key, third.key);
|
|
1541
1850
|
}
|
|
1542
1851
|
if (count > state.maxLeafEntries) {
|
|
1543
1852
|
throw new BTreeInvariantError("Leaf node exceeds maximum occupancy.");
|
|
@@ -1546,9 +1855,14 @@ var validateLeafNodeOrdering = (state, node) => {
|
|
|
1546
1855
|
var validateLeafNode = (state, node, depth) => {
|
|
1547
1856
|
validateLeafNodeOrdering(state, node);
|
|
1548
1857
|
const count = leafEntryCount(node);
|
|
1549
|
-
|
|
1858
|
+
let baseMinLeaf = state.autoScale ? Math.ceil(computeAutoScaleTier(0).maxLeaf / 2) : state.minLeafEntries;
|
|
1859
|
+
if (state.deleteRebalancePolicy === "lazy") {
|
|
1860
|
+
baseMinLeaf = applyLazyThreshold(baseMinLeaf);
|
|
1861
|
+
}
|
|
1550
1862
|
if (node !== state.root && count < baseMinLeaf) {
|
|
1551
|
-
throw new BTreeInvariantError(
|
|
1863
|
+
throw new BTreeInvariantError(
|
|
1864
|
+
"Non-root leaf node violates minimum occupancy."
|
|
1865
|
+
);
|
|
1552
1866
|
}
|
|
1553
1867
|
const first = count === 0 ? null : leafEntryAt(node, 0);
|
|
1554
1868
|
const last = count === 0 ? null : leafEntryAt(node, count - 1);
|
|
@@ -1570,76 +1884,110 @@ var validateBranchStructure = (state, node) => {
|
|
|
1570
1884
|
}
|
|
1571
1885
|
const baseMinBranch = state.autoScale ? Math.ceil(computeAutoScaleTier(0).maxBranch / 2) : state.minBranchChildren;
|
|
1572
1886
|
if (node !== state.root && liveCount < baseMinBranch) {
|
|
1573
|
-
throw new BTreeInvariantError(
|
|
1887
|
+
throw new BTreeInvariantError(
|
|
1888
|
+
"Non-root branch node violates minimum occupancy."
|
|
1889
|
+
);
|
|
1574
1890
|
}
|
|
1575
1891
|
if (liveCount > state.maxBranchChildren) {
|
|
1576
1892
|
throw new BTreeInvariantError("Branch node exceeds maximum occupancy.");
|
|
1577
1893
|
}
|
|
1578
1894
|
if (node.keys.length !== node.children.length) {
|
|
1579
|
-
throw new BTreeInvariantError(
|
|
1895
|
+
throw new BTreeInvariantError(
|
|
1896
|
+
"Branch keys array length does not match children array length."
|
|
1897
|
+
);
|
|
1580
1898
|
}
|
|
1581
1899
|
};
|
|
1582
1900
|
var validateBranchChild = (state, node, childIndex, depth) => {
|
|
1583
1901
|
const child = node.children[childIndex];
|
|
1584
1902
|
if (child.parent !== node) {
|
|
1585
|
-
throw new BTreeInvariantError(
|
|
1903
|
+
throw new BTreeInvariantError(
|
|
1904
|
+
"Child-parent pointer mismatch in branch node."
|
|
1905
|
+
);
|
|
1586
1906
|
}
|
|
1587
1907
|
if (child.indexInParent !== childIndex) {
|
|
1588
|
-
throw new BTreeInvariantError(
|
|
1908
|
+
throw new BTreeInvariantError(
|
|
1909
|
+
"Child indexInParent does not match actual position in parent."
|
|
1910
|
+
);
|
|
1589
1911
|
}
|
|
1590
1912
|
const childValidation = validateNode(state, child, depth + 1);
|
|
1591
1913
|
if (childValidation.minKey === null || childValidation.maxKey === null) {
|
|
1592
|
-
throw new BTreeInvariantError(
|
|
1914
|
+
throw new BTreeInvariantError(
|
|
1915
|
+
"Branch child must not be empty in non-root branch tree."
|
|
1916
|
+
);
|
|
1593
1917
|
}
|
|
1594
1918
|
const cachedMinKey = node.keys[childIndex];
|
|
1595
1919
|
const actualMinKey = nodeMinKey(child);
|
|
1596
|
-
if (actualMinKey === null || compareNodeKeys(
|
|
1597
|
-
|
|
1920
|
+
if (actualMinKey === null || compareNodeKeys(
|
|
1921
|
+
state.compareKeys,
|
|
1922
|
+
cachedMinKey.key,
|
|
1923
|
+
cachedMinKey.sequence,
|
|
1924
|
+
actualMinKey.key,
|
|
1925
|
+
actualMinKey.sequence
|
|
1926
|
+
) !== 0) {
|
|
1927
|
+
throw new BTreeInvariantError(
|
|
1928
|
+
"Branch cached key does not match actual child minimum key."
|
|
1929
|
+
);
|
|
1598
1930
|
}
|
|
1599
1931
|
return childValidation;
|
|
1600
1932
|
};
|
|
1933
|
+
var mergeChildValidation = (state, accumulated, childValidation) => {
|
|
1934
|
+
if (accumulated.leafDepth !== null && childValidation.leafDepth !== null && childValidation.leafDepth !== accumulated.leafDepth) {
|
|
1935
|
+
throw new BTreeInvariantError("Leaf depth mismatch detected in tree.");
|
|
1936
|
+
}
|
|
1937
|
+
if (accumulated.leafDepth === null && childValidation.leafDepth !== null) {
|
|
1938
|
+
accumulated.leafDepth = childValidation.leafDepth;
|
|
1939
|
+
}
|
|
1940
|
+
if (accumulated.previousChildMax !== null && compareNodeKeys(
|
|
1941
|
+
state.compareKeys,
|
|
1942
|
+
accumulated.previousChildMax.key,
|
|
1943
|
+
accumulated.previousChildMax.sequence,
|
|
1944
|
+
childValidation.minKey.key,
|
|
1945
|
+
childValidation.minKey.sequence
|
|
1946
|
+
) >= 0) {
|
|
1947
|
+
throw new BTreeInvariantError(
|
|
1948
|
+
"Branch child key ranges are not strictly ordered."
|
|
1949
|
+
);
|
|
1950
|
+
}
|
|
1951
|
+
if (accumulated.minKey === null) accumulated.minKey = childValidation.minKey;
|
|
1952
|
+
accumulated.maxKey = childValidation.maxKey;
|
|
1953
|
+
accumulated.previousChildMax = childValidation.maxKey;
|
|
1954
|
+
accumulated.leafCount += childValidation.leafCount;
|
|
1955
|
+
accumulated.branchCount += childValidation.branchCount;
|
|
1956
|
+
accumulated.entryCount += childValidation.entryCount;
|
|
1957
|
+
};
|
|
1601
1958
|
var validateNode = (state, node, depth) => {
|
|
1602
1959
|
if (isLeafNode(node)) {
|
|
1603
1960
|
return validateLeafNode(state, node, depth);
|
|
1604
1961
|
}
|
|
1605
1962
|
validateBranchStructure(state, node);
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1963
|
+
const acc = {
|
|
1964
|
+
leafDepth: null,
|
|
1965
|
+
leafCount: 0,
|
|
1966
|
+
branchCount: 1,
|
|
1967
|
+
entryCount: 0,
|
|
1968
|
+
minKey: null,
|
|
1969
|
+
maxKey: null,
|
|
1970
|
+
previousChildMax: null
|
|
1971
|
+
};
|
|
1613
1972
|
for (let childIndex = node.childOffset; childIndex < node.children.length; childIndex += 1) {
|
|
1614
1973
|
const childValidation = validateBranchChild(state, node, childIndex, depth);
|
|
1615
|
-
|
|
1616
|
-
throw new BTreeInvariantError("Leaf depth mismatch detected in tree.");
|
|
1617
|
-
}
|
|
1618
|
-
if (leafDepth === null && childValidation.leafDepth !== null) {
|
|
1619
|
-
leafDepth = childValidation.leafDepth;
|
|
1620
|
-
}
|
|
1621
|
-
if (previousChildMax !== null && compareNodeKeys(
|
|
1622
|
-
state.compareKeys,
|
|
1623
|
-
previousChildMax.key,
|
|
1624
|
-
previousChildMax.sequence,
|
|
1625
|
-
childValidation.minKey.key,
|
|
1626
|
-
childValidation.minKey.sequence
|
|
1627
|
-
) >= 0) {
|
|
1628
|
-
throw new BTreeInvariantError("Branch child key ranges are not strictly ordered.");
|
|
1629
|
-
}
|
|
1630
|
-
if (minKey === null) minKey = childValidation.minKey;
|
|
1631
|
-
maxKey = childValidation.maxKey;
|
|
1632
|
-
previousChildMax = childValidation.maxKey;
|
|
1633
|
-
leafCount += childValidation.leafCount;
|
|
1634
|
-
branchCount += childValidation.branchCount;
|
|
1635
|
-
entryCount += childValidation.entryCount;
|
|
1974
|
+
mergeChildValidation(state, acc, childValidation);
|
|
1636
1975
|
}
|
|
1637
|
-
return {
|
|
1976
|
+
return {
|
|
1977
|
+
minKey: acc.minKey,
|
|
1978
|
+
maxKey: acc.maxKey,
|
|
1979
|
+
leafDepth: acc.leafDepth,
|
|
1980
|
+
leafCount: acc.leafCount,
|
|
1981
|
+
branchCount: acc.branchCount,
|
|
1982
|
+
entryCount: acc.entryCount
|
|
1983
|
+
};
|
|
1638
1984
|
};
|
|
1639
1985
|
var assertInvariants = (state) => {
|
|
1640
1986
|
const validation = validateNode(state, state.root, 0);
|
|
1641
1987
|
if (validation.entryCount !== state.entryCount) {
|
|
1642
|
-
throw new BTreeInvariantError(
|
|
1988
|
+
throw new BTreeInvariantError(
|
|
1989
|
+
"Index entry count mismatch between tree traversal and tracked state."
|
|
1990
|
+
);
|
|
1643
1991
|
}
|
|
1644
1992
|
validateLeafLinks(state, validation.leafCount);
|
|
1645
1993
|
};
|
|
@@ -1693,44 +2041,56 @@ var InMemoryBTree = class _InMemoryBTree {
|
|
|
1693
2041
|
return putManyEntries(this.state, entries);
|
|
1694
2042
|
}
|
|
1695
2043
|
remove(key) {
|
|
1696
|
-
|
|
2044
|
+
const entry = removeFirstMatchingEntry(this.state, key);
|
|
2045
|
+
if (entry === null) return null;
|
|
2046
|
+
return freezeEntry(entry);
|
|
1697
2047
|
}
|
|
1698
2048
|
removeById(entryId) {
|
|
1699
2049
|
if (this.state.entryKeys === null) {
|
|
1700
2050
|
throw new BTreeValidationError("Requires enableEntryIdLookup: true.");
|
|
1701
2051
|
}
|
|
1702
|
-
|
|
2052
|
+
const entry = removeEntryById(this.state, entryId);
|
|
2053
|
+
if (entry === null) return null;
|
|
2054
|
+
return freezeEntry(entry);
|
|
1703
2055
|
}
|
|
1704
2056
|
peekById(entryId) {
|
|
1705
2057
|
if (this.state.entryKeys === null) {
|
|
1706
2058
|
throw new BTreeValidationError("Requires enableEntryIdLookup: true.");
|
|
1707
2059
|
}
|
|
1708
|
-
|
|
2060
|
+
const entry = peekEntryById(this.state, entryId);
|
|
2061
|
+
if (entry === null) return null;
|
|
2062
|
+
return freezeEntry(entry);
|
|
1709
2063
|
}
|
|
1710
2064
|
updateById(entryId, value) {
|
|
1711
2065
|
if (this.state.entryKeys === null) {
|
|
1712
2066
|
throw new BTreeValidationError("Requires enableEntryIdLookup: true.");
|
|
1713
2067
|
}
|
|
1714
|
-
|
|
2068
|
+
const entry = updateEntryById(this.state, entryId, value);
|
|
2069
|
+
if (entry === null) return null;
|
|
2070
|
+
return freezeEntry(entry);
|
|
1715
2071
|
}
|
|
1716
2072
|
popFirst() {
|
|
1717
|
-
|
|
2073
|
+
const entry = popFirstEntry(this.state);
|
|
2074
|
+
if (entry === null) return null;
|
|
2075
|
+
return freezeEntry(entry);
|
|
1718
2076
|
}
|
|
1719
2077
|
peekFirst() {
|
|
1720
2078
|
if (this.state.entryCount === 0) {
|
|
1721
2079
|
return null;
|
|
1722
2080
|
}
|
|
1723
|
-
return leafEntryAt(this.state.leftmostLeaf, 0);
|
|
2081
|
+
return freezeEntry(leafEntryAt(this.state.leftmostLeaf, 0));
|
|
1724
2082
|
}
|
|
1725
2083
|
peekLast() {
|
|
1726
2084
|
if (this.state.entryCount === 0) {
|
|
1727
2085
|
return null;
|
|
1728
2086
|
}
|
|
1729
2087
|
const leaf = this.state.rightmostLeaf;
|
|
1730
|
-
return leafEntryAt(leaf, leafEntryCount(leaf) - 1);
|
|
2088
|
+
return freezeEntry(leafEntryAt(leaf, leafEntryCount(leaf) - 1));
|
|
1731
2089
|
}
|
|
1732
2090
|
popLast() {
|
|
1733
|
-
|
|
2091
|
+
const entry = popLastEntry(this.state);
|
|
2092
|
+
if (entry === null) return null;
|
|
2093
|
+
return freezeEntry(entry);
|
|
1734
2094
|
}
|
|
1735
2095
|
clear() {
|
|
1736
2096
|
const emptyLeaf = createLeafNode([], null);
|
|
@@ -1740,16 +2100,9 @@ var InMemoryBTree = class _InMemoryBTree {
|
|
|
1740
2100
|
this.state.entryCount = 0;
|
|
1741
2101
|
this.state._cursor.leaf = emptyLeaf;
|
|
1742
2102
|
this.state._cursor.index = 0;
|
|
1743
|
-
|
|
1744
|
-
this.state.entryKeys.clear();
|
|
1745
|
-
}
|
|
2103
|
+
this.state.entryKeys?.clear();
|
|
1746
2104
|
if (this.state.autoScale) {
|
|
1747
|
-
|
|
1748
|
-
this.state.maxLeafEntries = tier.maxLeaf;
|
|
1749
|
-
this.state.maxBranchChildren = tier.maxBranch;
|
|
1750
|
-
this.state.minLeafEntries = Math.ceil(tier.maxLeaf / 2);
|
|
1751
|
-
this.state.minBranchChildren = Math.ceil(tier.maxBranch / 2);
|
|
1752
|
-
this.state._nextAutoScaleThreshold = computeNextAutoScaleThreshold(0);
|
|
2105
|
+
resetAutoScaleToTier0(this.state);
|
|
1753
2106
|
}
|
|
1754
2107
|
}
|
|
1755
2108
|
get(key) {
|
|
@@ -1763,12 +2116,12 @@ var InMemoryBTree = class _InMemoryBTree {
|
|
|
1763
2116
|
findFirst(key) {
|
|
1764
2117
|
const found = findFirstMatchingUserKey(this.state, key);
|
|
1765
2118
|
if (found === null) return null;
|
|
1766
|
-
return leafEntryAt(found.leaf, found.index);
|
|
2119
|
+
return freezeEntry(leafEntryAt(found.leaf, found.index));
|
|
1767
2120
|
}
|
|
1768
2121
|
findLast(key) {
|
|
1769
2122
|
const found = findLastMatchingUserKey(this.state, key);
|
|
1770
2123
|
if (found === null) return null;
|
|
1771
|
-
return leafEntryAt(found.leaf, found.index);
|
|
2124
|
+
return freezeEntry(leafEntryAt(found.leaf, found.index));
|
|
1772
2125
|
}
|
|
1773
2126
|
nextHigherKey(key) {
|
|
1774
2127
|
return findNextHigherKey(this.state, key);
|
|
@@ -1779,7 +2132,7 @@ var InMemoryBTree = class _InMemoryBTree {
|
|
|
1779
2132
|
getPairOrNextLower(key) {
|
|
1780
2133
|
const found = findPairOrNextLower(this.state, key);
|
|
1781
2134
|
if (found === null) return null;
|
|
1782
|
-
return leafEntryAt(found.leaf, found.index);
|
|
2135
|
+
return freezeEntry(leafEntryAt(found.leaf, found.index));
|
|
1783
2136
|
}
|
|
1784
2137
|
count(startKey, endKey, options) {
|
|
1785
2138
|
return countRangeEntries(this.state, startKey, endKey, options);
|
|
@@ -1788,14 +2141,14 @@ var InMemoryBTree = class _InMemoryBTree {
|
|
|
1788
2141
|
return deleteRangeEntries(this.state, startKey, endKey, options);
|
|
1789
2142
|
}
|
|
1790
2143
|
range(startKey, endKey, options) {
|
|
1791
|
-
return
|
|
2144
|
+
return rangeQueryPublicEntries(this.state, startKey, endKey, options);
|
|
1792
2145
|
}
|
|
1793
2146
|
*entries() {
|
|
1794
2147
|
let leaf = this.state.leftmostLeaf;
|
|
1795
2148
|
while (leaf !== null) {
|
|
1796
2149
|
const count = leafEntryCount(leaf);
|
|
1797
2150
|
for (let i = 0; i < count; i += 1) {
|
|
1798
|
-
yield leafEntryAt(leaf, i);
|
|
2151
|
+
yield freezeEntry(leafEntryAt(leaf, i));
|
|
1799
2152
|
}
|
|
1800
2153
|
leaf = leaf.next;
|
|
1801
2154
|
}
|
|
@@ -1805,7 +2158,7 @@ var InMemoryBTree = class _InMemoryBTree {
|
|
|
1805
2158
|
while (leaf !== null) {
|
|
1806
2159
|
const count = leafEntryCount(leaf);
|
|
1807
2160
|
for (let i = count - 1; i >= 0; i -= 1) {
|
|
1808
|
-
yield leafEntryAt(leaf, i);
|
|
2161
|
+
yield freezeEntry(leafEntryAt(leaf, i));
|
|
1809
2162
|
}
|
|
1810
2163
|
leaf = leaf.prev;
|
|
1811
2164
|
}
|
|
@@ -1823,48 +2176,26 @@ var InMemoryBTree = class _InMemoryBTree {
|
|
|
1823
2176
|
[Symbol.iterator]() {
|
|
1824
2177
|
return this.entries();
|
|
1825
2178
|
}
|
|
2179
|
+
forEachRange(startKey, endKey, callback, options) {
|
|
2180
|
+
forEachRangeEntries(this.state, startKey, endKey, callback, options);
|
|
2181
|
+
}
|
|
1826
2182
|
forEach(callback, thisArg) {
|
|
1827
|
-
|
|
1828
|
-
while (leaf !== null) {
|
|
1829
|
-
const count = leafEntryCount(leaf);
|
|
1830
|
-
for (let i = 0; i < count; i += 1) {
|
|
1831
|
-
callback.call(thisArg, leafEntryAt(leaf, i));
|
|
1832
|
-
}
|
|
1833
|
-
leaf = leaf.next;
|
|
1834
|
-
}
|
|
2183
|
+
forEachEntry(this.state, callback, thisArg);
|
|
1835
2184
|
}
|
|
1836
2185
|
snapshot() {
|
|
1837
|
-
|
|
1838
|
-
let leaf = this.state.leftmostLeaf;
|
|
1839
|
-
let writeIdx = 0;
|
|
1840
|
-
while (leaf !== null) {
|
|
1841
|
-
const count = leafEntryCount(leaf);
|
|
1842
|
-
for (let i = 0; i < count; i += 1) {
|
|
1843
|
-
result[writeIdx++] = leafEntryAt(leaf, i);
|
|
1844
|
-
}
|
|
1845
|
-
leaf = leaf.next;
|
|
1846
|
-
}
|
|
1847
|
-
return result;
|
|
2186
|
+
return snapshotEntries(this.state);
|
|
1848
2187
|
}
|
|
1849
2188
|
clone() {
|
|
1850
|
-
const cloned = new _InMemoryBTree(
|
|
2189
|
+
const cloned = new _InMemoryBTree(
|
|
2190
|
+
buildConfigFromState(this.state)
|
|
2191
|
+
);
|
|
1851
2192
|
applyAutoScaleCapacitySnapshot(
|
|
1852
2193
|
cloned.state,
|
|
1853
2194
|
this.state.maxLeafEntries,
|
|
1854
2195
|
this.state.maxBranchChildren
|
|
1855
2196
|
);
|
|
1856
2197
|
if (this.state.entryCount > 0) {
|
|
1857
|
-
|
|
1858
|
-
let leaf = this.state.leftmostLeaf;
|
|
1859
|
-
let writeIdx = 0;
|
|
1860
|
-
while (leaf !== null) {
|
|
1861
|
-
const count = leafEntryCount(leaf);
|
|
1862
|
-
for (let i = 0; i < count; i += 1) {
|
|
1863
|
-
pairs[writeIdx++] = leafEntryAt(leaf, i);
|
|
1864
|
-
}
|
|
1865
|
-
leaf = leaf.next;
|
|
1866
|
-
}
|
|
1867
|
-
cloned.putMany(pairs);
|
|
2198
|
+
cloned.putMany(collectInternalEntries(this.state));
|
|
1868
2199
|
}
|
|
1869
2200
|
return cloned;
|
|
1870
2201
|
}
|
|
@@ -1873,26 +2204,19 @@ var InMemoryBTree = class _InMemoryBTree {
|
|
|
1873
2204
|
}
|
|
1874
2205
|
static fromJSON(json, compareKeys) {
|
|
1875
2206
|
validateBTreeJSON(json);
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
throw new BTreeValidationError("fromJSON: entries not sorted.");
|
|
1881
|
-
}
|
|
1882
|
-
if (strict && cmp === 0) {
|
|
1883
|
-
throw new BTreeValidationError(
|
|
1884
|
-
'fromJSON: duplicate keys require duplicateKeys "allow".'
|
|
1885
|
-
);
|
|
1886
|
-
}
|
|
1887
|
-
}
|
|
1888
|
-
const tree = new _InMemoryBTree(buildConfigFromJSON(json, compareKeys));
|
|
2207
|
+
validateBTreeJSONSortOrder(json, compareKeys);
|
|
2208
|
+
const tree = new _InMemoryBTree(
|
|
2209
|
+
buildConfigFromJSON(json, compareKeys)
|
|
2210
|
+
);
|
|
1889
2211
|
applyAutoScaleCapacitySnapshot(
|
|
1890
2212
|
tree.state,
|
|
1891
2213
|
json.config.maxLeafEntries,
|
|
1892
2214
|
json.config.maxBranchChildren
|
|
1893
2215
|
);
|
|
1894
2216
|
if (json.entries.length > 0) {
|
|
1895
|
-
const pairs = new Array(
|
|
2217
|
+
const pairs = new Array(
|
|
2218
|
+
json.entries.length
|
|
2219
|
+
);
|
|
1896
2220
|
for (let i = 0; i < json.entries.length; i += 1) {
|
|
1897
2221
|
pairs[i] = { key: json.entries[i][0], value: json.entries[i][1] };
|
|
1898
2222
|
}
|
|
@@ -1933,51 +2257,110 @@ var assertNeverMutation = (mutation) => {
|
|
|
1933
2257
|
`Unsupported mutation type from shared store: ${String(unknownMutation.type)}`
|
|
1934
2258
|
);
|
|
1935
2259
|
};
|
|
1936
|
-
var
|
|
1937
|
-
for (const
|
|
1938
|
-
if (typeof
|
|
1939
|
-
throw new BTreeConcurrencyError(
|
|
2260
|
+
var validatePutManyEntries = (entries) => {
|
|
2261
|
+
for (const entry of entries) {
|
|
2262
|
+
if (typeof entry !== "object" || entry === null || !("key" in entry) || !("value" in entry)) {
|
|
2263
|
+
throw new BTreeConcurrencyError(
|
|
2264
|
+
"Malformed putMany mutation: each entry must have key and value."
|
|
2265
|
+
);
|
|
1940
2266
|
}
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
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:
|
|
2267
|
+
}
|
|
2268
|
+
};
|
|
2269
|
+
var validateInitMutation = (m, expectedConfigFingerprint) => {
|
|
2270
|
+
if (typeof m.configFingerprint !== "string") {
|
|
2271
|
+
throw new BTreeConcurrencyError(
|
|
2272
|
+
"Malformed init mutation: missing configFingerprint."
|
|
2273
|
+
);
|
|
2274
|
+
}
|
|
2275
|
+
if (expectedConfigFingerprint !== void 0 && m.configFingerprint !== expectedConfigFingerprint) {
|
|
2276
|
+
throw new BTreeConcurrencyError(
|
|
2277
|
+
"Config mismatch: store peers must share identical tree config."
|
|
2278
|
+
);
|
|
2279
|
+
}
|
|
2280
|
+
};
|
|
2281
|
+
var validateMutationFields = (m) => {
|
|
2282
|
+
switch (m.type) {
|
|
2283
|
+
case "put":
|
|
2284
|
+
if (!("key" in m) || !("value" in m)) {
|
|
1977
2285
|
throw new BTreeConcurrencyError(
|
|
1978
|
-
|
|
2286
|
+
"Malformed put mutation: missing key or value."
|
|
1979
2287
|
);
|
|
2288
|
+
}
|
|
2289
|
+
break;
|
|
2290
|
+
case "remove":
|
|
2291
|
+
if (!("key" in m)) {
|
|
2292
|
+
throw new BTreeConcurrencyError(
|
|
2293
|
+
"Malformed remove mutation: missing key."
|
|
2294
|
+
);
|
|
2295
|
+
}
|
|
2296
|
+
break;
|
|
2297
|
+
case "removeById":
|
|
2298
|
+
if (!("entryId" in m)) {
|
|
2299
|
+
throw new BTreeConcurrencyError(
|
|
2300
|
+
"Malformed removeById mutation: missing entryId."
|
|
2301
|
+
);
|
|
2302
|
+
}
|
|
2303
|
+
break;
|
|
2304
|
+
case "updateById":
|
|
2305
|
+
if (!("entryId" in m) || !("value" in m)) {
|
|
2306
|
+
throw new BTreeConcurrencyError(
|
|
2307
|
+
"Malformed updateById mutation: missing entryId or value."
|
|
2308
|
+
);
|
|
2309
|
+
}
|
|
2310
|
+
break;
|
|
2311
|
+
case "putMany":
|
|
2312
|
+
if (!("entries" in m) || !Array.isArray(m.entries)) {
|
|
2313
|
+
throw new BTreeConcurrencyError(
|
|
2314
|
+
"Malformed putMany mutation: missing entries array."
|
|
2315
|
+
);
|
|
2316
|
+
}
|
|
2317
|
+
validatePutManyEntries(m.entries);
|
|
2318
|
+
break;
|
|
2319
|
+
case "deleteRange":
|
|
2320
|
+
if (!("startKey" in m) || !("endKey" in m)) {
|
|
2321
|
+
throw new BTreeConcurrencyError(
|
|
2322
|
+
"Malformed deleteRange mutation: missing startKey or endKey."
|
|
2323
|
+
);
|
|
2324
|
+
}
|
|
2325
|
+
break;
|
|
2326
|
+
default:
|
|
2327
|
+
break;
|
|
2328
|
+
}
|
|
2329
|
+
};
|
|
2330
|
+
var validateSingleMutation = (m, expectedConfigFingerprint) => {
|
|
2331
|
+
switch (m.type) {
|
|
2332
|
+
case "init":
|
|
2333
|
+
validateInitMutation(m, expectedConfigFingerprint);
|
|
2334
|
+
break;
|
|
2335
|
+
case "put":
|
|
2336
|
+
case "remove":
|
|
2337
|
+
case "removeById":
|
|
2338
|
+
case "updateById":
|
|
2339
|
+
case "putMany":
|
|
2340
|
+
case "deleteRange":
|
|
2341
|
+
validateMutationFields(m);
|
|
2342
|
+
break;
|
|
2343
|
+
case "popFirst":
|
|
2344
|
+
case "popLast":
|
|
2345
|
+
case "clear":
|
|
2346
|
+
break;
|
|
2347
|
+
default:
|
|
2348
|
+
throw new BTreeConcurrencyError(
|
|
2349
|
+
`Unsupported mutation type from shared store: ${String(m.type)}`
|
|
2350
|
+
);
|
|
2351
|
+
}
|
|
2352
|
+
};
|
|
2353
|
+
var validateMutationBatch = (mutations, expectedConfigFingerprint) => {
|
|
2354
|
+
for (const mutation of mutations) {
|
|
2355
|
+
if (typeof mutation !== "object" || mutation === null) {
|
|
2356
|
+
throw new BTreeConcurrencyError(
|
|
2357
|
+
"Malformed mutation: expected an object."
|
|
2358
|
+
);
|
|
1980
2359
|
}
|
|
2360
|
+
validateSingleMutation(
|
|
2361
|
+
mutation,
|
|
2362
|
+
expectedConfigFingerprint
|
|
2363
|
+
);
|
|
1981
2364
|
}
|
|
1982
2365
|
};
|
|
1983
2366
|
var normalizeMaxRetries = (value) => {
|
|
@@ -2007,9 +2390,7 @@ var normalizeReadMode = (value) => {
|
|
|
2007
2390
|
return "strong";
|
|
2008
2391
|
}
|
|
2009
2392
|
if (value !== "strong" && value !== "local") {
|
|
2010
|
-
throw new BTreeConcurrencyError(
|
|
2011
|
-
`readMode: must be 'strong' or 'local'.`
|
|
2012
|
-
);
|
|
2393
|
+
throw new BTreeConcurrencyError(`readMode: must be 'strong' or 'local'.`);
|
|
2013
2394
|
}
|
|
2014
2395
|
return value;
|
|
2015
2396
|
};
|
|
@@ -2038,79 +2419,170 @@ function assertAppendVersionContract(expectedVersion, appendResult) {
|
|
|
2038
2419
|
}
|
|
2039
2420
|
}
|
|
2040
2421
|
|
|
2041
|
-
// src/concurrency/
|
|
2042
|
-
var
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2422
|
+
// src/concurrency/writeOps.ts
|
|
2423
|
+
var applyMutationLocal = (tree, mutation, onInit) => {
|
|
2424
|
+
switch (mutation.type) {
|
|
2425
|
+
case "init":
|
|
2426
|
+
onInit();
|
|
2427
|
+
return null;
|
|
2428
|
+
case "put":
|
|
2429
|
+
return tree.put(mutation.key, mutation.value);
|
|
2430
|
+
case "putMany":
|
|
2431
|
+
return tree.putMany(mutation.entries);
|
|
2432
|
+
case "remove":
|
|
2433
|
+
return tree.remove(mutation.key);
|
|
2434
|
+
case "removeById":
|
|
2435
|
+
return tree.removeById(mutation.entryId);
|
|
2436
|
+
case "updateById":
|
|
2437
|
+
return tree.updateById(mutation.entryId, mutation.value);
|
|
2438
|
+
case "popFirst":
|
|
2439
|
+
return tree.popFirst();
|
|
2440
|
+
case "popLast":
|
|
2441
|
+
return tree.popLast();
|
|
2442
|
+
case "deleteRange":
|
|
2443
|
+
return tree.deleteRange(
|
|
2444
|
+
mutation.startKey,
|
|
2445
|
+
mutation.endKey,
|
|
2446
|
+
mutation.options
|
|
2447
|
+
);
|
|
2448
|
+
case "clear":
|
|
2449
|
+
tree.clear();
|
|
2450
|
+
return null;
|
|
2451
|
+
default:
|
|
2452
|
+
return assertNeverMutation(mutation);
|
|
2453
|
+
}
|
|
2454
|
+
};
|
|
2455
|
+
var createPutEvaluator = (duplicateKeys, key, value) => {
|
|
2456
|
+
return (tree) => {
|
|
2457
|
+
if (duplicateKeys === "reject" && tree.hasKey(key)) {
|
|
2458
|
+
throw new BTreeValidationError("Duplicate key rejected.");
|
|
2459
|
+
}
|
|
2460
|
+
return { type: "put", key, value };
|
|
2461
|
+
};
|
|
2462
|
+
};
|
|
2463
|
+
var createRemoveEvaluator = (key) => {
|
|
2464
|
+
return (tree) => {
|
|
2465
|
+
return tree.hasKey(key) ? { type: "remove", key } : null;
|
|
2466
|
+
};
|
|
2467
|
+
};
|
|
2468
|
+
var createRemoveByIdEvaluator = (entryId) => {
|
|
2469
|
+
return (tree) => {
|
|
2470
|
+
return tree.peekById(entryId) !== null ? { type: "removeById", entryId } : null;
|
|
2471
|
+
};
|
|
2472
|
+
};
|
|
2473
|
+
var createUpdateByIdEvaluator = (entryId, value) => {
|
|
2474
|
+
return (tree) => {
|
|
2475
|
+
return tree.peekById(entryId) !== null ? { type: "updateById", entryId, value } : null;
|
|
2476
|
+
};
|
|
2477
|
+
};
|
|
2478
|
+
var createPopFirstEvaluator = () => {
|
|
2479
|
+
return (tree) => {
|
|
2480
|
+
return tree.peekFirst() !== null ? { type: "popFirst" } : null;
|
|
2481
|
+
};
|
|
2482
|
+
};
|
|
2483
|
+
var createPopLastEvaluator = () => {
|
|
2484
|
+
return (tree) => {
|
|
2485
|
+
return tree.peekLast() !== null ? { type: "popLast" } : null;
|
|
2486
|
+
};
|
|
2487
|
+
};
|
|
2488
|
+
var createPutManyEvaluator = (entries, duplicateKeys, compareKeys) => {
|
|
2489
|
+
return (tree) => {
|
|
2490
|
+
const strictlyAscending = duplicateKeys !== "allow";
|
|
2491
|
+
for (let i = 1; i < entries.length; i += 1) {
|
|
2492
|
+
const cmp = compareKeys(entries[i - 1].key, entries[i].key);
|
|
2493
|
+
if (cmp > 0) {
|
|
2494
|
+
throw new BTreeValidationError(
|
|
2495
|
+
"putMany: entries not in ascending order."
|
|
2496
|
+
);
|
|
2497
|
+
}
|
|
2498
|
+
if (strictlyAscending && cmp === 0) {
|
|
2499
|
+
throw new BTreeValidationError(
|
|
2500
|
+
duplicateKeys === "reject" ? "putMany: duplicate key rejected." : "putMany: equal keys not allowed in strict mode."
|
|
2501
|
+
);
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
if (duplicateKeys === "reject") {
|
|
2505
|
+
for (const entry of entries) {
|
|
2506
|
+
if (tree.hasKey(entry.key)) {
|
|
2507
|
+
throw new BTreeValidationError("Duplicate key rejected.");
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
return { type: "putMany", entries };
|
|
2512
|
+
};
|
|
2513
|
+
};
|
|
2514
|
+
var createDeleteRangeEvaluator = (startKey, endKey, options) => {
|
|
2515
|
+
return (tree) => {
|
|
2516
|
+
const count = tree.count(startKey, endKey, options);
|
|
2517
|
+
if (count === 0) {
|
|
2518
|
+
return null;
|
|
2519
|
+
}
|
|
2520
|
+
return { type: "deleteRange", startKey, endKey, options };
|
|
2521
|
+
};
|
|
2522
|
+
};
|
|
2523
|
+
var createClearEvaluator = () => {
|
|
2524
|
+
return () => ({ type: "clear" });
|
|
2525
|
+
};
|
|
2526
|
+
|
|
2527
|
+
// src/concurrency/syncLogValidation.ts
|
|
2528
|
+
var validateSyncLog = (log, maxSyncMutationsPerBatch) => {
|
|
2529
|
+
if (typeof log.version !== "bigint") {
|
|
2530
|
+
throw new BTreeConcurrencyError("Store contract: version must be bigint.");
|
|
2531
|
+
}
|
|
2532
|
+
if (!Array.isArray(log.mutations)) {
|
|
2533
|
+
throw new BTreeConcurrencyError(
|
|
2534
|
+
"Store contract: mutations must be an array."
|
|
2048
2535
|
);
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2536
|
+
}
|
|
2537
|
+
if (log.mutations.length > maxSyncMutationsPerBatch) {
|
|
2538
|
+
throw new BTreeConcurrencyError(
|
|
2539
|
+
`Sync batch exceeded limit (${String(maxSyncMutationsPerBatch)}).`
|
|
2540
|
+
);
|
|
2541
|
+
}
|
|
2542
|
+
};
|
|
2543
|
+
|
|
2544
|
+
// src/concurrency/coordinator.ts
|
|
2545
|
+
var Coordinator = class {
|
|
2546
|
+
constructor(tree, store, maxRetries, maxSyncMutationsPerBatch, configFingerprint, readMode) {
|
|
2547
|
+
this.tree = tree;
|
|
2548
|
+
this.store = store;
|
|
2549
|
+
this.maxRetries = maxRetries;
|
|
2550
|
+
this.maxSyncMutationsPerBatch = maxSyncMutationsPerBatch;
|
|
2551
|
+
this.configFingerprint = configFingerprint;
|
|
2552
|
+
this.readMode = readMode;
|
|
2060
2553
|
this.currentVersion = 0n;
|
|
2061
2554
|
this.operationQueue = Promise.resolve();
|
|
2062
2555
|
this.initSeen = false;
|
|
2063
|
-
|
|
2064
|
-
async sync() {
|
|
2065
|
-
await this.runExclusive(async () => {
|
|
2066
|
-
await this.syncUnlocked();
|
|
2067
|
-
});
|
|
2556
|
+
this.corrupted = false;
|
|
2068
2557
|
}
|
|
2069
2558
|
async syncUnlocked() {
|
|
2070
2559
|
const log = await this.store.getLogEntriesSince(this.currentVersion);
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
}
|
|
2074
|
-
if (!Array.isArray(log.mutations)) {
|
|
2075
|
-
throw new BTreeConcurrencyError("Store contract: mutations must be an array.");
|
|
2076
|
-
}
|
|
2077
|
-
if (log.mutations.length > this.maxSyncMutationsPerBatch) {
|
|
2078
|
-
throw new BTreeConcurrencyError(
|
|
2079
|
-
`Sync batch exceeded limit (${String(this.maxSyncMutationsPerBatch)}).`
|
|
2080
|
-
);
|
|
2081
|
-
}
|
|
2082
|
-
if (log.version <= this.currentVersion) {
|
|
2083
|
-
return;
|
|
2084
|
-
}
|
|
2560
|
+
validateSyncLog(log, this.maxSyncMutationsPerBatch);
|
|
2561
|
+
if (log.version <= this.currentVersion) return;
|
|
2085
2562
|
validateMutationBatch(log.mutations, this.configFingerprint);
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
}
|
|
2089
|
-
this.currentVersion = log.version;
|
|
2090
|
-
}
|
|
2091
|
-
applyMutationLocal(mutation) {
|
|
2092
|
-
switch (mutation.type) {
|
|
2093
|
-
case "init":
|
|
2563
|
+
try {
|
|
2564
|
+
const markInit = () => {
|
|
2094
2565
|
this.initSeen = true;
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
return this.tree.popFirst();
|
|
2106
|
-
case "popLast":
|
|
2107
|
-
return this.tree.popLast();
|
|
2108
|
-
default:
|
|
2109
|
-
return assertNeverMutation(mutation);
|
|
2566
|
+
};
|
|
2567
|
+
for (const mutation of log.mutations)
|
|
2568
|
+
applyMutationLocal(this.tree, mutation, markInit);
|
|
2569
|
+
this.currentVersion = log.version;
|
|
2570
|
+
} catch (error) {
|
|
2571
|
+
this.corrupted = true;
|
|
2572
|
+
const cause = error instanceof Error ? error.message : String(error);
|
|
2573
|
+
throw new BTreeConcurrencyError(
|
|
2574
|
+
`Replay failure: instance is permanently corrupted. Discard and create a new instance. Cause: ${cause}`
|
|
2575
|
+
);
|
|
2110
2576
|
}
|
|
2111
2577
|
}
|
|
2112
2578
|
runExclusive(operation) {
|
|
2113
|
-
const run = async () =>
|
|
2579
|
+
const run = async () => {
|
|
2580
|
+
if (this.corrupted)
|
|
2581
|
+
throw new BTreeConcurrencyError(
|
|
2582
|
+
"Instance is permanently corrupted. Discard and create a new instance."
|
|
2583
|
+
);
|
|
2584
|
+
return operation();
|
|
2585
|
+
};
|
|
2114
2586
|
const result = this.operationQueue.then(run, run);
|
|
2115
2587
|
this.operationQueue = result.then(
|
|
2116
2588
|
() => void 0,
|
|
@@ -2120,137 +2592,207 @@ var ConcurrentInMemoryBTree = class {
|
|
|
2120
2592
|
}
|
|
2121
2593
|
readOp(fn) {
|
|
2122
2594
|
return this.runExclusive(async () => {
|
|
2123
|
-
if (this.readMode === "strong")
|
|
2124
|
-
await this.syncUnlocked();
|
|
2125
|
-
}
|
|
2595
|
+
if (this.readMode === "strong") await this.syncUnlocked();
|
|
2126
2596
|
return fn(this.tree);
|
|
2127
2597
|
});
|
|
2128
2598
|
}
|
|
2129
|
-
async
|
|
2599
|
+
async appendAndApply(evaluate) {
|
|
2130
2600
|
for (let attempt = 0; attempt < this.maxRetries; attempt += 1) {
|
|
2131
2601
|
await this.syncUnlocked();
|
|
2132
2602
|
const mutation = evaluate(this.tree);
|
|
2133
|
-
if (mutation === null)
|
|
2134
|
-
return null;
|
|
2135
|
-
}
|
|
2603
|
+
if (mutation === null) return null;
|
|
2136
2604
|
const expectedVersion = this.currentVersion;
|
|
2137
|
-
const mutations = this.initSeen ? [mutation] : [
|
|
2605
|
+
const mutations = this.initSeen ? [mutation] : [
|
|
2606
|
+
{ type: "init", configFingerprint: this.configFingerprint },
|
|
2607
|
+
mutation
|
|
2608
|
+
];
|
|
2138
2609
|
const appendResult = await this.store.append(expectedVersion, mutations);
|
|
2139
2610
|
assertAppendVersionContract(expectedVersion, appendResult);
|
|
2140
2611
|
if (appendResult.applied) {
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2612
|
+
try {
|
|
2613
|
+
const markInit = () => {
|
|
2614
|
+
this.initSeen = true;
|
|
2615
|
+
};
|
|
2616
|
+
for (const m of mutations) {
|
|
2617
|
+
if (m === mutation) break;
|
|
2618
|
+
applyMutationLocal(this.tree, m, markInit);
|
|
2619
|
+
}
|
|
2620
|
+
const result = applyMutationLocal(
|
|
2621
|
+
this.tree,
|
|
2622
|
+
mutation,
|
|
2623
|
+
markInit
|
|
2624
|
+
);
|
|
2625
|
+
this.currentVersion = appendResult.version;
|
|
2626
|
+
return result;
|
|
2627
|
+
} catch (error) {
|
|
2628
|
+
this.corrupted = true;
|
|
2629
|
+
const cause = error instanceof Error ? error.message : String(error);
|
|
2630
|
+
throw new BTreeConcurrencyError(
|
|
2631
|
+
`Local apply failure after successful append: instance is permanently corrupted. Discard and create a new instance. Cause: ${cause}`
|
|
2632
|
+
);
|
|
2144
2633
|
}
|
|
2145
|
-
const localResult = this.applyMutationLocal(mutation);
|
|
2146
|
-
this.currentVersion = appendResult.version;
|
|
2147
|
-
return localResult;
|
|
2148
2634
|
}
|
|
2149
2635
|
}
|
|
2150
2636
|
throw new BTreeConcurrencyError(
|
|
2151
2637
|
`Mutation failed after ${String(this.maxRetries)} retries.`
|
|
2152
2638
|
);
|
|
2153
2639
|
}
|
|
2154
|
-
|
|
2155
|
-
return this.runExclusive(async () =>
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2640
|
+
writeOp(evaluator) {
|
|
2641
|
+
return this.runExclusive(async () => this.appendAndApply(evaluator));
|
|
2642
|
+
}
|
|
2643
|
+
};
|
|
2644
|
+
|
|
2645
|
+
// src/concurrency/ConcurrentInMemoryBTree.ts
|
|
2646
|
+
var ConcurrentInMemoryBTree = class {
|
|
2647
|
+
constructor(config) {
|
|
2648
|
+
this.compareKeys = config.compareKeys;
|
|
2649
|
+
this.duplicateKeys = config.duplicateKeys ?? "replace";
|
|
2650
|
+
const tree = new InMemoryBTree({
|
|
2651
|
+
compareKeys: config.compareKeys,
|
|
2652
|
+
maxLeafEntries: config.maxLeafEntries,
|
|
2653
|
+
maxBranchChildren: config.maxBranchChildren,
|
|
2654
|
+
duplicateKeys: config.duplicateKeys,
|
|
2655
|
+
enableEntryIdLookup: config.enableEntryIdLookup,
|
|
2656
|
+
autoScale: config.autoScale,
|
|
2657
|
+
deleteRebalancePolicy: config.deleteRebalancePolicy
|
|
2164
2658
|
});
|
|
2659
|
+
this.coord = new Coordinator(
|
|
2660
|
+
tree,
|
|
2661
|
+
config.store,
|
|
2662
|
+
normalizeMaxRetries(config.maxRetries),
|
|
2663
|
+
normalizeMaxSyncMutationsPerBatch(config.maxSyncMutationsPerBatch),
|
|
2664
|
+
computeConfigFingerprint(config),
|
|
2665
|
+
normalizeReadMode(config.readMode)
|
|
2666
|
+
);
|
|
2165
2667
|
}
|
|
2166
|
-
async
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
(tree) => {
|
|
2170
|
-
return tree.hasKey(key) ? { type: "remove", key } : null;
|
|
2171
|
-
}
|
|
2172
|
-
);
|
|
2668
|
+
async sync() {
|
|
2669
|
+
await this.coord.runExclusive(async () => {
|
|
2670
|
+
await this.coord.syncUnlocked();
|
|
2173
2671
|
});
|
|
2174
2672
|
}
|
|
2175
|
-
async
|
|
2176
|
-
return this.runExclusive(async () => {
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
return tree.peekById(entryId) !== null ? { type: "removeById", entryId } : null;
|
|
2180
|
-
}
|
|
2181
|
-
);
|
|
2673
|
+
async syncThenRead(fn) {
|
|
2674
|
+
return this.coord.runExclusive(async () => {
|
|
2675
|
+
await this.coord.syncUnlocked();
|
|
2676
|
+
return fn(this.coord.tree);
|
|
2182
2677
|
});
|
|
2183
2678
|
}
|
|
2679
|
+
async put(key, value) {
|
|
2680
|
+
return this.coord.writeOp(
|
|
2681
|
+
createPutEvaluator(this.duplicateKeys, key, value)
|
|
2682
|
+
);
|
|
2683
|
+
}
|
|
2684
|
+
async remove(key) {
|
|
2685
|
+
return this.coord.writeOp(createRemoveEvaluator(key));
|
|
2686
|
+
}
|
|
2687
|
+
async removeById(entryId) {
|
|
2688
|
+
return this.coord.writeOp(createRemoveByIdEvaluator(entryId));
|
|
2689
|
+
}
|
|
2184
2690
|
async updateById(entryId, value) {
|
|
2185
|
-
return this.
|
|
2186
|
-
return this.appendMutationAndApplyUnlocked(
|
|
2187
|
-
(tree) => {
|
|
2188
|
-
return tree.peekById(entryId) !== null ? { type: "updateById", entryId, value } : null;
|
|
2189
|
-
}
|
|
2190
|
-
);
|
|
2191
|
-
});
|
|
2691
|
+
return this.coord.writeOp(createUpdateByIdEvaluator(entryId, value));
|
|
2192
2692
|
}
|
|
2193
2693
|
async popFirst() {
|
|
2194
|
-
return this.
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2694
|
+
return this.coord.writeOp(createPopFirstEvaluator());
|
|
2695
|
+
}
|
|
2696
|
+
async popLast() {
|
|
2697
|
+
return this.coord.writeOp(createPopLastEvaluator());
|
|
2698
|
+
}
|
|
2699
|
+
async putMany(entries) {
|
|
2700
|
+
if (entries.length === 0) return [];
|
|
2701
|
+
return this.coord.writeOp(
|
|
2702
|
+
createPutManyEvaluator(entries, this.duplicateKeys, this.compareKeys)
|
|
2703
|
+
);
|
|
2704
|
+
}
|
|
2705
|
+
async deleteRange(startKey, endKey, options) {
|
|
2706
|
+
const result = await this.coord.writeOp(
|
|
2707
|
+
createDeleteRangeEvaluator(startKey, endKey, options)
|
|
2708
|
+
);
|
|
2709
|
+
return result ?? 0;
|
|
2710
|
+
}
|
|
2711
|
+
async clear() {
|
|
2712
|
+
await this.coord.writeOp(createClearEvaluator());
|
|
2199
2713
|
}
|
|
2200
2714
|
async get(key) {
|
|
2201
|
-
return this.readOp((
|
|
2715
|
+
return this.coord.readOp((t) => t.get(key));
|
|
2202
2716
|
}
|
|
2203
2717
|
async hasKey(key) {
|
|
2204
|
-
return this.readOp((
|
|
2718
|
+
return this.coord.readOp((t) => t.hasKey(key));
|
|
2205
2719
|
}
|
|
2206
2720
|
async findFirst(key) {
|
|
2207
|
-
return this.readOp((
|
|
2721
|
+
return this.coord.readOp((t) => t.findFirst(key));
|
|
2208
2722
|
}
|
|
2209
2723
|
async findLast(key) {
|
|
2210
|
-
return this.readOp((
|
|
2724
|
+
return this.coord.readOp((t) => t.findLast(key));
|
|
2211
2725
|
}
|
|
2212
2726
|
async range(startKey, endKey, options) {
|
|
2213
|
-
return this.readOp((
|
|
2727
|
+
return this.coord.readOp((t) => t.range(startKey, endKey, options));
|
|
2214
2728
|
}
|
|
2215
2729
|
async snapshot() {
|
|
2216
|
-
return this.readOp((
|
|
2730
|
+
return this.coord.readOp((t) => t.snapshot());
|
|
2217
2731
|
}
|
|
2218
2732
|
async size() {
|
|
2219
|
-
return this.readOp((
|
|
2733
|
+
return this.coord.readOp((t) => t.size());
|
|
2220
2734
|
}
|
|
2221
2735
|
async assertInvariants() {
|
|
2222
|
-
await this.readOp((
|
|
2736
|
+
await this.coord.readOp((t) => t.assertInvariants());
|
|
2223
2737
|
}
|
|
2224
2738
|
async getStats() {
|
|
2225
|
-
return this.readOp((
|
|
2739
|
+
return this.coord.readOp((t) => t.getStats());
|
|
2226
2740
|
}
|
|
2227
2741
|
async peekFirst() {
|
|
2228
|
-
return this.readOp((
|
|
2742
|
+
return this.coord.readOp((t) => t.peekFirst());
|
|
2229
2743
|
}
|
|
2230
2744
|
async peekLast() {
|
|
2231
|
-
return this.readOp((
|
|
2232
|
-
}
|
|
2233
|
-
async popLast() {
|
|
2234
|
-
return this.runExclusive(async () => {
|
|
2235
|
-
return this.appendMutationAndApplyUnlocked((tree) => {
|
|
2236
|
-
return tree.peekLast() !== null ? { type: "popLast" } : null;
|
|
2237
|
-
});
|
|
2238
|
-
});
|
|
2745
|
+
return this.coord.readOp((t) => t.peekLast());
|
|
2239
2746
|
}
|
|
2240
2747
|
async peekById(entryId) {
|
|
2241
|
-
return this.readOp((
|
|
2748
|
+
return this.coord.readOp((t) => t.peekById(entryId));
|
|
2242
2749
|
}
|
|
2243
2750
|
async count(startKey, endKey, options) {
|
|
2244
|
-
return this.readOp((
|
|
2751
|
+
return this.coord.readOp((t) => t.count(startKey, endKey, options));
|
|
2245
2752
|
}
|
|
2246
2753
|
async nextHigherKey(key) {
|
|
2247
|
-
return this.readOp((
|
|
2754
|
+
return this.coord.readOp((t) => t.nextHigherKey(key));
|
|
2248
2755
|
}
|
|
2249
2756
|
async nextLowerKey(key) {
|
|
2250
|
-
return this.readOp((
|
|
2757
|
+
return this.coord.readOp((t) => t.nextLowerKey(key));
|
|
2251
2758
|
}
|
|
2252
2759
|
async getPairOrNextLower(key) {
|
|
2253
|
-
return this.readOp((
|
|
2760
|
+
return this.coord.readOp((t) => t.getPairOrNextLower(key));
|
|
2761
|
+
}
|
|
2762
|
+
async entries() {
|
|
2763
|
+
return this.coord.readOp((t) => Array.from(t.entries()));
|
|
2764
|
+
}
|
|
2765
|
+
async entriesReversed() {
|
|
2766
|
+
return this.coord.readOp((t) => Array.from(t.entriesReversed()));
|
|
2767
|
+
}
|
|
2768
|
+
async keys() {
|
|
2769
|
+
return this.coord.readOp((t) => Array.from(t.keys()));
|
|
2770
|
+
}
|
|
2771
|
+
async values() {
|
|
2772
|
+
return this.coord.readOp((t) => Array.from(t.values()));
|
|
2773
|
+
}
|
|
2774
|
+
async forEach(callback) {
|
|
2775
|
+
await this.coord.readOp((t) => {
|
|
2776
|
+
t.forEach(callback);
|
|
2777
|
+
});
|
|
2778
|
+
}
|
|
2779
|
+
async forEachRange(startKey, endKey, callback, options) {
|
|
2780
|
+
await this.coord.readOp((t) => {
|
|
2781
|
+
t.forEachRange(startKey, endKey, callback, options);
|
|
2782
|
+
});
|
|
2783
|
+
}
|
|
2784
|
+
async *[Symbol.asyncIterator]() {
|
|
2785
|
+
const all = await this.entries();
|
|
2786
|
+
for (const entry of all) yield entry;
|
|
2787
|
+
}
|
|
2788
|
+
async clone() {
|
|
2789
|
+
return this.coord.readOp((t) => t.clone());
|
|
2790
|
+
}
|
|
2791
|
+
async toJSON() {
|
|
2792
|
+
return this.coord.readOp((t) => t.toJSON());
|
|
2793
|
+
}
|
|
2794
|
+
static fromJSON(json, compareKeys) {
|
|
2795
|
+
return InMemoryBTree.fromJSON(json, compareKeys);
|
|
2254
2796
|
}
|
|
2255
2797
|
};
|
|
2256
2798
|
// Annotate the CommonJS export names for ESM import in node:
|