@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/core.cjs CHANGED
@@ -42,54 +42,10 @@ var BTreeInvariantError = class extends Error {
42
42
  }
43
43
  };
44
44
 
45
- // src/btree/types.ts
46
- var DEFAULT_MAX_LEAF_ENTRIES = 64;
47
- var DEFAULT_MAX_BRANCH_CHILDREN = 64;
48
- var MIN_NODE_CAPACITY = 3;
49
- var MAX_NODE_CAPACITY = 16384;
50
- var NODE_LEAF = 0;
51
- var NODE_BRANCH = 1;
52
- var normalizeDuplicateKeyPolicy = (value) => {
53
- if (value === void 0) {
54
- return "replace";
55
- }
56
- if (value !== "allow" && value !== "reject" && value !== "replace") {
57
- throw new BTreeValidationError(
58
- `Invalid duplicateKeys option.`
59
- );
60
- }
61
- return value;
62
- };
63
- var isLeafNode = (node) => {
64
- return node.kind === NODE_LEAF;
65
- };
66
- var writeMinKeyTo = (node, target) => {
67
- if (node.kind === NODE_LEAF) {
68
- if (node.entryOffset >= node.entries.length) return false;
69
- const e = node.entries[node.entryOffset];
70
- target.key = e.key;
71
- target.sequence = e.entryId;
72
- return true;
73
- }
74
- if (node.childOffset >= node.keys.length) return false;
75
- target.key = node.keys[node.childOffset].key;
76
- target.sequence = node.keys[node.childOffset].sequence;
77
- return true;
78
- };
79
- var normalizeNodeCapacity = (value, field, defaultValue) => {
80
- if (value === void 0) {
81
- return defaultValue;
82
- }
83
- if (!Number.isInteger(value) || value < MIN_NODE_CAPACITY || value > MAX_NODE_CAPACITY) {
84
- throw new BTreeValidationError(
85
- `${field}: integer ${MIN_NODE_CAPACITY}\u2013${MAX_NODE_CAPACITY} required.`
86
- );
87
- }
88
- return value;
89
- };
45
+ // src/btree/node-ops.ts
90
46
  var createLeafNode = (entries, parent) => {
91
47
  return {
92
- kind: NODE_LEAF,
48
+ kind: 0,
93
49
  entries,
94
50
  entryOffset: 0,
95
51
  parent,
@@ -101,7 +57,7 @@ var createLeafNode = (entries, parent) => {
101
57
  var createBranchNode = (children, parent) => {
102
58
  const keys = [];
103
59
  const branch = {
104
- kind: NODE_BRANCH,
60
+ kind: 1,
105
61
  children,
106
62
  keys,
107
63
  childOffset: 0,
@@ -240,6 +196,56 @@ var branchRemoveAt = (branch, physIndex) => {
240
196
  }
241
197
  };
242
198
 
199
+ // src/btree/types.ts
200
+ var DEFAULT_MAX_LEAF_ENTRIES = 64;
201
+ var DEFAULT_MAX_BRANCH_CHILDREN = 64;
202
+ var MIN_NODE_CAPACITY = 3;
203
+ var MAX_NODE_CAPACITY = 16384;
204
+ var NODE_LEAF = 0;
205
+ var normalizeDuplicateKeyPolicy = (value) => {
206
+ if (value === void 0) {
207
+ return "replace";
208
+ }
209
+ if (value !== "allow" && value !== "reject" && value !== "replace") {
210
+ throw new BTreeValidationError(
211
+ `Invalid duplicateKeys option.`
212
+ );
213
+ }
214
+ return value;
215
+ };
216
+ var toPublicEntry = (entry) => ({
217
+ entryId: entry.entryId,
218
+ key: entry.key,
219
+ value: entry.value
220
+ });
221
+ var isLeafNode = (node) => {
222
+ return node.kind === NODE_LEAF;
223
+ };
224
+ var writeMinKeyTo = (node, target) => {
225
+ if (node.kind === NODE_LEAF) {
226
+ if (node.entryOffset >= node.entries.length) return false;
227
+ const e = node.entries[node.entryOffset];
228
+ target.key = e.key;
229
+ target.sequence = e.entryId;
230
+ return true;
231
+ }
232
+ if (node.childOffset >= node.keys.length) return false;
233
+ target.key = node.keys[node.childOffset].key;
234
+ target.sequence = node.keys[node.childOffset].sequence;
235
+ return true;
236
+ };
237
+ var normalizeNodeCapacity = (value, field, defaultValue) => {
238
+ if (value === void 0) {
239
+ return defaultValue;
240
+ }
241
+ if (!Number.isInteger(value) || value < MIN_NODE_CAPACITY || value > MAX_NODE_CAPACITY) {
242
+ throw new BTreeValidationError(
243
+ `${field}: integer ${MIN_NODE_CAPACITY}\u2013${MAX_NODE_CAPACITY} required.`
244
+ );
245
+ }
246
+ return value;
247
+ };
248
+
243
249
  // src/btree/navigation.ts
244
250
  var selectBranchChild = (compare, branch, userKey, sequence) => {
245
251
  const off = branch.childOffset;
@@ -253,7 +259,7 @@ var selectBranchChild = (compare, branch, userKey, sequence) => {
253
259
  const mid = lower + upper >>> 1;
254
260
  const k = branch.keys[mid];
255
261
  const cmp = compare(k.key, userKey);
256
- if ((cmp !== 0 ? cmp : k.sequence - sequence) <= 0) {
262
+ if ((cmp !== 0 ? cmp : k.sequence < sequence ? -1 : k.sequence > sequence ? 1 : 0) <= 0) {
257
263
  selectedIndex = mid;
258
264
  lower = mid + 1;
259
265
  } else {
@@ -278,7 +284,7 @@ var lowerBoundInLeaf = (state, leaf, userKey, sequence) => {
278
284
  const mid = lower + upper >>> 1;
279
285
  const e = leaf.entries[mid];
280
286
  const cmp = compare(e.key, userKey);
281
- if ((cmp !== 0 ? cmp : e.entryId - sequence) < 0) {
287
+ if ((cmp !== 0 ? cmp : e.entryId < sequence ? -1 : e.entryId > sequence ? 1 : 0) < 0) {
282
288
  lower = mid + 1;
283
289
  } else {
284
290
  upper = mid;
@@ -294,7 +300,7 @@ var upperBoundInLeaf = (state, leaf, userKey, sequence) => {
294
300
  const mid = lower + upper >>> 1;
295
301
  const e = leaf.entries[mid];
296
302
  const cmp = compare(e.key, userKey);
297
- if ((cmp !== 0 ? cmp : e.entryId - sequence) <= 0) {
303
+ if ((cmp !== 0 ? cmp : e.entryId < sequence ? -1 : e.entryId > sequence ? 1 : 0) <= 0) {
298
304
  lower = mid + 1;
299
305
  } else {
300
306
  upper = mid;
@@ -431,6 +437,114 @@ var findPairOrNextLower = (state, key) => {
431
437
  return null;
432
438
  };
433
439
 
440
+ // src/btree/rangeQuery.ts
441
+ function isEmptyRange(compare, startKey, endKey, options) {
442
+ const cmp = compare(startKey, endKey);
443
+ if (cmp > 0) return true;
444
+ const lowerExclusive = options?.lowerBound === "exclusive";
445
+ const upperExclusive = options?.upperBound === "exclusive";
446
+ return lowerExclusive && upperExclusive && cmp === 0;
447
+ }
448
+ var initRangeCursor = (state, startKey, endKey, options) => {
449
+ if (state.entryCount === 0) return null;
450
+ const compare = state.compareKeys;
451
+ if (isEmptyRange(compare, startKey, endKey, options)) return null;
452
+ const lowerExclusive = options?.lowerBound === "exclusive";
453
+ const upperExclusive = options?.upperBound === "exclusive";
454
+ const startSeq = lowerExclusive ? Number.MAX_SAFE_INTEGER : 0;
455
+ const leaf = findLeafForKey(state, startKey, startSeq);
456
+ const index = lowerExclusive ? upperBoundInLeaf(state, leaf, startKey, Number.MAX_SAFE_INTEGER) : lowerBoundInLeaf(state, leaf, startKey, 0);
457
+ return { leaf, index, compare, upperExclusive };
458
+ };
459
+ var countRangeEntries = (state, startKey, endKey, options) => {
460
+ const cursor = initRangeCursor(state, startKey, endKey, options);
461
+ if (cursor === null) return 0;
462
+ let cursorLeaf = cursor.leaf;
463
+ let cursorIndex = cursor.index;
464
+ const { compare, upperExclusive } = cursor;
465
+ let count = 0;
466
+ while (cursorLeaf !== null) {
467
+ const leafCount = leafEntryCount(cursorLeaf);
468
+ if (cursorIndex >= leafCount) {
469
+ cursorLeaf = cursorLeaf.next;
470
+ cursorIndex = 0;
471
+ continue;
472
+ }
473
+ const lastEntry = leafEntryAt(cursorLeaf, leafCount - 1);
474
+ const cmpLast = compare(lastEntry.key, endKey);
475
+ if (upperExclusive ? cmpLast < 0 : cmpLast <= 0) {
476
+ count += leafCount - cursorIndex;
477
+ cursorLeaf = cursorLeaf.next;
478
+ cursorIndex = 0;
479
+ continue;
480
+ }
481
+ const endSeq = upperExclusive ? 0 : Number.MAX_SAFE_INTEGER;
482
+ const endBound = upperExclusive ? lowerBoundInLeaf(state, cursorLeaf, endKey, endSeq) : upperBoundInLeaf(state, cursorLeaf, endKey, endSeq);
483
+ const limit = endBound < leafCount ? endBound : leafCount;
484
+ count += limit - cursorIndex;
485
+ return count;
486
+ }
487
+ return count;
488
+ };
489
+ var RANGE_PREALLOC_THRESHOLD = 200;
490
+ var allocateRangeOutput = (state, cursorLeaf, cursorIndex, compare, upperExclusive, startKey, endKey, options) => {
491
+ const firstLeafCount = leafEntryCount(cursorLeaf);
492
+ const firstLeafRemainder = firstLeafCount - cursorIndex;
493
+ if (firstLeafRemainder >= RANGE_PREALLOC_THRESHOLD && cursorLeaf.next !== null) {
494
+ const lastEntry = leafEntryAt(cursorLeaf, firstLeafCount - 1);
495
+ const cmpLast = compare(lastEntry.key, endKey);
496
+ if (upperExclusive ? cmpLast < 0 : cmpLast <= 0) {
497
+ const total = countRangeEntries(state, startKey, endKey, options);
498
+ return new Array(total);
499
+ }
500
+ }
501
+ return [];
502
+ };
503
+ var appendLeafSlice = (leaf, from, to, output, useIndexed, writeIdx) => {
504
+ if (useIndexed) {
505
+ for (let i = from; i < to; i += 1) {
506
+ output[writeIdx++] = leafEntryAt(leaf, i);
507
+ }
508
+ } else {
509
+ for (let i = from; i < to; i += 1) {
510
+ output.push(leafEntryAt(leaf, i));
511
+ }
512
+ }
513
+ return writeIdx;
514
+ };
515
+ var rangeQueryEntries = (state, startKey, endKey, options) => {
516
+ const cursor = initRangeCursor(state, startKey, endKey, options);
517
+ if (cursor === null) return [];
518
+ let cursorLeaf = cursor.leaf;
519
+ let cursorIndex = cursor.index;
520
+ const { compare, upperExclusive } = cursor;
521
+ const output = allocateRangeOutput(state, cursorLeaf, cursorIndex, compare, upperExclusive, startKey, endKey, options);
522
+ let writeIdx = 0;
523
+ const useIndexed = output.length > 0;
524
+ while (cursorLeaf !== null) {
525
+ const leafCount = leafEntryCount(cursorLeaf);
526
+ if (cursorIndex >= leafCount) {
527
+ cursorLeaf = cursorLeaf.next;
528
+ cursorIndex = 0;
529
+ continue;
530
+ }
531
+ const lastEntry = leafEntryAt(cursorLeaf, leafCount - 1);
532
+ const cmpLast = compare(lastEntry.key, endKey);
533
+ if (upperExclusive ? cmpLast < 0 : cmpLast <= 0) {
534
+ writeIdx = appendLeafSlice(cursorLeaf, cursorIndex, leafCount, output, useIndexed, writeIdx);
535
+ cursorLeaf = cursorLeaf.next;
536
+ cursorIndex = 0;
537
+ continue;
538
+ }
539
+ const endSeq = upperExclusive ? 0 : Number.MAX_SAFE_INTEGER;
540
+ const endBound = upperExclusive ? lowerBoundInLeaf(state, cursorLeaf, endKey, endSeq) : upperBoundInLeaf(state, cursorLeaf, endKey, endSeq);
541
+ const limit = endBound < leafCount ? endBound : leafCount;
542
+ appendLeafSlice(cursorLeaf, cursorIndex, limit, output, useIndexed, writeIdx);
543
+ return output;
544
+ }
545
+ return output;
546
+ };
547
+
434
548
  // src/btree/rebalance.ts
435
549
  var updateMinKeyInAncestors = (node) => {
436
550
  let current = node;
@@ -656,16 +770,7 @@ var findRemoveEnd = (state, leaf, idx, endKey, upperExclusive) => {
656
770
  }
657
771
  return removeEnd;
658
772
  };
659
- var computeTreeHeight = (state) => {
660
- let h = 0;
661
- let n = state.root;
662
- while (!isLeafNode(n)) {
663
- n = n.children[n.childOffset];
664
- h += 1;
665
- }
666
- return h;
667
- };
668
- var spliceLeafAndRebalance = (state, leaf, idx, removeCount, maxRebalanceDepth) => {
773
+ var spliceLeafAndRebalance = (state, leaf, idx, removeCount) => {
669
774
  if (state.entryKeys !== null) {
670
775
  for (let i = idx; i < idx + removeCount; i += 1) {
671
776
  state.entryKeys.delete(leafEntryAt(leaf, i).entryId);
@@ -680,11 +785,11 @@ var spliceLeafAndRebalance = (state, leaf, idx, removeCount, maxRebalanceDepth)
680
785
  updateMinKeyInAncestors(leaf);
681
786
  }
682
787
  const countAfterSplice = leafEntryCount(leaf);
683
- let maxIter = maxRebalanceDepth;
684
- while (maxIter > 0 && leaf !== state.root && leafEntryCount(leaf) < state.minLeafEntries) {
788
+ let safetyGuard = state.minLeafEntries + 4;
789
+ while (safetyGuard > 0 && leaf !== state.root && leafEntryCount(leaf) < state.minLeafEntries) {
685
790
  rebalanceAfterLeafRemoval(state, leaf);
686
791
  if (leaf.parent !== null && leaf.parent.children[leaf.indexInParent] !== leaf) break;
687
- maxIter -= 1;
792
+ safetyGuard -= 1;
688
793
  }
689
794
  if (leafEmptied && leafEntryCount(leaf) > 0 && leaf.parent !== null && leaf.parent.children[leaf.indexInParent] === leaf) {
690
795
  updateMinKeyInAncestors(leaf);
@@ -694,12 +799,9 @@ var spliceLeafAndRebalance = (state, leaf, idx, removeCount, maxRebalanceDepth)
694
799
  var isLeafStillValid = (state, leaf) => leaf.parent === null ? leaf === state.root : leaf.parent.children[leaf.indexInParent] === leaf;
695
800
  var deleteRangeEntries = (state, startKey, endKey, options) => {
696
801
  if (state.entryCount === 0) return 0;
697
- const boundCompared = state.compareKeys(startKey, endKey);
698
- if (boundCompared > 0) return 0;
802
+ if (isEmptyRange(state.compareKeys, startKey, endKey, options)) return 0;
699
803
  const lowerExclusive = options?.lowerBound === "exclusive";
700
804
  const upperExclusive = options?.upperBound === "exclusive";
701
- if (lowerExclusive && upperExclusive && boundCompared === 0) return 0;
702
- const treeHeight = computeTreeHeight(state);
703
805
  let deleted = 0;
704
806
  let needsNavigate = true;
705
807
  let leaf = null;
@@ -717,7 +819,7 @@ var deleteRangeEntries = (state, startKey, endKey, options) => {
717
819
  const removeEnd = findRemoveEnd(state, leaf, idx, endKey, upperExclusive);
718
820
  const removeCount = removeEnd - idx;
719
821
  if (removeCount === 0) break;
720
- const countAfterSplice = spliceLeafAndRebalance(state, leaf, idx, removeCount, treeHeight);
822
+ const countAfterSplice = spliceLeafAndRebalance(state, leaf, idx, removeCount);
721
823
  deleted += removeCount;
722
824
  if (removeEnd < count) break;
723
825
  if (!isLeafStillValid(state, leaf)) {
@@ -736,6 +838,7 @@ var deleteRangeEntries = (state, startKey, endKey, options) => {
736
838
  };
737
839
 
738
840
  // src/btree/autoScale.ts
841
+ var minOccupancy = (max) => Math.ceil(max / 2);
739
842
  var AUTO_SCALE_TIERS = [
740
843
  { threshold: 0, maxLeaf: 32, maxBranch: 32 },
741
844
  { threshold: 1e3, maxLeaf: 64, maxBranch: 64 },
@@ -787,8 +890,8 @@ var createInitialState = (config) => {
787
890
  maxLeafEntries,
788
891
  maxBranchChildren,
789
892
  duplicateKeys,
790
- minLeafEntries: Math.ceil(maxLeafEntries / 2),
791
- minBranchChildren: Math.ceil(maxBranchChildren / 2),
893
+ minLeafEntries: minOccupancy(maxLeafEntries),
894
+ minBranchChildren: minOccupancy(maxBranchChildren),
792
895
  root: emptyLeaf,
793
896
  leftmostLeaf: emptyLeaf,
794
897
  rightmostLeaf: emptyLeaf,
@@ -805,11 +908,11 @@ var maybeAutoScale = (state) => {
805
908
  const { maxLeaf, maxBranch } = computeAutoScaleTier(state.entryCount);
806
909
  if (maxLeaf > state.maxLeafEntries) {
807
910
  state.maxLeafEntries = maxLeaf;
808
- state.minLeafEntries = Math.ceil(maxLeaf / 2);
911
+ state.minLeafEntries = minOccupancy(maxLeaf);
809
912
  }
810
913
  if (maxBranch > state.maxBranchChildren) {
811
914
  state.maxBranchChildren = maxBranch;
812
- state.minBranchChildren = Math.ceil(maxBranch / 2);
915
+ state.minBranchChildren = minOccupancy(maxBranch);
813
916
  }
814
917
  state._nextAutoScaleThreshold = computeNextAutoScaleThreshold(state.entryCount);
815
918
  };
@@ -835,8 +938,8 @@ var applyAutoScaleCapacitySnapshot = (state, maxLeafEntries, maxBranchChildren)
835
938
  }
836
939
  state.maxLeafEntries = normalizedLeaf;
837
940
  state.maxBranchChildren = normalizedBranch;
838
- state.minLeafEntries = Math.ceil(normalizedLeaf / 2);
839
- state.minBranchChildren = Math.ceil(normalizedBranch / 2);
941
+ state.minLeafEntries = minOccupancy(normalizedLeaf);
942
+ state.minBranchChildren = minOccupancy(normalizedBranch);
840
943
  };
841
944
 
842
945
  // src/btree/bulkLoad.ts
@@ -1061,6 +1164,9 @@ var findLeafEntryBySequence = (state, userKey, sequence) => {
1061
1164
  return { leaf: targetLeaf, index };
1062
1165
  };
1063
1166
  var removeEntryById = (state, entryId) => {
1167
+ if (state.entryKeys === null) {
1168
+ throw new BTreeInvariantError("entryKeys lookup map is not enabled on this tree.");
1169
+ }
1064
1170
  const userKey = state.entryKeys.get(entryId);
1065
1171
  if (userKey === void 0) return null;
1066
1172
  const found = findLeafEntryBySequence(state, userKey, entryId);
@@ -1078,6 +1184,9 @@ var removeEntryById = (state, entryId) => {
1078
1184
  return entry;
1079
1185
  };
1080
1186
  var peekEntryById = (state, entryId) => {
1187
+ if (state.entryKeys === null) {
1188
+ throw new BTreeInvariantError("entryKeys lookup map is not enabled on this tree.");
1189
+ }
1081
1190
  const userKey = state.entryKeys.get(entryId);
1082
1191
  if (userKey === void 0) return null;
1083
1192
  const found = findLeafEntryBySequence(state, userKey, entryId);
@@ -1086,6 +1195,9 @@ var peekEntryById = (state, entryId) => {
1086
1195
  return entry;
1087
1196
  };
1088
1197
  var updateEntryById = (state, entryId, newValue) => {
1198
+ if (state.entryKeys === null) {
1199
+ throw new BTreeInvariantError("entryKeys lookup map is not enabled on this tree.");
1200
+ }
1089
1201
  const userKey = state.entryKeys.get(entryId);
1090
1202
  if (userKey === void 0) return null;
1091
1203
  const found = findLeafEntryBySequence(state, userKey, entryId);
@@ -1099,9 +1211,12 @@ var putManyEntries = (state, entries) => {
1099
1211
  const strictlyAscending = state.duplicateKeys !== "allow";
1100
1212
  for (let i = 1; i < entries.length; i += 1) {
1101
1213
  const cmp = state.compareKeys(entries[i - 1].key, entries[i].key);
1102
- if (strictlyAscending ? cmp >= 0 : cmp > 0) {
1214
+ if (cmp > 0) {
1215
+ throw new BTreeValidationError("putMany: entries not in ascending order.");
1216
+ }
1217
+ if (strictlyAscending && cmp === 0) {
1103
1218
  throw new BTreeValidationError(
1104
- strictlyAscending ? "putMany: not sorted in strict ascending order." : "putMany: not sorted in non-descending order."
1219
+ state.duplicateKeys === "reject" ? "putMany: duplicate key rejected." : "putMany: equal keys not allowed in strict mode."
1105
1220
  );
1106
1221
  }
1107
1222
  }
@@ -1119,109 +1234,6 @@ var putManyEntries = (state, entries) => {
1119
1234
  return bulkLoadEntries(state, entries);
1120
1235
  };
1121
1236
 
1122
- // src/btree/rangeQuery.ts
1123
- var initRangeCursor = (state, startKey, endKey, options) => {
1124
- if (state.entryCount === 0) return null;
1125
- const compare = state.compareKeys;
1126
- const boundCompared = compare(startKey, endKey);
1127
- if (boundCompared > 0) return null;
1128
- const lowerExclusive = options?.lowerBound === "exclusive";
1129
- const upperExclusive = options?.upperBound === "exclusive";
1130
- if (lowerExclusive && upperExclusive && boundCompared === 0) return null;
1131
- const startSeq = lowerExclusive ? Number.MAX_SAFE_INTEGER : 0;
1132
- const leaf = findLeafForKey(state, startKey, startSeq);
1133
- const index = lowerExclusive ? upperBoundInLeaf(state, leaf, startKey, Number.MAX_SAFE_INTEGER) : lowerBoundInLeaf(state, leaf, startKey, 0);
1134
- return { leaf, index, compare, upperExclusive };
1135
- };
1136
- var countRangeEntries = (state, startKey, endKey, options) => {
1137
- const cursor = initRangeCursor(state, startKey, endKey, options);
1138
- if (cursor === null) return 0;
1139
- let cursorLeaf = cursor.leaf;
1140
- let cursorIndex = cursor.index;
1141
- const { compare, upperExclusive } = cursor;
1142
- let count = 0;
1143
- while (cursorLeaf !== null) {
1144
- const leafCount = leafEntryCount(cursorLeaf);
1145
- if (cursorIndex >= leafCount) {
1146
- cursorLeaf = cursorLeaf.next;
1147
- cursorIndex = 0;
1148
- continue;
1149
- }
1150
- const lastEntry = leafEntryAt(cursorLeaf, leafCount - 1);
1151
- const cmpLast = compare(lastEntry.key, endKey);
1152
- if (upperExclusive ? cmpLast < 0 : cmpLast <= 0) {
1153
- count += leafCount - cursorIndex;
1154
- cursorLeaf = cursorLeaf.next;
1155
- cursorIndex = 0;
1156
- continue;
1157
- }
1158
- const endSeq = upperExclusive ? 0 : Number.MAX_SAFE_INTEGER;
1159
- const endBound = upperExclusive ? lowerBoundInLeaf(state, cursorLeaf, endKey, endSeq) : upperBoundInLeaf(state, cursorLeaf, endKey, endSeq);
1160
- const limit = endBound < leafCount ? endBound : leafCount;
1161
- count += limit - cursorIndex;
1162
- return count;
1163
- }
1164
- return count;
1165
- };
1166
- var RANGE_PREALLOC_THRESHOLD = 200;
1167
- var allocateRangeOutput = (state, cursorLeaf, cursorIndex, compare, upperExclusive, startKey, endKey, options) => {
1168
- const firstLeafCount = leafEntryCount(cursorLeaf);
1169
- const firstLeafRemainder = firstLeafCount - cursorIndex;
1170
- if (firstLeafRemainder >= RANGE_PREALLOC_THRESHOLD && cursorLeaf.next !== null) {
1171
- const lastEntry = leafEntryAt(cursorLeaf, firstLeafCount - 1);
1172
- const cmpLast = compare(lastEntry.key, endKey);
1173
- if (upperExclusive ? cmpLast < 0 : cmpLast <= 0) {
1174
- const total = countRangeEntries(state, startKey, endKey, options);
1175
- return new Array(total);
1176
- }
1177
- }
1178
- return [];
1179
- };
1180
- var appendLeafSlice = (leaf, from, to, output, useIndexed, writeIdx) => {
1181
- if (useIndexed) {
1182
- for (let i = from; i < to; i += 1) {
1183
- output[writeIdx++] = leafEntryAt(leaf, i);
1184
- }
1185
- } else {
1186
- for (let i = from; i < to; i += 1) {
1187
- output.push(leafEntryAt(leaf, i));
1188
- }
1189
- }
1190
- return writeIdx;
1191
- };
1192
- var rangeQueryEntries = (state, startKey, endKey, options) => {
1193
- const cursor = initRangeCursor(state, startKey, endKey, options);
1194
- if (cursor === null) return [];
1195
- let cursorLeaf = cursor.leaf;
1196
- let cursorIndex = cursor.index;
1197
- const { compare, upperExclusive } = cursor;
1198
- const output = allocateRangeOutput(state, cursorLeaf, cursorIndex, compare, upperExclusive, startKey, endKey, options);
1199
- let writeIdx = 0;
1200
- const useIndexed = output.length > 0;
1201
- while (cursorLeaf !== null) {
1202
- const leafCount = leafEntryCount(cursorLeaf);
1203
- if (cursorIndex >= leafCount) {
1204
- cursorLeaf = cursorLeaf.next;
1205
- cursorIndex = 0;
1206
- continue;
1207
- }
1208
- const lastEntry = leafEntryAt(cursorLeaf, leafCount - 1);
1209
- const cmpLast = compare(lastEntry.key, endKey);
1210
- if (upperExclusive ? cmpLast < 0 : cmpLast <= 0) {
1211
- writeIdx = appendLeafSlice(cursorLeaf, cursorIndex, leafCount, output, useIndexed, writeIdx);
1212
- cursorLeaf = cursorLeaf.next;
1213
- cursorIndex = 0;
1214
- continue;
1215
- }
1216
- const endSeq = upperExclusive ? 0 : Number.MAX_SAFE_INTEGER;
1217
- const endBound = upperExclusive ? lowerBoundInLeaf(state, cursorLeaf, endKey, endSeq) : upperBoundInLeaf(state, cursorLeaf, endKey, endSeq);
1218
- const limit = endBound < leafCount ? endBound : leafCount;
1219
- appendLeafSlice(cursorLeaf, cursorIndex, limit, output, useIndexed, writeIdx);
1220
- return output;
1221
- }
1222
- return output;
1223
- };
1224
-
1225
1237
  // src/btree/serialization.ts
1226
1238
  var buildConfigFromState = (state) => {
1227
1239
  const config = {
@@ -1351,7 +1363,7 @@ var compareNodeKeys = (comparator, leftKey, leftSeq, rightKey, rightSeq) => {
1351
1363
  if (cmp !== 0) {
1352
1364
  return cmp;
1353
1365
  }
1354
- return leftSeq - rightSeq;
1366
+ return leftSeq < rightSeq ? -1 : leftSeq > rightSeq ? 1 : 0;
1355
1367
  };
1356
1368
  var getNodeMaxKey = (node) => {
1357
1369
  if (isLeafNode(node)) {
@@ -1684,44 +1696,56 @@ var InMemoryBTree = class _InMemoryBTree {
1684
1696
  return putManyEntries(this.state, entries);
1685
1697
  }
1686
1698
  remove(key) {
1687
- return removeFirstMatchingEntry(this.state, key);
1699
+ const entry = removeFirstMatchingEntry(this.state, key);
1700
+ if (entry === null) return null;
1701
+ return toPublicEntry(entry);
1688
1702
  }
1689
1703
  removeById(entryId) {
1690
1704
  if (this.state.entryKeys === null) {
1691
1705
  throw new BTreeValidationError("Requires enableEntryIdLookup: true.");
1692
1706
  }
1693
- return removeEntryById(this.state, entryId);
1707
+ const entry = removeEntryById(this.state, entryId);
1708
+ if (entry === null) return null;
1709
+ return toPublicEntry(entry);
1694
1710
  }
1695
1711
  peekById(entryId) {
1696
1712
  if (this.state.entryKeys === null) {
1697
1713
  throw new BTreeValidationError("Requires enableEntryIdLookup: true.");
1698
1714
  }
1699
- return peekEntryById(this.state, entryId);
1715
+ const entry = peekEntryById(this.state, entryId);
1716
+ if (entry === null) return null;
1717
+ return toPublicEntry(entry);
1700
1718
  }
1701
1719
  updateById(entryId, value) {
1702
1720
  if (this.state.entryKeys === null) {
1703
1721
  throw new BTreeValidationError("Requires enableEntryIdLookup: true.");
1704
1722
  }
1705
- return updateEntryById(this.state, entryId, value);
1723
+ const entry = updateEntryById(this.state, entryId, value);
1724
+ if (entry === null) return null;
1725
+ return toPublicEntry(entry);
1706
1726
  }
1707
1727
  popFirst() {
1708
- return popFirstEntry(this.state);
1728
+ const entry = popFirstEntry(this.state);
1729
+ if (entry === null) return null;
1730
+ return toPublicEntry(entry);
1709
1731
  }
1710
1732
  peekFirst() {
1711
1733
  if (this.state.entryCount === 0) {
1712
1734
  return null;
1713
1735
  }
1714
- return leafEntryAt(this.state.leftmostLeaf, 0);
1736
+ return toPublicEntry(leafEntryAt(this.state.leftmostLeaf, 0));
1715
1737
  }
1716
1738
  peekLast() {
1717
1739
  if (this.state.entryCount === 0) {
1718
1740
  return null;
1719
1741
  }
1720
1742
  const leaf = this.state.rightmostLeaf;
1721
- return leafEntryAt(leaf, leafEntryCount(leaf) - 1);
1743
+ return toPublicEntry(leafEntryAt(leaf, leafEntryCount(leaf) - 1));
1722
1744
  }
1723
1745
  popLast() {
1724
- return popLastEntry(this.state);
1746
+ const entry = popLastEntry(this.state);
1747
+ if (entry === null) return null;
1748
+ return toPublicEntry(entry);
1725
1749
  }
1726
1750
  clear() {
1727
1751
  const emptyLeaf = createLeafNode([], null);
@@ -1738,8 +1762,8 @@ var InMemoryBTree = class _InMemoryBTree {
1738
1762
  const tier = computeAutoScaleTier(0);
1739
1763
  this.state.maxLeafEntries = tier.maxLeaf;
1740
1764
  this.state.maxBranchChildren = tier.maxBranch;
1741
- this.state.minLeafEntries = Math.ceil(tier.maxLeaf / 2);
1742
- this.state.minBranchChildren = Math.ceil(tier.maxBranch / 2);
1765
+ this.state.minLeafEntries = minOccupancy(tier.maxLeaf);
1766
+ this.state.minBranchChildren = minOccupancy(tier.maxBranch);
1743
1767
  this.state._nextAutoScaleThreshold = computeNextAutoScaleThreshold(0);
1744
1768
  }
1745
1769
  }
@@ -1754,39 +1778,65 @@ var InMemoryBTree = class _InMemoryBTree {
1754
1778
  findFirst(key) {
1755
1779
  const found = findFirstMatchingUserKey(this.state, key);
1756
1780
  if (found === null) return null;
1757
- return leafEntryAt(found.leaf, found.index);
1781
+ return toPublicEntry(leafEntryAt(found.leaf, found.index));
1758
1782
  }
1783
+ /**
1784
+ * Returns the last entry whose key matches `key`, or `null` if not found.
1785
+ * Useful when `duplicateKeys` is `'allow'` and multiple entries share the same key.
1786
+ */
1759
1787
  findLast(key) {
1760
1788
  const found = findLastMatchingUserKey(this.state, key);
1761
1789
  if (found === null) return null;
1762
- return leafEntryAt(found.leaf, found.index);
1790
+ return toPublicEntry(leafEntryAt(found.leaf, found.index));
1763
1791
  }
1792
+ /**
1793
+ * Returns the smallest key in the tree that is strictly greater than `key`,
1794
+ * or `null` if no such key exists.
1795
+ */
1764
1796
  nextHigherKey(key) {
1765
1797
  return findNextHigherKey(this.state, key);
1766
1798
  }
1799
+ /**
1800
+ * Returns the largest key in the tree that is strictly less than `key`,
1801
+ * or `null` if no such key exists.
1802
+ */
1767
1803
  nextLowerKey(key) {
1768
1804
  return findNextLowerKey(this.state, key);
1769
1805
  }
1806
+ /**
1807
+ * Returns the entry for `key` if it exists; otherwise returns the entry with
1808
+ * the largest key strictly less than `key`. Returns `null` when the tree is
1809
+ * empty or every key is greater than `key`.
1810
+ */
1770
1811
  getPairOrNextLower(key) {
1771
1812
  const found = findPairOrNextLower(this.state, key);
1772
1813
  if (found === null) return null;
1773
- return leafEntryAt(found.leaf, found.index);
1814
+ return toPublicEntry(leafEntryAt(found.leaf, found.index));
1774
1815
  }
1816
+ /**
1817
+ * Returns the number of entries whose keys fall within [`startKey`, `endKey`].
1818
+ * Pass `options` to make either bound exclusive.
1819
+ */
1775
1820
  count(startKey, endKey, options) {
1776
1821
  return countRangeEntries(this.state, startKey, endKey, options);
1777
1822
  }
1823
+ /**
1824
+ * Deletes all entries whose keys fall within [`startKey`, `endKey`].
1825
+ * Pass `options` to make either bound exclusive.
1826
+ * @returns The number of entries deleted.
1827
+ */
1778
1828
  deleteRange(startKey, endKey, options) {
1779
1829
  return deleteRangeEntries(this.state, startKey, endKey, options);
1780
1830
  }
1781
1831
  range(startKey, endKey, options) {
1782
- return rangeQueryEntries(this.state, startKey, endKey, options);
1832
+ return rangeQueryEntries(this.state, startKey, endKey, options).map(toPublicEntry);
1783
1833
  }
1784
1834
  *entries() {
1785
1835
  let leaf = this.state.leftmostLeaf;
1786
1836
  while (leaf !== null) {
1787
1837
  const count = leafEntryCount(leaf);
1788
1838
  for (let i = 0; i < count; i += 1) {
1789
- yield leafEntryAt(leaf, i);
1839
+ yield toPublicEntry(leafEntryAt(leaf, i));
1790
1840
  }
1791
1841
  leaf = leaf.next;
1792
1842
  }
@@ -1796,29 +1846,19 @@ var InMemoryBTree = class _InMemoryBTree {
1796
1846
  while (leaf !== null) {
1797
1847
  const count = leafEntryCount(leaf);
1798
1848
  for (let i = count - 1; i >= 0; i -= 1) {
1799
- yield leafEntryAt(leaf, i);
1849
+ yield toPublicEntry(leafEntryAt(leaf, i));
1800
1850
  }
1801
1851
  leaf = leaf.prev;
1802
1852
  }
1803
1853
  }
1804
1854
  *keys() {
1805
- let leaf = this.state.leftmostLeaf;
1806
- while (leaf !== null) {
1807
- const count = leafEntryCount(leaf);
1808
- for (let i = 0; i < count; i += 1) {
1809
- yield leafEntryAt(leaf, i).key;
1810
- }
1811
- leaf = leaf.next;
1855
+ for (const entry of this.entries()) {
1856
+ yield entry.key;
1812
1857
  }
1813
1858
  }
1814
1859
  *values() {
1815
- let leaf = this.state.leftmostLeaf;
1816
- while (leaf !== null) {
1817
- const count = leafEntryCount(leaf);
1818
- for (let i = 0; i < count; i += 1) {
1819
- yield leafEntryAt(leaf, i).value;
1820
- }
1821
- leaf = leaf.next;
1860
+ for (const entry of this.entries()) {
1861
+ yield entry.value;
1822
1862
  }
1823
1863
  }
1824
1864
  [Symbol.iterator]() {
@@ -1829,7 +1869,7 @@ var InMemoryBTree = class _InMemoryBTree {
1829
1869
  while (leaf !== null) {
1830
1870
  const count = leafEntryCount(leaf);
1831
1871
  for (let i = 0; i < count; i += 1) {
1832
- callback.call(thisArg, leafEntryAt(leaf, i));
1872
+ callback.call(thisArg, toPublicEntry(leafEntryAt(leaf, i)));
1833
1873
  }
1834
1874
  leaf = leaf.next;
1835
1875
  }
@@ -1841,12 +1881,19 @@ var InMemoryBTree = class _InMemoryBTree {
1841
1881
  while (leaf !== null) {
1842
1882
  const count = leafEntryCount(leaf);
1843
1883
  for (let i = 0; i < count; i += 1) {
1844
- result[writeIdx++] = leafEntryAt(leaf, i);
1884
+ result[writeIdx++] = toPublicEntry(leafEntryAt(leaf, i));
1845
1885
  }
1846
1886
  leaf = leaf.next;
1847
1887
  }
1848
1888
  return result;
1849
1889
  }
1890
+ /**
1891
+ * Returns a structurally independent `InMemoryBTree` with identical
1892
+ * configuration and entries. The tree structure (nodes, links, entry IDs)
1893
+ * is fully independent, but stored key and value references are shared
1894
+ * with the source tree.
1895
+ * Note: `EntryId` values are reassigned in the clone — IDs from the source tree are not valid for the clone.
1896
+ */
1850
1897
  clone() {
1851
1898
  const cloned = new _InMemoryBTree(buildConfigFromState(this.state));
1852
1899
  applyAutoScaleCapacitySnapshot(