serializable-bptree 6.0.2 → 6.1.1

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.
@@ -9,6 +9,33 @@ var ValueComparator = class {
9
9
  isHigher(value, than) {
10
10
  return this.asc(value, than) > 0;
11
11
  }
12
+ /**
13
+ * This method is used for range queries with composite values.
14
+ * By default, it calls the `asc` method, so existing code works without changes.
15
+ *
16
+ * When using composite values (e.g., `{ k: number, v: number }`),
17
+ * override this method to compare only the primary sorting field (e.g., `v`),
18
+ * ignoring the unique identifier field (e.g., `k`).
19
+ *
20
+ * This enables efficient range queries like `primaryEqual` that find all entries
21
+ * with the same primary value regardless of their unique identifiers.
22
+ *
23
+ * @param a Value a.
24
+ * @param b Value b.
25
+ * @returns Negative if a < b, 0 if equal, positive if a > b (based on primary field only).
26
+ */
27
+ primaryAsc(a, b) {
28
+ return this.asc(a, b);
29
+ }
30
+ isPrimarySame(value, than) {
31
+ return this.primaryAsc(value, than) === 0;
32
+ }
33
+ isPrimaryLower(value, than) {
34
+ return this.primaryAsc(value, than) < 0;
35
+ }
36
+ isPrimaryHigher(value, than) {
37
+ return this.primaryAsc(value, than) > 0;
38
+ }
12
39
  };
13
40
  var NumericComparator = class extends ValueComparator {
14
41
  asc(a, b) {
@@ -424,6 +451,13 @@ var BPTree = class {
424
451
  equal: (nv, v) => this.comparator.isSame(nv, v),
425
452
  notEqual: (nv, v) => this.comparator.isSame(nv, v) === false,
426
453
  or: (nv, v) => this.ensureValues(v).some((v2) => this.comparator.isSame(nv, v2)),
454
+ primaryGt: (nv, v) => this.comparator.isPrimaryHigher(nv, v),
455
+ primaryGte: (nv, v) => this.comparator.isPrimaryHigher(nv, v) || this.comparator.isPrimarySame(nv, v),
456
+ primaryLt: (nv, v) => this.comparator.isPrimaryLower(nv, v),
457
+ primaryLte: (nv, v) => this.comparator.isPrimaryLower(nv, v) || this.comparator.isPrimarySame(nv, v),
458
+ primaryEqual: (nv, v) => this.comparator.isPrimarySame(nv, v),
459
+ primaryNotEqual: (nv, v) => this.comparator.isPrimarySame(nv, v) === false,
460
+ primaryOr: (nv, v) => this.ensureValues(v).some((v2) => this.comparator.isPrimarySame(nv, v2)),
427
461
  like: (nv, v) => {
428
462
  const nodeValue = this.comparator.match(nv);
429
463
  const value = this.comparator.match(v);
@@ -440,6 +474,13 @@ var BPTree = class {
440
474
  equal: (v) => this.insertableNode(v),
441
475
  notEqual: (v) => this.leftestNode(),
442
476
  or: (v) => this.insertableNode(this.lowestValue(this.ensureValues(v))),
477
+ primaryGt: (v) => this.insertableNodeByPrimary(v),
478
+ primaryGte: (v) => this.insertableNodeByPrimary(v),
479
+ primaryLt: (v) => this.insertableNodeByPrimary(v),
480
+ primaryLte: (v) => this.insertableRightestNodeByPrimary(v),
481
+ primaryEqual: (v) => this.insertableNodeByPrimary(v),
482
+ primaryNotEqual: (v) => this.leftestNode(),
483
+ primaryOr: (v) => this.insertableNodeByPrimary(this.lowestPrimaryValue(this.ensureValues(v))),
443
484
  like: (v) => this.leftestNode()
444
485
  };
445
486
  verifierEndNode = {
@@ -453,6 +494,15 @@ var BPTree = class {
453
494
  this.highestValue(this.ensureValues(v)),
454
495
  this.verifierDirection.or
455
496
  ),
497
+ primaryGt: (v) => null,
498
+ primaryGte: (v) => null,
499
+ primaryLt: (v) => null,
500
+ primaryLte: (v) => null,
501
+ primaryEqual: (v) => null,
502
+ primaryNotEqual: (v) => null,
503
+ primaryOr: (v) => this.insertableRightestEndNodeByPrimary(
504
+ this.highestPrimaryValue(this.ensureValues(v))
505
+ ),
456
506
  like: (v) => null
457
507
  };
458
508
  verifierDirection = {
@@ -463,8 +513,37 @@ var BPTree = class {
463
513
  equal: 1,
464
514
  notEqual: 1,
465
515
  or: 1,
516
+ primaryGt: 1,
517
+ primaryGte: 1,
518
+ primaryLt: -1,
519
+ primaryLte: -1,
520
+ primaryEqual: 1,
521
+ primaryNotEqual: 1,
522
+ primaryOr: 1,
466
523
  like: 1
467
524
  };
525
+ /**
526
+ * Determines whether early termination is allowed for each condition.
527
+ * When true, the search will stop once a match is found and then a non-match is encountered.
528
+ * Only applicable for conditions that guarantee contiguous matches in a sorted B+Tree.
529
+ */
530
+ verifierEarlyTerminate = {
531
+ gt: false,
532
+ gte: false,
533
+ lt: false,
534
+ lte: false,
535
+ equal: true,
536
+ notEqual: false,
537
+ or: false,
538
+ primaryGt: false,
539
+ primaryGte: false,
540
+ primaryLt: false,
541
+ primaryLte: false,
542
+ primaryEqual: true,
543
+ primaryNotEqual: false,
544
+ primaryOr: false,
545
+ like: false
546
+ };
468
547
  constructor(strategy, comparator, option) {
469
548
  this.strategy = strategy;
470
549
  this.comparator = comparator;
@@ -498,6 +577,14 @@ var BPTree = class {
498
577
  const i = v.length - 1;
499
578
  return [...v].sort((a, b) => this.comparator.asc(a, b))[i];
500
579
  }
580
+ lowestPrimaryValue(v) {
581
+ const i = 0;
582
+ return [...v].sort((a, b) => this.comparator.primaryAsc(a, b))[i];
583
+ }
584
+ highestPrimaryValue(v) {
585
+ const i = v.length - 1;
586
+ return [...v].sort((a, b) => this.comparator.primaryAsc(a, b))[i];
587
+ }
501
588
  _insertAtLeaf(node, key, value) {
502
589
  if (node.values.length) {
503
590
  for (let i = 0, len = node.values.length; i < len; i++) {
@@ -577,10 +664,11 @@ var BPTreeSync = class extends BPTree {
577
664
  capacity: this.option.capacity ?? 1e3
578
665
  });
579
666
  }
580
- getPairsRightToLeft(value, startNode, endNode, comparator) {
667
+ getPairsRightToLeft(value, startNode, endNode, comparator, earlyTerminate) {
581
668
  const pairs = [];
582
669
  let node = startNode;
583
670
  let done = false;
671
+ let hasMatched = false;
584
672
  while (!done) {
585
673
  if (endNode && node.id === endNode.id) {
586
674
  done = true;
@@ -591,12 +679,17 @@ var BPTreeSync = class extends BPTree {
591
679
  const nValue = node.values[i];
592
680
  const keys = node.keys[i];
593
681
  if (comparator(nValue, value)) {
682
+ hasMatched = true;
594
683
  let j = keys.length;
595
684
  while (j--) {
596
685
  pairs.push([keys[j], nValue]);
597
686
  }
687
+ } else if (earlyTerminate && hasMatched) {
688
+ done = true;
689
+ break;
598
690
  }
599
691
  }
692
+ if (done) break;
600
693
  if (!node.prev) {
601
694
  done = true;
602
695
  break;
@@ -605,10 +698,11 @@ var BPTreeSync = class extends BPTree {
605
698
  }
606
699
  return new Map(pairs.reverse());
607
700
  }
608
- getPairsLeftToRight(value, startNode, endNode, comparator) {
701
+ getPairsLeftToRight(value, startNode, endNode, comparator, earlyTerminate) {
609
702
  const pairs = [];
610
703
  let node = startNode;
611
704
  let done = false;
705
+ let hasMatched = false;
612
706
  while (!done) {
613
707
  if (endNode && node.id === endNode.id) {
614
708
  done = true;
@@ -618,12 +712,17 @@ var BPTreeSync = class extends BPTree {
618
712
  const nValue = node.values[i];
619
713
  const keys = node.keys[i];
620
714
  if (comparator(nValue, value)) {
715
+ hasMatched = true;
621
716
  for (let j = 0, len2 = keys.length; j < len2; j++) {
622
717
  const key = keys[j];
623
718
  pairs.push([key, nValue]);
624
719
  }
720
+ } else if (earlyTerminate && hasMatched) {
721
+ done = true;
722
+ break;
625
723
  }
626
724
  }
725
+ if (done) break;
627
726
  if (!node.next) {
628
727
  done = true;
629
728
  break;
@@ -632,12 +731,12 @@ var BPTreeSync = class extends BPTree {
632
731
  }
633
732
  return new Map(pairs);
634
733
  }
635
- getPairs(value, startNode, endNode, comparator, direction) {
734
+ getPairs(value, startNode, endNode, comparator, direction, earlyTerminate) {
636
735
  switch (direction) {
637
736
  case -1:
638
- return this.getPairsRightToLeft(value, startNode, endNode, comparator);
737
+ return this.getPairsRightToLeft(value, startNode, endNode, comparator, earlyTerminate);
639
738
  case 1:
640
- return this.getPairsLeftToRight(value, startNode, endNode, comparator);
739
+ return this.getPairsLeftToRight(value, startNode, endNode, comparator, earlyTerminate);
641
740
  default:
642
741
  throw new Error(`Direction must be -1 or 1. but got a ${direction}`);
643
742
  }
@@ -986,6 +1085,55 @@ var BPTreeSync = class extends BPTree {
986
1085
  }
987
1086
  return node;
988
1087
  }
1088
+ /**
1089
+ * Find the insertable node using primaryAsc comparison.
1090
+ * This allows finding nodes by primary value only, ignoring unique identifiers.
1091
+ */
1092
+ insertableNodeByPrimary(value) {
1093
+ let node = this.getNode(this.root.id);
1094
+ while (!node.leaf) {
1095
+ for (let i = 0, len = node.values.length; i < len; i++) {
1096
+ const nValue = node.values[i];
1097
+ const k = node.keys;
1098
+ if (this.comparator.isPrimarySame(value, nValue)) {
1099
+ node = this.getNode(k[i]);
1100
+ break;
1101
+ } else if (this.comparator.isPrimaryLower(value, nValue)) {
1102
+ node = this.getNode(k[i]);
1103
+ break;
1104
+ } else if (i + 1 === node.values.length) {
1105
+ node = this.getNode(k[i + 1]);
1106
+ break;
1107
+ }
1108
+ }
1109
+ }
1110
+ return node;
1111
+ }
1112
+ insertableRightestNodeByPrimary(value) {
1113
+ let node = this.getNode(this.root.id);
1114
+ while (!node.leaf) {
1115
+ for (let i = 0, len = node.values.length; i < len; i++) {
1116
+ const nValue = node.values[i];
1117
+ const k = node.keys;
1118
+ if (this.comparator.isPrimaryLower(value, nValue)) {
1119
+ node = this.getNode(k[i]);
1120
+ break;
1121
+ }
1122
+ if (i + 1 === node.values.length) {
1123
+ node = this.getNode(k[i + 1]);
1124
+ break;
1125
+ }
1126
+ }
1127
+ }
1128
+ return node;
1129
+ }
1130
+ insertableRightestEndNodeByPrimary(value) {
1131
+ const node = this.insertableRightestNodeByPrimary(value);
1132
+ if (!node.next) {
1133
+ return null;
1134
+ }
1135
+ return this.getNode(node.next);
1136
+ }
989
1137
  insertableEndNode(value, direction) {
990
1138
  const insertableNode = this.insertableNode(value);
991
1139
  let key;
@@ -1054,7 +1202,8 @@ var BPTreeSync = class extends BPTree {
1054
1202
  const endNode = this.verifierEndNode[key](value);
1055
1203
  const direction = this.verifierDirection[key];
1056
1204
  const comparator = this.verifierMap[key];
1057
- const pairs = this.getPairs(value, startNode, endNode, comparator, direction);
1205
+ const earlyTerminate = this.verifierEarlyTerminate[key];
1206
+ const pairs = this.getPairs(value, startNode, endNode, comparator, direction, earlyTerminate);
1058
1207
  if (!filterValues) {
1059
1208
  filterValues = new Set(pairs.keys());
1060
1209
  } else {
@@ -1079,7 +1228,8 @@ var BPTreeSync = class extends BPTree {
1079
1228
  const endNode = this.verifierEndNode[key](value);
1080
1229
  const direction = this.verifierDirection[key];
1081
1230
  const comparator = this.verifierMap[key];
1082
- const pairs = this.getPairs(value, startNode, endNode, comparator, direction);
1231
+ const earlyTerminate = this.verifierEarlyTerminate[key];
1232
+ const pairs = this.getPairs(value, startNode, endNode, comparator, direction, earlyTerminate);
1083
1233
  if (result === null) {
1084
1234
  result = pairs;
1085
1235
  } else {
@@ -1467,10 +1617,11 @@ var BPTreeAsync = class extends BPTree {
1467
1617
  this.lock.writeUnlock(lockId);
1468
1618
  });
1469
1619
  }
1470
- async getPairsRightToLeft(value, startNode, endNode, comparator) {
1620
+ async getPairsRightToLeft(value, startNode, endNode, comparator, earlyTerminate) {
1471
1621
  const pairs = [];
1472
1622
  let node = startNode;
1473
1623
  let done = false;
1624
+ let hasMatched = false;
1474
1625
  while (!done) {
1475
1626
  if (endNode && node.id === endNode.id) {
1476
1627
  done = true;
@@ -1481,12 +1632,17 @@ var BPTreeAsync = class extends BPTree {
1481
1632
  const nValue = node.values[i];
1482
1633
  const keys = node.keys[i];
1483
1634
  if (comparator(nValue, value)) {
1635
+ hasMatched = true;
1484
1636
  let j = keys.length;
1485
1637
  while (j--) {
1486
1638
  pairs.push([keys[j], nValue]);
1487
1639
  }
1640
+ } else if (earlyTerminate && hasMatched) {
1641
+ done = true;
1642
+ break;
1488
1643
  }
1489
1644
  }
1645
+ if (done) break;
1490
1646
  if (!node.prev) {
1491
1647
  done = true;
1492
1648
  break;
@@ -1495,10 +1651,11 @@ var BPTreeAsync = class extends BPTree {
1495
1651
  }
1496
1652
  return new Map(pairs.reverse());
1497
1653
  }
1498
- async getPairsLeftToRight(value, startNode, endNode, comparator) {
1654
+ async getPairsLeftToRight(value, startNode, endNode, comparator, earlyTerminate) {
1499
1655
  const pairs = [];
1500
1656
  let node = startNode;
1501
1657
  let done = false;
1658
+ let hasMatched = false;
1502
1659
  while (!done) {
1503
1660
  if (endNode && node.id === endNode.id) {
1504
1661
  done = true;
@@ -1508,12 +1665,17 @@ var BPTreeAsync = class extends BPTree {
1508
1665
  const nValue = node.values[i];
1509
1666
  const keys = node.keys[i];
1510
1667
  if (comparator(nValue, value)) {
1668
+ hasMatched = true;
1511
1669
  for (let j = 0, len2 = keys.length; j < len2; j++) {
1512
1670
  const key = keys[j];
1513
1671
  pairs.push([key, nValue]);
1514
1672
  }
1673
+ } else if (earlyTerminate && hasMatched) {
1674
+ done = true;
1675
+ break;
1515
1676
  }
1516
1677
  }
1678
+ if (done) break;
1517
1679
  if (!node.next) {
1518
1680
  done = true;
1519
1681
  break;
@@ -1522,12 +1684,12 @@ var BPTreeAsync = class extends BPTree {
1522
1684
  }
1523
1685
  return new Map(pairs);
1524
1686
  }
1525
- async getPairs(value, startNode, endNode, comparator, direction) {
1687
+ async getPairs(value, startNode, endNode, comparator, direction, earlyTerminate) {
1526
1688
  switch (direction) {
1527
1689
  case -1:
1528
- return await this.getPairsRightToLeft(value, startNode, endNode, comparator);
1690
+ return await this.getPairsRightToLeft(value, startNode, endNode, comparator, earlyTerminate);
1529
1691
  case 1:
1530
- return await this.getPairsLeftToRight(value, startNode, endNode, comparator);
1692
+ return await this.getPairsLeftToRight(value, startNode, endNode, comparator, earlyTerminate);
1531
1693
  default:
1532
1694
  throw new Error(`Direction must be -1 or 1. but got a ${direction}`);
1533
1695
  }
@@ -1876,6 +2038,55 @@ var BPTreeAsync = class extends BPTree {
1876
2038
  }
1877
2039
  return node;
1878
2040
  }
2041
+ /**
2042
+ * Find the insertable node using primaryAsc comparison.
2043
+ * This allows finding nodes by primary value only, ignoring unique identifiers.
2044
+ */
2045
+ async insertableNodeByPrimary(value) {
2046
+ let node = await this.getNode(this.root.id);
2047
+ while (!node.leaf) {
2048
+ for (let i = 0, len = node.values.length; i < len; i++) {
2049
+ const nValue = node.values[i];
2050
+ const k = node.keys;
2051
+ if (this.comparator.isPrimarySame(value, nValue)) {
2052
+ node = await this.getNode(k[i]);
2053
+ break;
2054
+ } else if (this.comparator.isPrimaryLower(value, nValue)) {
2055
+ node = await this.getNode(k[i]);
2056
+ break;
2057
+ } else if (i + 1 === node.values.length) {
2058
+ node = await this.getNode(k[i + 1]);
2059
+ break;
2060
+ }
2061
+ }
2062
+ }
2063
+ return node;
2064
+ }
2065
+ async insertableRightestNodeByPrimary(value) {
2066
+ let node = await this.getNode(this.root.id);
2067
+ while (!node.leaf) {
2068
+ for (let i = 0, len = node.values.length; i < len; i++) {
2069
+ const nValue = node.values[i];
2070
+ const k = node.keys;
2071
+ if (this.comparator.isPrimaryLower(value, nValue)) {
2072
+ node = await this.getNode(k[i]);
2073
+ break;
2074
+ }
2075
+ if (i + 1 === node.values.length) {
2076
+ node = await this.getNode(k[i + 1]);
2077
+ break;
2078
+ }
2079
+ }
2080
+ }
2081
+ return node;
2082
+ }
2083
+ async insertableRightestEndNodeByPrimary(value) {
2084
+ const node = await this.insertableRightestNodeByPrimary(value);
2085
+ if (!node.next) {
2086
+ return null;
2087
+ }
2088
+ return await this.getNode(node.next);
2089
+ }
1879
2090
  async insertableEndNode(value, direction) {
1880
2091
  const insertableNode = await this.insertableNode(value);
1881
2092
  let key;
@@ -1945,7 +2156,8 @@ var BPTreeAsync = class extends BPTree {
1945
2156
  const endNode = await this.verifierEndNode[key](value);
1946
2157
  const direction = this.verifierDirection[key];
1947
2158
  const comparator = this.verifierMap[key];
1948
- const pairs = await this.getPairs(value, startNode, endNode, comparator, direction);
2159
+ const earlyTerminate = this.verifierEarlyTerminate[key];
2160
+ const pairs = await this.getPairs(value, startNode, endNode, comparator, direction, earlyTerminate);
1949
2161
  if (!filterValues) {
1950
2162
  filterValues = new Set(pairs.keys());
1951
2163
  } else {
@@ -1972,7 +2184,8 @@ var BPTreeAsync = class extends BPTree {
1972
2184
  const endNode = await this.verifierEndNode[key](value);
1973
2185
  const direction = this.verifierDirection[key];
1974
2186
  const comparator = this.verifierMap[key];
1975
- const pairs = await this.getPairs(value, startNode, endNode, comparator, direction);
2187
+ const earlyTerminate = this.verifierEarlyTerminate[key];
2188
+ const pairs = await this.getPairs(value, startNode, endNode, comparator, direction, earlyTerminate);
1976
2189
  if (result === null) {
1977
2190
  result = pairs;
1978
2191
  } else {
@@ -10,9 +10,9 @@ export declare class BPTreeAsync<K, V> extends BPTree<K, V> {
10
10
  private _createCachedNode;
11
11
  protected readLock<T>(callback: () => Promise<T>): Promise<T>;
12
12
  protected writeLock<T>(callback: () => Promise<T>): Promise<T>;
13
- protected getPairsRightToLeft(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean): Promise<BPTreePair<K, V>>;
14
- protected getPairsLeftToRight(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean): Promise<BPTreePair<K, V>>;
15
- protected getPairs(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean, direction: 1 | -1): Promise<BPTreePair<K, V>>;
13
+ protected getPairsRightToLeft(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean, earlyTerminate: boolean): Promise<BPTreePair<K, V>>;
14
+ protected getPairsLeftToRight(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean, earlyTerminate: boolean): Promise<BPTreePair<K, V>>;
15
+ protected getPairs(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean, direction: 1 | -1, earlyTerminate: boolean): Promise<BPTreePair<K, V>>;
16
16
  protected _createNodeId(isLeaf: boolean): Promise<string>;
17
17
  protected _createNode(isLeaf: boolean, keys: string[] | K[][], values: V[], leaf?: boolean, parent?: string | null, next?: string | null, prev?: string | null): Promise<BPTreeUnknownNode<K, V>>;
18
18
  protected _deleteEntry(node: BPTreeUnknownNode<K, V>, key: BPTreeNodeKey<K>, value: V): Promise<void>;
@@ -20,6 +20,13 @@ export declare class BPTreeAsync<K, V> extends BPTree<K, V> {
20
20
  init(): Promise<void>;
21
21
  protected getNode(id: string): Promise<BPTreeUnknownNode<K, V>>;
22
22
  protected insertableNode(value: V): Promise<BPTreeLeafNode<K, V>>;
23
+ /**
24
+ * Find the insertable node using primaryAsc comparison.
25
+ * This allows finding nodes by primary value only, ignoring unique identifiers.
26
+ */
27
+ protected insertableNodeByPrimary(value: V): Promise<BPTreeLeafNode<K, V>>;
28
+ protected insertableRightestNodeByPrimary(value: V): Promise<BPTreeLeafNode<K, V>>;
29
+ protected insertableRightestEndNodeByPrimary(value: V): Promise<BPTreeLeafNode<K, V> | null>;
23
30
  protected insertableEndNode(value: V, direction: 1 | -1): Promise<BPTreeLeafNode<K, V> | null>;
24
31
  protected leftestNode(): Promise<BPTreeLeafNode<K, V>>;
25
32
  protected rightestNode(): Promise<BPTreeLeafNode<K, V>>;
@@ -7,9 +7,9 @@ export declare class BPTreeSync<K, V> extends BPTree<K, V> {
7
7
  protected readonly nodes: ReturnType<typeof this._createCachedNode>;
8
8
  constructor(strategy: SerializeStrategySync<K, V>, comparator: ValueComparator<V>, option?: BPTreeConstructorOption);
9
9
  private _createCachedNode;
10
- protected getPairsRightToLeft(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean): BPTreePair<K, V>;
11
- protected getPairsLeftToRight(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean): BPTreePair<K, V>;
12
- protected getPairs(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean, direction: 1 | -1): BPTreePair<K, V>;
10
+ protected getPairsRightToLeft(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean, earlyTerminate: boolean): BPTreePair<K, V>;
11
+ protected getPairsLeftToRight(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean, earlyTerminate: boolean): BPTreePair<K, V>;
12
+ protected getPairs(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean, direction: 1 | -1, earlyTerminate: boolean): BPTreePair<K, V>;
13
13
  protected _createNodeId(isLeaf: boolean): string;
14
14
  protected _createNode(isLeaf: boolean, keys: string[] | K[][], values: V[], leaf?: boolean, parent?: string | null, next?: string | null, prev?: string | null): BPTreeUnknownNode<K, V>;
15
15
  protected _deleteEntry(node: BPTreeUnknownNode<K, V>, key: BPTreeNodeKey<K>, value: V): void;
@@ -17,6 +17,13 @@ export declare class BPTreeSync<K, V> extends BPTree<K, V> {
17
17
  init(): void;
18
18
  protected getNode(id: string): BPTreeUnknownNode<K, V>;
19
19
  protected insertableNode(value: V): BPTreeLeafNode<K, V>;
20
+ /**
21
+ * Find the insertable node using primaryAsc comparison.
22
+ * This allows finding nodes by primary value only, ignoring unique identifiers.
23
+ */
24
+ protected insertableNodeByPrimary(value: V): BPTreeLeafNode<K, V>;
25
+ protected insertableRightestNodeByPrimary(value: V): BPTreeLeafNode<K, V>;
26
+ protected insertableRightestEndNodeByPrimary(value: V): BPTreeLeafNode<K, V> | null;
20
27
  protected insertableEndNode(value: V, direction: 1 | -1): BPTreeLeafNode<K, V> | null;
21
28
  protected leftestNode(): BPTreeLeafNode<K, V>;
22
29
  protected rightestNode(): BPTreeLeafNode<K, V>;
@@ -22,6 +22,24 @@ export type BPTreeCondition<V> = Partial<{
22
22
  or: Partial<V>[];
23
23
  /** Searches for values matching the given pattern. '%' matches zero or more characters, and '_' matches exactly one character. */
24
24
  like: Partial<V>;
25
+ /**
26
+ * Searches for pairs where the primary field equals the given value.
27
+ * Uses `primaryAsc` method for comparison, which compares only the primary sorting field.
28
+ * Useful for composite values where you want to find all entries with the same primary value.
29
+ */
30
+ primaryEqual: Partial<V>;
31
+ /** Searches for pairs where the primary field is greater than the given value. */
32
+ primaryGt: Partial<V>;
33
+ /** Searches for pairs where the primary field is greater than or equal to the given value. */
34
+ primaryGte: Partial<V>;
35
+ /** Searches for pairs where the primary field is less than the given value. */
36
+ primaryLt: Partial<V>;
37
+ /** Searches for pairs where the primary field is less than or equal to the given value. */
38
+ primaryLte: Partial<V>;
39
+ /** Searches for pairs where the primary field is not equal to the given value. */
40
+ primaryNotEqual: Partial<V>;
41
+ /** Searches for pairs where the primary field matches at least one of the given values. */
42
+ primaryOr: Partial<V>[];
25
43
  }>;
26
44
  export type BPTreePair<K, V> = Map<K, V>;
27
45
  export type BPTreeUnknownNode<K, V> = BPTreeInternalNode<K, V> | BPTreeLeafNode<K, V>;
@@ -66,17 +84,26 @@ export declare abstract class BPTree<K, V> {
66
84
  protected readonly verifierStartNode: Record<keyof BPTreeCondition<V>, (value: V) => Deferred<BPTreeLeafNode<K, V>>>;
67
85
  protected readonly verifierEndNode: Record<keyof BPTreeCondition<V>, (value: V) => Deferred<BPTreeLeafNode<K, V> | null>>;
68
86
  protected readonly verifierDirection: Record<keyof BPTreeCondition<V>, -1 | 1>;
87
+ /**
88
+ * Determines whether early termination is allowed for each condition.
89
+ * When true, the search will stop once a match is found and then a non-match is encountered.
90
+ * Only applicable for conditions that guarantee contiguous matches in a sorted B+Tree.
91
+ */
92
+ protected readonly verifierEarlyTerminate: Record<keyof BPTreeCondition<V>, boolean>;
69
93
  protected constructor(strategy: SerializeStrategy<K, V>, comparator: ValueComparator<V>, option?: BPTreeConstructorOption);
70
94
  private _createCachedRegexp;
71
- protected abstract getPairsRightToLeft(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean): Deferred<BPTreePair<K, V>>;
72
- protected abstract getPairsLeftToRight(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean): Deferred<BPTreePair<K, V>>;
73
- protected abstract getPairs(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean, direction: -1 | 1): Deferred<BPTreePair<K, V>>;
95
+ protected abstract getPairsRightToLeft(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean, earlyTerminate: boolean): Deferred<BPTreePair<K, V>>;
96
+ protected abstract getPairsLeftToRight(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean, earlyTerminate: boolean): Deferred<BPTreePair<K, V>>;
97
+ protected abstract getPairs(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean, direction: -1 | 1, earlyTerminate: boolean): Deferred<BPTreePair<K, V>>;
74
98
  protected abstract _createNodeId(isLeaf: boolean): Deferred<string>;
75
99
  protected abstract _createNode(isLeaf: boolean, keys: string[] | K[][], values: V[], leaf?: boolean, parent?: string | null, next?: string | null, prev?: string | null): Deferred<BPTreeUnknownNode<K, V>>;
76
100
  protected abstract _deleteEntry(node: BPTreeUnknownNode<K, V>, key: BPTreeNodeKey<K>, value: V): Deferred<void>;
77
101
  protected abstract _insertInParent(node: BPTreeUnknownNode<K, V>, value: V, pointer: BPTreeUnknownNode<K, V>): Deferred<void>;
78
102
  protected abstract getNode(id: string): Deferred<BPTreeUnknownNode<K, V>>;
79
103
  protected abstract insertableNode(value: V): Deferred<BPTreeLeafNode<K, V>>;
104
+ protected abstract insertableNodeByPrimary(value: V): Deferred<BPTreeLeafNode<K, V>>;
105
+ protected abstract insertableRightestNodeByPrimary(value: V): Deferred<BPTreeLeafNode<K, V>>;
106
+ protected abstract insertableRightestEndNodeByPrimary(value: V): Deferred<BPTreeLeafNode<K, V> | null>;
80
107
  protected abstract insertableEndNode(value: V, direction: 1 | -1): Deferred<BPTreeLeafNode<K, V> | null>;
81
108
  protected abstract leftestNode(): Deferred<BPTreeLeafNode<K, V>>;
82
109
  protected abstract rightestNode(): Deferred<BPTreeLeafNode<K, V>>;
@@ -140,6 +167,8 @@ export declare abstract class BPTree<K, V> {
140
167
  protected ensureValues(v: V | V[]): V[];
141
168
  protected lowestValue(v: V[]): V;
142
169
  protected highestValue(v: V[]): V;
170
+ protected lowestPrimaryValue(v: V[]): V;
171
+ protected highestPrimaryValue(v: V[]): V;
143
172
  protected _insertAtLeaf(node: BPTreeLeafNode<K, V>, key: K, value: V): void;
144
173
  protected bufferForNodeCreate(node: BPTreeUnknownNode<K, V>): void;
145
174
  protected bufferForNodeUpdate(node: BPTreeUnknownNode<K, V>): void;
@@ -45,6 +45,25 @@ export declare abstract class ValueComparator<V> {
45
45
  isLower(value: V, than: V): boolean;
46
46
  isSame(value: V, than: V): boolean;
47
47
  isHigher(value: V, than: V): boolean;
48
+ /**
49
+ * This method is used for range queries with composite values.
50
+ * By default, it calls the `asc` method, so existing code works without changes.
51
+ *
52
+ * When using composite values (e.g., `{ k: number, v: number }`),
53
+ * override this method to compare only the primary sorting field (e.g., `v`),
54
+ * ignoring the unique identifier field (e.g., `k`).
55
+ *
56
+ * This enables efficient range queries like `primaryEqual` that find all entries
57
+ * with the same primary value regardless of their unique identifiers.
58
+ *
59
+ * @param a Value a.
60
+ * @param b Value b.
61
+ * @returns Negative if a < b, 0 if equal, positive if a > b (based on primary field only).
62
+ */
63
+ primaryAsc(a: V, b: V): number;
64
+ isPrimarySame(value: V, than: V): boolean;
65
+ isPrimaryLower(value: V, than: V): boolean;
66
+ isPrimaryHigher(value: V, than: V): boolean;
48
67
  }
49
68
  export declare class NumericComparator extends ValueComparator<number> {
50
69
  asc(a: number, b: number): number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "serializable-bptree",
3
- "version": "6.0.2",
3
+ "version": "6.1.1",
4
4
  "description": "Store the B+tree flexibly, not only in-memory.",
5
5
  "types": "./dist/types/index.d.ts",
6
6
  "main": "./dist/cjs/index.cjs",