@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.
@@ -21,54 +21,10 @@ var BTreeConcurrencyError = class extends Error {
21
21
  }
22
22
  };
23
23
 
24
- // src/btree/types.ts
25
- var DEFAULT_MAX_LEAF_ENTRIES = 64;
26
- var DEFAULT_MAX_BRANCH_CHILDREN = 64;
27
- var MIN_NODE_CAPACITY = 3;
28
- var MAX_NODE_CAPACITY = 16384;
29
- var NODE_LEAF = 0;
30
- var NODE_BRANCH = 1;
31
- var normalizeDuplicateKeyPolicy = (value) => {
32
- if (value === void 0) {
33
- return "replace";
34
- }
35
- if (value !== "allow" && value !== "reject" && value !== "replace") {
36
- throw new BTreeValidationError(
37
- `Invalid duplicateKeys option.`
38
- );
39
- }
40
- return value;
41
- };
42
- var isLeafNode = (node) => {
43
- return node.kind === NODE_LEAF;
44
- };
45
- var writeMinKeyTo = (node, target) => {
46
- if (node.kind === NODE_LEAF) {
47
- if (node.entryOffset >= node.entries.length) return false;
48
- const e = node.entries[node.entryOffset];
49
- target.key = e.key;
50
- target.sequence = e.entryId;
51
- return true;
52
- }
53
- if (node.childOffset >= node.keys.length) return false;
54
- target.key = node.keys[node.childOffset].key;
55
- target.sequence = node.keys[node.childOffset].sequence;
56
- return true;
57
- };
58
- var normalizeNodeCapacity = (value, field, defaultValue) => {
59
- if (value === void 0) {
60
- return defaultValue;
61
- }
62
- if (!Number.isInteger(value) || value < MIN_NODE_CAPACITY || value > MAX_NODE_CAPACITY) {
63
- throw new BTreeValidationError(
64
- `${field}: integer ${MIN_NODE_CAPACITY}\u2013${MAX_NODE_CAPACITY} required.`
65
- );
66
- }
67
- return value;
68
- };
24
+ // src/btree/node-ops.ts
69
25
  var createLeafNode = (entries, parent) => {
70
26
  return {
71
- kind: NODE_LEAF,
27
+ kind: 0,
72
28
  entries,
73
29
  entryOffset: 0,
74
30
  parent,
@@ -80,7 +36,7 @@ var createLeafNode = (entries, parent) => {
80
36
  var createBranchNode = (children, parent) => {
81
37
  const keys = [];
82
38
  const branch = {
83
- kind: NODE_BRANCH,
39
+ kind: 1,
84
40
  children,
85
41
  keys,
86
42
  childOffset: 0,
@@ -219,6 +175,56 @@ var branchRemoveAt = (branch, physIndex) => {
219
175
  }
220
176
  };
221
177
 
178
+ // src/btree/types.ts
179
+ var DEFAULT_MAX_LEAF_ENTRIES = 64;
180
+ var DEFAULT_MAX_BRANCH_CHILDREN = 64;
181
+ var MIN_NODE_CAPACITY = 3;
182
+ var MAX_NODE_CAPACITY = 16384;
183
+ var NODE_LEAF = 0;
184
+ var normalizeDuplicateKeyPolicy = (value) => {
185
+ if (value === void 0) {
186
+ return "replace";
187
+ }
188
+ if (value !== "allow" && value !== "reject" && value !== "replace") {
189
+ throw new BTreeValidationError(
190
+ `Invalid duplicateKeys option.`
191
+ );
192
+ }
193
+ return value;
194
+ };
195
+ var toPublicEntry = (entry) => ({
196
+ entryId: entry.entryId,
197
+ key: entry.key,
198
+ value: entry.value
199
+ });
200
+ var isLeafNode = (node) => {
201
+ return node.kind === NODE_LEAF;
202
+ };
203
+ var writeMinKeyTo = (node, target) => {
204
+ if (node.kind === NODE_LEAF) {
205
+ if (node.entryOffset >= node.entries.length) return false;
206
+ const e = node.entries[node.entryOffset];
207
+ target.key = e.key;
208
+ target.sequence = e.entryId;
209
+ return true;
210
+ }
211
+ if (node.childOffset >= node.keys.length) return false;
212
+ target.key = node.keys[node.childOffset].key;
213
+ target.sequence = node.keys[node.childOffset].sequence;
214
+ return true;
215
+ };
216
+ var normalizeNodeCapacity = (value, field, defaultValue) => {
217
+ if (value === void 0) {
218
+ return defaultValue;
219
+ }
220
+ if (!Number.isInteger(value) || value < MIN_NODE_CAPACITY || value > MAX_NODE_CAPACITY) {
221
+ throw new BTreeValidationError(
222
+ `${field}: integer ${MIN_NODE_CAPACITY}\u2013${MAX_NODE_CAPACITY} required.`
223
+ );
224
+ }
225
+ return value;
226
+ };
227
+
222
228
  // src/btree/navigation.ts
223
229
  var selectBranchChild = (compare, branch, userKey, sequence) => {
224
230
  const off = branch.childOffset;
@@ -232,7 +238,7 @@ var selectBranchChild = (compare, branch, userKey, sequence) => {
232
238
  const mid = lower + upper >>> 1;
233
239
  const k = branch.keys[mid];
234
240
  const cmp = compare(k.key, userKey);
235
- if ((cmp !== 0 ? cmp : k.sequence - sequence) <= 0) {
241
+ if ((cmp !== 0 ? cmp : k.sequence < sequence ? -1 : k.sequence > sequence ? 1 : 0) <= 0) {
236
242
  selectedIndex = mid;
237
243
  lower = mid + 1;
238
244
  } else {
@@ -257,7 +263,7 @@ var lowerBoundInLeaf = (state, leaf, userKey, sequence) => {
257
263
  const mid = lower + upper >>> 1;
258
264
  const e = leaf.entries[mid];
259
265
  const cmp = compare(e.key, userKey);
260
- if ((cmp !== 0 ? cmp : e.entryId - sequence) < 0) {
266
+ if ((cmp !== 0 ? cmp : e.entryId < sequence ? -1 : e.entryId > sequence ? 1 : 0) < 0) {
261
267
  lower = mid + 1;
262
268
  } else {
263
269
  upper = mid;
@@ -273,7 +279,7 @@ var upperBoundInLeaf = (state, leaf, userKey, sequence) => {
273
279
  const mid = lower + upper >>> 1;
274
280
  const e = leaf.entries[mid];
275
281
  const cmp = compare(e.key, userKey);
276
- if ((cmp !== 0 ? cmp : e.entryId - sequence) <= 0) {
282
+ if ((cmp !== 0 ? cmp : e.entryId < sequence ? -1 : e.entryId > sequence ? 1 : 0) <= 0) {
277
283
  lower = mid + 1;
278
284
  } else {
279
285
  upper = mid;
@@ -410,6 +416,114 @@ var findPairOrNextLower = (state, key) => {
410
416
  return null;
411
417
  };
412
418
 
419
+ // src/btree/rangeQuery.ts
420
+ function isEmptyRange(compare, startKey, endKey, options) {
421
+ const cmp = compare(startKey, endKey);
422
+ if (cmp > 0) return true;
423
+ const lowerExclusive = options?.lowerBound === "exclusive";
424
+ const upperExclusive = options?.upperBound === "exclusive";
425
+ return lowerExclusive && upperExclusive && cmp === 0;
426
+ }
427
+ var initRangeCursor = (state, startKey, endKey, options) => {
428
+ if (state.entryCount === 0) return null;
429
+ const compare = state.compareKeys;
430
+ if (isEmptyRange(compare, startKey, endKey, options)) return null;
431
+ const lowerExclusive = options?.lowerBound === "exclusive";
432
+ const upperExclusive = options?.upperBound === "exclusive";
433
+ const startSeq = lowerExclusive ? Number.MAX_SAFE_INTEGER : 0;
434
+ const leaf = findLeafForKey(state, startKey, startSeq);
435
+ const index = lowerExclusive ? upperBoundInLeaf(state, leaf, startKey, Number.MAX_SAFE_INTEGER) : lowerBoundInLeaf(state, leaf, startKey, 0);
436
+ return { leaf, index, compare, upperExclusive };
437
+ };
438
+ var countRangeEntries = (state, startKey, endKey, options) => {
439
+ const cursor = initRangeCursor(state, startKey, endKey, options);
440
+ if (cursor === null) return 0;
441
+ let cursorLeaf = cursor.leaf;
442
+ let cursorIndex = cursor.index;
443
+ const { compare, upperExclusive } = cursor;
444
+ let count = 0;
445
+ while (cursorLeaf !== null) {
446
+ const leafCount = leafEntryCount(cursorLeaf);
447
+ if (cursorIndex >= leafCount) {
448
+ cursorLeaf = cursorLeaf.next;
449
+ cursorIndex = 0;
450
+ continue;
451
+ }
452
+ const lastEntry = leafEntryAt(cursorLeaf, leafCount - 1);
453
+ const cmpLast = compare(lastEntry.key, endKey);
454
+ if (upperExclusive ? cmpLast < 0 : cmpLast <= 0) {
455
+ count += leafCount - cursorIndex;
456
+ cursorLeaf = cursorLeaf.next;
457
+ cursorIndex = 0;
458
+ continue;
459
+ }
460
+ const endSeq = upperExclusive ? 0 : Number.MAX_SAFE_INTEGER;
461
+ const endBound = upperExclusive ? lowerBoundInLeaf(state, cursorLeaf, endKey, endSeq) : upperBoundInLeaf(state, cursorLeaf, endKey, endSeq);
462
+ const limit = endBound < leafCount ? endBound : leafCount;
463
+ count += limit - cursorIndex;
464
+ return count;
465
+ }
466
+ return count;
467
+ };
468
+ var RANGE_PREALLOC_THRESHOLD = 200;
469
+ var allocateRangeOutput = (state, cursorLeaf, cursorIndex, compare, upperExclusive, startKey, endKey, options) => {
470
+ const firstLeafCount = leafEntryCount(cursorLeaf);
471
+ const firstLeafRemainder = firstLeafCount - cursorIndex;
472
+ if (firstLeafRemainder >= RANGE_PREALLOC_THRESHOLD && cursorLeaf.next !== null) {
473
+ const lastEntry = leafEntryAt(cursorLeaf, firstLeafCount - 1);
474
+ const cmpLast = compare(lastEntry.key, endKey);
475
+ if (upperExclusive ? cmpLast < 0 : cmpLast <= 0) {
476
+ const total = countRangeEntries(state, startKey, endKey, options);
477
+ return new Array(total);
478
+ }
479
+ }
480
+ return [];
481
+ };
482
+ var appendLeafSlice = (leaf, from, to, output, useIndexed, writeIdx) => {
483
+ if (useIndexed) {
484
+ for (let i = from; i < to; i += 1) {
485
+ output[writeIdx++] = leafEntryAt(leaf, i);
486
+ }
487
+ } else {
488
+ for (let i = from; i < to; i += 1) {
489
+ output.push(leafEntryAt(leaf, i));
490
+ }
491
+ }
492
+ return writeIdx;
493
+ };
494
+ var rangeQueryEntries = (state, startKey, endKey, options) => {
495
+ const cursor = initRangeCursor(state, startKey, endKey, options);
496
+ if (cursor === null) return [];
497
+ let cursorLeaf = cursor.leaf;
498
+ let cursorIndex = cursor.index;
499
+ const { compare, upperExclusive } = cursor;
500
+ const output = allocateRangeOutput(state, cursorLeaf, cursorIndex, compare, upperExclusive, startKey, endKey, options);
501
+ let writeIdx = 0;
502
+ const useIndexed = output.length > 0;
503
+ while (cursorLeaf !== null) {
504
+ const leafCount = leafEntryCount(cursorLeaf);
505
+ if (cursorIndex >= leafCount) {
506
+ cursorLeaf = cursorLeaf.next;
507
+ cursorIndex = 0;
508
+ continue;
509
+ }
510
+ const lastEntry = leafEntryAt(cursorLeaf, leafCount - 1);
511
+ const cmpLast = compare(lastEntry.key, endKey);
512
+ if (upperExclusive ? cmpLast < 0 : cmpLast <= 0) {
513
+ writeIdx = appendLeafSlice(cursorLeaf, cursorIndex, leafCount, output, useIndexed, writeIdx);
514
+ cursorLeaf = cursorLeaf.next;
515
+ cursorIndex = 0;
516
+ continue;
517
+ }
518
+ const endSeq = upperExclusive ? 0 : Number.MAX_SAFE_INTEGER;
519
+ const endBound = upperExclusive ? lowerBoundInLeaf(state, cursorLeaf, endKey, endSeq) : upperBoundInLeaf(state, cursorLeaf, endKey, endSeq);
520
+ const limit = endBound < leafCount ? endBound : leafCount;
521
+ appendLeafSlice(cursorLeaf, cursorIndex, limit, output, useIndexed, writeIdx);
522
+ return output;
523
+ }
524
+ return output;
525
+ };
526
+
413
527
  // src/btree/rebalance.ts
414
528
  var updateMinKeyInAncestors = (node) => {
415
529
  let current = node;
@@ -635,16 +749,7 @@ var findRemoveEnd = (state, leaf, idx, endKey, upperExclusive) => {
635
749
  }
636
750
  return removeEnd;
637
751
  };
638
- var computeTreeHeight = (state) => {
639
- let h = 0;
640
- let n = state.root;
641
- while (!isLeafNode(n)) {
642
- n = n.children[n.childOffset];
643
- h += 1;
644
- }
645
- return h;
646
- };
647
- var spliceLeafAndRebalance = (state, leaf, idx, removeCount, maxRebalanceDepth) => {
752
+ var spliceLeafAndRebalance = (state, leaf, idx, removeCount) => {
648
753
  if (state.entryKeys !== null) {
649
754
  for (let i = idx; i < idx + removeCount; i += 1) {
650
755
  state.entryKeys.delete(leafEntryAt(leaf, i).entryId);
@@ -659,11 +764,11 @@ var spliceLeafAndRebalance = (state, leaf, idx, removeCount, maxRebalanceDepth)
659
764
  updateMinKeyInAncestors(leaf);
660
765
  }
661
766
  const countAfterSplice = leafEntryCount(leaf);
662
- let maxIter = maxRebalanceDepth;
663
- while (maxIter > 0 && leaf !== state.root && leafEntryCount(leaf) < state.minLeafEntries) {
767
+ let safetyGuard = state.minLeafEntries + 4;
768
+ while (safetyGuard > 0 && leaf !== state.root && leafEntryCount(leaf) < state.minLeafEntries) {
664
769
  rebalanceAfterLeafRemoval(state, leaf);
665
770
  if (leaf.parent !== null && leaf.parent.children[leaf.indexInParent] !== leaf) break;
666
- maxIter -= 1;
771
+ safetyGuard -= 1;
667
772
  }
668
773
  if (leafEmptied && leafEntryCount(leaf) > 0 && leaf.parent !== null && leaf.parent.children[leaf.indexInParent] === leaf) {
669
774
  updateMinKeyInAncestors(leaf);
@@ -673,12 +778,9 @@ var spliceLeafAndRebalance = (state, leaf, idx, removeCount, maxRebalanceDepth)
673
778
  var isLeafStillValid = (state, leaf) => leaf.parent === null ? leaf === state.root : leaf.parent.children[leaf.indexInParent] === leaf;
674
779
  var deleteRangeEntries = (state, startKey, endKey, options) => {
675
780
  if (state.entryCount === 0) return 0;
676
- const boundCompared = state.compareKeys(startKey, endKey);
677
- if (boundCompared > 0) return 0;
781
+ if (isEmptyRange(state.compareKeys, startKey, endKey, options)) return 0;
678
782
  const lowerExclusive = options?.lowerBound === "exclusive";
679
783
  const upperExclusive = options?.upperBound === "exclusive";
680
- if (lowerExclusive && upperExclusive && boundCompared === 0) return 0;
681
- const treeHeight = computeTreeHeight(state);
682
784
  let deleted = 0;
683
785
  let needsNavigate = true;
684
786
  let leaf = null;
@@ -696,7 +798,7 @@ var deleteRangeEntries = (state, startKey, endKey, options) => {
696
798
  const removeEnd = findRemoveEnd(state, leaf, idx, endKey, upperExclusive);
697
799
  const removeCount = removeEnd - idx;
698
800
  if (removeCount === 0) break;
699
- const countAfterSplice = spliceLeafAndRebalance(state, leaf, idx, removeCount, treeHeight);
801
+ const countAfterSplice = spliceLeafAndRebalance(state, leaf, idx, removeCount);
700
802
  deleted += removeCount;
701
803
  if (removeEnd < count) break;
702
804
  if (!isLeafStillValid(state, leaf)) {
@@ -715,6 +817,7 @@ var deleteRangeEntries = (state, startKey, endKey, options) => {
715
817
  };
716
818
 
717
819
  // src/btree/autoScale.ts
820
+ var minOccupancy = (max) => Math.ceil(max / 2);
718
821
  var AUTO_SCALE_TIERS = [
719
822
  { threshold: 0, maxLeaf: 32, maxBranch: 32 },
720
823
  { threshold: 1e3, maxLeaf: 64, maxBranch: 64 },
@@ -766,8 +869,8 @@ var createInitialState = (config) => {
766
869
  maxLeafEntries,
767
870
  maxBranchChildren,
768
871
  duplicateKeys,
769
- minLeafEntries: Math.ceil(maxLeafEntries / 2),
770
- minBranchChildren: Math.ceil(maxBranchChildren / 2),
872
+ minLeafEntries: minOccupancy(maxLeafEntries),
873
+ minBranchChildren: minOccupancy(maxBranchChildren),
771
874
  root: emptyLeaf,
772
875
  leftmostLeaf: emptyLeaf,
773
876
  rightmostLeaf: emptyLeaf,
@@ -784,11 +887,11 @@ var maybeAutoScale = (state) => {
784
887
  const { maxLeaf, maxBranch } = computeAutoScaleTier(state.entryCount);
785
888
  if (maxLeaf > state.maxLeafEntries) {
786
889
  state.maxLeafEntries = maxLeaf;
787
- state.minLeafEntries = Math.ceil(maxLeaf / 2);
890
+ state.minLeafEntries = minOccupancy(maxLeaf);
788
891
  }
789
892
  if (maxBranch > state.maxBranchChildren) {
790
893
  state.maxBranchChildren = maxBranch;
791
- state.minBranchChildren = Math.ceil(maxBranch / 2);
894
+ state.minBranchChildren = minOccupancy(maxBranch);
792
895
  }
793
896
  state._nextAutoScaleThreshold = computeNextAutoScaleThreshold(state.entryCount);
794
897
  };
@@ -814,8 +917,8 @@ var applyAutoScaleCapacitySnapshot = (state, maxLeafEntries, maxBranchChildren)
814
917
  }
815
918
  state.maxLeafEntries = normalizedLeaf;
816
919
  state.maxBranchChildren = normalizedBranch;
817
- state.minLeafEntries = Math.ceil(normalizedLeaf / 2);
818
- state.minBranchChildren = Math.ceil(normalizedBranch / 2);
920
+ state.minLeafEntries = minOccupancy(normalizedLeaf);
921
+ state.minBranchChildren = minOccupancy(normalizedBranch);
819
922
  };
820
923
 
821
924
  // src/btree/bulkLoad.ts
@@ -1040,6 +1143,9 @@ var findLeafEntryBySequence = (state, userKey, sequence) => {
1040
1143
  return { leaf: targetLeaf, index };
1041
1144
  };
1042
1145
  var removeEntryById = (state, entryId) => {
1146
+ if (state.entryKeys === null) {
1147
+ throw new BTreeInvariantError("entryKeys lookup map is not enabled on this tree.");
1148
+ }
1043
1149
  const userKey = state.entryKeys.get(entryId);
1044
1150
  if (userKey === void 0) return null;
1045
1151
  const found = findLeafEntryBySequence(state, userKey, entryId);
@@ -1057,6 +1163,9 @@ var removeEntryById = (state, entryId) => {
1057
1163
  return entry;
1058
1164
  };
1059
1165
  var peekEntryById = (state, entryId) => {
1166
+ if (state.entryKeys === null) {
1167
+ throw new BTreeInvariantError("entryKeys lookup map is not enabled on this tree.");
1168
+ }
1060
1169
  const userKey = state.entryKeys.get(entryId);
1061
1170
  if (userKey === void 0) return null;
1062
1171
  const found = findLeafEntryBySequence(state, userKey, entryId);
@@ -1065,6 +1174,9 @@ var peekEntryById = (state, entryId) => {
1065
1174
  return entry;
1066
1175
  };
1067
1176
  var updateEntryById = (state, entryId, newValue) => {
1177
+ if (state.entryKeys === null) {
1178
+ throw new BTreeInvariantError("entryKeys lookup map is not enabled on this tree.");
1179
+ }
1068
1180
  const userKey = state.entryKeys.get(entryId);
1069
1181
  if (userKey === void 0) return null;
1070
1182
  const found = findLeafEntryBySequence(state, userKey, entryId);
@@ -1078,9 +1190,12 @@ var putManyEntries = (state, entries) => {
1078
1190
  const strictlyAscending = state.duplicateKeys !== "allow";
1079
1191
  for (let i = 1; i < entries.length; i += 1) {
1080
1192
  const cmp = state.compareKeys(entries[i - 1].key, entries[i].key);
1081
- if (strictlyAscending ? cmp >= 0 : cmp > 0) {
1193
+ if (cmp > 0) {
1194
+ throw new BTreeValidationError("putMany: entries not in ascending order.");
1195
+ }
1196
+ if (strictlyAscending && cmp === 0) {
1082
1197
  throw new BTreeValidationError(
1083
- strictlyAscending ? "putMany: not sorted in strict ascending order." : "putMany: not sorted in non-descending order."
1198
+ state.duplicateKeys === "reject" ? "putMany: duplicate key rejected." : "putMany: equal keys not allowed in strict mode."
1084
1199
  );
1085
1200
  }
1086
1201
  }
@@ -1098,109 +1213,6 @@ var putManyEntries = (state, entries) => {
1098
1213
  return bulkLoadEntries(state, entries);
1099
1214
  };
1100
1215
 
1101
- // src/btree/rangeQuery.ts
1102
- var initRangeCursor = (state, startKey, endKey, options) => {
1103
- if (state.entryCount === 0) return null;
1104
- const compare = state.compareKeys;
1105
- const boundCompared = compare(startKey, endKey);
1106
- if (boundCompared > 0) return null;
1107
- const lowerExclusive = options?.lowerBound === "exclusive";
1108
- const upperExclusive = options?.upperBound === "exclusive";
1109
- if (lowerExclusive && upperExclusive && boundCompared === 0) return null;
1110
- const startSeq = lowerExclusive ? Number.MAX_SAFE_INTEGER : 0;
1111
- const leaf = findLeafForKey(state, startKey, startSeq);
1112
- const index = lowerExclusive ? upperBoundInLeaf(state, leaf, startKey, Number.MAX_SAFE_INTEGER) : lowerBoundInLeaf(state, leaf, startKey, 0);
1113
- return { leaf, index, compare, upperExclusive };
1114
- };
1115
- var countRangeEntries = (state, startKey, endKey, options) => {
1116
- const cursor = initRangeCursor(state, startKey, endKey, options);
1117
- if (cursor === null) return 0;
1118
- let cursorLeaf = cursor.leaf;
1119
- let cursorIndex = cursor.index;
1120
- const { compare, upperExclusive } = cursor;
1121
- let count = 0;
1122
- while (cursorLeaf !== null) {
1123
- const leafCount = leafEntryCount(cursorLeaf);
1124
- if (cursorIndex >= leafCount) {
1125
- cursorLeaf = cursorLeaf.next;
1126
- cursorIndex = 0;
1127
- continue;
1128
- }
1129
- const lastEntry = leafEntryAt(cursorLeaf, leafCount - 1);
1130
- const cmpLast = compare(lastEntry.key, endKey);
1131
- if (upperExclusive ? cmpLast < 0 : cmpLast <= 0) {
1132
- count += leafCount - cursorIndex;
1133
- cursorLeaf = cursorLeaf.next;
1134
- cursorIndex = 0;
1135
- continue;
1136
- }
1137
- const endSeq = upperExclusive ? 0 : Number.MAX_SAFE_INTEGER;
1138
- const endBound = upperExclusive ? lowerBoundInLeaf(state, cursorLeaf, endKey, endSeq) : upperBoundInLeaf(state, cursorLeaf, endKey, endSeq);
1139
- const limit = endBound < leafCount ? endBound : leafCount;
1140
- count += limit - cursorIndex;
1141
- return count;
1142
- }
1143
- return count;
1144
- };
1145
- var RANGE_PREALLOC_THRESHOLD = 200;
1146
- var allocateRangeOutput = (state, cursorLeaf, cursorIndex, compare, upperExclusive, startKey, endKey, options) => {
1147
- const firstLeafCount = leafEntryCount(cursorLeaf);
1148
- const firstLeafRemainder = firstLeafCount - cursorIndex;
1149
- if (firstLeafRemainder >= RANGE_PREALLOC_THRESHOLD && cursorLeaf.next !== null) {
1150
- const lastEntry = leafEntryAt(cursorLeaf, firstLeafCount - 1);
1151
- const cmpLast = compare(lastEntry.key, endKey);
1152
- if (upperExclusive ? cmpLast < 0 : cmpLast <= 0) {
1153
- const total = countRangeEntries(state, startKey, endKey, options);
1154
- return new Array(total);
1155
- }
1156
- }
1157
- return [];
1158
- };
1159
- var appendLeafSlice = (leaf, from, to, output, useIndexed, writeIdx) => {
1160
- if (useIndexed) {
1161
- for (let i = from; i < to; i += 1) {
1162
- output[writeIdx++] = leafEntryAt(leaf, i);
1163
- }
1164
- } else {
1165
- for (let i = from; i < to; i += 1) {
1166
- output.push(leafEntryAt(leaf, i));
1167
- }
1168
- }
1169
- return writeIdx;
1170
- };
1171
- var rangeQueryEntries = (state, startKey, endKey, options) => {
1172
- const cursor = initRangeCursor(state, startKey, endKey, options);
1173
- if (cursor === null) return [];
1174
- let cursorLeaf = cursor.leaf;
1175
- let cursorIndex = cursor.index;
1176
- const { compare, upperExclusive } = cursor;
1177
- const output = allocateRangeOutput(state, cursorLeaf, cursorIndex, compare, upperExclusive, startKey, endKey, options);
1178
- let writeIdx = 0;
1179
- const useIndexed = output.length > 0;
1180
- while (cursorLeaf !== null) {
1181
- const leafCount = leafEntryCount(cursorLeaf);
1182
- if (cursorIndex >= leafCount) {
1183
- cursorLeaf = cursorLeaf.next;
1184
- cursorIndex = 0;
1185
- continue;
1186
- }
1187
- const lastEntry = leafEntryAt(cursorLeaf, leafCount - 1);
1188
- const cmpLast = compare(lastEntry.key, endKey);
1189
- if (upperExclusive ? cmpLast < 0 : cmpLast <= 0) {
1190
- writeIdx = appendLeafSlice(cursorLeaf, cursorIndex, leafCount, output, useIndexed, writeIdx);
1191
- cursorLeaf = cursorLeaf.next;
1192
- cursorIndex = 0;
1193
- continue;
1194
- }
1195
- const endSeq = upperExclusive ? 0 : Number.MAX_SAFE_INTEGER;
1196
- const endBound = upperExclusive ? lowerBoundInLeaf(state, cursorLeaf, endKey, endSeq) : upperBoundInLeaf(state, cursorLeaf, endKey, endSeq);
1197
- const limit = endBound < leafCount ? endBound : leafCount;
1198
- appendLeafSlice(cursorLeaf, cursorIndex, limit, output, useIndexed, writeIdx);
1199
- return output;
1200
- }
1201
- return output;
1202
- };
1203
-
1204
1216
  // src/btree/serialization.ts
1205
1217
  var buildConfigFromState = (state) => {
1206
1218
  const config = {
@@ -1330,7 +1342,7 @@ var compareNodeKeys = (comparator, leftKey, leftSeq, rightKey, rightSeq) => {
1330
1342
  if (cmp !== 0) {
1331
1343
  return cmp;
1332
1344
  }
1333
- return leftSeq - rightSeq;
1345
+ return leftSeq < rightSeq ? -1 : leftSeq > rightSeq ? 1 : 0;
1334
1346
  };
1335
1347
  var getNodeMaxKey = (node) => {
1336
1348
  if (isLeafNode(node)) {
@@ -1663,44 +1675,56 @@ var InMemoryBTree = class _InMemoryBTree {
1663
1675
  return putManyEntries(this.state, entries);
1664
1676
  }
1665
1677
  remove(key) {
1666
- return removeFirstMatchingEntry(this.state, key);
1678
+ const entry = removeFirstMatchingEntry(this.state, key);
1679
+ if (entry === null) return null;
1680
+ return toPublicEntry(entry);
1667
1681
  }
1668
1682
  removeById(entryId) {
1669
1683
  if (this.state.entryKeys === null) {
1670
1684
  throw new BTreeValidationError("Requires enableEntryIdLookup: true.");
1671
1685
  }
1672
- return removeEntryById(this.state, entryId);
1686
+ const entry = removeEntryById(this.state, entryId);
1687
+ if (entry === null) return null;
1688
+ return toPublicEntry(entry);
1673
1689
  }
1674
1690
  peekById(entryId) {
1675
1691
  if (this.state.entryKeys === null) {
1676
1692
  throw new BTreeValidationError("Requires enableEntryIdLookup: true.");
1677
1693
  }
1678
- return peekEntryById(this.state, entryId);
1694
+ const entry = peekEntryById(this.state, entryId);
1695
+ if (entry === null) return null;
1696
+ return toPublicEntry(entry);
1679
1697
  }
1680
1698
  updateById(entryId, value) {
1681
1699
  if (this.state.entryKeys === null) {
1682
1700
  throw new BTreeValidationError("Requires enableEntryIdLookup: true.");
1683
1701
  }
1684
- return updateEntryById(this.state, entryId, value);
1702
+ const entry = updateEntryById(this.state, entryId, value);
1703
+ if (entry === null) return null;
1704
+ return toPublicEntry(entry);
1685
1705
  }
1686
1706
  popFirst() {
1687
- return popFirstEntry(this.state);
1707
+ const entry = popFirstEntry(this.state);
1708
+ if (entry === null) return null;
1709
+ return toPublicEntry(entry);
1688
1710
  }
1689
1711
  peekFirst() {
1690
1712
  if (this.state.entryCount === 0) {
1691
1713
  return null;
1692
1714
  }
1693
- return leafEntryAt(this.state.leftmostLeaf, 0);
1715
+ return toPublicEntry(leafEntryAt(this.state.leftmostLeaf, 0));
1694
1716
  }
1695
1717
  peekLast() {
1696
1718
  if (this.state.entryCount === 0) {
1697
1719
  return null;
1698
1720
  }
1699
1721
  const leaf = this.state.rightmostLeaf;
1700
- return leafEntryAt(leaf, leafEntryCount(leaf) - 1);
1722
+ return toPublicEntry(leafEntryAt(leaf, leafEntryCount(leaf) - 1));
1701
1723
  }
1702
1724
  popLast() {
1703
- return popLastEntry(this.state);
1725
+ const entry = popLastEntry(this.state);
1726
+ if (entry === null) return null;
1727
+ return toPublicEntry(entry);
1704
1728
  }
1705
1729
  clear() {
1706
1730
  const emptyLeaf = createLeafNode([], null);
@@ -1717,8 +1741,8 @@ var InMemoryBTree = class _InMemoryBTree {
1717
1741
  const tier = computeAutoScaleTier(0);
1718
1742
  this.state.maxLeafEntries = tier.maxLeaf;
1719
1743
  this.state.maxBranchChildren = tier.maxBranch;
1720
- this.state.minLeafEntries = Math.ceil(tier.maxLeaf / 2);
1721
- this.state.minBranchChildren = Math.ceil(tier.maxBranch / 2);
1744
+ this.state.minLeafEntries = minOccupancy(tier.maxLeaf);
1745
+ this.state.minBranchChildren = minOccupancy(tier.maxBranch);
1722
1746
  this.state._nextAutoScaleThreshold = computeNextAutoScaleThreshold(0);
1723
1747
  }
1724
1748
  }
@@ -1733,39 +1757,65 @@ var InMemoryBTree = class _InMemoryBTree {
1733
1757
  findFirst(key) {
1734
1758
  const found = findFirstMatchingUserKey(this.state, key);
1735
1759
  if (found === null) return null;
1736
- return leafEntryAt(found.leaf, found.index);
1760
+ return toPublicEntry(leafEntryAt(found.leaf, found.index));
1737
1761
  }
1762
+ /**
1763
+ * Returns the last entry whose key matches `key`, or `null` if not found.
1764
+ * Useful when `duplicateKeys` is `'allow'` and multiple entries share the same key.
1765
+ */
1738
1766
  findLast(key) {
1739
1767
  const found = findLastMatchingUserKey(this.state, key);
1740
1768
  if (found === null) return null;
1741
- return leafEntryAt(found.leaf, found.index);
1769
+ return toPublicEntry(leafEntryAt(found.leaf, found.index));
1742
1770
  }
1771
+ /**
1772
+ * Returns the smallest key in the tree that is strictly greater than `key`,
1773
+ * or `null` if no such key exists.
1774
+ */
1743
1775
  nextHigherKey(key) {
1744
1776
  return findNextHigherKey(this.state, key);
1745
1777
  }
1778
+ /**
1779
+ * Returns the largest key in the tree that is strictly less than `key`,
1780
+ * or `null` if no such key exists.
1781
+ */
1746
1782
  nextLowerKey(key) {
1747
1783
  return findNextLowerKey(this.state, key);
1748
1784
  }
1785
+ /**
1786
+ * Returns the entry for `key` if it exists; otherwise returns the entry with
1787
+ * the largest key strictly less than `key`. Returns `null` when the tree is
1788
+ * empty or every key is greater than `key`.
1789
+ */
1749
1790
  getPairOrNextLower(key) {
1750
1791
  const found = findPairOrNextLower(this.state, key);
1751
1792
  if (found === null) return null;
1752
- return leafEntryAt(found.leaf, found.index);
1793
+ return toPublicEntry(leafEntryAt(found.leaf, found.index));
1753
1794
  }
1795
+ /**
1796
+ * Returns the number of entries whose keys fall within [`startKey`, `endKey`].
1797
+ * Pass `options` to make either bound exclusive.
1798
+ */
1754
1799
  count(startKey, endKey, options) {
1755
1800
  return countRangeEntries(this.state, startKey, endKey, options);
1756
1801
  }
1802
+ /**
1803
+ * Deletes all entries whose keys fall within [`startKey`, `endKey`].
1804
+ * Pass `options` to make either bound exclusive.
1805
+ * @returns The number of entries deleted.
1806
+ */
1757
1807
  deleteRange(startKey, endKey, options) {
1758
1808
  return deleteRangeEntries(this.state, startKey, endKey, options);
1759
1809
  }
1760
1810
  range(startKey, endKey, options) {
1761
- return rangeQueryEntries(this.state, startKey, endKey, options);
1811
+ return rangeQueryEntries(this.state, startKey, endKey, options).map(toPublicEntry);
1762
1812
  }
1763
1813
  *entries() {
1764
1814
  let leaf = this.state.leftmostLeaf;
1765
1815
  while (leaf !== null) {
1766
1816
  const count = leafEntryCount(leaf);
1767
1817
  for (let i = 0; i < count; i += 1) {
1768
- yield leafEntryAt(leaf, i);
1818
+ yield toPublicEntry(leafEntryAt(leaf, i));
1769
1819
  }
1770
1820
  leaf = leaf.next;
1771
1821
  }
@@ -1775,7 +1825,7 @@ var InMemoryBTree = class _InMemoryBTree {
1775
1825
  while (leaf !== null) {
1776
1826
  const count = leafEntryCount(leaf);
1777
1827
  for (let i = count - 1; i >= 0; i -= 1) {
1778
- yield leafEntryAt(leaf, i);
1828
+ yield toPublicEntry(leafEntryAt(leaf, i));
1779
1829
  }
1780
1830
  leaf = leaf.prev;
1781
1831
  }
@@ -1798,7 +1848,7 @@ var InMemoryBTree = class _InMemoryBTree {
1798
1848
  while (leaf !== null) {
1799
1849
  const count = leafEntryCount(leaf);
1800
1850
  for (let i = 0; i < count; i += 1) {
1801
- callback.call(thisArg, leafEntryAt(leaf, i));
1851
+ callback.call(thisArg, toPublicEntry(leafEntryAt(leaf, i)));
1802
1852
  }
1803
1853
  leaf = leaf.next;
1804
1854
  }
@@ -1810,12 +1860,19 @@ var InMemoryBTree = class _InMemoryBTree {
1810
1860
  while (leaf !== null) {
1811
1861
  const count = leafEntryCount(leaf);
1812
1862
  for (let i = 0; i < count; i += 1) {
1813
- result[writeIdx++] = leafEntryAt(leaf, i);
1863
+ result[writeIdx++] = toPublicEntry(leafEntryAt(leaf, i));
1814
1864
  }
1815
1865
  leaf = leaf.next;
1816
1866
  }
1817
1867
  return result;
1818
1868
  }
1869
+ /**
1870
+ * Returns a structurally independent `InMemoryBTree` with identical
1871
+ * configuration and entries. The tree structure (nodes, links, entry IDs)
1872
+ * is fully independent, but stored key and value references are shared
1873
+ * with the source tree.
1874
+ * Note: `EntryId` values are reassigned in the clone — IDs from the source tree are not valid for the clone.
1875
+ */
1819
1876
  clone() {
1820
1877
  const cloned = new _InMemoryBTree(buildConfigFromState(this.state));
1821
1878
  applyAutoScaleCapacitySnapshot(