serializable-bptree 8.3.3 → 8.3.4

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.
@@ -94,39 +94,125 @@ var StringComparator = class extends ValueComparator {
94
94
  var MVCCStrategy = class {
95
95
  };
96
96
  var LRUMap = class {
97
- cache = /* @__PURE__ */ new Map();
98
97
  capacity;
98
+ map;
99
+ head = null;
100
+ tail = null;
101
+ /**
102
+ * Creates an instance of LRUMap.
103
+ * @param capacity The maximum number of items the cache can hold.
104
+ */
99
105
  constructor(capacity) {
100
106
  this.capacity = capacity;
107
+ this.map = /* @__PURE__ */ new Map();
101
108
  }
102
- get(key) {
103
- if (!this.cache.has(key)) return void 0;
104
- const value = this.cache.get(key);
105
- this.cache.delete(key);
106
- this.cache.set(key, value);
107
- return value;
109
+ /**
110
+ * Promotes a node to the head of the linked list (marks as most recently used).
111
+ * @param node The node to promote.
112
+ */
113
+ promote(node) {
114
+ this.extract(node);
115
+ this.prepend(node);
108
116
  }
117
+ /**
118
+ * Disconnects a node from the doubly linked list.
119
+ * @param node The node to extract.
120
+ */
121
+ extract(node) {
122
+ if (node.prev) node.prev.next = node.next;
123
+ else this.head = node.next;
124
+ if (node.next) node.next.prev = node.prev;
125
+ else this.tail = node.prev;
126
+ node.prev = null;
127
+ node.next = null;
128
+ }
129
+ /**
130
+ * Inserts a node at the head of the doubly linked list.
131
+ * @param node The node to prepend.
132
+ */
133
+ prepend(node) {
134
+ node.next = this.head;
135
+ if (this.head) this.head.prev = node;
136
+ this.head = node;
137
+ if (!this.tail) this.tail = node;
138
+ }
139
+ /**
140
+ * Stores or updates a value by key.
141
+ * If the capacity is exceeded, the least recently used item (tail) is removed.
142
+ * @param key The key to store.
143
+ * @param value The value to store.
144
+ */
109
145
  set(key, value) {
110
- if (this.cache.has(key)) {
111
- this.cache.delete(key);
112
- } else if (this.cache.size >= this.capacity) {
113
- const oldestKey = this.cache.keys().next().value;
114
- if (oldestKey !== void 0) this.cache.delete(oldestKey);
146
+ const existing = this.map.get(key);
147
+ if (existing) {
148
+ existing.value = value;
149
+ this.promote(existing);
150
+ return;
115
151
  }
116
- this.cache.set(key, value);
117
- return this;
152
+ const newNode = { key, value, prev: null, next: null };
153
+ this.map.set(key, newNode);
154
+ this.prepend(newNode);
155
+ if (this.map.size > this.capacity && this.tail) {
156
+ this.map.delete(this.tail.key);
157
+ this.extract(this.tail);
158
+ }
159
+ }
160
+ /**
161
+ * Retrieves a value by key.
162
+ * Accessing the item moves it to the "most recently used" position.
163
+ * @param key The key to look for.
164
+ * @returns The value associated with the key, or undefined if not found.
165
+ */
166
+ get(key) {
167
+ const node = this.map.get(key);
168
+ if (!node) return void 0;
169
+ this.promote(node);
170
+ return node.value;
118
171
  }
172
+ /**
173
+ * Checks if a key exists in the cache without changing its access order.
174
+ * @param key The key to check.
175
+ * @returns True if the key exists, false otherwise.
176
+ */
119
177
  has(key) {
120
- return this.cache.has(key);
178
+ return this.map.has(key);
121
179
  }
180
+ /**
181
+ * Removes a key and its associated value from the cache.
182
+ * @param key The key to remove.
183
+ * @returns True if the key was found and removed, false otherwise.
184
+ */
122
185
  delete(key) {
123
- return this.cache.delete(key);
186
+ const node = this.map.get(key);
187
+ if (!node) return false;
188
+ this.extract(node);
189
+ this.map.delete(key);
190
+ return true;
124
191
  }
125
- clear() {
126
- this.cache.clear();
192
+ /**
193
+ * Returns an iterator of keys in the order of most recently used to least recently used.
194
+ * @returns An iterable iterator of keys.
195
+ */
196
+ *keys() {
197
+ let current = this.head;
198
+ while (current) {
199
+ yield current.key;
200
+ current = current.next;
201
+ }
127
202
  }
203
+ /**
204
+ * Returns the current number of items in the cache.
205
+ */
128
206
  get size() {
129
- return this.cache.size;
207
+ return this.map.size;
208
+ }
209
+ /**
210
+ * Clears all items from the cache.
211
+ */
212
+ clear() {
213
+ this.map.clear();
214
+ this.head = null;
215
+ this.tail = null;
130
216
  }
131
217
  };
132
218
  var MVCCTransaction = class {
@@ -1522,7 +1608,7 @@ var BPTreeTransaction = class _BPTreeTransaction {
1522
1608
  searchConfigs = {
1523
1609
  gt: {
1524
1610
  asc: {
1525
- start: (tx, v) => tx.insertableNodeByPrimary(v[0]),
1611
+ start: (tx, v) => tx.insertableRightestNodeByPrimary(v[0]),
1526
1612
  end: () => null,
1527
1613
  direction: 1,
1528
1614
  earlyTerminate: false
@@ -1620,7 +1706,7 @@ var BPTreeTransaction = class _BPTreeTransaction {
1620
1706
  },
1621
1707
  primaryGt: {
1622
1708
  asc: {
1623
- start: (tx, v) => tx.insertableRightestEndNodeByPrimary(v[0]),
1709
+ start: (tx, v) => tx.insertableRightestNodeByPrimary(v[0]),
1624
1710
  end: () => null,
1625
1711
  direction: 1,
1626
1712
  earlyTerminate: false
@@ -1682,7 +1768,7 @@ var BPTreeTransaction = class _BPTreeTransaction {
1682
1768
  earlyTerminate: true
1683
1769
  },
1684
1770
  desc: {
1685
- start: (tx, v) => tx.insertableRightestEndNodeByPrimary(v[0]),
1771
+ start: (tx, v) => tx.insertableRightestNodeByPrimary(v[0]),
1686
1772
  end: (tx, v) => tx.insertableEndNode(v[0], -1),
1687
1773
  direction: -1,
1688
1774
  earlyTerminate: true
@@ -1710,7 +1796,7 @@ var BPTreeTransaction = class _BPTreeTransaction {
1710
1796
  earlyTerminate: false
1711
1797
  },
1712
1798
  desc: {
1713
- start: (tx, v) => tx.insertableRightestEndNodeByPrimary(tx.highestPrimaryValue(v)),
1799
+ start: (tx, v) => tx.insertableRightestNodeByPrimary(tx.highestPrimaryValue(v)),
1714
1800
  end: (tx, v) => tx.insertableEndNode(tx.lowestPrimaryValue(v), -1),
1715
1801
  direction: -1,
1716
1802
  earlyTerminate: false
@@ -1867,30 +1953,62 @@ var BPTreeTransaction = class _BPTreeTransaction {
1867
1953
  return JSON.parse(JSON.stringify(node));
1868
1954
  }
1869
1955
  /**
1870
- * Selects the best driver key from a condition object.
1871
- * The driver key determines the starting point and traversal direction for queries.
1872
- *
1956
+ * Resolves the best start/end configuration by independently examining
1957
+ * all conditions. Selects the tightest lower bound for start and the
1958
+ * tightest upper bound for end (in asc; reversed for desc).
1959
+ *
1873
1960
  * @param condition The condition to analyze.
1874
- * @returns The best driver key or null if no valid key found.
1961
+ * @param order The sort order ('asc' or 'desc').
1962
+ * @returns The resolved start/end keys, values, and traversal direction.
1875
1963
  */
1876
- getDriverKey(condition) {
1877
- if ("primaryEqual" in condition) return "primaryEqual";
1878
- if ("equal" in condition) return "equal";
1879
- if ("gt" in condition) return "gt";
1880
- if ("gte" in condition) return "gte";
1881
- if ("lt" in condition) return "lt";
1882
- if ("lte" in condition) return "lte";
1883
- if ("primaryGt" in condition) return "primaryGt";
1884
- if ("primaryGte" in condition) return "primaryGte";
1885
- if ("primaryLt" in condition) return "primaryLt";
1886
- if ("primaryLte" in condition) return "primaryLte";
1887
- if ("like" in condition) return "like";
1888
- if ("notEqual" in condition) return "notEqual";
1889
- if ("primaryNotEqual" in condition) return "primaryNotEqual";
1890
- if ("or" in condition) return "or";
1891
- if ("primaryOr" in condition) return "primaryOr";
1892
- return null;
1893
- }
1964
+ resolveStartEndConfigs(condition, order) {
1965
+ const direction = order === "asc" ? 1 : -1;
1966
+ const startCandidates = order === "asc" ? _BPTreeTransaction._lowerBoundKeys : _BPTreeTransaction._upperBoundKeys;
1967
+ const endCandidates = order === "asc" ? _BPTreeTransaction._upperBoundKeys : _BPTreeTransaction._lowerBoundKeys;
1968
+ let startKey = null;
1969
+ let endKey = null;
1970
+ let startValues = [];
1971
+ let endValues = [];
1972
+ for (const key of startCandidates) {
1973
+ if (key in condition) {
1974
+ startKey = key;
1975
+ startValues = this.ensureValues(condition[key]);
1976
+ break;
1977
+ }
1978
+ }
1979
+ for (const key of endCandidates) {
1980
+ if (key in condition) {
1981
+ endKey = key;
1982
+ endValues = this.ensureValues(condition[key]);
1983
+ break;
1984
+ }
1985
+ }
1986
+ return { startKey, endKey, startValues, endValues, direction };
1987
+ }
1988
+ // Lower bound providers, ordered by selectivity (tightest first)
1989
+ // Used for asc start / desc end
1990
+ static _lowerBoundKeys = [
1991
+ "primaryEqual",
1992
+ "equal",
1993
+ "primaryGt",
1994
+ "gt",
1995
+ "primaryGte",
1996
+ "gte",
1997
+ "primaryOr",
1998
+ "or"
1999
+ ];
2000
+ // Upper bound providers, ordered by selectivity (tightest first)
2001
+ // Used for asc end / desc start
2002
+ static _upperBoundKeys = [
2003
+ "primaryEqual",
2004
+ "equal",
2005
+ "primaryLt",
2006
+ "lt",
2007
+ "primaryLte",
2008
+ "lte",
2009
+ "primaryOr",
2010
+ "or"
2011
+ ];
1894
2012
  constructor(rootTx, mvccRoot, mvcc, strategy, comparator, option) {
1895
2013
  this.rootTx = rootTx === null ? this : rootTx;
1896
2014
  this.mvccRoot = mvccRoot;
@@ -2198,13 +2316,10 @@ var BPTreeSyncTransaction = class extends BPTreeTransaction {
2198
2316
  }
2199
2317
  return node;
2200
2318
  }
2201
- *getPairsGenerator(value, startNode, endNode, comparator, direction, earlyTerminate) {
2319
+ *getPairsGenerator(startNode, endNode, direction) {
2202
2320
  let node = startNode;
2203
- let done = false;
2204
- let hasMatched = false;
2205
- while (!done) {
2321
+ while (true) {
2206
2322
  if (endNode && node.id === endNode.id) {
2207
- done = true;
2208
2323
  break;
2209
2324
  }
2210
2325
  const len = node.values.length;
@@ -2212,14 +2327,8 @@ var BPTreeSyncTransaction = class extends BPTreeTransaction {
2212
2327
  for (let i = 0; i < len; i++) {
2213
2328
  const nValue = node.values[i];
2214
2329
  const keys = node.keys[i];
2215
- if (comparator(nValue, value)) {
2216
- hasMatched = true;
2217
- for (let j = 0; j < keys.length; j++) {
2218
- yield [keys[j], nValue];
2219
- }
2220
- } else if (earlyTerminate && hasMatched) {
2221
- done = true;
2222
- break;
2330
+ for (let j = 0, kLen = keys.length; j < kLen; j++) {
2331
+ yield [keys[j], nValue];
2223
2332
  }
2224
2333
  }
2225
2334
  } else {
@@ -2227,30 +2336,17 @@ var BPTreeSyncTransaction = class extends BPTreeTransaction {
2227
2336
  while (i--) {
2228
2337
  const nValue = node.values[i];
2229
2338
  const keys = node.keys[i];
2230
- if (comparator(nValue, value)) {
2231
- hasMatched = true;
2232
- let j = keys.length;
2233
- while (j--) {
2234
- yield [keys[j], nValue];
2235
- }
2236
- } else if (earlyTerminate && hasMatched) {
2237
- done = true;
2238
- break;
2339
+ let j = keys.length;
2340
+ while (j--) {
2341
+ yield [keys[j], nValue];
2239
2342
  }
2240
2343
  }
2241
2344
  }
2242
- if (done) break;
2243
2345
  if (direction === 1) {
2244
- if (!node.next) {
2245
- done = true;
2246
- break;
2247
- }
2346
+ if (!node.next) break;
2248
2347
  node = this.getNode(node.next);
2249
2348
  } else {
2250
- if (!node.prev) {
2251
- done = true;
2252
- break;
2253
- }
2349
+ if (!node.prev) break;
2254
2350
  node = this.getNode(node.prev);
2255
2351
  }
2256
2352
  }
@@ -2339,49 +2435,36 @@ var BPTreeSyncTransaction = class extends BPTreeTransaction {
2339
2435
  }
2340
2436
  *whereStream(condition, options) {
2341
2437
  const { filterValues, limit, order = "asc" } = options ?? {};
2342
- const driverKey = this.getDriverKey(condition);
2343
- if (!driverKey) return;
2344
- const value = condition[driverKey];
2345
- const v = this.ensureValues(value);
2346
- const config = this.searchConfigs[driverKey][order];
2347
- let startNode = config.start(this, v);
2348
- let endNode = config.end(this, v);
2349
- const direction = config.direction;
2350
- const earlyTerminate = config.earlyTerminate;
2351
- if (order === "desc" && !startNode) {
2352
- startNode = this.rightestNode();
2353
- }
2354
- if (order === "asc" && !startNode) {
2355
- startNode = this.leftestNode();
2438
+ const conditionKeys = Object.keys(condition);
2439
+ if (conditionKeys.length === 0) return;
2440
+ const resolved = this.resolveStartEndConfigs(condition, order);
2441
+ const direction = resolved.direction;
2442
+ let startNode;
2443
+ if (resolved.startKey) {
2444
+ const startConfig = this.searchConfigs[resolved.startKey][order];
2445
+ startNode = startConfig.start(this, resolved.startValues);
2446
+ } else {
2447
+ startNode = order === "asc" ? this.leftestNode() : this.rightestNode();
2448
+ }
2449
+ let endNode = null;
2450
+ if (resolved.endKey) {
2451
+ const endConfig = this.searchConfigs[resolved.endKey][order];
2452
+ endNode = endConfig.end(this, resolved.endValues);
2356
2453
  }
2357
2454
  if (!startNode) return;
2358
- const comparator = this.verifierMap[driverKey];
2359
2455
  const generator = this.getPairsGenerator(
2360
- value,
2361
2456
  startNode,
2362
2457
  endNode,
2363
- comparator,
2364
- direction,
2365
- earlyTerminate
2458
+ direction
2366
2459
  );
2367
2460
  let count = 0;
2368
2461
  const intersection = filterValues && filterValues.size > 0 ? filterValues : null;
2369
2462
  for (const pair of generator) {
2370
- const [k, v2] = pair;
2463
+ const [k, v] = pair;
2371
2464
  if (intersection && !intersection.has(k)) {
2372
2465
  continue;
2373
2466
  }
2374
- let isMatch = true;
2375
- for (const key in condition) {
2376
- if (key === driverKey) continue;
2377
- const verify = this.verifierMap[key];
2378
- const condValue = condition[key];
2379
- if (!verify(v2, condValue)) {
2380
- isMatch = false;
2381
- break;
2382
- }
2383
- }
2384
- if (isMatch) {
2467
+ if (this.verify(v, condition)) {
2385
2468
  yield pair;
2386
2469
  count++;
2387
2470
  if (limit !== void 0 && count >= limit) {
@@ -3334,22 +3417,19 @@ var BPTreeAsyncTransaction = class extends BPTreeTransaction {
3334
3417
  }
3335
3418
  return node;
3336
3419
  }
3337
- async *getPairsGenerator(value, startNode, endNode, comparator, direction, earlyTerminate) {
3420
+ async *getPairsGenerator(startNode, endNode, direction) {
3338
3421
  let node = startNode;
3339
- let done = false;
3340
- let hasMatched = false;
3341
3422
  let nextNodePromise = null;
3342
- while (!done) {
3423
+ while (true) {
3343
3424
  if (endNode && node.id === endNode.id) {
3344
- done = true;
3345
3425
  break;
3346
3426
  }
3347
3427
  if (direction === 1) {
3348
- if (node.next && !done) {
3428
+ if (node.next) {
3349
3429
  nextNodePromise = this.getNode(node.next);
3350
3430
  }
3351
3431
  } else {
3352
- if (node.prev && !done) {
3432
+ if (node.prev) {
3353
3433
  nextNodePromise = this.getNode(node.prev);
3354
3434
  }
3355
3435
  }
@@ -3358,14 +3438,8 @@ var BPTreeAsyncTransaction = class extends BPTreeTransaction {
3358
3438
  for (let i = 0; i < len; i++) {
3359
3439
  const nValue = node.values[i];
3360
3440
  const keys = node.keys[i];
3361
- if (comparator(nValue, value)) {
3362
- hasMatched = true;
3363
- for (let j = 0; j < keys.length; j++) {
3364
- yield [keys[j], nValue];
3365
- }
3366
- } else if (earlyTerminate && hasMatched) {
3367
- done = true;
3368
- break;
3441
+ for (let j = 0, kLen = keys.length; j < kLen; j++) {
3442
+ yield [keys[j], nValue];
3369
3443
  }
3370
3444
  }
3371
3445
  } else {
@@ -3373,27 +3447,17 @@ var BPTreeAsyncTransaction = class extends BPTreeTransaction {
3373
3447
  while (i--) {
3374
3448
  const nValue = node.values[i];
3375
3449
  const keys = node.keys[i];
3376
- if (comparator(nValue, value)) {
3377
- hasMatched = true;
3378
- let j = keys.length;
3379
- while (j--) {
3380
- yield [keys[j], nValue];
3381
- }
3382
- } else if (earlyTerminate && hasMatched) {
3383
- done = true;
3384
- break;
3450
+ let j = keys.length;
3451
+ while (j--) {
3452
+ yield [keys[j], nValue];
3385
3453
  }
3386
3454
  }
3387
3455
  }
3388
- if (done) {
3389
- if (nextNodePromise) await nextNodePromise;
3390
- break;
3391
- }
3392
3456
  if (nextNodePromise) {
3393
3457
  node = await nextNodePromise;
3394
3458
  nextNodePromise = null;
3395
3459
  } else {
3396
- done = true;
3460
+ break;
3397
3461
  }
3398
3462
  }
3399
3463
  }
@@ -3481,49 +3545,36 @@ var BPTreeAsyncTransaction = class extends BPTreeTransaction {
3481
3545
  }
3482
3546
  async *whereStream(condition, options) {
3483
3547
  const { filterValues, limit, order = "asc" } = options ?? {};
3484
- const driverKey = this.getDriverKey(condition);
3485
- if (!driverKey) return;
3486
- const value = condition[driverKey];
3487
- const v = this.ensureValues(value);
3488
- const config = this.searchConfigs[driverKey][order];
3489
- let startNode = await config.start(this, v);
3490
- let endNode = await config.end(this, v);
3491
- const direction = config.direction;
3492
- const earlyTerminate = config.earlyTerminate;
3493
- if (order === "desc" && !startNode) {
3494
- startNode = await this.rightestNode();
3495
- }
3496
- if (order === "asc" && !startNode) {
3497
- startNode = await this.leftestNode();
3548
+ const conditionKeys = Object.keys(condition);
3549
+ if (conditionKeys.length === 0) return;
3550
+ const resolved = this.resolveStartEndConfigs(condition, order);
3551
+ const direction = resolved.direction;
3552
+ let startNode;
3553
+ if (resolved.startKey) {
3554
+ const startConfig = this.searchConfigs[resolved.startKey][order];
3555
+ startNode = await startConfig.start(this, resolved.startValues);
3556
+ } else {
3557
+ startNode = order === "asc" ? await this.leftestNode() : await this.rightestNode();
3558
+ }
3559
+ let endNode = null;
3560
+ if (resolved.endKey) {
3561
+ const endConfig = this.searchConfigs[resolved.endKey][order];
3562
+ endNode = await endConfig.end(this, resolved.endValues);
3498
3563
  }
3499
3564
  if (!startNode) return;
3500
- const comparator = this.verifierMap[driverKey];
3501
3565
  const generator = this.getPairsGenerator(
3502
- value,
3503
3566
  startNode,
3504
3567
  endNode,
3505
- comparator,
3506
- direction,
3507
- earlyTerminate
3568
+ direction
3508
3569
  );
3509
3570
  let count = 0;
3510
3571
  const intersection = filterValues && filterValues.size > 0 ? filterValues : null;
3511
3572
  for await (const pair of generator) {
3512
- const [k, v2] = pair;
3573
+ const [k, v] = pair;
3513
3574
  if (intersection && !intersection.has(k)) {
3514
3575
  continue;
3515
3576
  }
3516
- let isMatch = true;
3517
- for (const key in condition) {
3518
- if (key === driverKey) continue;
3519
- const verify = this.verifierMap[key];
3520
- const condValue = condition[key];
3521
- if (!verify(v2, condValue)) {
3522
- isMatch = false;
3523
- break;
3524
- }
3525
- }
3526
- if (isMatch) {
3577
+ if (this.verify(v, condition)) {
3527
3578
  yield pair;
3528
3579
  count++;
3529
3580
  if (limit !== void 0 && count >= limit) {
@@ -58,39 +58,125 @@ var StringComparator = class extends ValueComparator {
58
58
  var MVCCStrategy = class {
59
59
  };
60
60
  var LRUMap = class {
61
- cache = /* @__PURE__ */ new Map();
62
61
  capacity;
62
+ map;
63
+ head = null;
64
+ tail = null;
65
+ /**
66
+ * Creates an instance of LRUMap.
67
+ * @param capacity The maximum number of items the cache can hold.
68
+ */
63
69
  constructor(capacity) {
64
70
  this.capacity = capacity;
71
+ this.map = /* @__PURE__ */ new Map();
65
72
  }
66
- get(key) {
67
- if (!this.cache.has(key)) return void 0;
68
- const value = this.cache.get(key);
69
- this.cache.delete(key);
70
- this.cache.set(key, value);
71
- return value;
73
+ /**
74
+ * Promotes a node to the head of the linked list (marks as most recently used).
75
+ * @param node The node to promote.
76
+ */
77
+ promote(node) {
78
+ this.extract(node);
79
+ this.prepend(node);
72
80
  }
81
+ /**
82
+ * Disconnects a node from the doubly linked list.
83
+ * @param node The node to extract.
84
+ */
85
+ extract(node) {
86
+ if (node.prev) node.prev.next = node.next;
87
+ else this.head = node.next;
88
+ if (node.next) node.next.prev = node.prev;
89
+ else this.tail = node.prev;
90
+ node.prev = null;
91
+ node.next = null;
92
+ }
93
+ /**
94
+ * Inserts a node at the head of the doubly linked list.
95
+ * @param node The node to prepend.
96
+ */
97
+ prepend(node) {
98
+ node.next = this.head;
99
+ if (this.head) this.head.prev = node;
100
+ this.head = node;
101
+ if (!this.tail) this.tail = node;
102
+ }
103
+ /**
104
+ * Stores or updates a value by key.
105
+ * If the capacity is exceeded, the least recently used item (tail) is removed.
106
+ * @param key The key to store.
107
+ * @param value The value to store.
108
+ */
73
109
  set(key, value) {
74
- if (this.cache.has(key)) {
75
- this.cache.delete(key);
76
- } else if (this.cache.size >= this.capacity) {
77
- const oldestKey = this.cache.keys().next().value;
78
- if (oldestKey !== void 0) this.cache.delete(oldestKey);
110
+ const existing = this.map.get(key);
111
+ if (existing) {
112
+ existing.value = value;
113
+ this.promote(existing);
114
+ return;
79
115
  }
80
- this.cache.set(key, value);
81
- return this;
116
+ const newNode = { key, value, prev: null, next: null };
117
+ this.map.set(key, newNode);
118
+ this.prepend(newNode);
119
+ if (this.map.size > this.capacity && this.tail) {
120
+ this.map.delete(this.tail.key);
121
+ this.extract(this.tail);
122
+ }
123
+ }
124
+ /**
125
+ * Retrieves a value by key.
126
+ * Accessing the item moves it to the "most recently used" position.
127
+ * @param key The key to look for.
128
+ * @returns The value associated with the key, or undefined if not found.
129
+ */
130
+ get(key) {
131
+ const node = this.map.get(key);
132
+ if (!node) return void 0;
133
+ this.promote(node);
134
+ return node.value;
82
135
  }
136
+ /**
137
+ * Checks if a key exists in the cache without changing its access order.
138
+ * @param key The key to check.
139
+ * @returns True if the key exists, false otherwise.
140
+ */
83
141
  has(key) {
84
- return this.cache.has(key);
142
+ return this.map.has(key);
85
143
  }
144
+ /**
145
+ * Removes a key and its associated value from the cache.
146
+ * @param key The key to remove.
147
+ * @returns True if the key was found and removed, false otherwise.
148
+ */
86
149
  delete(key) {
87
- return this.cache.delete(key);
150
+ const node = this.map.get(key);
151
+ if (!node) return false;
152
+ this.extract(node);
153
+ this.map.delete(key);
154
+ return true;
88
155
  }
89
- clear() {
90
- this.cache.clear();
156
+ /**
157
+ * Returns an iterator of keys in the order of most recently used to least recently used.
158
+ * @returns An iterable iterator of keys.
159
+ */
160
+ *keys() {
161
+ let current = this.head;
162
+ while (current) {
163
+ yield current.key;
164
+ current = current.next;
165
+ }
91
166
  }
167
+ /**
168
+ * Returns the current number of items in the cache.
169
+ */
92
170
  get size() {
93
- return this.cache.size;
171
+ return this.map.size;
172
+ }
173
+ /**
174
+ * Clears all items from the cache.
175
+ */
176
+ clear() {
177
+ this.map.clear();
178
+ this.head = null;
179
+ this.tail = null;
94
180
  }
95
181
  };
96
182
  var MVCCTransaction = class {
@@ -1486,7 +1572,7 @@ var BPTreeTransaction = class _BPTreeTransaction {
1486
1572
  searchConfigs = {
1487
1573
  gt: {
1488
1574
  asc: {
1489
- start: (tx, v) => tx.insertableNodeByPrimary(v[0]),
1575
+ start: (tx, v) => tx.insertableRightestNodeByPrimary(v[0]),
1490
1576
  end: () => null,
1491
1577
  direction: 1,
1492
1578
  earlyTerminate: false
@@ -1584,7 +1670,7 @@ var BPTreeTransaction = class _BPTreeTransaction {
1584
1670
  },
1585
1671
  primaryGt: {
1586
1672
  asc: {
1587
- start: (tx, v) => tx.insertableRightestEndNodeByPrimary(v[0]),
1673
+ start: (tx, v) => tx.insertableRightestNodeByPrimary(v[0]),
1588
1674
  end: () => null,
1589
1675
  direction: 1,
1590
1676
  earlyTerminate: false
@@ -1646,7 +1732,7 @@ var BPTreeTransaction = class _BPTreeTransaction {
1646
1732
  earlyTerminate: true
1647
1733
  },
1648
1734
  desc: {
1649
- start: (tx, v) => tx.insertableRightestEndNodeByPrimary(v[0]),
1735
+ start: (tx, v) => tx.insertableRightestNodeByPrimary(v[0]),
1650
1736
  end: (tx, v) => tx.insertableEndNode(v[0], -1),
1651
1737
  direction: -1,
1652
1738
  earlyTerminate: true
@@ -1674,7 +1760,7 @@ var BPTreeTransaction = class _BPTreeTransaction {
1674
1760
  earlyTerminate: false
1675
1761
  },
1676
1762
  desc: {
1677
- start: (tx, v) => tx.insertableRightestEndNodeByPrimary(tx.highestPrimaryValue(v)),
1763
+ start: (tx, v) => tx.insertableRightestNodeByPrimary(tx.highestPrimaryValue(v)),
1678
1764
  end: (tx, v) => tx.insertableEndNode(tx.lowestPrimaryValue(v), -1),
1679
1765
  direction: -1,
1680
1766
  earlyTerminate: false
@@ -1831,30 +1917,62 @@ var BPTreeTransaction = class _BPTreeTransaction {
1831
1917
  return JSON.parse(JSON.stringify(node));
1832
1918
  }
1833
1919
  /**
1834
- * Selects the best driver key from a condition object.
1835
- * The driver key determines the starting point and traversal direction for queries.
1836
- *
1920
+ * Resolves the best start/end configuration by independently examining
1921
+ * all conditions. Selects the tightest lower bound for start and the
1922
+ * tightest upper bound for end (in asc; reversed for desc).
1923
+ *
1837
1924
  * @param condition The condition to analyze.
1838
- * @returns The best driver key or null if no valid key found.
1925
+ * @param order The sort order ('asc' or 'desc').
1926
+ * @returns The resolved start/end keys, values, and traversal direction.
1839
1927
  */
1840
- getDriverKey(condition) {
1841
- if ("primaryEqual" in condition) return "primaryEqual";
1842
- if ("equal" in condition) return "equal";
1843
- if ("gt" in condition) return "gt";
1844
- if ("gte" in condition) return "gte";
1845
- if ("lt" in condition) return "lt";
1846
- if ("lte" in condition) return "lte";
1847
- if ("primaryGt" in condition) return "primaryGt";
1848
- if ("primaryGte" in condition) return "primaryGte";
1849
- if ("primaryLt" in condition) return "primaryLt";
1850
- if ("primaryLte" in condition) return "primaryLte";
1851
- if ("like" in condition) return "like";
1852
- if ("notEqual" in condition) return "notEqual";
1853
- if ("primaryNotEqual" in condition) return "primaryNotEqual";
1854
- if ("or" in condition) return "or";
1855
- if ("primaryOr" in condition) return "primaryOr";
1856
- return null;
1857
- }
1928
+ resolveStartEndConfigs(condition, order) {
1929
+ const direction = order === "asc" ? 1 : -1;
1930
+ const startCandidates = order === "asc" ? _BPTreeTransaction._lowerBoundKeys : _BPTreeTransaction._upperBoundKeys;
1931
+ const endCandidates = order === "asc" ? _BPTreeTransaction._upperBoundKeys : _BPTreeTransaction._lowerBoundKeys;
1932
+ let startKey = null;
1933
+ let endKey = null;
1934
+ let startValues = [];
1935
+ let endValues = [];
1936
+ for (const key of startCandidates) {
1937
+ if (key in condition) {
1938
+ startKey = key;
1939
+ startValues = this.ensureValues(condition[key]);
1940
+ break;
1941
+ }
1942
+ }
1943
+ for (const key of endCandidates) {
1944
+ if (key in condition) {
1945
+ endKey = key;
1946
+ endValues = this.ensureValues(condition[key]);
1947
+ break;
1948
+ }
1949
+ }
1950
+ return { startKey, endKey, startValues, endValues, direction };
1951
+ }
1952
+ // Lower bound providers, ordered by selectivity (tightest first)
1953
+ // Used for asc start / desc end
1954
+ static _lowerBoundKeys = [
1955
+ "primaryEqual",
1956
+ "equal",
1957
+ "primaryGt",
1958
+ "gt",
1959
+ "primaryGte",
1960
+ "gte",
1961
+ "primaryOr",
1962
+ "or"
1963
+ ];
1964
+ // Upper bound providers, ordered by selectivity (tightest first)
1965
+ // Used for asc end / desc start
1966
+ static _upperBoundKeys = [
1967
+ "primaryEqual",
1968
+ "equal",
1969
+ "primaryLt",
1970
+ "lt",
1971
+ "primaryLte",
1972
+ "lte",
1973
+ "primaryOr",
1974
+ "or"
1975
+ ];
1858
1976
  constructor(rootTx, mvccRoot, mvcc, strategy, comparator, option) {
1859
1977
  this.rootTx = rootTx === null ? this : rootTx;
1860
1978
  this.mvccRoot = mvccRoot;
@@ -2162,13 +2280,10 @@ var BPTreeSyncTransaction = class extends BPTreeTransaction {
2162
2280
  }
2163
2281
  return node;
2164
2282
  }
2165
- *getPairsGenerator(value, startNode, endNode, comparator, direction, earlyTerminate) {
2283
+ *getPairsGenerator(startNode, endNode, direction) {
2166
2284
  let node = startNode;
2167
- let done = false;
2168
- let hasMatched = false;
2169
- while (!done) {
2285
+ while (true) {
2170
2286
  if (endNode && node.id === endNode.id) {
2171
- done = true;
2172
2287
  break;
2173
2288
  }
2174
2289
  const len = node.values.length;
@@ -2176,14 +2291,8 @@ var BPTreeSyncTransaction = class extends BPTreeTransaction {
2176
2291
  for (let i = 0; i < len; i++) {
2177
2292
  const nValue = node.values[i];
2178
2293
  const keys = node.keys[i];
2179
- if (comparator(nValue, value)) {
2180
- hasMatched = true;
2181
- for (let j = 0; j < keys.length; j++) {
2182
- yield [keys[j], nValue];
2183
- }
2184
- } else if (earlyTerminate && hasMatched) {
2185
- done = true;
2186
- break;
2294
+ for (let j = 0, kLen = keys.length; j < kLen; j++) {
2295
+ yield [keys[j], nValue];
2187
2296
  }
2188
2297
  }
2189
2298
  } else {
@@ -2191,30 +2300,17 @@ var BPTreeSyncTransaction = class extends BPTreeTransaction {
2191
2300
  while (i--) {
2192
2301
  const nValue = node.values[i];
2193
2302
  const keys = node.keys[i];
2194
- if (comparator(nValue, value)) {
2195
- hasMatched = true;
2196
- let j = keys.length;
2197
- while (j--) {
2198
- yield [keys[j], nValue];
2199
- }
2200
- } else if (earlyTerminate && hasMatched) {
2201
- done = true;
2202
- break;
2303
+ let j = keys.length;
2304
+ while (j--) {
2305
+ yield [keys[j], nValue];
2203
2306
  }
2204
2307
  }
2205
2308
  }
2206
- if (done) break;
2207
2309
  if (direction === 1) {
2208
- if (!node.next) {
2209
- done = true;
2210
- break;
2211
- }
2310
+ if (!node.next) break;
2212
2311
  node = this.getNode(node.next);
2213
2312
  } else {
2214
- if (!node.prev) {
2215
- done = true;
2216
- break;
2217
- }
2313
+ if (!node.prev) break;
2218
2314
  node = this.getNode(node.prev);
2219
2315
  }
2220
2316
  }
@@ -2303,49 +2399,36 @@ var BPTreeSyncTransaction = class extends BPTreeTransaction {
2303
2399
  }
2304
2400
  *whereStream(condition, options) {
2305
2401
  const { filterValues, limit, order = "asc" } = options ?? {};
2306
- const driverKey = this.getDriverKey(condition);
2307
- if (!driverKey) return;
2308
- const value = condition[driverKey];
2309
- const v = this.ensureValues(value);
2310
- const config = this.searchConfigs[driverKey][order];
2311
- let startNode = config.start(this, v);
2312
- let endNode = config.end(this, v);
2313
- const direction = config.direction;
2314
- const earlyTerminate = config.earlyTerminate;
2315
- if (order === "desc" && !startNode) {
2316
- startNode = this.rightestNode();
2317
- }
2318
- if (order === "asc" && !startNode) {
2319
- startNode = this.leftestNode();
2402
+ const conditionKeys = Object.keys(condition);
2403
+ if (conditionKeys.length === 0) return;
2404
+ const resolved = this.resolveStartEndConfigs(condition, order);
2405
+ const direction = resolved.direction;
2406
+ let startNode;
2407
+ if (resolved.startKey) {
2408
+ const startConfig = this.searchConfigs[resolved.startKey][order];
2409
+ startNode = startConfig.start(this, resolved.startValues);
2410
+ } else {
2411
+ startNode = order === "asc" ? this.leftestNode() : this.rightestNode();
2412
+ }
2413
+ let endNode = null;
2414
+ if (resolved.endKey) {
2415
+ const endConfig = this.searchConfigs[resolved.endKey][order];
2416
+ endNode = endConfig.end(this, resolved.endValues);
2320
2417
  }
2321
2418
  if (!startNode) return;
2322
- const comparator = this.verifierMap[driverKey];
2323
2419
  const generator = this.getPairsGenerator(
2324
- value,
2325
2420
  startNode,
2326
2421
  endNode,
2327
- comparator,
2328
- direction,
2329
- earlyTerminate
2422
+ direction
2330
2423
  );
2331
2424
  let count = 0;
2332
2425
  const intersection = filterValues && filterValues.size > 0 ? filterValues : null;
2333
2426
  for (const pair of generator) {
2334
- const [k, v2] = pair;
2427
+ const [k, v] = pair;
2335
2428
  if (intersection && !intersection.has(k)) {
2336
2429
  continue;
2337
2430
  }
2338
- let isMatch = true;
2339
- for (const key in condition) {
2340
- if (key === driverKey) continue;
2341
- const verify = this.verifierMap[key];
2342
- const condValue = condition[key];
2343
- if (!verify(v2, condValue)) {
2344
- isMatch = false;
2345
- break;
2346
- }
2347
- }
2348
- if (isMatch) {
2431
+ if (this.verify(v, condition)) {
2349
2432
  yield pair;
2350
2433
  count++;
2351
2434
  if (limit !== void 0 && count >= limit) {
@@ -3298,22 +3381,19 @@ var BPTreeAsyncTransaction = class extends BPTreeTransaction {
3298
3381
  }
3299
3382
  return node;
3300
3383
  }
3301
- async *getPairsGenerator(value, startNode, endNode, comparator, direction, earlyTerminate) {
3384
+ async *getPairsGenerator(startNode, endNode, direction) {
3302
3385
  let node = startNode;
3303
- let done = false;
3304
- let hasMatched = false;
3305
3386
  let nextNodePromise = null;
3306
- while (!done) {
3387
+ while (true) {
3307
3388
  if (endNode && node.id === endNode.id) {
3308
- done = true;
3309
3389
  break;
3310
3390
  }
3311
3391
  if (direction === 1) {
3312
- if (node.next && !done) {
3392
+ if (node.next) {
3313
3393
  nextNodePromise = this.getNode(node.next);
3314
3394
  }
3315
3395
  } else {
3316
- if (node.prev && !done) {
3396
+ if (node.prev) {
3317
3397
  nextNodePromise = this.getNode(node.prev);
3318
3398
  }
3319
3399
  }
@@ -3322,14 +3402,8 @@ var BPTreeAsyncTransaction = class extends BPTreeTransaction {
3322
3402
  for (let i = 0; i < len; i++) {
3323
3403
  const nValue = node.values[i];
3324
3404
  const keys = node.keys[i];
3325
- if (comparator(nValue, value)) {
3326
- hasMatched = true;
3327
- for (let j = 0; j < keys.length; j++) {
3328
- yield [keys[j], nValue];
3329
- }
3330
- } else if (earlyTerminate && hasMatched) {
3331
- done = true;
3332
- break;
3405
+ for (let j = 0, kLen = keys.length; j < kLen; j++) {
3406
+ yield [keys[j], nValue];
3333
3407
  }
3334
3408
  }
3335
3409
  } else {
@@ -3337,27 +3411,17 @@ var BPTreeAsyncTransaction = class extends BPTreeTransaction {
3337
3411
  while (i--) {
3338
3412
  const nValue = node.values[i];
3339
3413
  const keys = node.keys[i];
3340
- if (comparator(nValue, value)) {
3341
- hasMatched = true;
3342
- let j = keys.length;
3343
- while (j--) {
3344
- yield [keys[j], nValue];
3345
- }
3346
- } else if (earlyTerminate && hasMatched) {
3347
- done = true;
3348
- break;
3414
+ let j = keys.length;
3415
+ while (j--) {
3416
+ yield [keys[j], nValue];
3349
3417
  }
3350
3418
  }
3351
3419
  }
3352
- if (done) {
3353
- if (nextNodePromise) await nextNodePromise;
3354
- break;
3355
- }
3356
3420
  if (nextNodePromise) {
3357
3421
  node = await nextNodePromise;
3358
3422
  nextNodePromise = null;
3359
3423
  } else {
3360
- done = true;
3424
+ break;
3361
3425
  }
3362
3426
  }
3363
3427
  }
@@ -3445,49 +3509,36 @@ var BPTreeAsyncTransaction = class extends BPTreeTransaction {
3445
3509
  }
3446
3510
  async *whereStream(condition, options) {
3447
3511
  const { filterValues, limit, order = "asc" } = options ?? {};
3448
- const driverKey = this.getDriverKey(condition);
3449
- if (!driverKey) return;
3450
- const value = condition[driverKey];
3451
- const v = this.ensureValues(value);
3452
- const config = this.searchConfigs[driverKey][order];
3453
- let startNode = await config.start(this, v);
3454
- let endNode = await config.end(this, v);
3455
- const direction = config.direction;
3456
- const earlyTerminate = config.earlyTerminate;
3457
- if (order === "desc" && !startNode) {
3458
- startNode = await this.rightestNode();
3459
- }
3460
- if (order === "asc" && !startNode) {
3461
- startNode = await this.leftestNode();
3512
+ const conditionKeys = Object.keys(condition);
3513
+ if (conditionKeys.length === 0) return;
3514
+ const resolved = this.resolveStartEndConfigs(condition, order);
3515
+ const direction = resolved.direction;
3516
+ let startNode;
3517
+ if (resolved.startKey) {
3518
+ const startConfig = this.searchConfigs[resolved.startKey][order];
3519
+ startNode = await startConfig.start(this, resolved.startValues);
3520
+ } else {
3521
+ startNode = order === "asc" ? await this.leftestNode() : await this.rightestNode();
3522
+ }
3523
+ let endNode = null;
3524
+ if (resolved.endKey) {
3525
+ const endConfig = this.searchConfigs[resolved.endKey][order];
3526
+ endNode = await endConfig.end(this, resolved.endValues);
3462
3527
  }
3463
3528
  if (!startNode) return;
3464
- const comparator = this.verifierMap[driverKey];
3465
3529
  const generator = this.getPairsGenerator(
3466
- value,
3467
3530
  startNode,
3468
3531
  endNode,
3469
- comparator,
3470
- direction,
3471
- earlyTerminate
3532
+ direction
3472
3533
  );
3473
3534
  let count = 0;
3474
3535
  const intersection = filterValues && filterValues.size > 0 ? filterValues : null;
3475
3536
  for await (const pair of generator) {
3476
- const [k, v2] = pair;
3537
+ const [k, v] = pair;
3477
3538
  if (intersection && !intersection.has(k)) {
3478
3539
  continue;
3479
3540
  }
3480
- let isMatch = true;
3481
- for (const key in condition) {
3482
- if (key === driverKey) continue;
3483
- const verify = this.verifierMap[key];
3484
- const condValue = condition[key];
3485
- if (!verify(v2, condValue)) {
3486
- isMatch = false;
3487
- break;
3488
- }
3489
- }
3490
- if (isMatch) {
3541
+ if (this.verify(v, condition)) {
3491
3542
  yield pair;
3492
3543
  count++;
3493
3544
  if (limit !== void 0 && count >= limit) {
@@ -86,13 +86,23 @@ export declare abstract class BPTreeTransaction<K, V> {
86
86
  protected _insertValueIntoLeaf(leaf: BPTreeLeafNode<K, V>, key: K, value: V): boolean;
87
87
  protected _cloneNode<T extends BPTreeUnknownNode<K, V>>(node: T): T;
88
88
  /**
89
- * Selects the best driver key from a condition object.
90
- * The driver key determines the starting point and traversal direction for queries.
89
+ * Resolves the best start/end configuration by independently examining
90
+ * all conditions. Selects the tightest lower bound for start and the
91
+ * tightest upper bound for end (in asc; reversed for desc).
91
92
  *
92
93
  * @param condition The condition to analyze.
93
- * @returns The best driver key or null if no valid key found.
94
- */
95
- protected getDriverKey(condition: BPTreeCondition<V>): keyof BPTreeCondition<V> | null;
94
+ * @param order The sort order ('asc' or 'desc').
95
+ * @returns The resolved start/end keys, values, and traversal direction.
96
+ */
97
+ resolveStartEndConfigs(condition: BPTreeCondition<V>, order: 'asc' | 'desc'): {
98
+ startKey: keyof BPTreeCondition<V> | null;
99
+ endKey: keyof BPTreeCondition<V> | null;
100
+ startValues: V[];
101
+ endValues: V[];
102
+ direction: 1 | -1;
103
+ };
104
+ private static readonly _lowerBoundKeys;
105
+ private static readonly _upperBoundKeys;
96
106
  protected constructor(rootTx: BPTreeTransaction<K, V> | null, mvccRoot: BPTreeMVCC<K, V>, mvcc: BPTreeMVCC<K, V>, strategy: SerializeStrategy<K, V>, comparator: ValueComparator<V>, option?: BPTreeConstructorOption);
97
107
  protected abstract _createNode(leaf: boolean, keys: string[] | K[][], values: V[], parent?: string | null, next?: string | null, prev?: string | null): Deferred<BPTreeUnknownNode<K, V>>;
98
108
  protected abstract _deleteEntry(node: BPTreeUnknownNode<K, V>, key: BPTreeNodeKey<K>): Deferred<BPTreeUnknownNode<K, V>>;
@@ -32,7 +32,7 @@ export declare class BPTreeAsyncTransaction<K, V> extends BPTreeTransaction<K, V
32
32
  protected insertableEndNode(value: V, direction: 1 | -1): Promise<BPTreeLeafNode<K, V> | null>;
33
33
  protected leftestNode(): Promise<BPTreeLeafNode<K, V>>;
34
34
  protected rightestNode(): Promise<BPTreeLeafNode<K, V>>;
35
- protected getPairsGenerator(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean, direction: 1 | -1, earlyTerminate: boolean): AsyncGenerator<[K, V]>;
35
+ protected getPairsGenerator(startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, direction: 1 | -1): AsyncGenerator<[K, V]>;
36
36
  init(): Promise<void>;
37
37
  protected _initInternal(): Promise<void>;
38
38
  exists(key: K, value: V): Promise<boolean>;
@@ -29,7 +29,7 @@ export declare class BPTreeSyncTransaction<K, V> extends BPTreeTransaction<K, V>
29
29
  protected insertableEndNode(value: V, direction: 1 | -1): BPTreeLeafNode<K, V> | null;
30
30
  protected leftestNode(): BPTreeLeafNode<K, V>;
31
31
  protected rightestNode(): BPTreeLeafNode<K, V>;
32
- protected getPairsGenerator(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean, direction: 1 | -1, earlyTerminate: boolean): Generator<[K, V]>;
32
+ protected getPairsGenerator(startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, direction: 1 | -1): Generator<[K, V]>;
33
33
  init(): void;
34
34
  protected _initInternal(): void;
35
35
  exists(key: K, value: V): boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "serializable-bptree",
3
- "version": "8.3.3",
3
+ "version": "8.3.4",
4
4
  "description": "Store the B+tree flexibly, not only in-memory.",
5
5
  "types": "./dist/types/index.d.ts",
6
6
  "main": "./dist/cjs/index.cjs",
@@ -44,7 +44,7 @@
44
44
  "typescript": "^5.9.3"
45
45
  },
46
46
  "dependencies": {
47
- "mvcc-api": "^1.3.4",
47
+ "mvcc-api": "^1.3.5",
48
48
  "ryoiki": "^1.2.0"
49
49
  }
50
- }
50
+ }