dataply 0.0.24-alpha.0 → 0.0.24-alpha.2

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
@@ -1549,83 +1549,217 @@ var BPTreeTransaction = class _BPTreeTransaction {
1549
1549
  return regexp.test(nodeValue);
1550
1550
  }
1551
1551
  };
1552
- verifierStartNode = {
1553
- gt: (v) => this.insertableNodeByPrimary(v),
1554
- gte: (v) => this.insertableNodeByPrimary(v),
1555
- lt: (v) => this.insertableNodeByPrimary(v),
1556
- lte: (v) => this.insertableRightestNodeByPrimary(v),
1557
- equal: (v) => this.insertableNodeByPrimary(v),
1558
- notEqual: (v) => this.leftestNode(),
1559
- or: (v) => this.insertableNodeByPrimary(this.lowestPrimaryValue(this.ensureValues(v))),
1560
- primaryGt: (v) => this.insertableNodeByPrimary(v),
1561
- primaryGte: (v) => this.insertableNodeByPrimary(v),
1562
- primaryLt: (v) => this.insertableNodeByPrimary(v),
1563
- primaryLte: (v) => this.insertableRightestNodeByPrimary(v),
1564
- primaryEqual: (v) => this.insertableNodeByPrimary(v),
1565
- primaryNotEqual: (v) => this.leftestNode(),
1566
- primaryOr: (v) => this.insertableNodeByPrimary(this.lowestPrimaryValue(this.ensureValues(v))),
1567
- like: (v) => this.leftestNode()
1568
- };
1569
- verifierEndNode = {
1570
- gt: (v) => null,
1571
- gte: (v) => null,
1572
- lt: (v) => null,
1573
- lte: (v) => null,
1574
- equal: (v) => this.insertableEndNode(v, this.verifierDirection.equal),
1575
- notEqual: (v) => null,
1576
- or: (v) => this.insertableEndNode(
1577
- this.highestValue(this.ensureValues(v)),
1578
- this.verifierDirection.or
1579
- ),
1580
- primaryGt: (v) => null,
1581
- primaryGte: (v) => null,
1582
- primaryLt: (v) => null,
1583
- primaryLte: (v) => null,
1584
- primaryEqual: (v) => this.insertableRightestEndNodeByPrimary(v),
1585
- primaryNotEqual: (v) => null,
1586
- primaryOr: (v) => this.insertableRightestEndNodeByPrimary(
1587
- this.highestPrimaryValue(this.ensureValues(v))
1588
- ),
1589
- like: (v) => null
1590
- };
1591
- verifierDirection = {
1592
- gt: 1,
1593
- gte: 1,
1594
- lt: -1,
1595
- lte: -1,
1596
- equal: 1,
1597
- notEqual: 1,
1598
- or: 1,
1599
- primaryGt: 1,
1600
- primaryGte: 1,
1601
- primaryLt: -1,
1602
- primaryLte: -1,
1603
- primaryEqual: 1,
1604
- primaryNotEqual: 1,
1605
- primaryOr: 1,
1606
- like: 1
1607
- };
1608
- /**
1609
- * Determines whether early termination is allowed for each condition.
1610
- * When true, the search will stop once a match is found and then a non-match is encountered.
1611
- * Only applicable for conditions that guarantee contiguous matches in a sorted B+Tree.
1612
- */
1613
- verifierEarlyTerminate = {
1614
- gt: false,
1615
- gte: false,
1616
- lt: false,
1617
- lte: false,
1618
- equal: true,
1619
- notEqual: false,
1620
- or: false,
1621
- primaryGt: false,
1622
- primaryGte: false,
1623
- primaryLt: false,
1624
- primaryLte: false,
1625
- primaryEqual: true,
1626
- primaryNotEqual: false,
1627
- primaryOr: false,
1628
- like: false
1552
+ searchConfigs = {
1553
+ gt: {
1554
+ asc: {
1555
+ start: (tx, v) => tx.insertableNodeByPrimary(v[0]),
1556
+ end: () => null,
1557
+ direction: 1,
1558
+ earlyTerminate: false
1559
+ },
1560
+ desc: {
1561
+ start: (tx) => tx.rightestNode(),
1562
+ end: (tx, v) => tx.insertableEndNode(v[0], -1),
1563
+ direction: -1,
1564
+ earlyTerminate: true
1565
+ }
1566
+ },
1567
+ gte: {
1568
+ asc: {
1569
+ start: (tx, v) => tx.insertableNodeByPrimary(v[0]),
1570
+ end: () => null,
1571
+ direction: 1,
1572
+ earlyTerminate: false
1573
+ },
1574
+ desc: {
1575
+ start: (tx) => tx.rightestNode(),
1576
+ end: (tx, v) => tx.insertableEndNode(v[0], -1),
1577
+ direction: -1,
1578
+ earlyTerminate: true
1579
+ }
1580
+ },
1581
+ lt: {
1582
+ asc: {
1583
+ start: (tx) => tx.leftestNode(),
1584
+ end: (tx, v) => tx.insertableEndNode(v[0], 1),
1585
+ direction: 1,
1586
+ earlyTerminate: true
1587
+ },
1588
+ desc: {
1589
+ start: (tx, v) => tx.insertableNodeByPrimary(v[0]),
1590
+ end: () => null,
1591
+ direction: -1,
1592
+ earlyTerminate: false
1593
+ }
1594
+ },
1595
+ lte: {
1596
+ asc: {
1597
+ start: (tx) => tx.leftestNode(),
1598
+ end: (tx, v) => tx.insertableEndNode(v[0], 1),
1599
+ direction: 1,
1600
+ earlyTerminate: true
1601
+ },
1602
+ desc: {
1603
+ start: (tx, v) => tx.insertableRightestNodeByPrimary(v[0]),
1604
+ end: () => null,
1605
+ direction: -1,
1606
+ earlyTerminate: false
1607
+ }
1608
+ },
1609
+ equal: {
1610
+ asc: {
1611
+ start: (tx, v) => tx.insertableNodeByPrimary(v[0]),
1612
+ end: (tx, v) => tx.insertableEndNode(v[0], 1),
1613
+ direction: 1,
1614
+ earlyTerminate: true
1615
+ },
1616
+ desc: {
1617
+ start: (tx, v) => tx.insertableEndNode(v[0], 1),
1618
+ end: (tx, v) => tx.insertableEndNode(v[0], -1),
1619
+ direction: -1,
1620
+ earlyTerminate: true
1621
+ }
1622
+ },
1623
+ notEqual: {
1624
+ asc: {
1625
+ start: (tx) => tx.leftestNode(),
1626
+ end: () => null,
1627
+ direction: 1,
1628
+ earlyTerminate: false
1629
+ },
1630
+ desc: {
1631
+ start: (tx) => tx.rightestNode(),
1632
+ end: () => null,
1633
+ direction: -1,
1634
+ earlyTerminate: false
1635
+ }
1636
+ },
1637
+ or: {
1638
+ asc: {
1639
+ start: (tx, v) => tx.insertableNodeByPrimary(tx.lowestValue(v)),
1640
+ end: (tx, v) => tx.insertableEndNode(tx.highestValue(v), 1),
1641
+ direction: 1,
1642
+ earlyTerminate: false
1643
+ },
1644
+ desc: {
1645
+ start: (tx, v) => tx.insertableEndNode(tx.highestValue(v), 1),
1646
+ end: (tx, v) => tx.insertableEndNode(tx.lowestValue(v), -1),
1647
+ direction: -1,
1648
+ earlyTerminate: false
1649
+ }
1650
+ },
1651
+ primaryGt: {
1652
+ asc: {
1653
+ start: (tx, v) => tx.insertableNodeByPrimary(v[0]),
1654
+ end: () => null,
1655
+ direction: 1,
1656
+ earlyTerminate: false
1657
+ },
1658
+ desc: {
1659
+ start: (tx) => tx.rightestNode(),
1660
+ end: (tx, v) => tx.insertableEndNode(v[0], -1),
1661
+ direction: -1,
1662
+ earlyTerminate: true
1663
+ }
1664
+ },
1665
+ primaryGte: {
1666
+ asc: {
1667
+ start: (tx, v) => tx.insertableNodeByPrimary(v[0]),
1668
+ end: () => null,
1669
+ direction: 1,
1670
+ earlyTerminate: false
1671
+ },
1672
+ desc: {
1673
+ start: (tx) => tx.rightestNode(),
1674
+ end: (tx, v) => tx.insertableEndNode(v[0], -1),
1675
+ direction: -1,
1676
+ earlyTerminate: true
1677
+ }
1678
+ },
1679
+ primaryLt: {
1680
+ asc: {
1681
+ start: (tx) => tx.leftestNode(),
1682
+ end: (tx, v) => tx.insertableEndNode(v[0], 1),
1683
+ direction: 1,
1684
+ earlyTerminate: true
1685
+ },
1686
+ desc: {
1687
+ start: (tx, v) => tx.insertableNodeByPrimary(v[0]),
1688
+ end: () => null,
1689
+ direction: -1,
1690
+ earlyTerminate: false
1691
+ }
1692
+ },
1693
+ primaryLte: {
1694
+ asc: {
1695
+ start: (tx) => tx.leftestNode(),
1696
+ end: (tx, v) => tx.insertableEndNode(v[0], 1),
1697
+ direction: 1,
1698
+ earlyTerminate: true
1699
+ },
1700
+ desc: {
1701
+ start: (tx, v) => tx.insertableRightestNodeByPrimary(v[0]),
1702
+ end: () => null,
1703
+ direction: -1,
1704
+ earlyTerminate: false
1705
+ }
1706
+ },
1707
+ primaryEqual: {
1708
+ asc: {
1709
+ start: (tx, v) => tx.insertableNodeByPrimary(v[0]),
1710
+ end: (tx, v) => tx.insertableRightestEndNodeByPrimary(v[0]),
1711
+ direction: 1,
1712
+ earlyTerminate: true
1713
+ },
1714
+ desc: {
1715
+ start: (tx, v) => tx.insertableRightestEndNodeByPrimary(v[0]),
1716
+ end: (tx, v) => tx.insertableEndNode(v[0], -1),
1717
+ direction: -1,
1718
+ earlyTerminate: true
1719
+ }
1720
+ },
1721
+ primaryNotEqual: {
1722
+ asc: {
1723
+ start: (tx) => tx.leftestNode(),
1724
+ end: () => null,
1725
+ direction: 1,
1726
+ earlyTerminate: false
1727
+ },
1728
+ desc: {
1729
+ start: (tx) => tx.rightestNode(),
1730
+ end: () => null,
1731
+ direction: -1,
1732
+ earlyTerminate: false
1733
+ }
1734
+ },
1735
+ primaryOr: {
1736
+ asc: {
1737
+ start: (tx, v) => tx.insertableNodeByPrimary(tx.lowestPrimaryValue(v)),
1738
+ end: (tx, v) => tx.insertableRightestEndNodeByPrimary(tx.highestPrimaryValue(v)),
1739
+ direction: 1,
1740
+ earlyTerminate: false
1741
+ },
1742
+ desc: {
1743
+ start: (tx, v) => tx.insertableRightestEndNodeByPrimary(tx.highestPrimaryValue(v)),
1744
+ end: (tx, v) => tx.insertableEndNode(tx.lowestPrimaryValue(v), -1),
1745
+ direction: -1,
1746
+ earlyTerminate: false
1747
+ }
1748
+ },
1749
+ like: {
1750
+ asc: {
1751
+ start: (tx) => tx.leftestNode(),
1752
+ end: () => null,
1753
+ direction: 1,
1754
+ earlyTerminate: false
1755
+ },
1756
+ desc: {
1757
+ start: (tx) => tx.rightestNode(),
1758
+ end: () => null,
1759
+ direction: -1,
1760
+ earlyTerminate: false
1761
+ }
1762
+ }
1629
1763
  };
1630
1764
  /**
1631
1765
  * Priority map for condition types.
@@ -2236,16 +2370,20 @@ var BPTreeSyncTransaction = class extends BPTreeTransaction {
2236
2370
  const driverKey = this.getDriverKey(condition);
2237
2371
  if (!driverKey) return;
2238
2372
  const value = condition[driverKey];
2239
- let startNode = this.verifierStartNode[driverKey](value);
2240
- let endNode = this.verifierEndNode[driverKey](value);
2241
- let direction = this.verifierDirection[driverKey];
2373
+ const v = this.ensureValues(value);
2374
+ const config = this.searchConfigs[driverKey][order];
2375
+ let startNode = config.start(this, v);
2376
+ let endNode = config.end(this, v);
2377
+ const direction = config.direction;
2378
+ const earlyTerminate = config.earlyTerminate;
2379
+ if (order === "desc" && !startNode) {
2380
+ startNode = this.rightestNode();
2381
+ }
2382
+ if (order === "asc" && !startNode) {
2383
+ startNode = this.leftestNode();
2384
+ }
2385
+ if (!startNode) return;
2242
2386
  const comparator = this.verifierMap[driverKey];
2243
- const earlyTerminate = this.verifierEarlyTerminate[driverKey];
2244
- if (order === "desc") {
2245
- startNode = endNode ?? this.rightestNode();
2246
- endNode = null;
2247
- direction *= -1;
2248
- }
2249
2387
  const generator = this.getPairsGenerator(
2250
2388
  value,
2251
2389
  startNode,
@@ -2257,7 +2395,7 @@ var BPTreeSyncTransaction = class extends BPTreeTransaction {
2257
2395
  let count = 0;
2258
2396
  const intersection = filterValues && filterValues.size > 0 ? filterValues : null;
2259
2397
  for (const pair of generator) {
2260
- const [k, v] = pair;
2398
+ const [k, v2] = pair;
2261
2399
  if (intersection && !intersection.has(k)) {
2262
2400
  continue;
2263
2401
  }
@@ -2266,7 +2404,7 @@ var BPTreeSyncTransaction = class extends BPTreeTransaction {
2266
2404
  if (key === driverKey) continue;
2267
2405
  const verify = this.verifierMap[key];
2268
2406
  const condValue = condition[key];
2269
- if (!verify(v, condValue)) {
2407
+ if (!verify(v2, condValue)) {
2270
2408
  isMatch = false;
2271
2409
  break;
2272
2410
  }
@@ -3366,16 +3504,20 @@ var BPTreeAsyncTransaction = class extends BPTreeTransaction {
3366
3504
  const driverKey = this.getDriverKey(condition);
3367
3505
  if (!driverKey) return;
3368
3506
  const value = condition[driverKey];
3369
- let startNode = await this.verifierStartNode[driverKey](value);
3370
- let endNode = await this.verifierEndNode[driverKey](value);
3371
- let direction = this.verifierDirection[driverKey];
3507
+ const v = this.ensureValues(value);
3508
+ const config = this.searchConfigs[driverKey][order];
3509
+ let startNode = await config.start(this, v);
3510
+ let endNode = await config.end(this, v);
3511
+ const direction = config.direction;
3512
+ const earlyTerminate = config.earlyTerminate;
3513
+ if (order === "desc" && !startNode) {
3514
+ startNode = await this.rightestNode();
3515
+ }
3516
+ if (order === "asc" && !startNode) {
3517
+ startNode = await this.leftestNode();
3518
+ }
3519
+ if (!startNode) return;
3372
3520
  const comparator = this.verifierMap[driverKey];
3373
- const earlyTerminate = this.verifierEarlyTerminate[driverKey];
3374
- if (order === "desc") {
3375
- startNode = endNode ?? await this.rightestNode();
3376
- endNode = null;
3377
- direction *= -1;
3378
- }
3379
3521
  const generator = this.getPairsGenerator(
3380
3522
  value,
3381
3523
  startNode,
@@ -3387,7 +3529,7 @@ var BPTreeAsyncTransaction = class extends BPTreeTransaction {
3387
3529
  let count = 0;
3388
3530
  const intersection = filterValues && filterValues.size > 0 ? filterValues : null;
3389
3531
  for await (const pair of generator) {
3390
- const [k, v] = pair;
3532
+ const [k, v2] = pair;
3391
3533
  if (intersection && !intersection.has(k)) {
3392
3534
  continue;
3393
3535
  }
@@ -3396,7 +3538,7 @@ var BPTreeAsyncTransaction = class extends BPTreeTransaction {
3396
3538
  if (key === driverKey) continue;
3397
3539
  const verify = this.verifierMap[key];
3398
3540
  const condValue = condition[key];
3399
- if (!verify(v, condValue)) {
3541
+ if (!verify(v2, condValue)) {
3400
3542
  isMatch = false;
3401
3543
  break;
3402
3544
  }
@@ -8341,7 +8483,7 @@ var PageFileSystem = class {
8341
8483
  /**
8342
8484
  * Appends and inserts a new page.
8343
8485
  * If a free page is available in the free list, it reuses it.
8344
- * Otherwise, it appends a new page to the end of the file.
8486
+ * Otherwise, it preallocates `pagePreallocationCount` pages to support sequential reads.
8345
8487
  * @returns Created or reused page ID
8346
8488
  */
8347
8489
  async appendNewPage(pageType = PageManager.CONSTANT.PAGE_TYPE_EMPTY, tx) {
@@ -8362,12 +8504,26 @@ var PageFileSystem = class {
8362
8504
  await this.setPage(reusedPageId, newPage2, tx);
8363
8505
  return reusedPageId;
8364
8506
  }
8507
+ const preallocationCount = this.options.pagePreallocationCount;
8365
8508
  const pageCount = metadataManager.getPageCount(metadata);
8366
8509
  const newPageIndex = pageCount;
8367
- const newTotalCount = pageCount + 1;
8510
+ const newTotalCount = pageCount + preallocationCount;
8368
8511
  const manager = this.pageFactory.getManagerFromType(pageType);
8369
8512
  const newPage = manager.create(this.pageSize, newPageIndex);
8370
8513
  await this.setPage(newPageIndex, newPage, tx);
8514
+ const emptyManager = this.pageFactory.getManagerFromType(PageManager.CONSTANT.PAGE_TYPE_EMPTY);
8515
+ const firstFreeIndex = newPageIndex + 1;
8516
+ const lastFreeIndex = newPageIndex + preallocationCount - 1;
8517
+ for (let i = firstFreeIndex; i <= lastFreeIndex; i++) {
8518
+ const emptyPage = emptyManager.create(this.pageSize, i);
8519
+ const nextId = i < lastFreeIndex ? i + 1 : -1;
8520
+ emptyManager.setNextPageId(emptyPage, nextId);
8521
+ await this.setPage(i, emptyPage, tx);
8522
+ await this.updateBitmap(i, true, tx);
8523
+ }
8524
+ if (preallocationCount > 1) {
8525
+ metadataManager.setFreePageId(metadata, firstFreeIndex);
8526
+ }
8371
8527
  metadataManager.setPageCount(metadata, newTotalCount);
8372
8528
  await this.setPage(0, metadata, tx);
8373
8529
  return newPageIndex;
@@ -8451,7 +8607,8 @@ var PageFileSystem = class {
8451
8607
  }
8452
8608
  /**
8453
8609
  * Frees the page and marks it as available in the bitmap.
8454
- * It also adds the page to the linked list of free pages in metadata.
8610
+ * Inserts the page into the free list in ascending order by page ID
8611
+ * to support HDD sequential reads.
8455
8612
  * @param pageId Page ID
8456
8613
  * @param tx Transaction
8457
8614
  */
@@ -8461,13 +8618,30 @@ var PageFileSystem = class {
8461
8618
  await tx.__acquireWriteLock(pageId);
8462
8619
  const metadata = await this.getMetadata(tx);
8463
8620
  const metadataManager = this.pageFactory.getManager(metadata);
8464
- const currentHeadFreePageId = metadataManager.getFreePageId(metadata);
8465
8621
  const emptyPageManager = this.pageFactory.getManagerFromType(PageManager.CONSTANT.PAGE_TYPE_EMPTY);
8466
8622
  const emptyPage = emptyPageManager.create(this.pageSize, pageId);
8467
- emptyPageManager.setNextPageId(emptyPage, currentHeadFreePageId);
8468
- await this.setPage(pageId, emptyPage, tx);
8623
+ const headFreePageId = metadataManager.getFreePageId(metadata);
8624
+ if (headFreePageId === -1 || pageId < headFreePageId) {
8625
+ emptyPageManager.setNextPageId(emptyPage, headFreePageId);
8626
+ await this.setPage(pageId, emptyPage, tx);
8627
+ metadataManager.setFreePageId(metadata, pageId);
8628
+ } else {
8629
+ let prevPageId = headFreePageId;
8630
+ let prevPage = await this.get(prevPageId, tx);
8631
+ let prevManager = this.pageFactory.getManager(prevPage);
8632
+ let nextPageId = prevManager.getNextPageId(prevPage);
8633
+ while (nextPageId !== -1 && nextPageId < pageId) {
8634
+ prevPageId = nextPageId;
8635
+ prevPage = await this.get(prevPageId, tx);
8636
+ prevManager = this.pageFactory.getManager(prevPage);
8637
+ nextPageId = prevManager.getNextPageId(prevPage);
8638
+ }
8639
+ emptyPageManager.setNextPageId(emptyPage, nextPageId);
8640
+ await this.setPage(pageId, emptyPage, tx);
8641
+ prevManager.setNextPageId(prevPage, pageId);
8642
+ await this.setPage(prevPageId, prevPage, tx);
8643
+ }
8469
8644
  await this.updateBitmap(pageId, true, tx);
8470
- metadataManager.setFreePageId(metadata, pageId);
8471
8645
  await this.setPage(0, metadata, tx);
8472
8646
  }
8473
8647
  /**
@@ -9177,7 +9351,9 @@ var RowTableEngine = class {
9177
9351
  }
9178
9352
  pageGroupMap.get(pageId).push({ pk: pair.pk, slotIndex, index: pair.index });
9179
9353
  }
9180
- await Promise.all(Array.from(pageGroupMap).map(async ([pageId, items]) => {
9354
+ const sortedPageIds = Array.from(pageGroupMap.keys()).sort((a, b) => a - b);
9355
+ await Promise.all(sortedPageIds.map(async (pageId) => {
9356
+ const items = pageGroupMap.get(pageId);
9181
9357
  const page = await this.pfs.get(pageId, tx);
9182
9358
  if (!this.factory.isDataPage(page)) {
9183
9359
  throw new Error(`Page ${pageId} is not a data page`);
@@ -9538,6 +9714,7 @@ var DataplyAPI = class {
9538
9714
  return Object.assign({
9539
9715
  pageSize: 8192,
9540
9716
  pageCacheCapacity: 1e4,
9717
+ pagePreallocationCount: 1e3,
9541
9718
  wal: null,
9542
9719
  walCheckpointThreshold: 1e3
9543
9720
  }, options);
@@ -107,7 +107,7 @@ export declare class PageFileSystem {
107
107
  /**
108
108
  * Appends and inserts a new page.
109
109
  * If a free page is available in the free list, it reuses it.
110
- * Otherwise, it appends a new page to the end of the file.
110
+ * Otherwise, it preallocates `pagePreallocationCount` pages to support sequential reads.
111
111
  * @returns Created or reused page ID
112
112
  */
113
113
  appendNewPage(pageType: number | undefined, tx: Transaction): Promise<number>;
@@ -127,7 +127,8 @@ export declare class PageFileSystem {
127
127
  freeChain(startPageId: number, tx: Transaction): Promise<void>;
128
128
  /**
129
129
  * Frees the page and marks it as available in the bitmap.
130
- * It also adds the page to the linked list of free pages in metadata.
130
+ * Inserts the page into the free list in ascending order by page ID
131
+ * to support HDD sequential reads.
131
132
  * @param pageId Page ID
132
133
  * @param tx Transaction
133
134
  */
@@ -13,6 +13,11 @@ export interface DataplyOptions {
13
13
  * Default is 10000.
14
14
  */
15
15
  pageCacheCapacity?: number;
16
+ /**
17
+ * The number of pages to preallocate when creating a new page.
18
+ * Default is 1000.
19
+ */
20
+ pagePreallocationCount?: number;
16
21
  /**
17
22
  * The total number of pages written to the WAL before automatically clearing it.
18
23
  * Default is 1000.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dataply",
3
- "version": "0.0.24-alpha.0",
3
+ "version": "0.0.24-alpha.2",
4
4
  "description": "A lightweight storage engine for Node.js with support for MVCC, WAL.",
5
5
  "license": "MIT",
6
6
  "author": "izure <admin@izure.org>",
@@ -49,6 +49,6 @@
49
49
  "hookall": "^2.2.0",
50
50
  "mvcc-api": "^1.3.4",
51
51
  "ryoiki": "^1.2.0",
52
- "serializable-bptree": "^8.3.1"
52
+ "serializable-bptree": "^8.3.2"
53
53
  }
54
54
  }