document-dataply 0.0.9-alpha.11 → 0.0.9-alpha.13

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.
Files changed (2) hide show
  1. package/dist/cjs/index.js +435 -253
  2. package/package.json +3 -3
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,7 +1669,7 @@ 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.insertableRightestNodeByPrimary(v[0]),
1587
1673
  end: () => null,
1588
1674
  direction: 1,
1589
1675
  earlyTerminate: false
@@ -1681,7 +1767,7 @@ var require_cjs = __commonJS({
1681
1767
  },
1682
1768
  primaryGt: {
1683
1769
  asc: {
1684
- start: (tx, v) => tx.insertableRightestEndNodeByPrimary(v[0]),
1770
+ start: (tx, v) => tx.insertableRightestNodeByPrimary(v[0]),
1685
1771
  end: () => null,
1686
1772
  direction: 1,
1687
1773
  earlyTerminate: false
@@ -1743,7 +1829,7 @@ var require_cjs = __commonJS({
1743
1829
  earlyTerminate: true
1744
1830
  },
1745
1831
  desc: {
1746
- start: (tx, v) => tx.insertableRightestEndNodeByPrimary(v[0]),
1832
+ start: (tx, v) => tx.insertableRightestNodeByPrimary(v[0]),
1747
1833
  end: (tx, v) => tx.insertableEndNode(v[0], -1),
1748
1834
  direction: -1,
1749
1835
  earlyTerminate: true
@@ -1771,7 +1857,7 @@ var require_cjs = __commonJS({
1771
1857
  earlyTerminate: false
1772
1858
  },
1773
1859
  desc: {
1774
- start: (tx, v) => tx.insertableRightestEndNodeByPrimary(tx.highestPrimaryValue(v)),
1860
+ start: (tx, v) => tx.insertableRightestNodeByPrimary(tx.highestPrimaryValue(v)),
1775
1861
  end: (tx, v) => tx.insertableEndNode(tx.lowestPrimaryValue(v), -1),
1776
1862
  direction: -1,
1777
1863
  earlyTerminate: false
@@ -1928,30 +2014,62 @@ 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 (const key of startCandidates) {
2034
+ if (key in condition) {
2035
+ startKey = key;
2036
+ startValues = this.ensureValues(condition[key]);
2037
+ break;
2038
+ }
2039
+ }
2040
+ for (const key of endCandidates) {
2041
+ if (key in condition) {
2042
+ endKey = key;
2043
+ endValues = this.ensureValues(condition[key]);
2044
+ break;
2045
+ }
2046
+ }
2047
+ return { startKey, endKey, startValues, endValues, direction };
2048
+ }
2049
+ // Lower bound providers, ordered by selectivity (tightest first)
2050
+ // Used for asc start / desc end
2051
+ static _lowerBoundKeys = [
2052
+ "primaryEqual",
2053
+ "equal",
2054
+ "primaryGt",
2055
+ "gt",
2056
+ "primaryGte",
2057
+ "gte",
2058
+ "primaryOr",
2059
+ "or"
2060
+ ];
2061
+ // Upper bound providers, ordered by selectivity (tightest first)
2062
+ // Used for asc end / desc start
2063
+ static _upperBoundKeys = [
2064
+ "primaryEqual",
2065
+ "equal",
2066
+ "primaryLt",
2067
+ "lt",
2068
+ "primaryLte",
2069
+ "lte",
2070
+ "primaryOr",
2071
+ "or"
2072
+ ];
1955
2073
  constructor(rootTx, mvccRoot, mvcc, strategy, comparator, option) {
1956
2074
  this.rootTx = rootTx === null ? this : rootTx;
1957
2075
  this.mvccRoot = mvccRoot;
@@ -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
  }
@@ -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) {
@@ -3385,22 +3468,19 @@ var require_cjs = __commonJS({
3385
3468
  }
3386
3469
  return node;
3387
3470
  }
3388
- async *getPairsGenerator(value, startNode, endNode, comparator, direction, earlyTerminate) {
3471
+ async *getPairsGenerator(startNode, endNode, direction) {
3389
3472
  let node = startNode;
3390
- let done = false;
3391
- let hasMatched = false;
3392
3473
  let nextNodePromise = null;
3393
- while (!done) {
3474
+ while (true) {
3394
3475
  if (endNode && node.id === endNode.id) {
3395
- done = true;
3396
3476
  break;
3397
3477
  }
3398
3478
  if (direction === 1) {
3399
- if (node.next && !done) {
3479
+ if (node.next) {
3400
3480
  nextNodePromise = this.getNode(node.next);
3401
3481
  }
3402
3482
  } else {
3403
- if (node.prev && !done) {
3483
+ if (node.prev) {
3404
3484
  nextNodePromise = this.getNode(node.prev);
3405
3485
  }
3406
3486
  }
@@ -3409,14 +3489,8 @@ var require_cjs = __commonJS({
3409
3489
  for (let i = 0; i < len; i++) {
3410
3490
  const nValue = node.values[i];
3411
3491
  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;
3492
+ for (let j = 0, kLen = keys.length; j < kLen; j++) {
3493
+ yield [keys[j], nValue];
3420
3494
  }
3421
3495
  }
3422
3496
  } else {
@@ -3424,27 +3498,17 @@ var require_cjs = __commonJS({
3424
3498
  while (i--) {
3425
3499
  const nValue = node.values[i];
3426
3500
  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;
3501
+ let j = keys.length;
3502
+ while (j--) {
3503
+ yield [keys[j], nValue];
3436
3504
  }
3437
3505
  }
3438
3506
  }
3439
- if (done) {
3440
- if (nextNodePromise) await nextNodePromise;
3441
- break;
3442
- }
3443
3507
  if (nextNodePromise) {
3444
3508
  node = await nextNodePromise;
3445
3509
  nextNodePromise = null;
3446
3510
  } else {
3447
- done = true;
3511
+ break;
3448
3512
  }
3449
3513
  }
3450
3514
  }
@@ -3532,49 +3596,36 @@ var require_cjs = __commonJS({
3532
3596
  }
3533
3597
  async *whereStream(condition, options) {
3534
3598
  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();
3599
+ const conditionKeys = Object.keys(condition);
3600
+ if (conditionKeys.length === 0) return;
3601
+ const resolved = this.resolveStartEndConfigs(condition, order);
3602
+ const direction = resolved.direction;
3603
+ let startNode;
3604
+ if (resolved.startKey) {
3605
+ const startConfig = this.searchConfigs[resolved.startKey][order];
3606
+ startNode = await startConfig.start(this, resolved.startValues);
3607
+ } else {
3608
+ startNode = order === "asc" ? await this.leftestNode() : await this.rightestNode();
3609
+ }
3610
+ let endNode = null;
3611
+ if (resolved.endKey) {
3612
+ const endConfig = this.searchConfigs[resolved.endKey][order];
3613
+ endNode = await endConfig.end(this, resolved.endValues);
3549
3614
  }
3550
3615
  if (!startNode) return;
3551
- const comparator = this.verifierMap[driverKey];
3552
3616
  const generator = this.getPairsGenerator(
3553
- value,
3554
3617
  startNode,
3555
3618
  endNode,
3556
- comparator,
3557
- direction,
3558
- earlyTerminate
3619
+ direction
3559
3620
  );
3560
3621
  let count = 0;
3561
3622
  const intersection = filterValues && filterValues.size > 0 ? filterValues : null;
3562
3623
  for await (const pair of generator) {
3563
- const [k, v2] = pair;
3624
+ const [k, v] = pair;
3564
3625
  if (intersection && !intersection.has(k)) {
3565
3626
  continue;
3566
3627
  }
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) {
3628
+ if (this.verify(v, condition)) {
3578
3629
  yield pair;
3579
3630
  count++;
3580
3631
  if (limit !== void 0 && count >= limit) {
@@ -4869,39 +4920,125 @@ var require_cjs = __commonJS({
4869
4920
  var MVCCStrategy2 = class {
4870
4921
  };
4871
4922
  var LRUMap3 = class {
4872
- cache = /* @__PURE__ */ new Map();
4873
4923
  capacity;
4924
+ map;
4925
+ head = null;
4926
+ tail = null;
4927
+ /**
4928
+ * Creates an instance of LRUMap.
4929
+ * @param capacity The maximum number of items the cache can hold.
4930
+ */
4874
4931
  constructor(capacity) {
4875
4932
  this.capacity = capacity;
4933
+ this.map = /* @__PURE__ */ new Map();
4876
4934
  }
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;
4935
+ /**
4936
+ * Promotes a node to the head of the linked list (marks as most recently used).
4937
+ * @param node The node to promote.
4938
+ */
4939
+ promote(node) {
4940
+ this.extract(node);
4941
+ this.prepend(node);
4883
4942
  }
4943
+ /**
4944
+ * Disconnects a node from the doubly linked list.
4945
+ * @param node The node to extract.
4946
+ */
4947
+ extract(node) {
4948
+ if (node.prev) node.prev.next = node.next;
4949
+ else this.head = node.next;
4950
+ if (node.next) node.next.prev = node.prev;
4951
+ else this.tail = node.prev;
4952
+ node.prev = null;
4953
+ node.next = null;
4954
+ }
4955
+ /**
4956
+ * Inserts a node at the head of the doubly linked list.
4957
+ * @param node The node to prepend.
4958
+ */
4959
+ prepend(node) {
4960
+ node.next = this.head;
4961
+ if (this.head) this.head.prev = node;
4962
+ this.head = node;
4963
+ if (!this.tail) this.tail = node;
4964
+ }
4965
+ /**
4966
+ * Stores or updates a value by key.
4967
+ * If the capacity is exceeded, the least recently used item (tail) is removed.
4968
+ * @param key The key to store.
4969
+ * @param value The value to store.
4970
+ */
4884
4971
  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);
4972
+ const existing = this.map.get(key);
4973
+ if (existing) {
4974
+ existing.value = value;
4975
+ this.promote(existing);
4976
+ return;
4890
4977
  }
4891
- this.cache.set(key, value);
4892
- return this;
4978
+ const newNode = { key, value, prev: null, next: null };
4979
+ this.map.set(key, newNode);
4980
+ this.prepend(newNode);
4981
+ if (this.map.size > this.capacity && this.tail) {
4982
+ this.map.delete(this.tail.key);
4983
+ this.extract(this.tail);
4984
+ }
4985
+ }
4986
+ /**
4987
+ * Retrieves a value by key.
4988
+ * Accessing the item moves it to the "most recently used" position.
4989
+ * @param key The key to look for.
4990
+ * @returns The value associated with the key, or undefined if not found.
4991
+ */
4992
+ get(key) {
4993
+ const node = this.map.get(key);
4994
+ if (!node) return void 0;
4995
+ this.promote(node);
4996
+ return node.value;
4893
4997
  }
4998
+ /**
4999
+ * Checks if a key exists in the cache without changing its access order.
5000
+ * @param key The key to check.
5001
+ * @returns True if the key exists, false otherwise.
5002
+ */
4894
5003
  has(key) {
4895
- return this.cache.has(key);
5004
+ return this.map.has(key);
4896
5005
  }
5006
+ /**
5007
+ * Removes a key and its associated value from the cache.
5008
+ * @param key The key to remove.
5009
+ * @returns True if the key was found and removed, false otherwise.
5010
+ */
4897
5011
  delete(key) {
4898
- return this.cache.delete(key);
5012
+ const node = this.map.get(key);
5013
+ if (!node) return false;
5014
+ this.extract(node);
5015
+ this.map.delete(key);
5016
+ return true;
4899
5017
  }
4900
- clear() {
4901
- this.cache.clear();
5018
+ /**
5019
+ * Returns an iterator of keys in the order of most recently used to least recently used.
5020
+ * @returns An iterable iterator of keys.
5021
+ */
5022
+ *keys() {
5023
+ let current = this.head;
5024
+ while (current) {
5025
+ yield current.key;
5026
+ current = current.next;
5027
+ }
4902
5028
  }
5029
+ /**
5030
+ * Returns the current number of items in the cache.
5031
+ */
4903
5032
  get size() {
4904
- return this.cache.size;
5033
+ return this.map.size;
5034
+ }
5035
+ /**
5036
+ * Clears all items from the cache.
5037
+ */
5038
+ clear() {
5039
+ this.map.clear();
5040
+ this.head = null;
5041
+ this.tail = null;
4905
5042
  }
4906
5043
  };
4907
5044
  var MVCCTransaction2 = class {
@@ -6348,21 +6485,68 @@ var require_cjs = __commonJS({
6348
6485
  }
6349
6486
  return (crc ^ -1) >>> 0;
6350
6487
  }
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];
6488
+ function calcThreshold(sortedGaps, n) {
6489
+ const gLen = sortedGaps.length;
6490
+ if (gLen === 0) return 0;
6491
+ const median = sortedGaps[Math.floor(gLen * 0.5)];
6492
+ const q1 = sortedGaps[Math.floor(gLen * 0.25)];
6493
+ const q3 = sortedGaps[Math.floor(gLen * 0.75)];
6494
+ const iqr = q3 - q1;
6495
+ const logN = Math.max(1, Math.log10(n));
6496
+ if (iqr > 0) {
6497
+ const threshold2 = q3 + iqr * 1.5 * logN;
6498
+ const minJump = Math.max(median * 5, 20);
6499
+ return Math.max(threshold2, minJump);
6500
+ }
6501
+ const baseGap = median > 0 ? median : 1;
6502
+ const p90 = sortedGaps[Math.floor(gLen * 0.9)];
6503
+ if (p90 > baseGap) {
6504
+ const threshold2 = baseGap + (p90 - baseGap) * 0.5 * logN;
6505
+ return Math.max(threshold2, baseGap * 5, 20);
6506
+ }
6507
+ let mean = 0;
6508
+ for (let i = 0; i < gLen; i++) mean += sortedGaps[i];
6509
+ mean /= gLen;
6510
+ let variance = 0;
6511
+ for (let i = 0; i < gLen; i++) {
6512
+ const d = sortedGaps[i] - mean;
6513
+ variance += d * d;
6514
+ }
6515
+ const stddev = Math.sqrt(variance / gLen);
6516
+ if (stddev === 0) {
6517
+ return baseGap * 2;
6518
+ }
6519
+ const threshold = mean + stddev * logN;
6520
+ return Math.max(threshold, baseGap * 5, 20);
6521
+ }
6522
+ function clusterNumbers(numbers, gapMultiplier) {
6523
+ const n = numbers.length;
6524
+ if (n === 0) return [];
6525
+ if (n === 1) return [new Float64Array([numbers[0]])];
6526
+ const sorted = (numbers instanceof Float64Array ? numbers.slice() : Float64Array.from(numbers)).sort();
6527
+ const gaps = new Float64Array(n - 1);
6528
+ for (let i = 0, len = n - 1; i < len; i++) {
6529
+ gaps[i] = sorted[i + 1] - sorted[i];
6530
+ }
6531
+ const sortedGaps = gaps.slice().sort();
6532
+ let threshold;
6533
+ if (gapMultiplier !== void 0) {
6534
+ const q3 = sortedGaps[Math.floor((n - 1) * 0.75)];
6535
+ const iqr = q3 - sortedGaps[Math.floor((n - 1) * 0.25)];
6536
+ threshold = q3 + iqr * gapMultiplier;
6537
+ } else {
6538
+ threshold = calcThreshold(sortedGaps, n);
6539
+ }
6540
+ const clusters = [];
6541
+ let clusterStart = 0;
6542
+ for (let i = 0, len = n - 1; i < len; i++) {
6543
+ if (gaps[i] > threshold) {
6544
+ clusters.push(sorted.subarray(clusterStart, i + 1));
6545
+ clusterStart = i + 1;
6362
6546
  }
6363
- i++;
6364
6547
  }
6365
- return [min, max];
6548
+ clusters.push(sorted.subarray(clusterStart));
6549
+ return clusters;
6366
6550
  }
6367
6551
  var Row = class _Row {
6368
6552
  static CONSTANT = {
@@ -9302,14 +9486,30 @@ var require_cjs = __commonJS({
9302
9486
  for (let i = 0, len = pks.length; i < len; i++) {
9303
9487
  pkIndexMap.set(pks[i], i);
9304
9488
  }
9305
- const [minPk, maxPk] = getMinMaxValue(pks);
9306
9489
  const pkRidPairs = new Array(pks.length).fill(null);
9307
9490
  const btx = await this.getBPTreeTransaction(tx);
9308
- const stream = btx.whereStream({ gte: minPk, lte: maxPk });
9309
- for await (const [rid, pk] of stream) {
9310
- const index = pkIndexMap.get(pk);
9311
- if (index !== void 0) {
9312
- pkRidPairs[index] = { pk, rid, index };
9491
+ const clusters = clusterNumbers(pks);
9492
+ for (let i = 0, len = clusters.length; i < len; i++) {
9493
+ const cluster = clusters[i];
9494
+ const minPk = cluster[0];
9495
+ const maxPk = cluster[cluster.length - 1];
9496
+ if (minPk === maxPk) {
9497
+ const keys = await btx.keys({ equal: minPk });
9498
+ if (keys.size > 0) {
9499
+ const rid = keys.values().next().value;
9500
+ const index = pkIndexMap.get(minPk);
9501
+ if (index !== void 0) {
9502
+ pkRidPairs[index] = { pk: minPk, rid, index };
9503
+ }
9504
+ }
9505
+ continue;
9506
+ }
9507
+ const stream = btx.whereStream({ gte: minPk, lte: maxPk });
9508
+ for await (const [rid, pk] of stream) {
9509
+ const index = pkIndexMap.get(pk);
9510
+ if (index !== void 0) {
9511
+ pkRidPairs[index] = { pk, rid, index };
9512
+ }
9313
9513
  }
9314
9514
  }
9315
9515
  return this.fetchRowsByRids(pkRidPairs, tx);
@@ -10301,61 +10501,36 @@ var DocumentSerializeStrategyAsync = class extends import_dataply.SerializeStrat
10301
10501
 
10302
10502
  // src/core/bptree/documentComparator.ts
10303
10503
  var import_dataply2 = __toESM(require_cjs());
10304
- function comparePrimitive(a, b) {
10305
- if (a === b) return 0;
10306
- if (a === null) return -1;
10307
- if (b === null) return 1;
10308
- if (typeof a !== typeof b) {
10309
- const typeOrder = (v) => typeof v === "boolean" ? 0 : typeof v === "number" ? 1 : 2;
10310
- return typeOrder(a) - typeOrder(b);
10311
- }
10312
- if (typeof a === "string" && typeof b === "string") {
10313
- return a.localeCompare(b);
10504
+ function compareDiff(a, b) {
10505
+ if (typeof a !== "string" && typeof b !== "string") {
10506
+ return +a - +b;
10314
10507
  }
10315
- return +a - +b;
10508
+ return (a + "").localeCompare(b + "");
10316
10509
  }
10317
10510
  function compareValue(a, b) {
10318
10511
  const aArr = Array.isArray(a);
10319
10512
  const bArr = Array.isArray(b);
10320
10513
  if (!aArr && !bArr) {
10321
- return comparePrimitive(a, b);
10514
+ return compareDiff(a, b);
10322
10515
  }
10323
10516
  const aList = aArr ? a : [a];
10324
10517
  const bList = bArr ? b : [b];
10325
10518
  const len = Math.min(aList.length, bList.length);
10326
10519
  for (let i = 0; i < len; i++) {
10327
- const diff = comparePrimitive(aList[i], bList[i]);
10328
- if (diff !== 0) return diff;
10329
- }
10330
- return aList.length - bList.length;
10331
- }
10332
- function comparePrimaryValue(a, b) {
10333
- const aArr = Array.isArray(a);
10334
- const bArr = Array.isArray(b);
10335
- if (!aArr && !bArr) {
10336
- return comparePrimitive(a, b);
10337
- }
10338
- const aList = aArr ? a : [a];
10339
- const bList = bArr ? b : [b];
10340
- const len = Math.min(aList.length, bList.length);
10341
- for (let i = 0; i < len; i++) {
10342
- const diff = comparePrimitive(aList[i], bList[i]);
10520
+ const diff = compareDiff(aList[i], bList[i]);
10343
10521
  if (diff !== 0) return diff;
10344
10522
  }
10345
10523
  return 0;
10346
10524
  }
10347
10525
  var DocumentValueComparator = class extends import_dataply2.ValueComparator {
10348
10526
  primaryAsc(a, b) {
10349
- return comparePrimaryValue(a.v, b.v);
10527
+ return compareValue(a.v, b.v);
10350
10528
  }
10351
10529
  asc(a, b) {
10352
10530
  const diff = compareValue(a.v, b.v);
10353
10531
  return diff === 0 ? a.k - b.k : diff;
10354
10532
  }
10355
10533
  match(value) {
10356
- if (Array.isArray(value.v)) {
10357
- return value.v[0] + "";
10358
- }
10359
10534
  return value.v + "";
10360
10535
  }
10361
10536
  };
@@ -10598,8 +10773,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
10598
10773
  async registerIndexRuntime(name, option, tx) {
10599
10774
  const config = this.toIndexMetaConfig(option);
10600
10775
  if (this.registeredIndices.has(name)) {
10601
- const existing = this.registeredIndices.get(name);
10602
- if (JSON.stringify(existing) === JSON.stringify(config)) return;
10776
+ throw new Error(`Index "${name}" already exists.`);
10603
10777
  }
10604
10778
  await this.runWithDefaultWrite(async (tx2) => {
10605
10779
  const metadata = await this.getDocumentInnerMetadata(tx2);
@@ -10608,7 +10782,8 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
10608
10782
  this.indices = metadata.indices;
10609
10783
  this.registeredIndices.set(name, config);
10610
10784
  const fields = this.getFieldsFromConfig(config);
10611
- for (const field of fields) {
10785
+ for (let i = 0; i < fields.length; i++) {
10786
+ const field = fields[i];
10612
10787
  this.indexedFields.add(field);
10613
10788
  if (!this.fieldToIndices.has(field)) {
10614
10789
  this.fieldToIndices.set(field, []);
@@ -10640,14 +10815,14 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
10640
10815
  */
10641
10816
  async dropIndex(name, tx) {
10642
10817
  if (name === "_id") {
10643
- throw new Error("Cannot drop the _id index");
10818
+ throw new Error('Cannot drop the "_id" index.');
10644
10819
  }
10645
10820
  if (!this._initialized) {
10646
10821
  this.pendingCreateIndices.delete(name);
10647
10822
  return;
10648
10823
  }
10649
10824
  if (!this.registeredIndices.has(name)) {
10650
- throw new Error(`Index '${name}' does not exist`);
10825
+ throw new Error(`Index "${name}" does not exist.`);
10651
10826
  }
10652
10827
  await this.runWithDefaultWrite(async (tx2) => {
10653
10828
  const config = this.registeredIndices.get(name);
@@ -10657,7 +10832,8 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
10657
10832
  this.indices = metadata.indices;
10658
10833
  this.registeredIndices.delete(name);
10659
10834
  const fields = this.getFieldsFromConfig(config);
10660
- for (const field of fields) {
10835
+ for (let i = 0; i < fields.length; i++) {
10836
+ const field = fields[i];
10661
10837
  const indexNames = this.fieldToIndices.get(field);
10662
10838
  if (indexNames) {
10663
10839
  const filtered = indexNames.filter((n) => n !== name);
@@ -10688,7 +10864,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
10688
10864
  if (!Array.isArray(option.fields) || option.fields.length === 0) {
10689
10865
  throw new Error('btree index requires a non-empty "fields" array');
10690
10866
  }
10691
- for (let i = 0; i < option.fields.length; i++) {
10867
+ for (let i = 0, len = option.fields.length; i < len; i++) {
10692
10868
  if (typeof option.fields[i] !== "string" || option.fields[i].length === 0) {
10693
10869
  throw new Error(`btree index "fields[${i}]" must be a non-empty string, got: ${JSON.stringify(option.fields[i])}`);
10694
10870
  }
@@ -11135,7 +11311,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
11135
11311
  }
11136
11312
  candidates.sort((a, b) => b.score - a.score);
11137
11313
  const driver = candidates[0];
11138
- const others = candidates.slice(1);
11314
+ const others = candidates.slice(1).filter((c) => c.field !== driver.field);
11139
11315
  const compositeVerifyConditions = [];
11140
11316
  for (const field of driver.compositeVerifyFields) {
11141
11317
  if (query[field]) {
@@ -11609,8 +11785,12 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
11609
11785
  */
11610
11786
  async *processChunkedKeysWithVerify(keysStream, startIdx, initialChunkSize, ftsConditions, compositeVerifyConditions, others, tx) {
11611
11787
  const verifyOthers = others.filter((o) => !o.isFtsMatch);
11788
+ const isFts = ftsConditions.length > 0;
11789
+ const isCompositeVerify = compositeVerifyConditions.length > 0;
11790
+ const isVerifyOthers = verifyOthers.length > 0;
11612
11791
  let currentChunkSize = initialChunkSize;
11613
11792
  let chunk = [];
11793
+ let chunkSize = 0;
11614
11794
  let dropped = 0;
11615
11795
  const processChunk = async (pks) => {
11616
11796
  const docs = [];
@@ -11621,9 +11801,9 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
11621
11801
  if (!s) continue;
11622
11802
  const doc = JSON.parse(s);
11623
11803
  chunkTotalSize += s.length * 2;
11624
- if (ftsConditions.length > 0 && !this.verifyFts(doc, ftsConditions)) continue;
11625
- if (compositeVerifyConditions.length > 0 && this.verifyCompositeConditions(doc, compositeVerifyConditions) === false) continue;
11626
- if (verifyOthers.length > 0) {
11804
+ if (isFts && !this.verifyFts(doc, ftsConditions)) continue;
11805
+ if (isCompositeVerify && this.verifyCompositeConditions(doc, compositeVerifyConditions) === false) continue;
11806
+ if (isVerifyOthers) {
11627
11807
  const flatDoc = this.flattenDocument(doc);
11628
11808
  let passed = true;
11629
11809
  for (let k = 0, kLen = verifyOthers.length; k < kLen; k++) {
@@ -11652,15 +11832,17 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
11652
11832
  continue;
11653
11833
  }
11654
11834
  chunk.push(pk);
11655
- if (chunk.length >= currentChunkSize) {
11835
+ chunkSize++;
11836
+ if (chunkSize >= currentChunkSize) {
11656
11837
  const docs = await processChunk(chunk);
11657
- for (let j = 0; j < docs.length; j++) yield docs[j];
11838
+ for (let j = 0, dLen = docs.length; j < dLen; j++) yield docs[j];
11658
11839
  chunk = [];
11840
+ chunkSize = 0;
11659
11841
  }
11660
11842
  }
11661
- if (chunk.length > 0) {
11843
+ if (chunkSize > 0) {
11662
11844
  const docs = await processChunk(chunk);
11663
- for (let j = 0; j < docs.length; j++) yield docs[j];
11845
+ for (let j = 0, dLen = docs.length; j < dLen; j++) yield docs[j];
11664
11846
  }
11665
11847
  }
11666
11848
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "document-dataply",
3
- "version": "0.0.9-alpha.11",
3
+ "version": "0.0.9-alpha.13",
4
4
  "description": "Simple and powerful JSON document database supporting complex queries and flexible indexing policies.",
5
5
  "license": "MIT",
6
6
  "author": "izure <admin@izure.org>",
@@ -42,7 +42,7 @@
42
42
  "dataply"
43
43
  ],
44
44
  "dependencies": {
45
- "dataply": "^0.0.24-alpha.10"
45
+ "dataply": "^0.0.24-alpha.12"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/jest": "^30.0.0",
@@ -51,4 +51,4 @@
51
51
  "ts-jest": "^29.4.6",
52
52
  "typescript": "^5.9.3"
53
53
  }
54
- }
54
+ }