@frostpillar/frostpillar-btree 0.2.5 → 0.2.6

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