document-dataply 0.0.9-alpha.9 → 0.0.10-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
@@ -157,39 +157,125 @@ var require_cjs = __commonJS({
157
157
  var MVCCStrategy = class {
158
158
  };
159
159
  var LRUMap = class {
160
- cache = /* @__PURE__ */ new Map();
161
160
  capacity;
161
+ map;
162
+ head = null;
163
+ tail = null;
164
+ /**
165
+ * Creates an instance of LRUMap.
166
+ * @param capacity The maximum number of items the cache can hold.
167
+ */
162
168
  constructor(capacity) {
163
169
  this.capacity = capacity;
170
+ this.map = /* @__PURE__ */ new Map();
164
171
  }
165
- get(key) {
166
- if (!this.cache.has(key)) return void 0;
167
- const value = this.cache.get(key);
168
- this.cache.delete(key);
169
- this.cache.set(key, value);
170
- return value;
172
+ /**
173
+ * Promotes a node to the head of the linked list (marks as most recently used).
174
+ * @param node The node to promote.
175
+ */
176
+ promote(node) {
177
+ this.extract(node);
178
+ this.prepend(node);
179
+ }
180
+ /**
181
+ * Disconnects a node from the doubly linked list.
182
+ * @param node The node to extract.
183
+ */
184
+ extract(node) {
185
+ if (node.prev) node.prev.next = node.next;
186
+ else this.head = node.next;
187
+ if (node.next) node.next.prev = node.prev;
188
+ else this.tail = node.prev;
189
+ node.prev = null;
190
+ node.next = null;
191
+ }
192
+ /**
193
+ * Inserts a node at the head of the doubly linked list.
194
+ * @param node The node to prepend.
195
+ */
196
+ prepend(node) {
197
+ node.next = this.head;
198
+ if (this.head) this.head.prev = node;
199
+ this.head = node;
200
+ if (!this.tail) this.tail = node;
171
201
  }
202
+ /**
203
+ * Stores or updates a value by key.
204
+ * If the capacity is exceeded, the least recently used item (tail) is removed.
205
+ * @param key The key to store.
206
+ * @param value The value to store.
207
+ */
172
208
  set(key, value) {
173
- if (this.cache.has(key)) {
174
- this.cache.delete(key);
175
- } else if (this.cache.size >= this.capacity) {
176
- const oldestKey = this.cache.keys().next().value;
177
- if (oldestKey !== void 0) this.cache.delete(oldestKey);
209
+ const existing = this.map.get(key);
210
+ if (existing) {
211
+ existing.value = value;
212
+ this.promote(existing);
213
+ return;
178
214
  }
179
- this.cache.set(key, value);
180
- return this;
215
+ const newNode = { key, value, prev: null, next: null };
216
+ this.map.set(key, newNode);
217
+ this.prepend(newNode);
218
+ if (this.map.size > this.capacity && this.tail) {
219
+ this.map.delete(this.tail.key);
220
+ this.extract(this.tail);
221
+ }
222
+ }
223
+ /**
224
+ * Retrieves a value by key.
225
+ * Accessing the item moves it to the "most recently used" position.
226
+ * @param key The key to look for.
227
+ * @returns The value associated with the key, or undefined if not found.
228
+ */
229
+ get(key) {
230
+ const node = this.map.get(key);
231
+ if (!node) return void 0;
232
+ this.promote(node);
233
+ return node.value;
181
234
  }
235
+ /**
236
+ * Checks if a key exists in the cache without changing its access order.
237
+ * @param key The key to check.
238
+ * @returns True if the key exists, false otherwise.
239
+ */
182
240
  has(key) {
183
- return this.cache.has(key);
241
+ return this.map.has(key);
184
242
  }
243
+ /**
244
+ * Removes a key and its associated value from the cache.
245
+ * @param key The key to remove.
246
+ * @returns True if the key was found and removed, false otherwise.
247
+ */
185
248
  delete(key) {
186
- return this.cache.delete(key);
249
+ const node = this.map.get(key);
250
+ if (!node) return false;
251
+ this.extract(node);
252
+ this.map.delete(key);
253
+ return true;
187
254
  }
188
- clear() {
189
- this.cache.clear();
255
+ /**
256
+ * Returns an iterator of keys in the order of most recently used to least recently used.
257
+ * @returns An iterable iterator of keys.
258
+ */
259
+ *keys() {
260
+ let current = this.head;
261
+ while (current) {
262
+ yield current.key;
263
+ current = current.next;
264
+ }
190
265
  }
266
+ /**
267
+ * Returns the current number of items in the cache.
268
+ */
191
269
  get size() {
192
- return this.cache.size;
270
+ return this.map.size;
271
+ }
272
+ /**
273
+ * Clears all items from the cache.
274
+ */
275
+ clear() {
276
+ this.map.clear();
277
+ this.head = null;
278
+ this.tail = null;
193
279
  }
194
280
  };
195
281
  var MVCCTransaction = class {
@@ -1583,28 +1669,28 @@ var require_cjs = __commonJS({
1583
1669
  searchConfigs = {
1584
1670
  gt: {
1585
1671
  asc: {
1586
- start: (tx, v) => tx.insertableNodeByPrimary(v[0]),
1672
+ start: (tx, v) => tx.findUpperBoundLeaf(v[0]),
1587
1673
  end: () => null,
1588
1674
  direction: 1,
1589
1675
  earlyTerminate: false
1590
1676
  },
1591
1677
  desc: {
1592
1678
  start: (tx) => tx.rightestNode(),
1593
- end: (tx, v) => tx.insertableEndNode(v[0], -1),
1679
+ end: (tx, v) => tx.findOuterBoundaryLeaf(v[0], -1),
1594
1680
  direction: -1,
1595
1681
  earlyTerminate: true
1596
1682
  }
1597
1683
  },
1598
1684
  gte: {
1599
1685
  asc: {
1600
- start: (tx, v) => tx.insertableNodeByPrimary(v[0]),
1686
+ start: (tx, v) => tx.findLowerBoundLeaf(v[0]),
1601
1687
  end: () => null,
1602
1688
  direction: 1,
1603
1689
  earlyTerminate: false
1604
1690
  },
1605
1691
  desc: {
1606
1692
  start: (tx) => tx.rightestNode(),
1607
- end: (tx, v) => tx.insertableEndNode(v[0], -1),
1693
+ end: (tx, v) => tx.findOuterBoundaryLeaf(v[0], -1),
1608
1694
  direction: -1,
1609
1695
  earlyTerminate: true
1610
1696
  }
@@ -1612,12 +1698,12 @@ var require_cjs = __commonJS({
1612
1698
  lt: {
1613
1699
  asc: {
1614
1700
  start: (tx) => tx.leftestNode(),
1615
- end: (tx, v) => tx.insertableEndNode(v[0], 1),
1701
+ end: (tx, v) => tx.findOuterBoundaryLeaf(v[0], 1),
1616
1702
  direction: 1,
1617
1703
  earlyTerminate: true
1618
1704
  },
1619
1705
  desc: {
1620
- start: (tx, v) => tx.insertableNodeByPrimary(v[0]),
1706
+ start: (tx, v) => tx.findLowerBoundLeaf(v[0]),
1621
1707
  end: () => null,
1622
1708
  direction: -1,
1623
1709
  earlyTerminate: false
@@ -1626,12 +1712,12 @@ var require_cjs = __commonJS({
1626
1712
  lte: {
1627
1713
  asc: {
1628
1714
  start: (tx) => tx.leftestNode(),
1629
- end: (tx, v) => tx.insertableEndNode(v[0], 1),
1715
+ end: (tx, v) => tx.findOuterBoundaryLeaf(v[0], 1),
1630
1716
  direction: 1,
1631
1717
  earlyTerminate: true
1632
1718
  },
1633
1719
  desc: {
1634
- start: (tx, v) => tx.insertableRightestNodeByPrimary(v[0]),
1720
+ start: (tx, v) => tx.findUpperBoundLeaf(v[0]),
1635
1721
  end: () => null,
1636
1722
  direction: -1,
1637
1723
  earlyTerminate: false
@@ -1639,14 +1725,14 @@ var require_cjs = __commonJS({
1639
1725
  },
1640
1726
  equal: {
1641
1727
  asc: {
1642
- start: (tx, v) => tx.insertableNodeByPrimary(v[0]),
1643
- end: (tx, v) => tx.insertableEndNode(v[0], 1),
1728
+ start: (tx, v) => tx.findLowerBoundLeaf(v[0]),
1729
+ end: (tx, v) => tx.findOuterBoundaryLeaf(v[0], 1),
1644
1730
  direction: 1,
1645
1731
  earlyTerminate: true
1646
1732
  },
1647
1733
  desc: {
1648
- start: (tx, v) => tx.insertableEndNode(v[0], 1),
1649
- end: (tx, v) => tx.insertableEndNode(v[0], -1),
1734
+ start: (tx, v) => tx.findOuterBoundaryLeaf(v[0], 1),
1735
+ end: (tx, v) => tx.findOuterBoundaryLeaf(v[0], -1),
1650
1736
  direction: -1,
1651
1737
  earlyTerminate: true
1652
1738
  }
@@ -1667,42 +1753,42 @@ var require_cjs = __commonJS({
1667
1753
  },
1668
1754
  or: {
1669
1755
  asc: {
1670
- start: (tx, v) => tx.insertableNodeByPrimary(tx.lowestValue(v)),
1671
- end: (tx, v) => tx.insertableEndNode(tx.highestValue(v), 1),
1756
+ start: (tx, v) => tx.findLowerBoundLeaf(tx.lowestValue(v)),
1757
+ end: (tx, v) => tx.findOuterBoundaryLeaf(tx.highestValue(v), 1),
1672
1758
  direction: 1,
1673
1759
  earlyTerminate: false
1674
1760
  },
1675
1761
  desc: {
1676
- start: (tx, v) => tx.insertableEndNode(tx.highestValue(v), 1),
1677
- end: (tx, v) => tx.insertableEndNode(tx.lowestValue(v), -1),
1762
+ start: (tx, v) => tx.findOuterBoundaryLeaf(tx.highestValue(v), 1),
1763
+ end: (tx, v) => tx.findOuterBoundaryLeaf(tx.lowestValue(v), -1),
1678
1764
  direction: -1,
1679
1765
  earlyTerminate: false
1680
1766
  }
1681
1767
  },
1682
1768
  primaryGt: {
1683
1769
  asc: {
1684
- start: (tx, v) => tx.insertableNodeByPrimary(v[0]),
1770
+ start: (tx, v) => tx.findUpperBoundLeaf(v[0]),
1685
1771
  end: () => null,
1686
1772
  direction: 1,
1687
1773
  earlyTerminate: false
1688
1774
  },
1689
1775
  desc: {
1690
1776
  start: (tx) => tx.rightestNode(),
1691
- end: (tx, v) => tx.insertableEndNode(v[0], -1),
1777
+ end: (tx, v) => tx.findOuterBoundaryLeaf(v[0], -1),
1692
1778
  direction: -1,
1693
1779
  earlyTerminate: true
1694
1780
  }
1695
1781
  },
1696
1782
  primaryGte: {
1697
1783
  asc: {
1698
- start: (tx, v) => tx.insertableNodeByPrimary(v[0]),
1784
+ start: (tx, v) => tx.findLowerBoundLeaf(v[0]),
1699
1785
  end: () => null,
1700
1786
  direction: 1,
1701
1787
  earlyTerminate: false
1702
1788
  },
1703
1789
  desc: {
1704
1790
  start: (tx) => tx.rightestNode(),
1705
- end: (tx, v) => tx.insertableEndNode(v[0], -1),
1791
+ end: (tx, v) => tx.findOuterBoundaryLeaf(v[0], -1),
1706
1792
  direction: -1,
1707
1793
  earlyTerminate: true
1708
1794
  }
@@ -1710,12 +1796,12 @@ var require_cjs = __commonJS({
1710
1796
  primaryLt: {
1711
1797
  asc: {
1712
1798
  start: (tx) => tx.leftestNode(),
1713
- end: (tx, v) => tx.insertableEndNode(v[0], 1),
1799
+ end: (tx, v) => tx.findOuterBoundaryLeaf(v[0], 1),
1714
1800
  direction: 1,
1715
1801
  earlyTerminate: true
1716
1802
  },
1717
1803
  desc: {
1718
- start: (tx, v) => tx.insertableNodeByPrimary(v[0]),
1804
+ start: (tx, v) => tx.findLowerBoundLeaf(v[0]),
1719
1805
  end: () => null,
1720
1806
  direction: -1,
1721
1807
  earlyTerminate: false
@@ -1724,12 +1810,12 @@ var require_cjs = __commonJS({
1724
1810
  primaryLte: {
1725
1811
  asc: {
1726
1812
  start: (tx) => tx.leftestNode(),
1727
- end: (tx, v) => tx.insertableEndNode(v[0], 1),
1813
+ end: (tx, v) => tx.findOuterBoundaryLeaf(v[0], 1),
1728
1814
  direction: 1,
1729
1815
  earlyTerminate: true
1730
1816
  },
1731
1817
  desc: {
1732
- start: (tx, v) => tx.insertableRightestNodeByPrimary(v[0]),
1818
+ start: (tx, v) => tx.findUpperBoundLeaf(v[0]),
1733
1819
  end: () => null,
1734
1820
  direction: -1,
1735
1821
  earlyTerminate: false
@@ -1737,14 +1823,14 @@ var require_cjs = __commonJS({
1737
1823
  },
1738
1824
  primaryEqual: {
1739
1825
  asc: {
1740
- start: (tx, v) => tx.insertableNodeByPrimary(v[0]),
1741
- end: (tx, v) => tx.insertableRightestEndNodeByPrimary(v[0]),
1826
+ start: (tx, v) => tx.findLowerBoundLeaf(v[0]),
1827
+ end: (tx, v) => tx.findOuterBoundaryLeaf(v[0], 1),
1742
1828
  direction: 1,
1743
1829
  earlyTerminate: true
1744
1830
  },
1745
1831
  desc: {
1746
- start: (tx, v) => tx.insertableRightestEndNodeByPrimary(v[0]),
1747
- end: (tx, v) => tx.insertableEndNode(v[0], -1),
1832
+ start: (tx, v) => tx.findUpperBoundLeaf(v[0]),
1833
+ end: (tx, v) => tx.findOuterBoundaryLeaf(v[0], -1),
1748
1834
  direction: -1,
1749
1835
  earlyTerminate: true
1750
1836
  }
@@ -1765,14 +1851,14 @@ var require_cjs = __commonJS({
1765
1851
  },
1766
1852
  primaryOr: {
1767
1853
  asc: {
1768
- start: (tx, v) => tx.insertableNodeByPrimary(tx.lowestPrimaryValue(v)),
1769
- end: (tx, v) => tx.insertableRightestEndNodeByPrimary(tx.highestPrimaryValue(v)),
1854
+ start: (tx, v) => tx.findLowerBoundLeaf(tx.lowestPrimaryValue(v)),
1855
+ end: (tx, v) => tx.findOuterBoundaryLeaf(tx.highestPrimaryValue(v), 1),
1770
1856
  direction: 1,
1771
1857
  earlyTerminate: false
1772
1858
  },
1773
1859
  desc: {
1774
- start: (tx, v) => tx.insertableRightestEndNodeByPrimary(tx.highestPrimaryValue(v)),
1775
- end: (tx, v) => tx.insertableEndNode(tx.lowestPrimaryValue(v), -1),
1860
+ start: (tx, v) => tx.findUpperBoundLeaf(tx.highestPrimaryValue(v)),
1861
+ end: (tx, v) => tx.findOuterBoundaryLeaf(tx.lowestPrimaryValue(v), -1),
1776
1862
  direction: -1,
1777
1863
  earlyTerminate: false
1778
1864
  }
@@ -1928,30 +2014,69 @@ var require_cjs = __commonJS({
1928
2014
  return JSON.parse(JSON.stringify(node));
1929
2015
  }
1930
2016
  /**
1931
- * Selects the best driver key from a condition object.
1932
- * The driver key determines the starting point and traversal direction for queries.
1933
- *
2017
+ * Resolves the best start/end configuration by independently examining
2018
+ * all conditions. Selects the tightest lower bound for start and the
2019
+ * tightest upper bound for end (in asc; reversed for desc).
2020
+ *
1934
2021
  * @param condition The condition to analyze.
1935
- * @returns The best driver key or null if no valid key found.
1936
- */
1937
- getDriverKey(condition) {
1938
- if ("primaryEqual" in condition) return "primaryEqual";
1939
- if ("equal" in condition) return "equal";
1940
- if ("gt" in condition) return "gt";
1941
- if ("gte" in condition) return "gte";
1942
- if ("lt" in condition) return "lt";
1943
- if ("lte" in condition) return "lte";
1944
- if ("primaryGt" in condition) return "primaryGt";
1945
- if ("primaryGte" in condition) return "primaryGte";
1946
- if ("primaryLt" in condition) return "primaryLt";
1947
- if ("primaryLte" in condition) return "primaryLte";
1948
- if ("like" in condition) return "like";
1949
- if ("notEqual" in condition) return "notEqual";
1950
- if ("primaryNotEqual" in condition) return "primaryNotEqual";
1951
- if ("or" in condition) return "or";
1952
- if ("primaryOr" in condition) return "primaryOr";
1953
- return null;
1954
- }
2022
+ * @param order The sort order ('asc' or 'desc').
2023
+ * @returns The resolved start/end keys, values, and traversal direction.
2024
+ */
2025
+ resolveStartEndConfigs(condition, order) {
2026
+ const direction = order === "asc" ? 1 : -1;
2027
+ const startCandidates = order === "asc" ? _BPTreeTransaction._lowerBoundKeys : _BPTreeTransaction._upperBoundKeys;
2028
+ const endCandidates = order === "asc" ? _BPTreeTransaction._upperBoundKeys : _BPTreeTransaction._lowerBoundKeys;
2029
+ let startKey = null;
2030
+ let endKey = null;
2031
+ let startValues = [];
2032
+ let endValues = [];
2033
+ for (let i = 0, len = startCandidates.length; i < len; i++) {
2034
+ const key = startCandidates[i];
2035
+ if (key in condition) {
2036
+ startKey = key;
2037
+ startValues = _BPTreeTransaction._multiValueKeys.includes(key) ? this.ensureValues(condition[key]) : [condition[key]];
2038
+ break;
2039
+ }
2040
+ }
2041
+ for (let i = 0, len = endCandidates.length; i < len; i++) {
2042
+ const key = endCandidates[i];
2043
+ if (key in condition) {
2044
+ endKey = key;
2045
+ endValues = _BPTreeTransaction._multiValueKeys.includes(key) ? this.ensureValues(condition[key]) : [condition[key]];
2046
+ break;
2047
+ }
2048
+ }
2049
+ return { startKey, endKey, startValues, endValues, direction };
2050
+ }
2051
+ // Lower bound providers, ordered by selectivity (tightest first)
2052
+ // Used for asc start / desc end
2053
+ static _lowerBoundKeys = [
2054
+ "primaryEqual",
2055
+ "equal",
2056
+ "primaryGt",
2057
+ "gt",
2058
+ "primaryGte",
2059
+ "gte",
2060
+ "primaryOr",
2061
+ "or"
2062
+ ];
2063
+ // Upper bound providers, ordered by selectivity (tightest first)
2064
+ // Used for asc end / desc start
2065
+ static _upperBoundKeys = [
2066
+ "primaryEqual",
2067
+ "equal",
2068
+ "primaryLt",
2069
+ "lt",
2070
+ "primaryLte",
2071
+ "lte",
2072
+ "primaryOr",
2073
+ "or"
2074
+ ];
2075
+ // Condition keys that accept multiple values (V[]) rather than a single value (V)
2076
+ static _multiValueKeys = [
2077
+ "or",
2078
+ "primaryOr"
2079
+ ];
1955
2080
  constructor(rootTx, mvccRoot, mvcc, strategy, comparator, option) {
1956
2081
  this.rootTx = rootTx === null ? this : rootTx;
1957
2082
  this.mvccRoot = mvccRoot;
@@ -2188,7 +2313,7 @@ var require_cjs = __commonJS({
2188
2313
  this._insertInParent(parentNode, midValue, newSiblingNodeRecursive);
2189
2314
  }
2190
2315
  }
2191
- insertableNode(value) {
2316
+ locateLeaf(value) {
2192
2317
  let node = this.getNode(this.rootId);
2193
2318
  while (!node.leaf) {
2194
2319
  const { index } = this._binarySearchValues(node.values, value, false, true);
@@ -2196,7 +2321,7 @@ var require_cjs = __commonJS({
2196
2321
  }
2197
2322
  return node;
2198
2323
  }
2199
- insertableNodeByPrimary(value) {
2324
+ findLowerBoundLeaf(value) {
2200
2325
  let node = this.getNode(this.rootId);
2201
2326
  while (!node.leaf) {
2202
2327
  const { index } = this._binarySearchValues(node.values, value, true, false);
@@ -2204,7 +2329,7 @@ var require_cjs = __commonJS({
2204
2329
  }
2205
2330
  return node;
2206
2331
  }
2207
- insertableRightestNodeByPrimary(value) {
2332
+ findUpperBoundLeaf(value) {
2208
2333
  let node = this.getNode(this.rootId);
2209
2334
  while (!node.leaf) {
2210
2335
  const { index } = this._binarySearchValues(node.values, value, true, true);
@@ -2212,15 +2337,8 @@ var require_cjs = __commonJS({
2212
2337
  }
2213
2338
  return node;
2214
2339
  }
2215
- insertableRightestEndNodeByPrimary(value) {
2216
- const node = this.insertableRightestNodeByPrimary(value);
2217
- if (!node.next) {
2218
- return null;
2219
- }
2220
- return this.getNode(node.next);
2221
- }
2222
- insertableEndNode(value, direction) {
2223
- const insertableNode = this.insertableNode(value);
2340
+ findOuterBoundaryLeaf(value, direction) {
2341
+ const insertableNode = direction === -1 ? this.findLowerBoundLeaf(value) : this.findUpperBoundLeaf(value);
2224
2342
  let key;
2225
2343
  switch (direction) {
2226
2344
  case -1:
@@ -2257,13 +2375,10 @@ var require_cjs = __commonJS({
2257
2375
  }
2258
2376
  return node;
2259
2377
  }
2260
- *getPairsGenerator(value, startNode, endNode, comparator, direction, earlyTerminate) {
2378
+ *getPairsGenerator(startNode, endNode, direction) {
2261
2379
  let node = startNode;
2262
- let done = false;
2263
- let hasMatched = false;
2264
- while (!done) {
2380
+ while (true) {
2265
2381
  if (endNode && node.id === endNode.id) {
2266
- done = true;
2267
2382
  break;
2268
2383
  }
2269
2384
  const len = node.values.length;
@@ -2271,14 +2386,8 @@ var require_cjs = __commonJS({
2271
2386
  for (let i = 0; i < len; i++) {
2272
2387
  const nValue = node.values[i];
2273
2388
  const keys = node.keys[i];
2274
- if (comparator(nValue, value)) {
2275
- hasMatched = true;
2276
- for (let j = 0; j < keys.length; j++) {
2277
- yield [keys[j], nValue];
2278
- }
2279
- } else if (earlyTerminate && hasMatched) {
2280
- done = true;
2281
- break;
2389
+ for (let j = 0, kLen = keys.length; j < kLen; j++) {
2390
+ yield [keys[j], nValue];
2282
2391
  }
2283
2392
  }
2284
2393
  } else {
@@ -2286,30 +2395,17 @@ var require_cjs = __commonJS({
2286
2395
  while (i--) {
2287
2396
  const nValue = node.values[i];
2288
2397
  const keys = node.keys[i];
2289
- if (comparator(nValue, value)) {
2290
- hasMatched = true;
2291
- let j = keys.length;
2292
- while (j--) {
2293
- yield [keys[j], nValue];
2294
- }
2295
- } else if (earlyTerminate && hasMatched) {
2296
- done = true;
2297
- break;
2398
+ let j = keys.length;
2399
+ while (j--) {
2400
+ yield [keys[j], nValue];
2298
2401
  }
2299
2402
  }
2300
2403
  }
2301
- if (done) break;
2302
2404
  if (direction === 1) {
2303
- if (!node.next) {
2304
- done = true;
2305
- break;
2306
- }
2405
+ if (!node.next) break;
2307
2406
  node = this.getNode(node.next);
2308
2407
  } else {
2309
- if (!node.prev) {
2310
- done = true;
2311
- break;
2312
- }
2408
+ if (!node.prev) break;
2313
2409
  node = this.getNode(node.prev);
2314
2410
  }
2315
2411
  }
@@ -2354,7 +2450,7 @@ var require_cjs = __commonJS({
2354
2450
  }
2355
2451
  }
2356
2452
  exists(key, value) {
2357
- const node = this.insertableNode(value);
2453
+ const node = this.locateLeaf(value);
2358
2454
  const { index, found } = this._binarySearchValues(node.values, value);
2359
2455
  if (found) {
2360
2456
  const keys = node.keys[index];
@@ -2398,49 +2494,36 @@ var require_cjs = __commonJS({
2398
2494
  }
2399
2495
  *whereStream(condition, options) {
2400
2496
  const { filterValues, limit, order = "asc" } = options ?? {};
2401
- const driverKey = this.getDriverKey(condition);
2402
- if (!driverKey) return;
2403
- const value = condition[driverKey];
2404
- const v = this.ensureValues(value);
2405
- const config = this.searchConfigs[driverKey][order];
2406
- let startNode = config.start(this, v);
2407
- let endNode = config.end(this, v);
2408
- const direction = config.direction;
2409
- const earlyTerminate = config.earlyTerminate;
2410
- if (order === "desc" && !startNode) {
2411
- startNode = this.rightestNode();
2412
- }
2413
- if (order === "asc" && !startNode) {
2414
- startNode = this.leftestNode();
2497
+ const conditionKeys = Object.keys(condition);
2498
+ if (conditionKeys.length === 0) return;
2499
+ const resolved = this.resolveStartEndConfigs(condition, order);
2500
+ const direction = resolved.direction;
2501
+ let startNode;
2502
+ if (resolved.startKey) {
2503
+ const startConfig = this.searchConfigs[resolved.startKey][order];
2504
+ startNode = startConfig.start(this, resolved.startValues);
2505
+ } else {
2506
+ startNode = order === "asc" ? this.leftestNode() : this.rightestNode();
2507
+ }
2508
+ let endNode = null;
2509
+ if (resolved.endKey) {
2510
+ const endConfig = this.searchConfigs[resolved.endKey][order];
2511
+ endNode = endConfig.end(this, resolved.endValues);
2415
2512
  }
2416
2513
  if (!startNode) return;
2417
- const comparator = this.verifierMap[driverKey];
2418
2514
  const generator = this.getPairsGenerator(
2419
- value,
2420
2515
  startNode,
2421
2516
  endNode,
2422
- comparator,
2423
- direction,
2424
- earlyTerminate
2517
+ direction
2425
2518
  );
2426
2519
  let count = 0;
2427
2520
  const intersection = filterValues && filterValues.size > 0 ? filterValues : null;
2428
2521
  for (const pair of generator) {
2429
- const [k, v2] = pair;
2522
+ const [k, v] = pair;
2430
2523
  if (intersection && !intersection.has(k)) {
2431
2524
  continue;
2432
2525
  }
2433
- let isMatch = true;
2434
- for (const key in condition) {
2435
- if (key === driverKey) continue;
2436
- const verify = this.verifierMap[key];
2437
- const condValue = condition[key];
2438
- if (!verify(v2, condValue)) {
2439
- isMatch = false;
2440
- break;
2441
- }
2442
- }
2443
- if (isMatch) {
2526
+ if (this.verify(v, condition)) {
2444
2527
  yield pair;
2445
2528
  count++;
2446
2529
  if (limit !== void 0 && count >= limit) {
@@ -2464,7 +2547,7 @@ var require_cjs = __commonJS({
2464
2547
  return map;
2465
2548
  }
2466
2549
  insert(key, value) {
2467
- let before = this.insertableNode(value);
2550
+ let before = this.locateLeaf(value);
2468
2551
  before = this._insertAtLeaf(before, key, value);
2469
2552
  if (before.values.length === this.order) {
2470
2553
  let after = this._createNode(
@@ -2492,7 +2575,7 @@ var require_cjs = __commonJS({
2492
2575
  let currentLeaf = null;
2493
2576
  let modified = false;
2494
2577
  for (const [key, value] of sorted) {
2495
- const targetLeaf = this.insertableNode(value);
2578
+ const targetLeaf = this.locateLeaf(value);
2496
2579
  if (currentLeaf !== null && currentLeaf.id === targetLeaf.id) {
2497
2580
  } else {
2498
2581
  if (currentLeaf !== null && modified) {
@@ -2734,7 +2817,7 @@ var require_cjs = __commonJS({
2734
2817
  if (value === void 0) {
2735
2818
  return;
2736
2819
  }
2737
- let node = this.insertableNodeByPrimary(value);
2820
+ let node = this.findLowerBoundLeaf(value);
2738
2821
  let found = false;
2739
2822
  while (true) {
2740
2823
  let i = node.values.length;
@@ -3316,7 +3399,7 @@ var require_cjs = __commonJS({
3316
3399
  await this._insertInParent(parentNode, midValue, newSiblingNodeRecursive);
3317
3400
  }
3318
3401
  }
3319
- async insertableNode(value) {
3402
+ async locateLeaf(value) {
3320
3403
  let node = await this.getNode(this.rootId);
3321
3404
  while (!node.leaf) {
3322
3405
  const { index } = this._binarySearchValues(node.values, value, false, true);
@@ -3324,7 +3407,7 @@ var require_cjs = __commonJS({
3324
3407
  }
3325
3408
  return node;
3326
3409
  }
3327
- async insertableNodeByPrimary(value) {
3410
+ async findLowerBoundLeaf(value) {
3328
3411
  let node = await this.getNode(this.rootId);
3329
3412
  while (!node.leaf) {
3330
3413
  const { index } = this._binarySearchValues(node.values, value, true, false);
@@ -3332,7 +3415,7 @@ var require_cjs = __commonJS({
3332
3415
  }
3333
3416
  return node;
3334
3417
  }
3335
- async insertableRightestNodeByPrimary(value) {
3418
+ async findUpperBoundLeaf(value) {
3336
3419
  let node = await this.getNode(this.rootId);
3337
3420
  while (!node.leaf) {
3338
3421
  const { index } = this._binarySearchValues(node.values, value, true, true);
@@ -3340,15 +3423,8 @@ var require_cjs = __commonJS({
3340
3423
  }
3341
3424
  return node;
3342
3425
  }
3343
- async insertableRightestEndNodeByPrimary(value) {
3344
- const node = await this.insertableRightestNodeByPrimary(value);
3345
- if (!node.next) {
3346
- return null;
3347
- }
3348
- return await this.getNode(node.next);
3349
- }
3350
- async insertableEndNode(value, direction) {
3351
- const insertableNode = await this.insertableNode(value);
3426
+ async findOuterBoundaryLeaf(value, direction) {
3427
+ const insertableNode = direction === -1 ? await this.findLowerBoundLeaf(value) : await this.findUpperBoundLeaf(value);
3352
3428
  let key;
3353
3429
  switch (direction) {
3354
3430
  case -1:
@@ -3385,22 +3461,19 @@ var require_cjs = __commonJS({
3385
3461
  }
3386
3462
  return node;
3387
3463
  }
3388
- async *getPairsGenerator(value, startNode, endNode, comparator, direction, earlyTerminate) {
3464
+ async *getPairsGenerator(startNode, endNode, direction) {
3389
3465
  let node = startNode;
3390
- let done = false;
3391
- let hasMatched = false;
3392
3466
  let nextNodePromise = null;
3393
- while (!done) {
3467
+ while (true) {
3394
3468
  if (endNode && node.id === endNode.id) {
3395
- done = true;
3396
3469
  break;
3397
3470
  }
3398
3471
  if (direction === 1) {
3399
- if (node.next && !done) {
3472
+ if (node.next) {
3400
3473
  nextNodePromise = this.getNode(node.next);
3401
3474
  }
3402
3475
  } else {
3403
- if (node.prev && !done) {
3476
+ if (node.prev) {
3404
3477
  nextNodePromise = this.getNode(node.prev);
3405
3478
  }
3406
3479
  }
@@ -3409,14 +3482,8 @@ var require_cjs = __commonJS({
3409
3482
  for (let i = 0; i < len; i++) {
3410
3483
  const nValue = node.values[i];
3411
3484
  const keys = node.keys[i];
3412
- if (comparator(nValue, value)) {
3413
- hasMatched = true;
3414
- for (let j = 0; j < keys.length; j++) {
3415
- yield [keys[j], nValue];
3416
- }
3417
- } else if (earlyTerminate && hasMatched) {
3418
- done = true;
3419
- break;
3485
+ for (let j = 0, kLen = keys.length; j < kLen; j++) {
3486
+ yield [keys[j], nValue];
3420
3487
  }
3421
3488
  }
3422
3489
  } else {
@@ -3424,27 +3491,17 @@ var require_cjs = __commonJS({
3424
3491
  while (i--) {
3425
3492
  const nValue = node.values[i];
3426
3493
  const keys = node.keys[i];
3427
- if (comparator(nValue, value)) {
3428
- hasMatched = true;
3429
- let j = keys.length;
3430
- while (j--) {
3431
- yield [keys[j], nValue];
3432
- }
3433
- } else if (earlyTerminate && hasMatched) {
3434
- done = true;
3435
- break;
3494
+ let j = keys.length;
3495
+ while (j--) {
3496
+ yield [keys[j], nValue];
3436
3497
  }
3437
3498
  }
3438
3499
  }
3439
- if (done) {
3440
- if (nextNodePromise) await nextNodePromise;
3441
- break;
3442
- }
3443
3500
  if (nextNodePromise) {
3444
3501
  node = await nextNodePromise;
3445
3502
  nextNodePromise = null;
3446
3503
  } else {
3447
- done = true;
3504
+ break;
3448
3505
  }
3449
3506
  }
3450
3507
  }
@@ -3488,7 +3545,7 @@ var require_cjs = __commonJS({
3488
3545
  }
3489
3546
  }
3490
3547
  async exists(key, value) {
3491
- const node = await this.insertableNode(value);
3548
+ const node = await this.locateLeaf(value);
3492
3549
  const { index, found } = this._binarySearchValues(node.values, value);
3493
3550
  if (found) {
3494
3551
  const keys = node.keys[index];
@@ -3532,49 +3589,36 @@ var require_cjs = __commonJS({
3532
3589
  }
3533
3590
  async *whereStream(condition, options) {
3534
3591
  const { filterValues, limit, order = "asc" } = options ?? {};
3535
- const driverKey = this.getDriverKey(condition);
3536
- if (!driverKey) return;
3537
- const value = condition[driverKey];
3538
- const v = this.ensureValues(value);
3539
- const config = this.searchConfigs[driverKey][order];
3540
- let startNode = await config.start(this, v);
3541
- let endNode = await config.end(this, v);
3542
- const direction = config.direction;
3543
- const earlyTerminate = config.earlyTerminate;
3544
- if (order === "desc" && !startNode) {
3545
- startNode = await this.rightestNode();
3546
- }
3547
- if (order === "asc" && !startNode) {
3548
- startNode = await this.leftestNode();
3592
+ const conditionKeys = Object.keys(condition);
3593
+ if (conditionKeys.length === 0) return;
3594
+ const resolved = this.resolveStartEndConfigs(condition, order);
3595
+ const direction = resolved.direction;
3596
+ let startNode;
3597
+ if (resolved.startKey) {
3598
+ const startConfig = this.searchConfigs[resolved.startKey][order];
3599
+ startNode = await startConfig.start(this, resolved.startValues);
3600
+ } else {
3601
+ startNode = order === "asc" ? await this.leftestNode() : await this.rightestNode();
3602
+ }
3603
+ let endNode = null;
3604
+ if (resolved.endKey) {
3605
+ const endConfig = this.searchConfigs[resolved.endKey][order];
3606
+ endNode = await endConfig.end(this, resolved.endValues);
3549
3607
  }
3550
3608
  if (!startNode) return;
3551
- const comparator = this.verifierMap[driverKey];
3552
3609
  const generator = this.getPairsGenerator(
3553
- value,
3554
3610
  startNode,
3555
3611
  endNode,
3556
- comparator,
3557
- direction,
3558
- earlyTerminate
3612
+ direction
3559
3613
  );
3560
3614
  let count = 0;
3561
3615
  const intersection = filterValues && filterValues.size > 0 ? filterValues : null;
3562
3616
  for await (const pair of generator) {
3563
- const [k, v2] = pair;
3617
+ const [k, v] = pair;
3564
3618
  if (intersection && !intersection.has(k)) {
3565
3619
  continue;
3566
3620
  }
3567
- let isMatch = true;
3568
- for (const key in condition) {
3569
- if (key === driverKey) continue;
3570
- const verify = this.verifierMap[key];
3571
- const condValue = condition[key];
3572
- if (!verify(v2, condValue)) {
3573
- isMatch = false;
3574
- break;
3575
- }
3576
- }
3577
- if (isMatch) {
3621
+ if (this.verify(v, condition)) {
3578
3622
  yield pair;
3579
3623
  count++;
3580
3624
  if (limit !== void 0 && count >= limit) {
@@ -3599,7 +3643,7 @@ var require_cjs = __commonJS({
3599
3643
  }
3600
3644
  async insert(key, value) {
3601
3645
  return this.writeLock(0, async () => {
3602
- let before = await this.insertableNode(value);
3646
+ let before = await this.locateLeaf(value);
3603
3647
  before = await this._insertAtLeaf(before, key, value);
3604
3648
  if (before.values.length === this.order) {
3605
3649
  let after = await this._createNode(
@@ -3629,7 +3673,7 @@ var require_cjs = __commonJS({
3629
3673
  let currentLeaf = null;
3630
3674
  let modified = false;
3631
3675
  for (const [key, value] of sorted) {
3632
- const targetLeaf = await this.insertableNode(value);
3676
+ const targetLeaf = await this.locateLeaf(value);
3633
3677
  if (currentLeaf !== null && currentLeaf.id === targetLeaf.id) {
3634
3678
  } else {
3635
3679
  if (currentLeaf !== null && modified) {
@@ -3873,7 +3917,7 @@ var require_cjs = __commonJS({
3873
3917
  if (value === void 0) {
3874
3918
  return;
3875
3919
  }
3876
- let node = await this.insertableNodeByPrimary(value);
3920
+ let node = await this.findLowerBoundLeaf(value);
3877
3921
  let found = false;
3878
3922
  while (true) {
3879
3923
  let i = node.values.length;
@@ -4869,39 +4913,125 @@ var require_cjs = __commonJS({
4869
4913
  var MVCCStrategy2 = class {
4870
4914
  };
4871
4915
  var LRUMap3 = class {
4872
- cache = /* @__PURE__ */ new Map();
4873
4916
  capacity;
4917
+ map;
4918
+ head = null;
4919
+ tail = null;
4920
+ /**
4921
+ * Creates an instance of LRUMap.
4922
+ * @param capacity The maximum number of items the cache can hold.
4923
+ */
4874
4924
  constructor(capacity) {
4875
4925
  this.capacity = capacity;
4926
+ this.map = /* @__PURE__ */ new Map();
4876
4927
  }
4877
- get(key) {
4878
- if (!this.cache.has(key)) return void 0;
4879
- const value = this.cache.get(key);
4880
- this.cache.delete(key);
4881
- this.cache.set(key, value);
4882
- return value;
4928
+ /**
4929
+ * Promotes a node to the head of the linked list (marks as most recently used).
4930
+ * @param node The node to promote.
4931
+ */
4932
+ promote(node) {
4933
+ this.extract(node);
4934
+ this.prepend(node);
4935
+ }
4936
+ /**
4937
+ * Disconnects a node from the doubly linked list.
4938
+ * @param node The node to extract.
4939
+ */
4940
+ extract(node) {
4941
+ if (node.prev) node.prev.next = node.next;
4942
+ else this.head = node.next;
4943
+ if (node.next) node.next.prev = node.prev;
4944
+ else this.tail = node.prev;
4945
+ node.prev = null;
4946
+ node.next = null;
4883
4947
  }
4948
+ /**
4949
+ * Inserts a node at the head of the doubly linked list.
4950
+ * @param node The node to prepend.
4951
+ */
4952
+ prepend(node) {
4953
+ node.next = this.head;
4954
+ if (this.head) this.head.prev = node;
4955
+ this.head = node;
4956
+ if (!this.tail) this.tail = node;
4957
+ }
4958
+ /**
4959
+ * Stores or updates a value by key.
4960
+ * If the capacity is exceeded, the least recently used item (tail) is removed.
4961
+ * @param key The key to store.
4962
+ * @param value The value to store.
4963
+ */
4884
4964
  set(key, value) {
4885
- if (this.cache.has(key)) {
4886
- this.cache.delete(key);
4887
- } else if (this.cache.size >= this.capacity) {
4888
- const oldestKey = this.cache.keys().next().value;
4889
- if (oldestKey !== void 0) this.cache.delete(oldestKey);
4965
+ const existing = this.map.get(key);
4966
+ if (existing) {
4967
+ existing.value = value;
4968
+ this.promote(existing);
4969
+ return;
4890
4970
  }
4891
- this.cache.set(key, value);
4892
- return this;
4971
+ const newNode = { key, value, prev: null, next: null };
4972
+ this.map.set(key, newNode);
4973
+ this.prepend(newNode);
4974
+ if (this.map.size > this.capacity && this.tail) {
4975
+ this.map.delete(this.tail.key);
4976
+ this.extract(this.tail);
4977
+ }
4978
+ }
4979
+ /**
4980
+ * Retrieves a value by key.
4981
+ * Accessing the item moves it to the "most recently used" position.
4982
+ * @param key The key to look for.
4983
+ * @returns The value associated with the key, or undefined if not found.
4984
+ */
4985
+ get(key) {
4986
+ const node = this.map.get(key);
4987
+ if (!node) return void 0;
4988
+ this.promote(node);
4989
+ return node.value;
4893
4990
  }
4991
+ /**
4992
+ * Checks if a key exists in the cache without changing its access order.
4993
+ * @param key The key to check.
4994
+ * @returns True if the key exists, false otherwise.
4995
+ */
4894
4996
  has(key) {
4895
- return this.cache.has(key);
4997
+ return this.map.has(key);
4896
4998
  }
4999
+ /**
5000
+ * Removes a key and its associated value from the cache.
5001
+ * @param key The key to remove.
5002
+ * @returns True if the key was found and removed, false otherwise.
5003
+ */
4897
5004
  delete(key) {
4898
- return this.cache.delete(key);
5005
+ const node = this.map.get(key);
5006
+ if (!node) return false;
5007
+ this.extract(node);
5008
+ this.map.delete(key);
5009
+ return true;
4899
5010
  }
4900
- clear() {
4901
- this.cache.clear();
5011
+ /**
5012
+ * Returns an iterator of keys in the order of most recently used to least recently used.
5013
+ * @returns An iterable iterator of keys.
5014
+ */
5015
+ *keys() {
5016
+ let current = this.head;
5017
+ while (current) {
5018
+ yield current.key;
5019
+ current = current.next;
5020
+ }
4902
5021
  }
5022
+ /**
5023
+ * Returns the current number of items in the cache.
5024
+ */
4903
5025
  get size() {
4904
- return this.cache.size;
5026
+ return this.map.size;
5027
+ }
5028
+ /**
5029
+ * Clears all items from the cache.
5030
+ */
5031
+ clear() {
5032
+ this.map.clear();
5033
+ this.head = null;
5034
+ this.tail = null;
4905
5035
  }
4906
5036
  };
4907
5037
  var MVCCTransaction2 = class {
@@ -6348,21 +6478,23 @@ var require_cjs = __commonJS({
6348
6478
  }
6349
6479
  return (crc ^ -1) >>> 0;
6350
6480
  }
6351
- function getMinMaxValue(array) {
6352
- let i = 0;
6353
- let min = Infinity;
6354
- let max = -Infinity;
6355
- let len = array.length;
6356
- while (i < len) {
6357
- if (array[i] < min) {
6358
- min = array[i];
6359
- }
6360
- if (array[i] > max) {
6361
- max = array[i];
6362
- }
6363
- i++;
6364
- }
6365
- return [min, max];
6481
+ function clusterNumbersByPagination(numbers, pagingSize, startPageId = 0) {
6482
+ const n = numbers.length;
6483
+ if (n === 0) return [];
6484
+ if (n === 1) return [new Float64Array([numbers[0]])];
6485
+ const sorted = (numbers instanceof Float64Array ? numbers.slice() : Float64Array.from(numbers)).sort();
6486
+ const clusters = [];
6487
+ let start = 0;
6488
+ for (let i = 0, len = n - 1; i < len; i++) {
6489
+ const paginationIndex = Math.floor((sorted[i] - startPageId) / pagingSize);
6490
+ const nextPaginationIndex = Math.floor((sorted[i + 1] - startPageId) / pagingSize);
6491
+ if (paginationIndex !== nextPaginationIndex) {
6492
+ clusters.push(sorted.subarray(start, i + 1));
6493
+ start = i + 1;
6494
+ }
6495
+ }
6496
+ clusters.push(sorted.subarray(start));
6497
+ return clusters;
6366
6498
  }
6367
6499
  var Row = class _Row {
6368
6500
  static CONSTANT = {
@@ -8180,9 +8312,6 @@ var require_cjs = __commonJS({
8180
8312
  */
8181
8313
  async write(pageId, data) {
8182
8314
  const pageStartPos = pageId * this.pageSize;
8183
- if (pageStartPos + this.pageSize > 512 * 1024 * 1024) {
8184
- throw new Error(`[Safety Limit] File write exceeds 512MB limit at position ${pageStartPos}`);
8185
- }
8186
8315
  const dataCopy = new Uint8Array(this.pageSize);
8187
8316
  dataCopy.set(data);
8188
8317
  this.dirtyPages.set(pageId, dataCopy);
@@ -8209,6 +8338,22 @@ var require_cjs = __commonJS({
8209
8338
  this.dirtyPages.delete(pageId);
8210
8339
  }
8211
8340
  }
8341
+ /**
8342
+ * 지정된 페이지들만 디스크에 기록합니다.
8343
+ * WAL 없이 트랜잭션 커밋 시, 해당 트랜잭션의 dirty pages만 선택적으로 flush합니다.
8344
+ * @param pages 기록할 페이지 맵 (PageID -> PageData)
8345
+ */
8346
+ async flushPages(pages) {
8347
+ if (pages.size === 0) {
8348
+ return;
8349
+ }
8350
+ const sortedPageIds = Array.from(pages.keys()).sort((a, b) => a - b);
8351
+ for (const pageId of sortedPageIds) {
8352
+ const data = pages.get(pageId);
8353
+ const position = pageId * this.pageSize;
8354
+ await this._writeToDisk(data, position);
8355
+ }
8356
+ }
8212
8357
  /**
8213
8358
  * 메인 DB 파일의 물리적 동기화를 수행합니다 (fsync).
8214
8359
  */
@@ -9282,24 +9427,58 @@ var require_cjs = __commonJS({
9282
9427
  * @returns Array of raw data of the rows in the same order as input PKs
9283
9428
  */
9284
9429
  async selectMany(pks, tx) {
9430
+ const collections = await this.collectItemsByPage(pks, tx);
9431
+ return this.fetchRowsByRids(collections, pks.length, tx);
9432
+ }
9433
+ /**
9434
+ * Collects items by page ID to minimize I/O.
9435
+ * @param pks Array of PKs to look up
9436
+ * @param tx Transaction
9437
+ * @returns Map of page ID to array of {pk, slotIndex, index} pairs
9438
+ */
9439
+ async collectItemsByPage(pks, tx) {
9285
9440
  if (pks.length === 0) {
9286
- return [];
9441
+ return /* @__PURE__ */ new Map();
9287
9442
  }
9288
9443
  const pkIndexMap = /* @__PURE__ */ new Map();
9289
9444
  for (let i = 0, len = pks.length; i < len; i++) {
9290
9445
  pkIndexMap.set(pks[i], i);
9291
9446
  }
9292
- const [minPk, maxPk] = getMinMaxValue(pks);
9293
- const pkRidPairs = new Array(pks.length).fill(null);
9294
9447
  const btx = await this.getBPTreeTransaction(tx);
9295
- const stream = btx.whereStream({ gte: minPk, lte: maxPk });
9296
- for await (const [rid, pk] of stream) {
9297
- const index = pkIndexMap.get(pk);
9298
- if (index !== void 0) {
9299
- pkRidPairs[index] = { pk, rid, index };
9448
+ const clusters = clusterNumbersByPagination(pks, this.order, 1);
9449
+ const collections = /* @__PURE__ */ new Map();
9450
+ const insertToCollections = (pk, rid, index) => {
9451
+ const slotIndex = rid % 65536;
9452
+ const pageId = Math.floor(rid / 65536);
9453
+ if (!collections.has(pageId)) {
9454
+ collections.set(pageId, []);
9455
+ }
9456
+ collections.get(pageId).push({ pk, slotIndex, index });
9457
+ };
9458
+ for (let i = 0, len = clusters.length; i < len; i++) {
9459
+ const cluster = clusters[i];
9460
+ const minPk = cluster[0];
9461
+ const maxPk = cluster[cluster.length - 1];
9462
+ if (minPk === maxPk) {
9463
+ const keys = await btx.keys({ equal: minPk });
9464
+ if (keys.size > 0) {
9465
+ const rid = keys.values().next().value;
9466
+ const index = pkIndexMap.get(minPk);
9467
+ if (index !== void 0) {
9468
+ insertToCollections(minPk, rid, index);
9469
+ }
9470
+ }
9471
+ continue;
9472
+ }
9473
+ const stream = btx.whereStream({ gte: minPk, lte: maxPk });
9474
+ for await (const [rid, pk] of stream) {
9475
+ const index = pkIndexMap.get(pk);
9476
+ if (index !== void 0) {
9477
+ insertToCollections(pk, rid, index);
9478
+ }
9300
9479
  }
9301
9480
  }
9302
- return this.fetchRowsByRids(pkRidPairs, tx);
9481
+ return collections;
9303
9482
  }
9304
9483
  /**
9305
9484
  * Fetches multiple rows by their RID and PK combinations, grouping by page ID to minimize I/O.
@@ -9307,29 +9486,19 @@ var require_cjs = __commonJS({
9307
9486
  * @param tx Transaction
9308
9487
  * @returns Array of row data in the same order as input PKs
9309
9488
  */
9310
- async fetchRowsByRids(pkRidPairs, tx) {
9311
- const result = new Array(pkRidPairs.length).fill(null);
9312
- if (pkRidPairs.length === 0) return result;
9313
- const pageGroupMap = /* @__PURE__ */ new Map();
9314
- for (const pair of pkRidPairs) {
9315
- if (pair === null) continue;
9316
- const rid = pair.rid;
9317
- const slotIndex = rid % 65536;
9318
- const pageId = Math.floor(rid / 65536);
9319
- if (!pageGroupMap.has(pageId)) {
9320
- pageGroupMap.set(pageId, []);
9321
- }
9322
- pageGroupMap.get(pageId).push({ pk: pair.pk, slotIndex, index: pair.index });
9323
- }
9324
- const sortedPageIds = Array.from(pageGroupMap.keys()).sort((a, b) => a - b);
9489
+ async fetchRowsByRids(collections, itemsCount, tx) {
9490
+ const result = new Array(itemsCount).fill(null);
9491
+ if (itemsCount === 0) return result;
9492
+ const sortedPageIds = Array.from(collections.keys()).sort((a, b) => a - b);
9325
9493
  await Promise.all(sortedPageIds.map(async (pageId) => {
9326
- const items = pageGroupMap.get(pageId);
9494
+ const items = collections.get(pageId);
9327
9495
  const page = await this.pfs.get(pageId, tx);
9328
9496
  if (!this.factory.isDataPage(page)) {
9329
9497
  throw new Error(`Page ${pageId} is not a data page`);
9330
9498
  }
9331
9499
  const manager = this.factory.getManager(page);
9332
- for (const item of items) {
9500
+ for (let i = 0, len = items.length; i < len; i++) {
9501
+ const item = items[i];
9333
9502
  const row = manager.getRow(page, item.slotIndex);
9334
9503
  if (this.rowManager.getDeletedFlag(row)) {
9335
9504
  result[item.index] = null;
@@ -9566,7 +9735,9 @@ var require_cjs = __commonJS({
9566
9735
  for (const [pageId, data] of this.dirtyPages) {
9567
9736
  await this.pageStrategy.write(pageId, data);
9568
9737
  }
9569
- if (this.pfs.wal) {
9738
+ if (!this.pfs.wal) {
9739
+ await this.pfs.strategy.flushPages(this.dirtyPages);
9740
+ } else {
9570
9741
  this.pfs.wal.incrementWrittenPages(this.dirtyPages.size);
9571
9742
  if (this.pfs.wal.shouldCheckpoint(this.pfs.options.walCheckpointThreshold)) {
9572
9743
  shouldTriggerCheckpoint = true;
@@ -10286,63 +10457,50 @@ var DocumentSerializeStrategyAsync = class extends import_dataply.SerializeStrat
10286
10457
 
10287
10458
  // src/core/bptree/documentComparator.ts
10288
10459
  var import_dataply2 = __toESM(require_cjs());
10289
- function comparePrimitive(a, b) {
10290
- if (a === b) return 0;
10291
- if (a === null) return -1;
10292
- if (b === null) return 1;
10293
- if (typeof a !== typeof b) {
10294
- const typeOrder = (v) => typeof v === "boolean" ? 0 : typeof v === "number" ? 1 : 2;
10295
- return typeOrder(a) - typeOrder(b);
10296
- }
10297
- if (typeof a === "string" && typeof b === "string") {
10298
- return a.localeCompare(b);
10299
- }
10300
- return +a - +b;
10301
- }
10302
- function compareValue(a, b) {
10303
- const aArr = Array.isArray(a);
10304
- const bArr = Array.isArray(b);
10305
- if (!aArr && !bArr) {
10306
- return comparePrimitive(a, b);
10307
- }
10308
- const aList = aArr ? a : [a];
10309
- const bList = bArr ? b : [b];
10310
- const len = Math.min(aList.length, bList.length);
10311
- for (let i = 0; i < len; i++) {
10312
- const diff = comparePrimitive(aList[i], bList[i]);
10313
- if (diff !== 0) return diff;
10314
- }
10315
- return aList.length - bList.length;
10316
- }
10317
- function comparePrimaryValue(a, b) {
10318
- const aArr = Array.isArray(a);
10319
- const bArr = Array.isArray(b);
10320
- if (!aArr && !bArr) {
10321
- return comparePrimitive(a, b);
10322
- }
10323
- const aList = aArr ? a : [a];
10324
- const bList = bArr ? b : [b];
10325
- const len = Math.min(aList.length, bList.length);
10326
- for (let i = 0; i < len; i++) {
10327
- const diff = comparePrimitive(aList[i], bList[i]);
10328
- if (diff !== 0) return diff;
10329
- }
10330
- return 0;
10331
- }
10332
10460
  var DocumentValueComparator = class extends import_dataply2.ValueComparator {
10461
+ _intlComparator = new Intl.Collator(void 0, {
10462
+ numeric: true,
10463
+ sensitivity: "variant",
10464
+ usage: "sort"
10465
+ });
10333
10466
  primaryAsc(a, b) {
10334
- return comparePrimaryValue(a.v, b.v);
10467
+ return this._compareValue(a.v, b.v);
10335
10468
  }
10336
10469
  asc(a, b) {
10337
- const diff = compareValue(a.v, b.v);
10470
+ const diff = this._compareValue(a.v, b.v);
10338
10471
  return diff === 0 ? a.k - b.k : diff;
10339
10472
  }
10340
10473
  match(value) {
10341
- if (Array.isArray(value.v)) {
10342
- return value.v[0] + "";
10343
- }
10344
10474
  return value.v + "";
10345
10475
  }
10476
+ /**
10477
+ * 두 Primitive 값을 비교합니다.
10478
+ */
10479
+ _compareDiff(a, b) {
10480
+ if (typeof a !== "string" && typeof b !== "string") {
10481
+ return +a - +b;
10482
+ }
10483
+ return this._intlComparator.compare(a + "", b + "");
10484
+ }
10485
+ /**
10486
+ * 두 v 값을 비교합니다. v는 Primitive 또는 Primitive[] (복합 인덱스)일 수 있습니다.
10487
+ * 배열인 경우 element-by-element로 비교합니다.
10488
+ */
10489
+ _compareValue(a, b) {
10490
+ const aArr = Array.isArray(a);
10491
+ const bArr = Array.isArray(b);
10492
+ if (!aArr && !bArr) {
10493
+ return this._compareDiff(a, b);
10494
+ }
10495
+ const aList = aArr ? a : [a];
10496
+ const bList = bArr ? b : [b];
10497
+ const len = Math.min(aList.length, bList.length);
10498
+ for (let i = 0; i < len; i++) {
10499
+ const diff = this._compareDiff(aList[i], bList[i]);
10500
+ if (diff !== 0) return diff;
10501
+ }
10502
+ return 0;
10503
+ }
10346
10504
  };
10347
10505
 
10348
10506
  // src/utils/catchPromise.ts
@@ -10583,8 +10741,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
10583
10741
  async registerIndexRuntime(name, option, tx) {
10584
10742
  const config = this.toIndexMetaConfig(option);
10585
10743
  if (this.registeredIndices.has(name)) {
10586
- const existing = this.registeredIndices.get(name);
10587
- if (JSON.stringify(existing) === JSON.stringify(config)) return;
10744
+ throw new Error(`Index "${name}" already exists.`);
10588
10745
  }
10589
10746
  await this.runWithDefaultWrite(async (tx2) => {
10590
10747
  const metadata = await this.getDocumentInnerMetadata(tx2);
@@ -10593,7 +10750,8 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
10593
10750
  this.indices = metadata.indices;
10594
10751
  this.registeredIndices.set(name, config);
10595
10752
  const fields = this.getFieldsFromConfig(config);
10596
- for (const field of fields) {
10753
+ for (let i = 0; i < fields.length; i++) {
10754
+ const field = fields[i];
10597
10755
  this.indexedFields.add(field);
10598
10756
  if (!this.fieldToIndices.has(field)) {
10599
10757
  this.fieldToIndices.set(field, []);
@@ -10625,14 +10783,14 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
10625
10783
  */
10626
10784
  async dropIndex(name, tx) {
10627
10785
  if (name === "_id") {
10628
- throw new Error("Cannot drop the _id index");
10786
+ throw new Error('Cannot drop the "_id" index.');
10629
10787
  }
10630
10788
  if (!this._initialized) {
10631
10789
  this.pendingCreateIndices.delete(name);
10632
10790
  return;
10633
10791
  }
10634
10792
  if (!this.registeredIndices.has(name)) {
10635
- throw new Error(`Index '${name}' does not exist`);
10793
+ throw new Error(`Index "${name}" does not exist.`);
10636
10794
  }
10637
10795
  await this.runWithDefaultWrite(async (tx2) => {
10638
10796
  const config = this.registeredIndices.get(name);
@@ -10642,7 +10800,8 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
10642
10800
  this.indices = metadata.indices;
10643
10801
  this.registeredIndices.delete(name);
10644
10802
  const fields = this.getFieldsFromConfig(config);
10645
- for (const field of fields) {
10803
+ for (let i = 0; i < fields.length; i++) {
10804
+ const field = fields[i];
10646
10805
  const indexNames = this.fieldToIndices.get(field);
10647
10806
  if (indexNames) {
10648
10807
  const filtered = indexNames.filter((n) => n !== name);
@@ -10673,7 +10832,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
10673
10832
  if (!Array.isArray(option.fields) || option.fields.length === 0) {
10674
10833
  throw new Error('btree index requires a non-empty "fields" array');
10675
10834
  }
10676
- for (let i = 0; i < option.fields.length; i++) {
10835
+ for (let i = 0, len = option.fields.length; i < len; i++) {
10677
10836
  if (typeof option.fields[i] !== "string" || option.fields[i].length === 0) {
10678
10837
  throw new Error(`btree index "fields[${i}]" must be a non-empty string, got: ${JSON.stringify(option.fields[i])}`);
10679
10838
  }
@@ -10806,7 +10965,8 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
10806
10965
  const doc = await this.getDocument(k, tx2);
10807
10966
  if (!doc) continue;
10808
10967
  const flatDoc = this.flattenDocument(doc);
10809
- for (const indexName of backfillTargets) {
10968
+ for (let i = 0, len = backfillTargets.length; i < len; i++) {
10969
+ const indexName = backfillTargets[i];
10810
10970
  if (!(indexName in indexTxMap)) continue;
10811
10971
  const config = this.registeredIndices.get(indexName);
10812
10972
  if (!config) continue;
@@ -10818,8 +10978,8 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
10818
10978
  const ftsConfig = this.getFtsConfig(config);
10819
10979
  const tokens = ftsConfig ? tokenize(v, ftsConfig) : [v];
10820
10980
  const batchInsertData = [];
10821
- for (let i = 0, len = tokens.length; i < len; i++) {
10822
- const token = tokens[i];
10981
+ for (let i2 = 0, len2 = tokens.length; i2 < len2; i2++) {
10982
+ const token = tokens[i2];
10823
10983
  const keyToInsert = this.getTokenKey(k, token);
10824
10984
  const entry = { k, v: token };
10825
10985
  batchInsertData.push([keyToInsert, entry]);
@@ -11003,6 +11163,191 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
11003
11163
  }
11004
11164
  return result;
11005
11165
  }
11166
+ /**
11167
+ * B-Tree 타입 인덱스의 선택도를 평가하고 트리에 부여할 조건을 산출합니다.
11168
+ * 필드 매칭 여부를 검사하고, 연속된(Prefix) 조건에 대해 점수를 부여하며 Start/End 바운드를 구성합니다.
11169
+ *
11170
+ * @param indexName 평가할 인덱스의 이름 (예: idx_nickname_createdat)
11171
+ * @param config 등록된 인덱스의 설정 객체
11172
+ * @param query 쿼리 객체
11173
+ * @param queryFields 쿼리에 포함된 필드 목록 집합
11174
+ * @param treeTx 조회를 수행할 B-Tree 트랜잭션 객체
11175
+ * @param orderByField 정렬에 사용할 필드명 (옵션)
11176
+ * @returns B-Tree 인덱스 후보 정보 (조건, 점수, 커버된 필드 등), 적합하지 않으면 null
11177
+ */
11178
+ evaluateBTreeCandidate(indexName, config, query, queryFields, treeTx, orderByField) {
11179
+ const primaryField = config.fields[0];
11180
+ if (!queryFields.has(primaryField)) return null;
11181
+ const builtCondition = {};
11182
+ let score = 0;
11183
+ let isConsecutive = true;
11184
+ const coveredFields = [];
11185
+ const compositeVerifyFields = [];
11186
+ const startValues = [];
11187
+ const endValues = [];
11188
+ let startOperator = null;
11189
+ let endOperator = null;
11190
+ for (let i = 0, len = config.fields.length; i < len; i++) {
11191
+ const field = config.fields[i];
11192
+ if (!queryFields.has(field)) {
11193
+ isConsecutive = false;
11194
+ continue;
11195
+ }
11196
+ coveredFields.push(field);
11197
+ score += 1;
11198
+ if (isConsecutive) {
11199
+ const cond = query[field];
11200
+ if (cond !== void 0) {
11201
+ let isBounded = false;
11202
+ if (typeof cond !== "object" || cond === null) {
11203
+ score += 100;
11204
+ startValues.push(cond);
11205
+ endValues.push(cond);
11206
+ startOperator = "primaryGte";
11207
+ endOperator = "primaryLte";
11208
+ isBounded = true;
11209
+ } else if ("primaryEqual" in cond || "equal" in cond) {
11210
+ const val = cond.primaryEqual?.v ?? cond.equal?.v ?? cond.primaryEqual ?? cond.equal;
11211
+ score += 100;
11212
+ startValues.push(val);
11213
+ endValues.push(val);
11214
+ startOperator = "primaryGte";
11215
+ endOperator = "primaryLte";
11216
+ isBounded = true;
11217
+ } else if ("primaryGte" in cond || "gte" in cond) {
11218
+ const val = cond.primaryGte?.v ?? cond.gte?.v ?? cond.primaryGte ?? cond.gte;
11219
+ score += 50;
11220
+ isConsecutive = false;
11221
+ startValues.push(val);
11222
+ startOperator = "primaryGte";
11223
+ if (endValues.length > 0) endOperator = "primaryLte";
11224
+ isBounded = true;
11225
+ } else if ("primaryGt" in cond || "gt" in cond) {
11226
+ const val = cond.primaryGt?.v ?? cond.gt?.v ?? cond.primaryGt ?? cond.gt;
11227
+ score += 50;
11228
+ isConsecutive = false;
11229
+ startValues.push(val);
11230
+ startOperator = "primaryGt";
11231
+ if (endValues.length > 0) endOperator = "primaryLte";
11232
+ isBounded = true;
11233
+ } else if ("primaryLte" in cond || "lte" in cond) {
11234
+ const val = cond.primaryLte?.v ?? cond.lte?.v ?? cond.primaryLte ?? cond.lte;
11235
+ score += 50;
11236
+ isConsecutive = false;
11237
+ endValues.push(val);
11238
+ endOperator = "primaryLte";
11239
+ if (startValues.length > 0) startOperator = "primaryGte";
11240
+ isBounded = true;
11241
+ } else if ("primaryLt" in cond || "lt" in cond) {
11242
+ const val = cond.primaryLt?.v ?? cond.lt?.v ?? cond.primaryLt ?? cond.lt;
11243
+ score += 50;
11244
+ isConsecutive = false;
11245
+ endValues.push(val);
11246
+ endOperator = "primaryLt";
11247
+ if (startValues.length > 0) startOperator = "primaryGte";
11248
+ isBounded = true;
11249
+ } else if ("primaryOr" in cond || "or" in cond) {
11250
+ score += 20;
11251
+ isConsecutive = false;
11252
+ } else if ("like" in cond) {
11253
+ score += 15;
11254
+ isConsecutive = false;
11255
+ } else {
11256
+ score += 10;
11257
+ isConsecutive = false;
11258
+ }
11259
+ if (!isBounded && field !== primaryField) {
11260
+ compositeVerifyFields.push(field);
11261
+ }
11262
+ }
11263
+ } else {
11264
+ if (field !== primaryField) {
11265
+ compositeVerifyFields.push(field);
11266
+ }
11267
+ }
11268
+ }
11269
+ if (coveredFields.length === 1 && config.fields.length === 1) {
11270
+ Object.assign(builtCondition, query[primaryField]);
11271
+ } else {
11272
+ if (startOperator && startValues.length > 0) {
11273
+ builtCondition[startOperator] = { v: startValues.length === 1 ? startValues[0] : startValues };
11274
+ }
11275
+ if (endOperator && endValues.length > 0) {
11276
+ if (startOperator && startValues.length === endValues.length && startValues.every((val, i) => val === endValues[i])) {
11277
+ delete builtCondition[startOperator];
11278
+ builtCondition["primaryEqual"] = { v: startValues.length === 1 ? startValues[0] : startValues };
11279
+ } else {
11280
+ builtCondition[endOperator] = { v: endValues.length === 1 ? endValues[0] : endValues };
11281
+ }
11282
+ }
11283
+ if (Object.keys(builtCondition).length === 0) {
11284
+ Object.assign(builtCondition, query[primaryField] || {});
11285
+ }
11286
+ }
11287
+ let isIndexOrderSupported = false;
11288
+ if (orderByField) {
11289
+ for (let i = 0, len = config.fields.length; i < len; i++) {
11290
+ const field = config.fields[i];
11291
+ if (field === orderByField) {
11292
+ isIndexOrderSupported = true;
11293
+ break;
11294
+ }
11295
+ const cond = query[field];
11296
+ let isExactMatch = false;
11297
+ if (cond !== void 0) {
11298
+ if (typeof cond !== "object" || cond === null) isExactMatch = true;
11299
+ else if ("primaryEqual" in cond || "equal" in cond) isExactMatch = true;
11300
+ }
11301
+ if (!isExactMatch) break;
11302
+ }
11303
+ if (isIndexOrderSupported) {
11304
+ score += 200;
11305
+ }
11306
+ }
11307
+ return {
11308
+ tree: treeTx,
11309
+ condition: builtCondition,
11310
+ field: primaryField,
11311
+ indexName,
11312
+ isFtsMatch: false,
11313
+ score,
11314
+ compositeVerifyFields,
11315
+ coveredFields,
11316
+ isIndexOrderSupported
11317
+ };
11318
+ }
11319
+ /**
11320
+ * FTS (Full Text Search) 타입 인덱스의 선택도를 평가합니다.
11321
+ * 'match' 연산자가 쿼리에 존재하는지 확인하고, 검색용 토큰으로 분해(tokenize)하여 점수를 매깁니다.
11322
+ *
11323
+ * @param indexName 평가할 인덱스의 이름
11324
+ * @param config 등록된 인덱스의 설정 객체
11325
+ * @param query 쿼리 객체
11326
+ * @param queryFields 쿼리에 포함된 필드 목록 집합
11327
+ * @param treeTx 조회를 수행할 B-Tree 트랜잭션 객체
11328
+ * @returns FTS 인덱스 후보 정보 (조건, 점수, 분석된 토큰 등), 적합하지 않으면 null
11329
+ */
11330
+ evaluateFTSCandidate(indexName, config, query, queryFields, treeTx) {
11331
+ const field = config.fields;
11332
+ if (!queryFields.has(field)) return null;
11333
+ const condition = query[field];
11334
+ if (!condition || typeof condition !== "object" || !("match" in condition)) return null;
11335
+ const ftsConfig = this.getFtsConfig(config);
11336
+ const matchTokens = ftsConfig ? tokenize(condition.match, ftsConfig) : [];
11337
+ return {
11338
+ tree: treeTx,
11339
+ condition,
11340
+ field,
11341
+ indexName,
11342
+ isFtsMatch: true,
11343
+ matchTokens,
11344
+ score: 90,
11345
+ // FTS 쿼리는 기본적인 B-Tree 단일 검색(대략 101점)보다는 우선순위를 조금 낮게 가져가도록 90점 부여
11346
+ compositeVerifyFields: [],
11347
+ coveredFields: [field],
11348
+ isIndexOrderSupported: false
11349
+ };
11350
+ }
11006
11351
  /**
11007
11352
  * Choose the best index (driver) for the given query.
11008
11353
  * Scores each index based on field coverage and condition type.
@@ -11018,95 +11363,26 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
11018
11363
  const tree = this.trees.get(indexName);
11019
11364
  if (!tree) continue;
11020
11365
  if (config.type === "btree") {
11021
- const primaryField = config.fields[0];
11022
- if (!queryFields.has(primaryField)) continue;
11023
- const condition = query[primaryField];
11024
11366
  const treeTx = await tree.createTransaction();
11025
- let score = 0;
11026
- let isConsecutive = true;
11027
- const coveredFields = [];
11028
- const compositeVerifyFields = [];
11029
- for (const field of config.fields) {
11030
- if (!queryFields.has(field)) {
11031
- isConsecutive = false;
11032
- continue;
11033
- }
11034
- coveredFields.push(field);
11035
- if (field !== primaryField) {
11036
- compositeVerifyFields.push(field);
11037
- }
11038
- score += 1;
11039
- if (isConsecutive) {
11040
- const cond = query[field];
11041
- if (cond !== void 0) {
11042
- if (typeof cond !== "object" || cond === null) {
11043
- score += 100;
11044
- } else if ("primaryEqual" in cond || "equal" in cond) {
11045
- score += 100;
11046
- } else if ("primaryGte" in cond || "primaryLte" in cond || "primaryGt" in cond || "primaryLt" in cond || "gte" in cond || "lte" in cond || "gt" in cond || "lt" in cond) {
11047
- score += 50;
11048
- isConsecutive = false;
11049
- } else if ("primaryOr" in cond || "or" in cond) {
11050
- score += 20;
11051
- isConsecutive = false;
11052
- } else if ("like" in cond) {
11053
- score += 15;
11054
- isConsecutive = false;
11055
- } else {
11056
- score += 10;
11057
- isConsecutive = false;
11058
- }
11059
- }
11060
- }
11061
- }
11062
- let isIndexOrderSupported = false;
11063
- if (orderByField) {
11064
- for (const field of config.fields) {
11065
- if (field === orderByField) {
11066
- isIndexOrderSupported = true;
11067
- break;
11068
- }
11069
- const cond = query[field];
11070
- let isExactMatch = false;
11071
- if (cond !== void 0) {
11072
- if (typeof cond !== "object" || cond === null) isExactMatch = true;
11073
- else if ("primaryEqual" in cond || "equal" in cond) isExactMatch = true;
11074
- }
11075
- if (!isExactMatch) break;
11076
- }
11077
- if (isIndexOrderSupported) {
11078
- score += 200;
11079
- }
11080
- }
11081
- candidates.push({
11082
- tree: treeTx,
11083
- condition,
11084
- field: primaryField,
11367
+ const candidate = this.evaluateBTreeCandidate(
11085
11368
  indexName,
11086
- isFtsMatch: false,
11087
- score,
11088
- compositeVerifyFields,
11089
- isIndexOrderSupported
11090
- });
11369
+ config,
11370
+ query,
11371
+ queryFields,
11372
+ treeTx,
11373
+ orderByField
11374
+ );
11375
+ if (candidate) candidates.push(candidate);
11091
11376
  } else if (config.type === "fts") {
11092
- const field = config.fields;
11093
- if (!queryFields.has(field)) continue;
11094
- const condition = query[field];
11095
- if (!condition || typeof condition !== "object" || !("match" in condition)) continue;
11096
11377
  const treeTx = await tree.createTransaction();
11097
- const ftsConfig = this.getFtsConfig(config);
11098
- const matchTokens = ftsConfig ? tokenize(condition.match, ftsConfig) : [];
11099
- candidates.push({
11100
- tree: treeTx,
11101
- condition,
11102
- field,
11378
+ const candidate = this.evaluateFTSCandidate(
11103
11379
  indexName,
11104
- isFtsMatch: true,
11105
- matchTokens,
11106
- score: 90,
11107
- compositeVerifyFields: [],
11108
- isIndexOrderSupported: false
11109
- });
11380
+ config,
11381
+ query,
11382
+ queryFields,
11383
+ treeTx
11384
+ );
11385
+ if (candidate) candidates.push(candidate);
11110
11386
  }
11111
11387
  }
11112
11388
  const rollback = () => {
@@ -11118,11 +11394,20 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
11118
11394
  rollback();
11119
11395
  return null;
11120
11396
  }
11121
- candidates.sort((a, b) => b.score - a.score);
11397
+ candidates.sort((a, b) => {
11398
+ if (b.score !== a.score) return b.score - a.score;
11399
+ const aConfig = this.registeredIndices.get(a.indexName);
11400
+ const bConfig = this.registeredIndices.get(b.indexName);
11401
+ const aFieldCount = aConfig ? Array.isArray(aConfig.fields) ? aConfig.fields.length : 1 : 0;
11402
+ const bFieldCount = bConfig ? Array.isArray(bConfig.fields) ? bConfig.fields.length : 1 : 0;
11403
+ return aFieldCount - bFieldCount;
11404
+ });
11122
11405
  const driver = candidates[0];
11123
- const others = candidates.slice(1);
11406
+ const driverCoveredFields = new Set(driver.coveredFields);
11407
+ const others = candidates.slice(1).filter((c) => !driverCoveredFields.has(c.field));
11124
11408
  const compositeVerifyConditions = [];
11125
- for (const field of driver.compositeVerifyFields) {
11409
+ for (let i = 0, len = driver.compositeVerifyFields.length; i < len; i++) {
11410
+ const field = driver.compositeVerifyFields[i];
11126
11411
  if (query[field]) {
11127
11412
  compositeVerifyConditions.push({ field, condition: query[field] });
11128
11413
  }
@@ -11592,10 +11877,15 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
11592
11877
  * Prefetch 방식으로 키 스트림을 청크 단위로 조회하여 문서를 순회합니다.
11593
11878
  * FTS 검증, 복합 인덱스 검증, others 후보에 대한 tree.verify() 검증을 통과한 문서만 yield 합니다.
11594
11879
  */
11595
- async *processChunkedKeysWithVerify(keysStream, startIdx, initialChunkSize, ftsConditions, compositeVerifyConditions, others, tx) {
11880
+ async *processChunkedKeysWithVerify(keysStream, startIdx, initialChunkSize, limit, ftsConditions, compositeVerifyConditions, others, tx) {
11596
11881
  const verifyOthers = others.filter((o) => !o.isFtsMatch);
11597
- let currentChunkSize = initialChunkSize;
11882
+ const isFts = ftsConditions.length > 0;
11883
+ const isCompositeVerify = compositeVerifyConditions.length > 0;
11884
+ const isVerifyOthers = verifyOthers.length > 0;
11885
+ const isInfiniteLimit = isFinite(limit);
11886
+ let currentChunkSize = isInfiniteLimit ? initialChunkSize : limit;
11598
11887
  let chunk = [];
11888
+ let chunkSize = 0;
11599
11889
  let dropped = 0;
11600
11890
  const processChunk = async (pks) => {
11601
11891
  const docs = [];
@@ -11606,9 +11896,9 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
11606
11896
  if (!s) continue;
11607
11897
  const doc = JSON.parse(s);
11608
11898
  chunkTotalSize += s.length * 2;
11609
- if (ftsConditions.length > 0 && !this.verifyFts(doc, ftsConditions)) continue;
11610
- if (compositeVerifyConditions.length > 0 && this.verifyCompositeConditions(doc, compositeVerifyConditions) === false) continue;
11611
- if (verifyOthers.length > 0) {
11899
+ if (isFts && !this.verifyFts(doc, ftsConditions)) continue;
11900
+ if (isCompositeVerify && this.verifyCompositeConditions(doc, compositeVerifyConditions) === false) continue;
11901
+ if (isVerifyOthers) {
11612
11902
  const flatDoc = this.flattenDocument(doc);
11613
11903
  let passed = true;
11614
11904
  for (let k = 0, kLen = verifyOthers.length; k < kLen; k++) {
@@ -11628,7 +11918,9 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
11628
11918
  }
11629
11919
  docs.push(doc);
11630
11920
  }
11631
- currentChunkSize = this.adjustChunkSize(currentChunkSize, chunkTotalSize);
11921
+ if (isInfiniteLimit) {
11922
+ currentChunkSize = this.adjustChunkSize(currentChunkSize, chunkTotalSize);
11923
+ }
11632
11924
  return docs;
11633
11925
  };
11634
11926
  for await (const pk of keysStream) {
@@ -11637,15 +11929,17 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
11637
11929
  continue;
11638
11930
  }
11639
11931
  chunk.push(pk);
11640
- if (chunk.length >= currentChunkSize) {
11932
+ chunkSize++;
11933
+ if (chunkSize >= currentChunkSize) {
11641
11934
  const docs = await processChunk(chunk);
11642
- for (let j = 0; j < docs.length; j++) yield docs[j];
11935
+ for (let j = 0, dLen = docs.length; j < dLen; j++) yield docs[j];
11643
11936
  chunk = [];
11937
+ chunkSize = 0;
11644
11938
  }
11645
11939
  }
11646
- if (chunk.length > 0) {
11940
+ if (chunkSize > 0) {
11647
11941
  const docs = await processChunk(chunk);
11648
- for (let j = 0; j < docs.length; j++) yield docs[j];
11942
+ for (let j = 0, dLen = docs.length; j < dLen; j++) yield docs[j];
11649
11943
  }
11650
11944
  }
11651
11945
  /**
@@ -11673,7 +11967,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
11673
11967
  orderBy: orderByField
11674
11968
  } = options;
11675
11969
  const self = this;
11676
- const stream = this.streamWithDefault(async function* (tx2) {
11970
+ const stream = () => this.streamWithDefault(async function* (tx2) {
11677
11971
  const ftsConditions = [];
11678
11972
  for (const field in query) {
11679
11973
  const q = query[field];
@@ -11694,6 +11988,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
11694
11988
  const driverResult = await self.getDriverKeys(query, orderByField, sortOrder);
11695
11989
  if (!driverResult) return;
11696
11990
  const { keysStream, others, compositeVerifyConditions, isDriverOrderByField, rollback } = driverResult;
11991
+ const initialChunkSize = self.options.pageSize;
11697
11992
  try {
11698
11993
  if (!isDriverOrderByField && orderByField) {
11699
11994
  const topK = limit === Infinity ? Infinity : offset + limit;
@@ -11710,7 +12005,8 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
11710
12005
  for await (const doc of self.processChunkedKeysWithVerify(
11711
12006
  keysStream,
11712
12007
  0,
11713
- self.options.pageSize,
12008
+ initialChunkSize,
12009
+ Infinity,
11714
12010
  ftsConditions,
11715
12011
  compositeVerifyConditions,
11716
12012
  others,
@@ -11751,7 +12047,8 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
11751
12047
  for await (const doc of self.processChunkedKeysWithVerify(
11752
12048
  keysStream,
11753
12049
  startIdx,
11754
- self.options.pageSize,
12050
+ initialChunkSize,
12051
+ limit,
11755
12052
  ftsConditions,
11756
12053
  compositeVerifyConditions,
11757
12054
  others,
@@ -11772,7 +12069,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
11772
12069
  }, tx);
11773
12070
  const drain = async () => {
11774
12071
  const result = [];
11775
- for await (const document of stream) {
12072
+ for await (const document of stream()) {
11776
12073
  result.push(document);
11777
12074
  }
11778
12075
  return result;