@frostpillar/frostpillar-btree 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,29 +1855,19 @@ var InMemoryBTree = class _InMemoryBTree {
1805
1855
  while (leaf !== null) {
1806
1856
  const count = leafEntryCount(leaf);
1807
1857
  for (let i = count - 1; i >= 0; i -= 1) {
1808
- yield leafEntryAt(leaf, i);
1858
+ yield toPublicEntry(leafEntryAt(leaf, i));
1809
1859
  }
1810
1860
  leaf = leaf.prev;
1811
1861
  }
1812
1862
  }
1813
1863
  *keys() {
1814
- let leaf = this.state.leftmostLeaf;
1815
- while (leaf !== null) {
1816
- const count = leafEntryCount(leaf);
1817
- for (let i = 0; i < count; i += 1) {
1818
- yield leafEntryAt(leaf, i).key;
1819
- }
1820
- leaf = leaf.next;
1864
+ for (const entry of this.entries()) {
1865
+ yield entry.key;
1821
1866
  }
1822
1867
  }
1823
1868
  *values() {
1824
- let leaf = this.state.leftmostLeaf;
1825
- while (leaf !== null) {
1826
- const count = leafEntryCount(leaf);
1827
- for (let i = 0; i < count; i += 1) {
1828
- yield leafEntryAt(leaf, i).value;
1829
- }
1830
- leaf = leaf.next;
1869
+ for (const entry of this.entries()) {
1870
+ yield entry.value;
1831
1871
  }
1832
1872
  }
1833
1873
  [Symbol.iterator]() {
@@ -1838,7 +1878,7 @@ var InMemoryBTree = class _InMemoryBTree {
1838
1878
  while (leaf !== null) {
1839
1879
  const count = leafEntryCount(leaf);
1840
1880
  for (let i = 0; i < count; i += 1) {
1841
- callback.call(thisArg, leafEntryAt(leaf, i));
1881
+ callback.call(thisArg, toPublicEntry(leafEntryAt(leaf, i)));
1842
1882
  }
1843
1883
  leaf = leaf.next;
1844
1884
  }
@@ -1850,12 +1890,19 @@ var InMemoryBTree = class _InMemoryBTree {
1850
1890
  while (leaf !== null) {
1851
1891
  const count = leafEntryCount(leaf);
1852
1892
  for (let i = 0; i < count; i += 1) {
1853
- result[writeIdx++] = leafEntryAt(leaf, i);
1893
+ result[writeIdx++] = toPublicEntry(leafEntryAt(leaf, i));
1854
1894
  }
1855
1895
  leaf = leaf.next;
1856
1896
  }
1857
1897
  return result;
1858
1898
  }
1899
+ /**
1900
+ * Returns a structurally independent `InMemoryBTree` with identical
1901
+ * configuration and entries. The tree structure (nodes, links, entry IDs)
1902
+ * is fully independent, but stored key and value references are shared
1903
+ * with the source tree.
1904
+ * Note: `EntryId` values are reassigned in the clone — IDs from the source tree are not valid for the clone.
1905
+ */
1859
1906
  clone() {
1860
1907
  const cloned = new _InMemoryBTree(buildConfigFromState(this.state));
1861
1908
  applyAutoScaleCapacitySnapshot(
@@ -1943,6 +1990,81 @@ var assertNeverMutation = (mutation) => {
1943
1990
  `Unsupported mutation type from shared store: ${String(unknownMutation.type)}`
1944
1991
  );
1945
1992
  };
1993
+ var validatePutManyEntries = (entries) => {
1994
+ for (const entry of entries) {
1995
+ if (typeof entry !== "object" || entry === null || !("key" in entry) || !("value" in entry)) {
1996
+ throw new BTreeConcurrencyError("Malformed putMany mutation: each entry must have key and value.");
1997
+ }
1998
+ }
1999
+ };
2000
+ var validateInitMutation = (m, expectedConfigFingerprint) => {
2001
+ if (typeof m.configFingerprint !== "string") {
2002
+ throw new BTreeConcurrencyError("Malformed init mutation: missing configFingerprint.");
2003
+ }
2004
+ if (expectedConfigFingerprint !== void 0 && m.configFingerprint !== expectedConfigFingerprint) {
2005
+ throw new BTreeConcurrencyError(
2006
+ "Config mismatch: store peers must share identical tree config."
2007
+ );
2008
+ }
2009
+ };
2010
+ var validateSingleMutation = (m, expectedConfigFingerprint) => {
2011
+ switch (m.type) {
2012
+ case "init":
2013
+ validateInitMutation(m, expectedConfigFingerprint);
2014
+ break;
2015
+ case "put":
2016
+ if (!("key" in m) || !("value" in m)) {
2017
+ throw new BTreeConcurrencyError("Malformed put mutation: missing key or value.");
2018
+ }
2019
+ break;
2020
+ case "remove":
2021
+ if (!("key" in m)) {
2022
+ throw new BTreeConcurrencyError("Malformed remove mutation: missing key.");
2023
+ }
2024
+ break;
2025
+ case "removeById":
2026
+ if (!("entryId" in m)) {
2027
+ throw new BTreeConcurrencyError("Malformed removeById mutation: missing entryId.");
2028
+ }
2029
+ break;
2030
+ case "updateById":
2031
+ if (!("entryId" in m) || !("value" in m)) {
2032
+ throw new BTreeConcurrencyError("Malformed updateById mutation: missing entryId or value.");
2033
+ }
2034
+ break;
2035
+ case "popFirst":
2036
+ case "popLast":
2037
+ break;
2038
+ case "putMany":
2039
+ if (!("entries" in m) || !Array.isArray(m.entries)) {
2040
+ throw new BTreeConcurrencyError("Malformed putMany mutation: missing entries array.");
2041
+ }
2042
+ validatePutManyEntries(m.entries);
2043
+ break;
2044
+ case "deleteRange":
2045
+ if (!("startKey" in m) || !("endKey" in m)) {
2046
+ throw new BTreeConcurrencyError("Malformed deleteRange mutation: missing startKey or endKey.");
2047
+ }
2048
+ break;
2049
+ case "clear":
2050
+ break;
2051
+ default:
2052
+ throw new BTreeConcurrencyError(
2053
+ `Unsupported mutation type from shared store: ${String(m.type)}`
2054
+ );
2055
+ }
2056
+ };
2057
+ var validateMutationBatch = (mutations, expectedConfigFingerprint) => {
2058
+ for (const mutation of mutations) {
2059
+ if (typeof mutation !== "object" || mutation === null) {
2060
+ throw new BTreeConcurrencyError("Malformed mutation: expected an object.");
2061
+ }
2062
+ validateSingleMutation(
2063
+ mutation,
2064
+ expectedConfigFingerprint
2065
+ );
2066
+ }
2067
+ };
1946
2068
  var normalizeMaxRetries = (value) => {
1947
2069
  if (value === void 0) {
1948
2070
  return DEFAULT_MAX_RETRIES;
@@ -2001,10 +2123,110 @@ function assertAppendVersionContract(expectedVersion, appendResult) {
2001
2123
  }
2002
2124
  }
2003
2125
 
2126
+ // src/concurrency/writeOps.ts
2127
+ var applyMutationLocal = (tree, mutation, onInit) => {
2128
+ switch (mutation.type) {
2129
+ case "init":
2130
+ onInit();
2131
+ return null;
2132
+ case "put":
2133
+ return tree.put(mutation.key, mutation.value);
2134
+ case "putMany":
2135
+ return tree.putMany(mutation.entries);
2136
+ case "remove":
2137
+ return tree.remove(mutation.key);
2138
+ case "removeById":
2139
+ return tree.removeById(mutation.entryId);
2140
+ case "updateById":
2141
+ return tree.updateById(mutation.entryId, mutation.value);
2142
+ case "popFirst":
2143
+ return tree.popFirst();
2144
+ case "popLast":
2145
+ return tree.popLast();
2146
+ case "deleteRange":
2147
+ return tree.deleteRange(mutation.startKey, mutation.endKey, mutation.options);
2148
+ case "clear":
2149
+ tree.clear();
2150
+ return null;
2151
+ default:
2152
+ return assertNeverMutation(mutation);
2153
+ }
2154
+ };
2155
+ var createPutEvaluator = (duplicateKeys, key, value) => {
2156
+ return (tree) => {
2157
+ if (duplicateKeys === "reject" && tree.hasKey(key)) {
2158
+ throw new BTreeValidationError("Duplicate key rejected.");
2159
+ }
2160
+ return { type: "put", key, value };
2161
+ };
2162
+ };
2163
+ var createRemoveEvaluator = (key) => {
2164
+ return (tree) => {
2165
+ return tree.hasKey(key) ? { type: "remove", key } : null;
2166
+ };
2167
+ };
2168
+ var createRemoveByIdEvaluator = (entryId) => {
2169
+ return (tree) => {
2170
+ return tree.peekById(entryId) !== null ? { type: "removeById", entryId } : null;
2171
+ };
2172
+ };
2173
+ var createUpdateByIdEvaluator = (entryId, value) => {
2174
+ return (tree) => {
2175
+ return tree.peekById(entryId) !== null ? { type: "updateById", entryId, value } : null;
2176
+ };
2177
+ };
2178
+ var createPopFirstEvaluator = () => {
2179
+ return (tree) => {
2180
+ return tree.peekFirst() !== null ? { type: "popFirst" } : null;
2181
+ };
2182
+ };
2183
+ var createPopLastEvaluator = () => {
2184
+ return (tree) => {
2185
+ return tree.peekLast() !== null ? { type: "popLast" } : null;
2186
+ };
2187
+ };
2188
+ var createPutManyEvaluator = (entries, duplicateKeys, compareKeys) => {
2189
+ return (tree) => {
2190
+ const strictlyAscending = duplicateKeys !== "allow";
2191
+ for (let i = 1; i < entries.length; i += 1) {
2192
+ const cmp = compareKeys(entries[i - 1].key, entries[i].key);
2193
+ if (cmp > 0) {
2194
+ throw new BTreeValidationError("putMany: entries not in ascending order.");
2195
+ }
2196
+ if (strictlyAscending && cmp === 0) {
2197
+ throw new BTreeValidationError(
2198
+ duplicateKeys === "reject" ? "putMany: duplicate key rejected." : "putMany: equal keys not allowed in strict mode."
2199
+ );
2200
+ }
2201
+ }
2202
+ if (duplicateKeys === "reject") {
2203
+ for (const entry of entries) {
2204
+ if (tree.hasKey(entry.key)) {
2205
+ throw new BTreeValidationError("Duplicate key rejected.");
2206
+ }
2207
+ }
2208
+ }
2209
+ return { type: "putMany", entries };
2210
+ };
2211
+ };
2212
+ var createDeleteRangeEvaluator = (startKey, endKey, options) => {
2213
+ return (tree) => {
2214
+ const count = tree.count(startKey, endKey, options);
2215
+ if (count === 0) {
2216
+ return null;
2217
+ }
2218
+ return { type: "deleteRange", startKey, endKey, options };
2219
+ };
2220
+ };
2221
+ var createClearEvaluator = () => {
2222
+ return () => ({ type: "clear" });
2223
+ };
2224
+
2004
2225
  // src/concurrency/ConcurrentInMemoryBTree.ts
2005
2226
  var ConcurrentInMemoryBTree = class {
2006
2227
  constructor(config) {
2007
2228
  this.store = config.store;
2229
+ this.compareKeys = config.compareKeys;
2008
2230
  this.maxRetries = normalizeMaxRetries(config.maxRetries);
2009
2231
  this.maxSyncMutationsPerBatch = normalizeMaxSyncMutationsPerBatch(
2010
2232
  config.maxSyncMutationsPerBatch
@@ -2023,6 +2245,7 @@ var ConcurrentInMemoryBTree = class {
2023
2245
  this.currentVersion = 0n;
2024
2246
  this.operationQueue = Promise.resolve();
2025
2247
  this.initSeen = false;
2248
+ this.corrupted = false;
2026
2249
  }
2027
2250
  async sync() {
2028
2251
  await this.runExclusive(async () => {
@@ -2045,39 +2268,31 @@ var ConcurrentInMemoryBTree = class {
2045
2268
  if (log.version <= this.currentVersion) {
2046
2269
  return;
2047
2270
  }
2048
- for (const mutation of log.mutations) {
2049
- this.applyMutationLocal(mutation);
2050
- }
2051
- this.currentVersion = log.version;
2052
- }
2053
- applyMutationLocal(mutation) {
2054
- switch (mutation.type) {
2055
- case "init":
2056
- if (mutation.configFingerprint !== this.configFingerprint) {
2057
- throw new BTreeConcurrencyError(
2058
- "Config mismatch: store peers must share identical tree config."
2059
- );
2060
- }
2061
- this.initSeen = true;
2062
- return null;
2063
- case "put":
2064
- return this.tree.put(mutation.key, mutation.value);
2065
- case "remove":
2066
- return this.tree.remove(mutation.key);
2067
- case "removeById":
2068
- return this.tree.removeById(mutation.entryId);
2069
- case "updateById":
2070
- return this.tree.updateById(mutation.entryId, mutation.value);
2071
- case "popFirst":
2072
- return this.tree.popFirst();
2073
- case "popLast":
2074
- return this.tree.popLast();
2075
- default:
2076
- return assertNeverMutation(mutation);
2271
+ validateMutationBatch(log.mutations, this.configFingerprint);
2272
+ try {
2273
+ for (const mutation of log.mutations) {
2274
+ applyMutationLocal(this.tree, mutation, () => {
2275
+ this.initSeen = true;
2276
+ });
2277
+ }
2278
+ this.currentVersion = log.version;
2279
+ } catch (error) {
2280
+ this.corrupted = true;
2281
+ const cause = error instanceof Error ? error.message : String(error);
2282
+ throw new BTreeConcurrencyError(
2283
+ `Replay failure: instance is permanently corrupted. Discard and create a new instance. Cause: ${cause}`
2284
+ );
2077
2285
  }
2078
2286
  }
2079
2287
  runExclusive(operation) {
2080
- const run = async () => 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
+ };
2081
2296
  const result = this.operationQueue.then(run, run);
2082
2297
  this.operationQueue = result.then(
2083
2298
  () => void 0,
@@ -2105,13 +2320,29 @@ var ConcurrentInMemoryBTree = class {
2105
2320
  const appendResult = await this.store.append(expectedVersion, mutations);
2106
2321
  assertAppendVersionContract(expectedVersion, appendResult);
2107
2322
  if (appendResult.applied) {
2108
- for (const m of mutations) {
2109
- if (m === mutation) break;
2110
- 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
+ );
2111
2345
  }
2112
- const localResult = this.applyMutationLocal(mutation);
2113
- this.currentVersion = appendResult.version;
2114
- return localResult;
2115
2346
  }
2116
2347
  }
2117
2348
  throw new BTreeConcurrencyError(
@@ -2121,47 +2352,56 @@ var ConcurrentInMemoryBTree = class {
2121
2352
  async put(key, value) {
2122
2353
  return this.runExclusive(async () => {
2123
2354
  return this.appendMutationAndApplyUnlocked(
2124
- (tree) => {
2125
- if (this.duplicateKeys === "reject" && tree.hasKey(key)) {
2126
- throw new BTreeValidationError("Duplicate key rejected.");
2127
- }
2128
- return { type: "put", key, value };
2129
- }
2355
+ createPutEvaluator(this.duplicateKeys, key, value)
2130
2356
  );
2131
2357
  });
2132
2358
  }
2133
2359
  async remove(key) {
2134
2360
  return this.runExclusive(async () => {
2135
- return this.appendMutationAndApplyUnlocked(
2136
- (tree) => {
2137
- return tree.hasKey(key) ? { type: "remove", key } : null;
2138
- }
2139
- );
2361
+ return this.appendMutationAndApplyUnlocked(createRemoveEvaluator(key));
2140
2362
  });
2141
2363
  }
2142
2364
  async removeById(entryId) {
2143
2365
  return this.runExclusive(async () => {
2144
- return this.appendMutationAndApplyUnlocked(
2145
- (tree) => {
2146
- return tree.peekById(entryId) !== null ? { type: "removeById", entryId } : null;
2147
- }
2148
- );
2366
+ return this.appendMutationAndApplyUnlocked(createRemoveByIdEvaluator(entryId));
2149
2367
  });
2150
2368
  }
2151
2369
  async updateById(entryId, value) {
2370
+ return this.runExclusive(async () => {
2371
+ return this.appendMutationAndApplyUnlocked(createUpdateByIdEvaluator(entryId, value));
2372
+ });
2373
+ }
2374
+ async popFirst() {
2375
+ return this.runExclusive(async () => {
2376
+ return this.appendMutationAndApplyUnlocked(createPopFirstEvaluator());
2377
+ });
2378
+ }
2379
+ async popLast() {
2380
+ return this.runExclusive(async () => {
2381
+ return this.appendMutationAndApplyUnlocked(createPopLastEvaluator());
2382
+ });
2383
+ }
2384
+ async putMany(entries) {
2385
+ if (entries.length === 0) {
2386
+ return [];
2387
+ }
2152
2388
  return this.runExclusive(async () => {
2153
2389
  return this.appendMutationAndApplyUnlocked(
2154
- (tree) => {
2155
- return tree.peekById(entryId) !== null ? { type: "updateById", entryId, value } : null;
2156
- }
2390
+ createPutManyEvaluator(entries, this.duplicateKeys, this.compareKeys)
2157
2391
  );
2158
2392
  });
2159
2393
  }
2160
- async popFirst() {
2394
+ async deleteRange(startKey, endKey, options) {
2161
2395
  return this.runExclusive(async () => {
2162
- return this.appendMutationAndApplyUnlocked((tree) => {
2163
- return tree.peekFirst() !== null ? { type: "popFirst" } : null;
2164
- });
2396
+ const result = await this.appendMutationAndApplyUnlocked(
2397
+ createDeleteRangeEvaluator(startKey, endKey, options)
2398
+ );
2399
+ return result ?? 0;
2400
+ });
2401
+ }
2402
+ async clear() {
2403
+ await this.runExclusive(async () => {
2404
+ await this.appendMutationAndApplyUnlocked(createClearEvaluator());
2165
2405
  });
2166
2406
  }
2167
2407
  async get(key) {
@@ -2197,13 +2437,6 @@ var ConcurrentInMemoryBTree = class {
2197
2437
  async peekLast() {
2198
2438
  return this.readOp((tree) => tree.peekLast());
2199
2439
  }
2200
- async popLast() {
2201
- return this.runExclusive(async () => {
2202
- return this.appendMutationAndApplyUnlocked((tree) => {
2203
- return tree.peekLast() !== null ? { type: "popLast" } : null;
2204
- });
2205
- });
2206
- }
2207
2440
  async peekById(entryId) {
2208
2441
  return this.readOp((tree) => tree.peekById(entryId));
2209
2442
  }
@@ -2219,6 +2452,38 @@ var ConcurrentInMemoryBTree = class {
2219
2452
  async getPairOrNextLower(key) {
2220
2453
  return this.readOp((tree) => tree.getPairOrNextLower(key));
2221
2454
  }
2455
+ async entries() {
2456
+ return this.readOp((tree) => Array.from(tree.entries()));
2457
+ }
2458
+ async entriesReversed() {
2459
+ return this.readOp((tree) => Array.from(tree.entriesReversed()));
2460
+ }
2461
+ async keys() {
2462
+ return this.readOp((tree) => Array.from(tree.keys()));
2463
+ }
2464
+ async values() {
2465
+ return this.readOp((tree) => Array.from(tree.values()));
2466
+ }
2467
+ async forEach(callback) {
2468
+ await this.readOp((tree) => {
2469
+ tree.forEach(callback);
2470
+ });
2471
+ }
2472
+ async *[Symbol.asyncIterator]() {
2473
+ const all = await this.entries();
2474
+ for (const entry of all) {
2475
+ yield entry;
2476
+ }
2477
+ }
2478
+ async clone() {
2479
+ return this.readOp((tree) => tree.clone());
2480
+ }
2481
+ async toJSON() {
2482
+ return this.readOp((tree) => tree.toJSON());
2483
+ }
2484
+ static fromJSON(json, compareKeys) {
2485
+ return InMemoryBTree.fromJSON(json, compareKeys);
2486
+ }
2222
2487
  };
2223
2488
  // Annotate the CommonJS export names for ESM import in node:
2224
2489
  0 && (module.exports = {