dataply 0.0.24-alpha.9 → 0.0.25-alpha.0

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/cjs/index.js CHANGED
@@ -126,39 +126,125 @@ var StringComparator = class extends ValueComparator {
126
126
  var MVCCStrategy = class {
127
127
  };
128
128
  var LRUMap = class {
129
- cache = /* @__PURE__ */ new Map();
130
129
  capacity;
130
+ map;
131
+ head = null;
132
+ tail = null;
133
+ /**
134
+ * Creates an instance of LRUMap.
135
+ * @param capacity The maximum number of items the cache can hold.
136
+ */
131
137
  constructor(capacity) {
132
138
  this.capacity = capacity;
139
+ this.map = /* @__PURE__ */ new Map();
133
140
  }
134
- get(key) {
135
- if (!this.cache.has(key)) return void 0;
136
- const value = this.cache.get(key);
137
- this.cache.delete(key);
138
- this.cache.set(key, value);
139
- return value;
141
+ /**
142
+ * Promotes a node to the head of the linked list (marks as most recently used).
143
+ * @param node The node to promote.
144
+ */
145
+ promote(node) {
146
+ this.extract(node);
147
+ this.prepend(node);
148
+ }
149
+ /**
150
+ * Disconnects a node from the doubly linked list.
151
+ * @param node The node to extract.
152
+ */
153
+ extract(node) {
154
+ if (node.prev) node.prev.next = node.next;
155
+ else this.head = node.next;
156
+ if (node.next) node.next.prev = node.prev;
157
+ else this.tail = node.prev;
158
+ node.prev = null;
159
+ node.next = null;
160
+ }
161
+ /**
162
+ * Inserts a node at the head of the doubly linked list.
163
+ * @param node The node to prepend.
164
+ */
165
+ prepend(node) {
166
+ node.next = this.head;
167
+ if (this.head) this.head.prev = node;
168
+ this.head = node;
169
+ if (!this.tail) this.tail = node;
140
170
  }
171
+ /**
172
+ * Stores or updates a value by key.
173
+ * If the capacity is exceeded, the least recently used item (tail) is removed.
174
+ * @param key The key to store.
175
+ * @param value The value to store.
176
+ */
141
177
  set(key, value) {
142
- if (this.cache.has(key)) {
143
- this.cache.delete(key);
144
- } else if (this.cache.size >= this.capacity) {
145
- const oldestKey = this.cache.keys().next().value;
146
- if (oldestKey !== void 0) this.cache.delete(oldestKey);
178
+ const existing = this.map.get(key);
179
+ if (existing) {
180
+ existing.value = value;
181
+ this.promote(existing);
182
+ return;
147
183
  }
148
- this.cache.set(key, value);
149
- return this;
184
+ const newNode = { key, value, prev: null, next: null };
185
+ this.map.set(key, newNode);
186
+ this.prepend(newNode);
187
+ if (this.map.size > this.capacity && this.tail) {
188
+ this.map.delete(this.tail.key);
189
+ this.extract(this.tail);
190
+ }
191
+ }
192
+ /**
193
+ * Retrieves a value by key.
194
+ * Accessing the item moves it to the "most recently used" position.
195
+ * @param key The key to look for.
196
+ * @returns The value associated with the key, or undefined if not found.
197
+ */
198
+ get(key) {
199
+ const node = this.map.get(key);
200
+ if (!node) return void 0;
201
+ this.promote(node);
202
+ return node.value;
150
203
  }
204
+ /**
205
+ * Checks if a key exists in the cache without changing its access order.
206
+ * @param key The key to check.
207
+ * @returns True if the key exists, false otherwise.
208
+ */
151
209
  has(key) {
152
- return this.cache.has(key);
210
+ return this.map.has(key);
153
211
  }
212
+ /**
213
+ * Removes a key and its associated value from the cache.
214
+ * @param key The key to remove.
215
+ * @returns True if the key was found and removed, false otherwise.
216
+ */
154
217
  delete(key) {
155
- return this.cache.delete(key);
218
+ const node = this.map.get(key);
219
+ if (!node) return false;
220
+ this.extract(node);
221
+ this.map.delete(key);
222
+ return true;
156
223
  }
157
- clear() {
158
- this.cache.clear();
224
+ /**
225
+ * Returns an iterator of keys in the order of most recently used to least recently used.
226
+ * @returns An iterable iterator of keys.
227
+ */
228
+ *keys() {
229
+ let current = this.head;
230
+ while (current) {
231
+ yield current.key;
232
+ current = current.next;
233
+ }
159
234
  }
235
+ /**
236
+ * Returns the current number of items in the cache.
237
+ */
160
238
  get size() {
161
- return this.cache.size;
239
+ return this.map.size;
240
+ }
241
+ /**
242
+ * Clears all items from the cache.
243
+ */
244
+ clear() {
245
+ this.map.clear();
246
+ this.head = null;
247
+ this.tail = null;
162
248
  }
163
249
  };
164
250
  var MVCCTransaction = class {
@@ -1552,28 +1638,28 @@ var BPTreeTransaction = class _BPTreeTransaction {
1552
1638
  searchConfigs = {
1553
1639
  gt: {
1554
1640
  asc: {
1555
- start: (tx, v) => tx.insertableNodeByPrimary(v[0]),
1641
+ start: (tx, v) => tx.findUpperBoundLeaf(v[0]),
1556
1642
  end: () => null,
1557
1643
  direction: 1,
1558
1644
  earlyTerminate: false
1559
1645
  },
1560
1646
  desc: {
1561
1647
  start: (tx) => tx.rightestNode(),
1562
- end: (tx, v) => tx.insertableEndNode(v[0], -1),
1648
+ end: (tx, v) => tx.findOuterBoundaryLeaf(v[0], -1),
1563
1649
  direction: -1,
1564
1650
  earlyTerminate: true
1565
1651
  }
1566
1652
  },
1567
1653
  gte: {
1568
1654
  asc: {
1569
- start: (tx, v) => tx.insertableNodeByPrimary(v[0]),
1655
+ start: (tx, v) => tx.findLowerBoundLeaf(v[0]),
1570
1656
  end: () => null,
1571
1657
  direction: 1,
1572
1658
  earlyTerminate: false
1573
1659
  },
1574
1660
  desc: {
1575
1661
  start: (tx) => tx.rightestNode(),
1576
- end: (tx, v) => tx.insertableEndNode(v[0], -1),
1662
+ end: (tx, v) => tx.findOuterBoundaryLeaf(v[0], -1),
1577
1663
  direction: -1,
1578
1664
  earlyTerminate: true
1579
1665
  }
@@ -1581,12 +1667,12 @@ var BPTreeTransaction = class _BPTreeTransaction {
1581
1667
  lt: {
1582
1668
  asc: {
1583
1669
  start: (tx) => tx.leftestNode(),
1584
- end: (tx, v) => tx.insertableEndNode(v[0], 1),
1670
+ end: (tx, v) => tx.findOuterBoundaryLeaf(v[0], 1),
1585
1671
  direction: 1,
1586
1672
  earlyTerminate: true
1587
1673
  },
1588
1674
  desc: {
1589
- start: (tx, v) => tx.insertableNodeByPrimary(v[0]),
1675
+ start: (tx, v) => tx.findLowerBoundLeaf(v[0]),
1590
1676
  end: () => null,
1591
1677
  direction: -1,
1592
1678
  earlyTerminate: false
@@ -1595,12 +1681,12 @@ var BPTreeTransaction = class _BPTreeTransaction {
1595
1681
  lte: {
1596
1682
  asc: {
1597
1683
  start: (tx) => tx.leftestNode(),
1598
- end: (tx, v) => tx.insertableEndNode(v[0], 1),
1684
+ end: (tx, v) => tx.findOuterBoundaryLeaf(v[0], 1),
1599
1685
  direction: 1,
1600
1686
  earlyTerminate: true
1601
1687
  },
1602
1688
  desc: {
1603
- start: (tx, v) => tx.insertableRightestNodeByPrimary(v[0]),
1689
+ start: (tx, v) => tx.findUpperBoundLeaf(v[0]),
1604
1690
  end: () => null,
1605
1691
  direction: -1,
1606
1692
  earlyTerminate: false
@@ -1608,14 +1694,14 @@ var BPTreeTransaction = class _BPTreeTransaction {
1608
1694
  },
1609
1695
  equal: {
1610
1696
  asc: {
1611
- start: (tx, v) => tx.insertableNodeByPrimary(v[0]),
1612
- end: (tx, v) => tx.insertableEndNode(v[0], 1),
1697
+ start: (tx, v) => tx.findLowerBoundLeaf(v[0]),
1698
+ end: (tx, v) => tx.findOuterBoundaryLeaf(v[0], 1),
1613
1699
  direction: 1,
1614
1700
  earlyTerminate: true
1615
1701
  },
1616
1702
  desc: {
1617
- start: (tx, v) => tx.insertableEndNode(v[0], 1),
1618
- end: (tx, v) => tx.insertableEndNode(v[0], -1),
1703
+ start: (tx, v) => tx.findOuterBoundaryLeaf(v[0], 1),
1704
+ end: (tx, v) => tx.findOuterBoundaryLeaf(v[0], -1),
1619
1705
  direction: -1,
1620
1706
  earlyTerminate: true
1621
1707
  }
@@ -1636,42 +1722,42 @@ var BPTreeTransaction = class _BPTreeTransaction {
1636
1722
  },
1637
1723
  or: {
1638
1724
  asc: {
1639
- start: (tx, v) => tx.insertableNodeByPrimary(tx.lowestValue(v)),
1640
- end: (tx, v) => tx.insertableEndNode(tx.highestValue(v), 1),
1725
+ start: (tx, v) => tx.findLowerBoundLeaf(tx.lowestValue(v)),
1726
+ end: (tx, v) => tx.findOuterBoundaryLeaf(tx.highestValue(v), 1),
1641
1727
  direction: 1,
1642
1728
  earlyTerminate: false
1643
1729
  },
1644
1730
  desc: {
1645
- start: (tx, v) => tx.insertableEndNode(tx.highestValue(v), 1),
1646
- end: (tx, v) => tx.insertableEndNode(tx.lowestValue(v), -1),
1731
+ start: (tx, v) => tx.findOuterBoundaryLeaf(tx.highestValue(v), 1),
1732
+ end: (tx, v) => tx.findOuterBoundaryLeaf(tx.lowestValue(v), -1),
1647
1733
  direction: -1,
1648
1734
  earlyTerminate: false
1649
1735
  }
1650
1736
  },
1651
1737
  primaryGt: {
1652
1738
  asc: {
1653
- start: (tx, v) => tx.insertableRightestEndNodeByPrimary(v[0]),
1739
+ start: (tx, v) => tx.findUpperBoundLeaf(v[0]),
1654
1740
  end: () => null,
1655
1741
  direction: 1,
1656
1742
  earlyTerminate: false
1657
1743
  },
1658
1744
  desc: {
1659
1745
  start: (tx) => tx.rightestNode(),
1660
- end: (tx, v) => tx.insertableEndNode(v[0], -1),
1746
+ end: (tx, v) => tx.findOuterBoundaryLeaf(v[0], -1),
1661
1747
  direction: -1,
1662
1748
  earlyTerminate: true
1663
1749
  }
1664
1750
  },
1665
1751
  primaryGte: {
1666
1752
  asc: {
1667
- start: (tx, v) => tx.insertableNodeByPrimary(v[0]),
1753
+ start: (tx, v) => tx.findLowerBoundLeaf(v[0]),
1668
1754
  end: () => null,
1669
1755
  direction: 1,
1670
1756
  earlyTerminate: false
1671
1757
  },
1672
1758
  desc: {
1673
1759
  start: (tx) => tx.rightestNode(),
1674
- end: (tx, v) => tx.insertableEndNode(v[0], -1),
1760
+ end: (tx, v) => tx.findOuterBoundaryLeaf(v[0], -1),
1675
1761
  direction: -1,
1676
1762
  earlyTerminate: true
1677
1763
  }
@@ -1679,12 +1765,12 @@ var BPTreeTransaction = class _BPTreeTransaction {
1679
1765
  primaryLt: {
1680
1766
  asc: {
1681
1767
  start: (tx) => tx.leftestNode(),
1682
- end: (tx, v) => tx.insertableEndNode(v[0], 1),
1768
+ end: (tx, v) => tx.findOuterBoundaryLeaf(v[0], 1),
1683
1769
  direction: 1,
1684
1770
  earlyTerminate: true
1685
1771
  },
1686
1772
  desc: {
1687
- start: (tx, v) => tx.insertableNodeByPrimary(v[0]),
1773
+ start: (tx, v) => tx.findLowerBoundLeaf(v[0]),
1688
1774
  end: () => null,
1689
1775
  direction: -1,
1690
1776
  earlyTerminate: false
@@ -1693,12 +1779,12 @@ var BPTreeTransaction = class _BPTreeTransaction {
1693
1779
  primaryLte: {
1694
1780
  asc: {
1695
1781
  start: (tx) => tx.leftestNode(),
1696
- end: (tx, v) => tx.insertableEndNode(v[0], 1),
1782
+ end: (tx, v) => tx.findOuterBoundaryLeaf(v[0], 1),
1697
1783
  direction: 1,
1698
1784
  earlyTerminate: true
1699
1785
  },
1700
1786
  desc: {
1701
- start: (tx, v) => tx.insertableRightestNodeByPrimary(v[0]),
1787
+ start: (tx, v) => tx.findUpperBoundLeaf(v[0]),
1702
1788
  end: () => null,
1703
1789
  direction: -1,
1704
1790
  earlyTerminate: false
@@ -1706,14 +1792,14 @@ var BPTreeTransaction = class _BPTreeTransaction {
1706
1792
  },
1707
1793
  primaryEqual: {
1708
1794
  asc: {
1709
- start: (tx, v) => tx.insertableNodeByPrimary(v[0]),
1710
- end: (tx, v) => tx.insertableRightestEndNodeByPrimary(v[0]),
1795
+ start: (tx, v) => tx.findLowerBoundLeaf(v[0]),
1796
+ end: (tx, v) => tx.findOuterBoundaryLeaf(v[0], 1),
1711
1797
  direction: 1,
1712
1798
  earlyTerminate: true
1713
1799
  },
1714
1800
  desc: {
1715
- start: (tx, v) => tx.insertableRightestEndNodeByPrimary(v[0]),
1716
- end: (tx, v) => tx.insertableEndNode(v[0], -1),
1801
+ start: (tx, v) => tx.findUpperBoundLeaf(v[0]),
1802
+ end: (tx, v) => tx.findOuterBoundaryLeaf(v[0], -1),
1717
1803
  direction: -1,
1718
1804
  earlyTerminate: true
1719
1805
  }
@@ -1734,14 +1820,14 @@ var BPTreeTransaction = class _BPTreeTransaction {
1734
1820
  },
1735
1821
  primaryOr: {
1736
1822
  asc: {
1737
- start: (tx, v) => tx.insertableNodeByPrimary(tx.lowestPrimaryValue(v)),
1738
- end: (tx, v) => tx.insertableRightestEndNodeByPrimary(tx.highestPrimaryValue(v)),
1823
+ start: (tx, v) => tx.findLowerBoundLeaf(tx.lowestPrimaryValue(v)),
1824
+ end: (tx, v) => tx.findOuterBoundaryLeaf(tx.highestPrimaryValue(v), 1),
1739
1825
  direction: 1,
1740
1826
  earlyTerminate: false
1741
1827
  },
1742
1828
  desc: {
1743
- start: (tx, v) => tx.insertableRightestEndNodeByPrimary(tx.highestPrimaryValue(v)),
1744
- end: (tx, v) => tx.insertableEndNode(tx.lowestPrimaryValue(v), -1),
1829
+ start: (tx, v) => tx.findUpperBoundLeaf(tx.highestPrimaryValue(v)),
1830
+ end: (tx, v) => tx.findOuterBoundaryLeaf(tx.lowestPrimaryValue(v), -1),
1745
1831
  direction: -1,
1746
1832
  earlyTerminate: false
1747
1833
  }
@@ -1897,30 +1983,69 @@ var BPTreeTransaction = class _BPTreeTransaction {
1897
1983
  return JSON.parse(JSON.stringify(node));
1898
1984
  }
1899
1985
  /**
1900
- * Selects the best driver key from a condition object.
1901
- * The driver key determines the starting point and traversal direction for queries.
1902
- *
1986
+ * Resolves the best start/end configuration by independently examining
1987
+ * all conditions. Selects the tightest lower bound for start and the
1988
+ * tightest upper bound for end (in asc; reversed for desc).
1989
+ *
1903
1990
  * @param condition The condition to analyze.
1904
- * @returns The best driver key or null if no valid key found.
1905
- */
1906
- getDriverKey(condition) {
1907
- if ("primaryEqual" in condition) return "primaryEqual";
1908
- if ("equal" in condition) return "equal";
1909
- if ("gt" in condition) return "gt";
1910
- if ("gte" in condition) return "gte";
1911
- if ("lt" in condition) return "lt";
1912
- if ("lte" in condition) return "lte";
1913
- if ("primaryGt" in condition) return "primaryGt";
1914
- if ("primaryGte" in condition) return "primaryGte";
1915
- if ("primaryLt" in condition) return "primaryLt";
1916
- if ("primaryLte" in condition) return "primaryLte";
1917
- if ("like" in condition) return "like";
1918
- if ("notEqual" in condition) return "notEqual";
1919
- if ("primaryNotEqual" in condition) return "primaryNotEqual";
1920
- if ("or" in condition) return "or";
1921
- if ("primaryOr" in condition) return "primaryOr";
1922
- return null;
1923
- }
1991
+ * @param order The sort order ('asc' or 'desc').
1992
+ * @returns The resolved start/end keys, values, and traversal direction.
1993
+ */
1994
+ resolveStartEndConfigs(condition, order) {
1995
+ const direction = order === "asc" ? 1 : -1;
1996
+ const startCandidates = order === "asc" ? _BPTreeTransaction._lowerBoundKeys : _BPTreeTransaction._upperBoundKeys;
1997
+ const endCandidates = order === "asc" ? _BPTreeTransaction._upperBoundKeys : _BPTreeTransaction._lowerBoundKeys;
1998
+ let startKey = null;
1999
+ let endKey = null;
2000
+ let startValues = [];
2001
+ let endValues = [];
2002
+ for (let i = 0, len = startCandidates.length; i < len; i++) {
2003
+ const key = startCandidates[i];
2004
+ if (key in condition) {
2005
+ startKey = key;
2006
+ startValues = _BPTreeTransaction._multiValueKeys.includes(key) ? this.ensureValues(condition[key]) : [condition[key]];
2007
+ break;
2008
+ }
2009
+ }
2010
+ for (let i = 0, len = endCandidates.length; i < len; i++) {
2011
+ const key = endCandidates[i];
2012
+ if (key in condition) {
2013
+ endKey = key;
2014
+ endValues = _BPTreeTransaction._multiValueKeys.includes(key) ? this.ensureValues(condition[key]) : [condition[key]];
2015
+ break;
2016
+ }
2017
+ }
2018
+ return { startKey, endKey, startValues, endValues, direction };
2019
+ }
2020
+ // Lower bound providers, ordered by selectivity (tightest first)
2021
+ // Used for asc start / desc end
2022
+ static _lowerBoundKeys = [
2023
+ "primaryEqual",
2024
+ "equal",
2025
+ "primaryGt",
2026
+ "gt",
2027
+ "primaryGte",
2028
+ "gte",
2029
+ "primaryOr",
2030
+ "or"
2031
+ ];
2032
+ // Upper bound providers, ordered by selectivity (tightest first)
2033
+ // Used for asc end / desc start
2034
+ static _upperBoundKeys = [
2035
+ "primaryEqual",
2036
+ "equal",
2037
+ "primaryLt",
2038
+ "lt",
2039
+ "primaryLte",
2040
+ "lte",
2041
+ "primaryOr",
2042
+ "or"
2043
+ ];
2044
+ // Condition keys that accept multiple values (V[]) rather than a single value (V)
2045
+ static _multiValueKeys = [
2046
+ "or",
2047
+ "primaryOr"
2048
+ ];
1924
2049
  constructor(rootTx, mvccRoot, mvcc, strategy, comparator, option) {
1925
2050
  this.rootTx = rootTx === null ? this : rootTx;
1926
2051
  this.mvccRoot = mvccRoot;
@@ -2157,7 +2282,7 @@ var BPTreeSyncTransaction = class extends BPTreeTransaction {
2157
2282
  this._insertInParent(parentNode, midValue, newSiblingNodeRecursive);
2158
2283
  }
2159
2284
  }
2160
- insertableNode(value) {
2285
+ locateLeaf(value) {
2161
2286
  let node = this.getNode(this.rootId);
2162
2287
  while (!node.leaf) {
2163
2288
  const { index } = this._binarySearchValues(node.values, value, false, true);
@@ -2165,7 +2290,7 @@ var BPTreeSyncTransaction = class extends BPTreeTransaction {
2165
2290
  }
2166
2291
  return node;
2167
2292
  }
2168
- insertableNodeByPrimary(value) {
2293
+ findLowerBoundLeaf(value) {
2169
2294
  let node = this.getNode(this.rootId);
2170
2295
  while (!node.leaf) {
2171
2296
  const { index } = this._binarySearchValues(node.values, value, true, false);
@@ -2173,7 +2298,7 @@ var BPTreeSyncTransaction = class extends BPTreeTransaction {
2173
2298
  }
2174
2299
  return node;
2175
2300
  }
2176
- insertableRightestNodeByPrimary(value) {
2301
+ findUpperBoundLeaf(value) {
2177
2302
  let node = this.getNode(this.rootId);
2178
2303
  while (!node.leaf) {
2179
2304
  const { index } = this._binarySearchValues(node.values, value, true, true);
@@ -2181,15 +2306,8 @@ var BPTreeSyncTransaction = class extends BPTreeTransaction {
2181
2306
  }
2182
2307
  return node;
2183
2308
  }
2184
- insertableRightestEndNodeByPrimary(value) {
2185
- const node = this.insertableRightestNodeByPrimary(value);
2186
- if (!node.next) {
2187
- return null;
2188
- }
2189
- return this.getNode(node.next);
2190
- }
2191
- insertableEndNode(value, direction) {
2192
- const insertableNode = direction === -1 ? this.insertableNodeByPrimary(value) : this.insertableRightestNodeByPrimary(value);
2309
+ findOuterBoundaryLeaf(value, direction) {
2310
+ const insertableNode = direction === -1 ? this.findLowerBoundLeaf(value) : this.findUpperBoundLeaf(value);
2193
2311
  let key;
2194
2312
  switch (direction) {
2195
2313
  case -1:
@@ -2226,13 +2344,10 @@ var BPTreeSyncTransaction = class extends BPTreeTransaction {
2226
2344
  }
2227
2345
  return node;
2228
2346
  }
2229
- *getPairsGenerator(value, startNode, endNode, comparator, direction, earlyTerminate) {
2347
+ *getPairsGenerator(startNode, endNode, direction) {
2230
2348
  let node = startNode;
2231
- let done = false;
2232
- let hasMatched = false;
2233
- while (!done) {
2349
+ while (true) {
2234
2350
  if (endNode && node.id === endNode.id) {
2235
- done = true;
2236
2351
  break;
2237
2352
  }
2238
2353
  const len = node.values.length;
@@ -2240,14 +2355,8 @@ var BPTreeSyncTransaction = class extends BPTreeTransaction {
2240
2355
  for (let i = 0; i < len; i++) {
2241
2356
  const nValue = node.values[i];
2242
2357
  const keys = node.keys[i];
2243
- if (comparator(nValue, value)) {
2244
- hasMatched = true;
2245
- for (let j = 0; j < keys.length; j++) {
2246
- yield [keys[j], nValue];
2247
- }
2248
- } else if (earlyTerminate && hasMatched) {
2249
- done = true;
2250
- break;
2358
+ for (let j = 0, kLen = keys.length; j < kLen; j++) {
2359
+ yield [keys[j], nValue];
2251
2360
  }
2252
2361
  }
2253
2362
  } else {
@@ -2255,30 +2364,17 @@ var BPTreeSyncTransaction = class extends BPTreeTransaction {
2255
2364
  while (i--) {
2256
2365
  const nValue = node.values[i];
2257
2366
  const keys = node.keys[i];
2258
- if (comparator(nValue, value)) {
2259
- hasMatched = true;
2260
- let j = keys.length;
2261
- while (j--) {
2262
- yield [keys[j], nValue];
2263
- }
2264
- } else if (earlyTerminate && hasMatched) {
2265
- done = true;
2266
- break;
2367
+ let j = keys.length;
2368
+ while (j--) {
2369
+ yield [keys[j], nValue];
2267
2370
  }
2268
2371
  }
2269
2372
  }
2270
- if (done) break;
2271
2373
  if (direction === 1) {
2272
- if (!node.next) {
2273
- done = true;
2274
- break;
2275
- }
2374
+ if (!node.next) break;
2276
2375
  node = this.getNode(node.next);
2277
2376
  } else {
2278
- if (!node.prev) {
2279
- done = true;
2280
- break;
2281
- }
2377
+ if (!node.prev) break;
2282
2378
  node = this.getNode(node.prev);
2283
2379
  }
2284
2380
  }
@@ -2323,7 +2419,7 @@ var BPTreeSyncTransaction = class extends BPTreeTransaction {
2323
2419
  }
2324
2420
  }
2325
2421
  exists(key, value) {
2326
- const node = this.insertableNode(value);
2422
+ const node = this.locateLeaf(value);
2327
2423
  const { index, found } = this._binarySearchValues(node.values, value);
2328
2424
  if (found) {
2329
2425
  const keys = node.keys[index];
@@ -2367,49 +2463,36 @@ var BPTreeSyncTransaction = class extends BPTreeTransaction {
2367
2463
  }
2368
2464
  *whereStream(condition, options) {
2369
2465
  const { filterValues, limit, order = "asc" } = options ?? {};
2370
- const driverKey = this.getDriverKey(condition);
2371
- if (!driverKey) return;
2372
- const value = condition[driverKey];
2373
- const v = this.ensureValues(value);
2374
- const config = this.searchConfigs[driverKey][order];
2375
- let startNode = config.start(this, v);
2376
- let endNode = config.end(this, v);
2377
- const direction = config.direction;
2378
- const earlyTerminate = config.earlyTerminate;
2379
- if (order === "desc" && !startNode) {
2380
- startNode = this.rightestNode();
2381
- }
2382
- if (order === "asc" && !startNode) {
2383
- startNode = this.leftestNode();
2466
+ const conditionKeys = Object.keys(condition);
2467
+ if (conditionKeys.length === 0) return;
2468
+ const resolved = this.resolveStartEndConfigs(condition, order);
2469
+ const direction = resolved.direction;
2470
+ let startNode;
2471
+ if (resolved.startKey) {
2472
+ const startConfig = this.searchConfigs[resolved.startKey][order];
2473
+ startNode = startConfig.start(this, resolved.startValues);
2474
+ } else {
2475
+ startNode = order === "asc" ? this.leftestNode() : this.rightestNode();
2476
+ }
2477
+ let endNode = null;
2478
+ if (resolved.endKey) {
2479
+ const endConfig = this.searchConfigs[resolved.endKey][order];
2480
+ endNode = endConfig.end(this, resolved.endValues);
2384
2481
  }
2385
2482
  if (!startNode) return;
2386
- const comparator = this.verifierMap[driverKey];
2387
2483
  const generator = this.getPairsGenerator(
2388
- value,
2389
2484
  startNode,
2390
2485
  endNode,
2391
- comparator,
2392
- direction,
2393
- earlyTerminate
2486
+ direction
2394
2487
  );
2395
2488
  let count = 0;
2396
2489
  const intersection = filterValues && filterValues.size > 0 ? filterValues : null;
2397
2490
  for (const pair of generator) {
2398
- const [k, v2] = pair;
2491
+ const [k, v] = pair;
2399
2492
  if (intersection && !intersection.has(k)) {
2400
2493
  continue;
2401
2494
  }
2402
- let isMatch = true;
2403
- for (const key in condition) {
2404
- if (key === driverKey) continue;
2405
- const verify = this.verifierMap[key];
2406
- const condValue = condition[key];
2407
- if (!verify(v2, condValue)) {
2408
- isMatch = false;
2409
- break;
2410
- }
2411
- }
2412
- if (isMatch) {
2495
+ if (this.verify(v, condition)) {
2413
2496
  yield pair;
2414
2497
  count++;
2415
2498
  if (limit !== void 0 && count >= limit) {
@@ -2433,7 +2516,7 @@ var BPTreeSyncTransaction = class extends BPTreeTransaction {
2433
2516
  return map;
2434
2517
  }
2435
2518
  insert(key, value) {
2436
- let before = this.insertableNode(value);
2519
+ let before = this.locateLeaf(value);
2437
2520
  before = this._insertAtLeaf(before, key, value);
2438
2521
  if (before.values.length === this.order) {
2439
2522
  let after = this._createNode(
@@ -2461,7 +2544,7 @@ var BPTreeSyncTransaction = class extends BPTreeTransaction {
2461
2544
  let currentLeaf = null;
2462
2545
  let modified = false;
2463
2546
  for (const [key, value] of sorted) {
2464
- const targetLeaf = this.insertableNode(value);
2547
+ const targetLeaf = this.locateLeaf(value);
2465
2548
  if (currentLeaf !== null && currentLeaf.id === targetLeaf.id) {
2466
2549
  } else {
2467
2550
  if (currentLeaf !== null && modified) {
@@ -2703,7 +2786,7 @@ var BPTreeSyncTransaction = class extends BPTreeTransaction {
2703
2786
  if (value === void 0) {
2704
2787
  return;
2705
2788
  }
2706
- let node = this.insertableNodeByPrimary(value);
2789
+ let node = this.findLowerBoundLeaf(value);
2707
2790
  let found = false;
2708
2791
  while (true) {
2709
2792
  let i = node.values.length;
@@ -3285,7 +3368,7 @@ var BPTreeAsyncTransaction = class extends BPTreeTransaction {
3285
3368
  await this._insertInParent(parentNode, midValue, newSiblingNodeRecursive);
3286
3369
  }
3287
3370
  }
3288
- async insertableNode(value) {
3371
+ async locateLeaf(value) {
3289
3372
  let node = await this.getNode(this.rootId);
3290
3373
  while (!node.leaf) {
3291
3374
  const { index } = this._binarySearchValues(node.values, value, false, true);
@@ -3293,7 +3376,7 @@ var BPTreeAsyncTransaction = class extends BPTreeTransaction {
3293
3376
  }
3294
3377
  return node;
3295
3378
  }
3296
- async insertableNodeByPrimary(value) {
3379
+ async findLowerBoundLeaf(value) {
3297
3380
  let node = await this.getNode(this.rootId);
3298
3381
  while (!node.leaf) {
3299
3382
  const { index } = this._binarySearchValues(node.values, value, true, false);
@@ -3301,7 +3384,7 @@ var BPTreeAsyncTransaction = class extends BPTreeTransaction {
3301
3384
  }
3302
3385
  return node;
3303
3386
  }
3304
- async insertableRightestNodeByPrimary(value) {
3387
+ async findUpperBoundLeaf(value) {
3305
3388
  let node = await this.getNode(this.rootId);
3306
3389
  while (!node.leaf) {
3307
3390
  const { index } = this._binarySearchValues(node.values, value, true, true);
@@ -3309,15 +3392,8 @@ var BPTreeAsyncTransaction = class extends BPTreeTransaction {
3309
3392
  }
3310
3393
  return node;
3311
3394
  }
3312
- async insertableRightestEndNodeByPrimary(value) {
3313
- const node = await this.insertableRightestNodeByPrimary(value);
3314
- if (!node.next) {
3315
- return null;
3316
- }
3317
- return await this.getNode(node.next);
3318
- }
3319
- async insertableEndNode(value, direction) {
3320
- const insertableNode = direction === -1 ? await this.insertableNodeByPrimary(value) : await this.insertableRightestNodeByPrimary(value);
3395
+ async findOuterBoundaryLeaf(value, direction) {
3396
+ const insertableNode = direction === -1 ? await this.findLowerBoundLeaf(value) : await this.findUpperBoundLeaf(value);
3321
3397
  let key;
3322
3398
  switch (direction) {
3323
3399
  case -1:
@@ -3354,22 +3430,19 @@ var BPTreeAsyncTransaction = class extends BPTreeTransaction {
3354
3430
  }
3355
3431
  return node;
3356
3432
  }
3357
- async *getPairsGenerator(value, startNode, endNode, comparator, direction, earlyTerminate) {
3433
+ async *getPairsGenerator(startNode, endNode, direction) {
3358
3434
  let node = startNode;
3359
- let done = false;
3360
- let hasMatched = false;
3361
3435
  let nextNodePromise = null;
3362
- while (!done) {
3436
+ while (true) {
3363
3437
  if (endNode && node.id === endNode.id) {
3364
- done = true;
3365
3438
  break;
3366
3439
  }
3367
3440
  if (direction === 1) {
3368
- if (node.next && !done) {
3441
+ if (node.next) {
3369
3442
  nextNodePromise = this.getNode(node.next);
3370
3443
  }
3371
3444
  } else {
3372
- if (node.prev && !done) {
3445
+ if (node.prev) {
3373
3446
  nextNodePromise = this.getNode(node.prev);
3374
3447
  }
3375
3448
  }
@@ -3378,14 +3451,8 @@ var BPTreeAsyncTransaction = class extends BPTreeTransaction {
3378
3451
  for (let i = 0; i < len; i++) {
3379
3452
  const nValue = node.values[i];
3380
3453
  const keys = node.keys[i];
3381
- if (comparator(nValue, value)) {
3382
- hasMatched = true;
3383
- for (let j = 0; j < keys.length; j++) {
3384
- yield [keys[j], nValue];
3385
- }
3386
- } else if (earlyTerminate && hasMatched) {
3387
- done = true;
3388
- break;
3454
+ for (let j = 0, kLen = keys.length; j < kLen; j++) {
3455
+ yield [keys[j], nValue];
3389
3456
  }
3390
3457
  }
3391
3458
  } else {
@@ -3393,27 +3460,17 @@ var BPTreeAsyncTransaction = class extends BPTreeTransaction {
3393
3460
  while (i--) {
3394
3461
  const nValue = node.values[i];
3395
3462
  const keys = node.keys[i];
3396
- if (comparator(nValue, value)) {
3397
- hasMatched = true;
3398
- let j = keys.length;
3399
- while (j--) {
3400
- yield [keys[j], nValue];
3401
- }
3402
- } else if (earlyTerminate && hasMatched) {
3403
- done = true;
3404
- break;
3463
+ let j = keys.length;
3464
+ while (j--) {
3465
+ yield [keys[j], nValue];
3405
3466
  }
3406
3467
  }
3407
3468
  }
3408
- if (done) {
3409
- if (nextNodePromise) await nextNodePromise;
3410
- break;
3411
- }
3412
3469
  if (nextNodePromise) {
3413
3470
  node = await nextNodePromise;
3414
3471
  nextNodePromise = null;
3415
3472
  } else {
3416
- done = true;
3473
+ break;
3417
3474
  }
3418
3475
  }
3419
3476
  }
@@ -3457,7 +3514,7 @@ var BPTreeAsyncTransaction = class extends BPTreeTransaction {
3457
3514
  }
3458
3515
  }
3459
3516
  async exists(key, value) {
3460
- const node = await this.insertableNode(value);
3517
+ const node = await this.locateLeaf(value);
3461
3518
  const { index, found } = this._binarySearchValues(node.values, value);
3462
3519
  if (found) {
3463
3520
  const keys = node.keys[index];
@@ -3501,49 +3558,36 @@ var BPTreeAsyncTransaction = class extends BPTreeTransaction {
3501
3558
  }
3502
3559
  async *whereStream(condition, options) {
3503
3560
  const { filterValues, limit, order = "asc" } = options ?? {};
3504
- const driverKey = this.getDriverKey(condition);
3505
- if (!driverKey) return;
3506
- const value = condition[driverKey];
3507
- const v = this.ensureValues(value);
3508
- const config = this.searchConfigs[driverKey][order];
3509
- let startNode = await config.start(this, v);
3510
- let endNode = await config.end(this, v);
3511
- const direction = config.direction;
3512
- const earlyTerminate = config.earlyTerminate;
3513
- if (order === "desc" && !startNode) {
3514
- startNode = await this.rightestNode();
3515
- }
3516
- if (order === "asc" && !startNode) {
3517
- startNode = await this.leftestNode();
3561
+ const conditionKeys = Object.keys(condition);
3562
+ if (conditionKeys.length === 0) return;
3563
+ const resolved = this.resolveStartEndConfigs(condition, order);
3564
+ const direction = resolved.direction;
3565
+ let startNode;
3566
+ if (resolved.startKey) {
3567
+ const startConfig = this.searchConfigs[resolved.startKey][order];
3568
+ startNode = await startConfig.start(this, resolved.startValues);
3569
+ } else {
3570
+ startNode = order === "asc" ? await this.leftestNode() : await this.rightestNode();
3571
+ }
3572
+ let endNode = null;
3573
+ if (resolved.endKey) {
3574
+ const endConfig = this.searchConfigs[resolved.endKey][order];
3575
+ endNode = await endConfig.end(this, resolved.endValues);
3518
3576
  }
3519
3577
  if (!startNode) return;
3520
- const comparator = this.verifierMap[driverKey];
3521
3578
  const generator = this.getPairsGenerator(
3522
- value,
3523
3579
  startNode,
3524
3580
  endNode,
3525
- comparator,
3526
- direction,
3527
- earlyTerminate
3581
+ direction
3528
3582
  );
3529
3583
  let count = 0;
3530
3584
  const intersection = filterValues && filterValues.size > 0 ? filterValues : null;
3531
3585
  for await (const pair of generator) {
3532
- const [k, v2] = pair;
3586
+ const [k, v] = pair;
3533
3587
  if (intersection && !intersection.has(k)) {
3534
3588
  continue;
3535
3589
  }
3536
- let isMatch = true;
3537
- for (const key in condition) {
3538
- if (key === driverKey) continue;
3539
- const verify = this.verifierMap[key];
3540
- const condValue = condition[key];
3541
- if (!verify(v2, condValue)) {
3542
- isMatch = false;
3543
- break;
3544
- }
3545
- }
3546
- if (isMatch) {
3590
+ if (this.verify(v, condition)) {
3547
3591
  yield pair;
3548
3592
  count++;
3549
3593
  if (limit !== void 0 && count >= limit) {
@@ -3568,7 +3612,7 @@ var BPTreeAsyncTransaction = class extends BPTreeTransaction {
3568
3612
  }
3569
3613
  async insert(key, value) {
3570
3614
  return this.writeLock(0, async () => {
3571
- let before = await this.insertableNode(value);
3615
+ let before = await this.locateLeaf(value);
3572
3616
  before = await this._insertAtLeaf(before, key, value);
3573
3617
  if (before.values.length === this.order) {
3574
3618
  let after = await this._createNode(
@@ -3598,7 +3642,7 @@ var BPTreeAsyncTransaction = class extends BPTreeTransaction {
3598
3642
  let currentLeaf = null;
3599
3643
  let modified = false;
3600
3644
  for (const [key, value] of sorted) {
3601
- const targetLeaf = await this.insertableNode(value);
3645
+ const targetLeaf = await this.locateLeaf(value);
3602
3646
  if (currentLeaf !== null && currentLeaf.id === targetLeaf.id) {
3603
3647
  } else {
3604
3648
  if (currentLeaf !== null && modified) {
@@ -3842,7 +3886,7 @@ var BPTreeAsyncTransaction = class extends BPTreeTransaction {
3842
3886
  if (value === void 0) {
3843
3887
  return;
3844
3888
  }
3845
- let node = await this.insertableNodeByPrimary(value);
3889
+ let node = await this.findLowerBoundLeaf(value);
3846
3890
  let found = false;
3847
3891
  while (true) {
3848
3892
  let i = node.values.length;
@@ -4844,39 +4888,125 @@ var InvertedWeakMap = class {
4844
4888
  var MVCCStrategy2 = class {
4845
4889
  };
4846
4890
  var LRUMap3 = class {
4847
- cache = /* @__PURE__ */ new Map();
4848
4891
  capacity;
4892
+ map;
4893
+ head = null;
4894
+ tail = null;
4895
+ /**
4896
+ * Creates an instance of LRUMap.
4897
+ * @param capacity The maximum number of items the cache can hold.
4898
+ */
4849
4899
  constructor(capacity) {
4850
4900
  this.capacity = capacity;
4901
+ this.map = /* @__PURE__ */ new Map();
4851
4902
  }
4852
- get(key) {
4853
- if (!this.cache.has(key)) return void 0;
4854
- const value = this.cache.get(key);
4855
- this.cache.delete(key);
4856
- this.cache.set(key, value);
4857
- return value;
4903
+ /**
4904
+ * Promotes a node to the head of the linked list (marks as most recently used).
4905
+ * @param node The node to promote.
4906
+ */
4907
+ promote(node) {
4908
+ this.extract(node);
4909
+ this.prepend(node);
4910
+ }
4911
+ /**
4912
+ * Disconnects a node from the doubly linked list.
4913
+ * @param node The node to extract.
4914
+ */
4915
+ extract(node) {
4916
+ if (node.prev) node.prev.next = node.next;
4917
+ else this.head = node.next;
4918
+ if (node.next) node.next.prev = node.prev;
4919
+ else this.tail = node.prev;
4920
+ node.prev = null;
4921
+ node.next = null;
4922
+ }
4923
+ /**
4924
+ * Inserts a node at the head of the doubly linked list.
4925
+ * @param node The node to prepend.
4926
+ */
4927
+ prepend(node) {
4928
+ node.next = this.head;
4929
+ if (this.head) this.head.prev = node;
4930
+ this.head = node;
4931
+ if (!this.tail) this.tail = node;
4858
4932
  }
4933
+ /**
4934
+ * Stores or updates a value by key.
4935
+ * If the capacity is exceeded, the least recently used item (tail) is removed.
4936
+ * @param key The key to store.
4937
+ * @param value The value to store.
4938
+ */
4859
4939
  set(key, value) {
4860
- if (this.cache.has(key)) {
4861
- this.cache.delete(key);
4862
- } else if (this.cache.size >= this.capacity) {
4863
- const oldestKey = this.cache.keys().next().value;
4864
- if (oldestKey !== void 0) this.cache.delete(oldestKey);
4940
+ const existing = this.map.get(key);
4941
+ if (existing) {
4942
+ existing.value = value;
4943
+ this.promote(existing);
4944
+ return;
4945
+ }
4946
+ const newNode = { key, value, prev: null, next: null };
4947
+ this.map.set(key, newNode);
4948
+ this.prepend(newNode);
4949
+ if (this.map.size > this.capacity && this.tail) {
4950
+ this.map.delete(this.tail.key);
4951
+ this.extract(this.tail);
4865
4952
  }
4866
- this.cache.set(key, value);
4867
- return this;
4868
4953
  }
4954
+ /**
4955
+ * Retrieves a value by key.
4956
+ * Accessing the item moves it to the "most recently used" position.
4957
+ * @param key The key to look for.
4958
+ * @returns The value associated with the key, or undefined if not found.
4959
+ */
4960
+ get(key) {
4961
+ const node = this.map.get(key);
4962
+ if (!node) return void 0;
4963
+ this.promote(node);
4964
+ return node.value;
4965
+ }
4966
+ /**
4967
+ * Checks if a key exists in the cache without changing its access order.
4968
+ * @param key The key to check.
4969
+ * @returns True if the key exists, false otherwise.
4970
+ */
4869
4971
  has(key) {
4870
- return this.cache.has(key);
4972
+ return this.map.has(key);
4871
4973
  }
4974
+ /**
4975
+ * Removes a key and its associated value from the cache.
4976
+ * @param key The key to remove.
4977
+ * @returns True if the key was found and removed, false otherwise.
4978
+ */
4872
4979
  delete(key) {
4873
- return this.cache.delete(key);
4980
+ const node = this.map.get(key);
4981
+ if (!node) return false;
4982
+ this.extract(node);
4983
+ this.map.delete(key);
4984
+ return true;
4874
4985
  }
4875
- clear() {
4876
- this.cache.clear();
4986
+ /**
4987
+ * Returns an iterator of keys in the order of most recently used to least recently used.
4988
+ * @returns An iterable iterator of keys.
4989
+ */
4990
+ *keys() {
4991
+ let current = this.head;
4992
+ while (current) {
4993
+ yield current.key;
4994
+ current = current.next;
4995
+ }
4877
4996
  }
4997
+ /**
4998
+ * Returns the current number of items in the cache.
4999
+ */
4878
5000
  get size() {
4879
- return this.cache.size;
5001
+ return this.map.size;
5002
+ }
5003
+ /**
5004
+ * Clears all items from the cache.
5005
+ */
5006
+ clear() {
5007
+ this.map.clear();
5008
+ this.head = null;
5009
+ this.tail = null;
4880
5010
  }
4881
5011
  };
4882
5012
  var MVCCTransaction2 = class {
@@ -6333,21 +6463,23 @@ function crc32(buf) {
6333
6463
  }
6334
6464
 
6335
6465
  // src/utils/array.ts
6336
- function getMinMaxValue(array) {
6337
- let i = 0;
6338
- let min = Infinity;
6339
- let max = -Infinity;
6340
- let len = array.length;
6341
- while (i < len) {
6342
- if (array[i] < min) {
6343
- min = array[i];
6344
- }
6345
- if (array[i] > max) {
6346
- max = array[i];
6347
- }
6348
- i++;
6349
- }
6350
- return [min, max];
6466
+ function clusterNumbersByPagination(numbers, pagingSize, startPageId = 0) {
6467
+ const n = numbers.length;
6468
+ if (n === 0) return [];
6469
+ if (n === 1) return [new Float64Array([numbers[0]])];
6470
+ const sorted = (numbers instanceof Float64Array ? numbers.slice() : Float64Array.from(numbers)).sort();
6471
+ const clusters = [];
6472
+ let start = 0;
6473
+ for (let i = 0, len = n - 1; i < len; i++) {
6474
+ const paginationIndex = Math.floor((sorted[i] - startPageId) / pagingSize);
6475
+ const nextPaginationIndex = Math.floor((sorted[i + 1] - startPageId) / pagingSize);
6476
+ if (paginationIndex !== nextPaginationIndex) {
6477
+ clusters.push(sorted.subarray(start, i + 1));
6478
+ start = i + 1;
6479
+ }
6480
+ }
6481
+ clusters.push(sorted.subarray(start));
6482
+ return clusters;
6351
6483
  }
6352
6484
 
6353
6485
  // src/core/Row.ts
@@ -9304,24 +9436,58 @@ var RowTableEngine = class {
9304
9436
  * @returns Array of raw data of the rows in the same order as input PKs
9305
9437
  */
9306
9438
  async selectMany(pks, tx) {
9439
+ const collections = await this.collectItemsByPage(pks, tx);
9440
+ return this.fetchRowsByRids(collections, pks.length, tx);
9441
+ }
9442
+ /**
9443
+ * Collects items by page ID to minimize I/O.
9444
+ * @param pks Array of PKs to look up
9445
+ * @param tx Transaction
9446
+ * @returns Map of page ID to array of {pk, slotIndex, index} pairs
9447
+ */
9448
+ async collectItemsByPage(pks, tx) {
9307
9449
  if (pks.length === 0) {
9308
- return [];
9450
+ return /* @__PURE__ */ new Map();
9309
9451
  }
9310
9452
  const pkIndexMap = /* @__PURE__ */ new Map();
9311
9453
  for (let i = 0, len = pks.length; i < len; i++) {
9312
9454
  pkIndexMap.set(pks[i], i);
9313
9455
  }
9314
- const [minPk, maxPk] = getMinMaxValue(pks);
9315
- const pkRidPairs = new Array(pks.length).fill(null);
9316
9456
  const btx = await this.getBPTreeTransaction(tx);
9317
- const stream = btx.whereStream({ gte: minPk, lte: maxPk });
9318
- for await (const [rid, pk] of stream) {
9319
- const index = pkIndexMap.get(pk);
9320
- if (index !== void 0) {
9321
- pkRidPairs[index] = { pk, rid, index };
9457
+ const clusters = clusterNumbersByPagination(pks, this.order, 1);
9458
+ const collections = /* @__PURE__ */ new Map();
9459
+ const insertToCollections = (pk, rid, index) => {
9460
+ const slotIndex = rid % 65536;
9461
+ const pageId = Math.floor(rid / 65536);
9462
+ if (!collections.has(pageId)) {
9463
+ collections.set(pageId, []);
9464
+ }
9465
+ collections.get(pageId).push({ pk, slotIndex, index });
9466
+ };
9467
+ for (let i = 0, len = clusters.length; i < len; i++) {
9468
+ const cluster = clusters[i];
9469
+ const minPk = cluster[0];
9470
+ const maxPk = cluster[cluster.length - 1];
9471
+ if (minPk === maxPk) {
9472
+ const keys = await btx.keys({ equal: minPk });
9473
+ if (keys.size > 0) {
9474
+ const rid = keys.values().next().value;
9475
+ const index = pkIndexMap.get(minPk);
9476
+ if (index !== void 0) {
9477
+ insertToCollections(minPk, rid, index);
9478
+ }
9479
+ }
9480
+ continue;
9481
+ }
9482
+ const stream = btx.whereStream({ gte: minPk, lte: maxPk });
9483
+ for await (const [rid, pk] of stream) {
9484
+ const index = pkIndexMap.get(pk);
9485
+ if (index !== void 0) {
9486
+ insertToCollections(pk, rid, index);
9487
+ }
9322
9488
  }
9323
9489
  }
9324
- return this.fetchRowsByRids(pkRidPairs, tx);
9490
+ return collections;
9325
9491
  }
9326
9492
  /**
9327
9493
  * Fetches multiple rows by their RID and PK combinations, grouping by page ID to minimize I/O.
@@ -9329,29 +9495,19 @@ var RowTableEngine = class {
9329
9495
  * @param tx Transaction
9330
9496
  * @returns Array of row data in the same order as input PKs
9331
9497
  */
9332
- async fetchRowsByRids(pkRidPairs, tx) {
9333
- const result = new Array(pkRidPairs.length).fill(null);
9334
- if (pkRidPairs.length === 0) return result;
9335
- const pageGroupMap = /* @__PURE__ */ new Map();
9336
- for (const pair of pkRidPairs) {
9337
- if (pair === null) continue;
9338
- const rid = pair.rid;
9339
- const slotIndex = rid % 65536;
9340
- const pageId = Math.floor(rid / 65536);
9341
- if (!pageGroupMap.has(pageId)) {
9342
- pageGroupMap.set(pageId, []);
9343
- }
9344
- pageGroupMap.get(pageId).push({ pk: pair.pk, slotIndex, index: pair.index });
9345
- }
9346
- const sortedPageIds = Array.from(pageGroupMap.keys()).sort((a, b) => a - b);
9498
+ async fetchRowsByRids(collections, itemsCount, tx) {
9499
+ const result = new Array(itemsCount).fill(null);
9500
+ if (itemsCount === 0) return result;
9501
+ const sortedPageIds = Array.from(collections.keys()).sort((a, b) => a - b);
9347
9502
  await Promise.all(sortedPageIds.map(async (pageId) => {
9348
- const items = pageGroupMap.get(pageId);
9503
+ const items = collections.get(pageId);
9349
9504
  const page = await this.pfs.get(pageId, tx);
9350
9505
  if (!this.factory.isDataPage(page)) {
9351
9506
  throw new Error(`Page ${pageId} is not a data page`);
9352
9507
  }
9353
9508
  const manager = this.factory.getManager(page);
9354
- for (const item of items) {
9509
+ for (let i = 0, len = items.length; i < len; i++) {
9510
+ const item = items[i];
9355
9511
  const row = manager.getRow(page, item.slotIndex);
9356
9512
  if (this.rowManager.getDeletedFlag(row)) {
9357
9513
  result[item.index] = null;
@@ -131,6 +131,17 @@ export declare class RowTableEngine {
131
131
  * @returns Array of raw data of the rows in the same order as input PKs
132
132
  */
133
133
  selectMany(pks: number[] | Float64Array, tx: Transaction): Promise<(Uint8Array | null)[]>;
134
+ /**
135
+ * Collects items by page ID to minimize I/O.
136
+ * @param pks Array of PKs to look up
137
+ * @param tx Transaction
138
+ * @returns Map of page ID to array of {pk, slotIndex, index} pairs
139
+ */
140
+ collectItemsByPage(pks: number[] | Float64Array, tx: Transaction): Promise<Map<number, {
141
+ pk: number;
142
+ slotIndex: number;
143
+ index: number;
144
+ }[]>>;
134
145
  /**
135
146
  * Fetches multiple rows by their RID and PK combinations, grouping by page ID to minimize I/O.
136
147
  * @param pkRidPairs Array of {pk, rid} pairs
@@ -2,4 +2,12 @@ type SupportedNumberArray = number[] | Uint8Array | Uint16Array | Uint32Array |
2
2
  export declare function getMinValue(array: SupportedNumberArray): number;
3
3
  export declare function getMaxValue(array: SupportedNumberArray): number;
4
4
  export declare function getMinMaxValue(array: SupportedNumberArray): [number, number];
5
+ /**
6
+ * Sorts the input array and splits it into clusters based on the gaps between consecutive elements.
7
+ * @param numbers Array of numbers to cluster
8
+ * @param maxGap Optional fixed gap threshold. If not provided, it is calculated automatically.
9
+ * @returns Array of clusters
10
+ */
11
+ export declare function clusterNumbersByGap(numbers: number[] | Float64Array, maxGap?: number): Float64Array[];
12
+ export declare function clusterNumbersByPagination(numbers: number[] | Float64Array, pagingSize: number, startPageId?: number): Float64Array[];
5
13
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dataply",
3
- "version": "0.0.24-alpha.9",
3
+ "version": "0.0.25-alpha.0",
4
4
  "description": "A lightweight storage engine for Node.js with support for MVCC, WAL.",
5
5
  "license": "MIT",
6
6
  "author": "izure <admin@izure.org>",
@@ -47,8 +47,8 @@
47
47
  "dependencies": {
48
48
  "cache-entanglement": "^1.7.1",
49
49
  "hookall": "^2.2.0",
50
- "mvcc-api": "^1.3.4",
50
+ "mvcc-api": "^1.3.5",
51
51
  "ryoiki": "^1.2.0",
52
- "serializable-bptree": "^8.3.3"
52
+ "serializable-bptree": "^8.3.5"
53
53
  }
54
54
  }
package/readme.md CHANGED
@@ -18,7 +18,7 @@ Dataply provides essential features for high-performance data management:
18
18
  - **MVCC & Isolation**: Snapshot isolation via Multi-Version Concurrency Control (MVCC) enables non-blocking reads.
19
19
  - **Reliability (WAL)**: Write-Ahead Logging (WAL) ensures data integrity and automatic crash recovery.
20
20
  - **Atomic Transactions**: Full support for ACID-compliant Commit and Rollback operations.
21
- - **Efficient Storage**: Fixed-size page management with LRU-based page caching and Bitmap space optimization.
21
+ - **Efficient Storage**: Fixed-size page management with LRU-based page caching and Free List space optimization (Bitmap-based management is deprecated).
22
22
  - **Type Safety**: Comprehensive TypeScript definitions for a seamless developer experience.
23
23
 
24
24
  ## Installation
@@ -166,6 +166,8 @@ Opens a database file. If the file does not exist, it creates and initializes a
166
166
  - `options.pageSize`: Size of a page (Default: 8192, must be a power of 2)
167
167
  - `options.pageCacheCapacity`: Maximum number of pages to keep in memory (Default: 10000)
168
168
  - `options.wal`: Path to the WAL file. If omitted, WAL is disabled.
169
+ - `options.pagePreallocationCount`: The number of pages to preallocate when creating a new page (Default: 1000).
170
+ - `options.walCheckpointThreshold`: The total number of pages written to the WAL before automatically clearing it (Default: 1000).
169
171
 
170
172
  #### `async init(): Promise<void>`
171
173
  Initializes the instance. Must be called before performing any CRUD operations.
@@ -173,12 +175,18 @@ Initializes the instance. Must be called before performing any CRUD operations.
173
175
  #### `async insert(data: string | Uint8Array, tx?: Transaction): Promise<number>`
174
176
  Inserts new data. Returns the Primary Key (PK) of the created row.
175
177
 
178
+ #### `async insertAsOverflow(data: string | Uint8Array, tx?: Transaction): Promise<number>`
179
+ Forcibly inserts data into an overflow page, even if it could fit within a standard data page. Returns the Primary Key (PK).
180
+
176
181
  #### `async insertBatch(dataList: (string | Uint8Array)[], tx?: Transaction): Promise<number[]>`
177
182
  Inserts multiple rows at once. This is significantly faster than multiple individual inserts as it minimizes internal transaction overhead.
178
183
 
179
184
  #### `async select(pk: number, asRaw?: boolean, tx?: Transaction): Promise<string | Uint8Array | null>`
180
185
  Retrieves data based on the PK. Returns `Uint8Array` if `asRaw` is true.
181
186
 
187
+ #### `async selectMany(pks: number[] | Float64Array, asRaw?: boolean, tx?: Transaction): Promise<(string | Uint8Array | null)[]>`
188
+ Retrieves multiple data records in batch based on the provided PKs. This is more efficient than individual `select` calls for multiple lookups.
189
+
182
190
  #### `async update(pk: number, data: string | Uint8Array, tx?: Transaction): Promise<void>`
183
191
  Updates existing data.
184
192
 
@@ -257,7 +265,7 @@ For a detailed visual guide on Dataply's internal architecture, class diagrams,
257
265
  - **Fixed-size Pages**: All data is managed in fixed-size units (default 8KB) called pages.
258
266
  - **Page Cache**: Minimizes disk I/O by caching frequently accessed pages in memory (LRU Strategy).
259
267
  - **Dirty Page Tracking**: Tracks modified pages (Dirty) to synchronize them with disk efficiently only at the time of commit.
260
- - **Bitmap Management**: Efficiently tracks the allocation and deallocation of pages using a bitmap structure, facilitating fast space reclamation and reuse. For more details on this mechanism, see [Page Reclamation and Reuse Guide](docs/page_reclamation.md).
268
+ - **Free List Management**: Efficiently tracks the allocation and deallocation of pages using a Free List (stack-like structure), facilitating fast space reclamation and reuse. (The older Bitmap-based mechanism is deprecated but remains for backward compatibility). For more details on this mechanism, see [Page Reclamation and Reuse Guide](docs/page_reclamation.md).
261
269
  - **Detailed Structure**: For technical details on the physical layout, see [structure.md](docs/structure.md).
262
270
 
263
271
  #### Page & Row Layout