dataply 0.0.9 → 0.0.11

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
@@ -71,6 +71,33 @@ var ValueComparator = class {
71
71
  isHigher(value, than) {
72
72
  return this.asc(value, than) > 0;
73
73
  }
74
+ /**
75
+ * This method is used for range queries with composite values.
76
+ * By default, it calls the `asc` method, so existing code works without changes.
77
+ *
78
+ * When using composite values (e.g., `{ k: number, v: number }`),
79
+ * override this method to compare only the primary sorting field (e.g., `v`),
80
+ * ignoring the unique identifier field (e.g., `k`).
81
+ *
82
+ * This enables efficient range queries like `primaryEqual` that find all entries
83
+ * with the same primary value regardless of their unique identifiers.
84
+ *
85
+ * @param a Value a.
86
+ * @param b Value b.
87
+ * @returns Negative if a < b, 0 if equal, positive if a > b (based on primary field only).
88
+ */
89
+ primaryAsc(a, b) {
90
+ return this.asc(a, b);
91
+ }
92
+ isPrimarySame(value, than) {
93
+ return this.primaryAsc(value, than) === 0;
94
+ }
95
+ isPrimaryLower(value, than) {
96
+ return this.primaryAsc(value, than) < 0;
97
+ }
98
+ isPrimaryHigher(value, than) {
99
+ return this.primaryAsc(value, than) > 0;
100
+ }
74
101
  };
75
102
  var NumericComparator = class extends ValueComparator {
76
103
  asc(a, b) {
@@ -480,6 +507,7 @@ var BPTree = class {
480
507
  lt: (nv, v) => this.comparator.isLower(nv, v),
481
508
  lte: (nv, v) => this.comparator.isLower(nv, v) || this.comparator.isSame(nv, v),
482
509
  equal: (nv, v) => this.comparator.isSame(nv, v),
510
+ primaryEqual: (nv, v) => this.comparator.isPrimarySame(nv, v),
483
511
  notEqual: (nv, v) => this.comparator.isSame(nv, v) === false,
484
512
  or: (nv, v) => this.ensureValues(v).some((v2) => this.comparator.isSame(nv, v2)),
485
513
  like: (nv, v) => {
@@ -496,6 +524,7 @@ var BPTree = class {
496
524
  lt: (v) => this.insertableNode(v),
497
525
  lte: (v) => this.insertableNode(v),
498
526
  equal: (v) => this.insertableNode(v),
527
+ primaryEqual: (v) => this.insertableNodeByPrimary(v),
499
528
  notEqual: (v) => this.leftestNode(),
500
529
  or: (v) => this.insertableNode(this.lowestValue(this.ensureValues(v))),
501
530
  like: (v) => this.leftestNode()
@@ -506,6 +535,7 @@ var BPTree = class {
506
535
  lt: (v) => null,
507
536
  lte: (v) => null,
508
537
  equal: (v) => this.insertableEndNode(v, this.verifierDirection.equal),
538
+ primaryEqual: (v) => null,
509
539
  notEqual: (v) => null,
510
540
  or: (v) => this.insertableEndNode(
511
541
  this.highestValue(this.ensureValues(v)),
@@ -519,10 +549,27 @@ var BPTree = class {
519
549
  lt: -1,
520
550
  lte: -1,
521
551
  equal: 1,
552
+ primaryEqual: 1,
522
553
  notEqual: 1,
523
554
  or: 1,
524
555
  like: 1
525
556
  };
557
+ /**
558
+ * Determines whether early termination is allowed for each condition.
559
+ * When true, the search will stop once a match is found and then a non-match is encountered.
560
+ * Only applicable for conditions that guarantee contiguous matches in a sorted B+Tree.
561
+ */
562
+ verifierEarlyTerminate = {
563
+ gt: false,
564
+ gte: false,
565
+ lt: false,
566
+ lte: false,
567
+ equal: true,
568
+ primaryEqual: true,
569
+ notEqual: false,
570
+ or: false,
571
+ like: false
572
+ };
526
573
  constructor(strategy, comparator, option) {
527
574
  this.strategy = strategy;
528
575
  this.comparator = comparator;
@@ -633,10 +680,11 @@ var BPTreeSync = class extends BPTree {
633
680
  capacity: this.option.capacity ?? 1e3
634
681
  });
635
682
  }
636
- getPairsRightToLeft(value, startNode, endNode, comparator) {
683
+ getPairsRightToLeft(value, startNode, endNode, comparator, earlyTerminate) {
637
684
  const pairs = [];
638
685
  let node = startNode;
639
686
  let done = false;
687
+ let hasMatched = false;
640
688
  while (!done) {
641
689
  if (endNode && node.id === endNode.id) {
642
690
  done = true;
@@ -647,12 +695,17 @@ var BPTreeSync = class extends BPTree {
647
695
  const nValue = node.values[i];
648
696
  const keys = node.keys[i];
649
697
  if (comparator(nValue, value)) {
698
+ hasMatched = true;
650
699
  let j = keys.length;
651
700
  while (j--) {
652
701
  pairs.push([keys[j], nValue]);
653
702
  }
703
+ } else if (earlyTerminate && hasMatched) {
704
+ done = true;
705
+ break;
654
706
  }
655
707
  }
708
+ if (done) break;
656
709
  if (!node.prev) {
657
710
  done = true;
658
711
  break;
@@ -661,10 +714,11 @@ var BPTreeSync = class extends BPTree {
661
714
  }
662
715
  return new Map(pairs.reverse());
663
716
  }
664
- getPairsLeftToRight(value, startNode, endNode, comparator) {
717
+ getPairsLeftToRight(value, startNode, endNode, comparator, earlyTerminate) {
665
718
  const pairs = [];
666
719
  let node = startNode;
667
720
  let done = false;
721
+ let hasMatched = false;
668
722
  while (!done) {
669
723
  if (endNode && node.id === endNode.id) {
670
724
  done = true;
@@ -674,12 +728,17 @@ var BPTreeSync = class extends BPTree {
674
728
  const nValue = node.values[i];
675
729
  const keys = node.keys[i];
676
730
  if (comparator(nValue, value)) {
731
+ hasMatched = true;
677
732
  for (let j = 0, len2 = keys.length; j < len2; j++) {
678
733
  const key = keys[j];
679
734
  pairs.push([key, nValue]);
680
735
  }
736
+ } else if (earlyTerminate && hasMatched) {
737
+ done = true;
738
+ break;
681
739
  }
682
740
  }
741
+ if (done) break;
683
742
  if (!node.next) {
684
743
  done = true;
685
744
  break;
@@ -688,12 +747,12 @@ var BPTreeSync = class extends BPTree {
688
747
  }
689
748
  return new Map(pairs);
690
749
  }
691
- getPairs(value, startNode, endNode, comparator, direction) {
750
+ getPairs(value, startNode, endNode, comparator, direction, earlyTerminate) {
692
751
  switch (direction) {
693
752
  case -1:
694
- return this.getPairsRightToLeft(value, startNode, endNode, comparator);
753
+ return this.getPairsRightToLeft(value, startNode, endNode, comparator, earlyTerminate);
695
754
  case 1:
696
- return this.getPairsLeftToRight(value, startNode, endNode, comparator);
755
+ return this.getPairsLeftToRight(value, startNode, endNode, comparator, earlyTerminate);
697
756
  default:
698
757
  throw new Error(`Direction must be -1 or 1. but got a ${direction}`);
699
758
  }
@@ -1042,6 +1101,30 @@ var BPTreeSync = class extends BPTree {
1042
1101
  }
1043
1102
  return node;
1044
1103
  }
1104
+ /**
1105
+ * Find the insertable node using primaryAsc comparison.
1106
+ * This allows finding nodes by primary value only, ignoring unique identifiers.
1107
+ */
1108
+ insertableNodeByPrimary(value) {
1109
+ let node = this.getNode(this.root.id);
1110
+ while (!node.leaf) {
1111
+ for (let i = 0, len = node.values.length; i < len; i++) {
1112
+ const nValue = node.values[i];
1113
+ const k = node.keys;
1114
+ if (this.comparator.isPrimarySame(value, nValue)) {
1115
+ node = this.getNode(k[i]);
1116
+ break;
1117
+ } else if (this.comparator.isPrimaryLower(value, nValue)) {
1118
+ node = this.getNode(k[i]);
1119
+ break;
1120
+ } else if (i + 1 === node.values.length) {
1121
+ node = this.getNode(k[i + 1]);
1122
+ break;
1123
+ }
1124
+ }
1125
+ }
1126
+ return node;
1127
+ }
1045
1128
  insertableEndNode(value, direction) {
1046
1129
  const insertableNode = this.insertableNode(value);
1047
1130
  let key;
@@ -1110,7 +1193,8 @@ var BPTreeSync = class extends BPTree {
1110
1193
  const endNode = this.verifierEndNode[key](value);
1111
1194
  const direction = this.verifierDirection[key];
1112
1195
  const comparator = this.verifierMap[key];
1113
- const pairs = this.getPairs(value, startNode, endNode, comparator, direction);
1196
+ const earlyTerminate = this.verifierEarlyTerminate[key];
1197
+ const pairs = this.getPairs(value, startNode, endNode, comparator, direction, earlyTerminate);
1114
1198
  if (!filterValues) {
1115
1199
  filterValues = new Set(pairs.keys());
1116
1200
  } else {
@@ -1135,7 +1219,8 @@ var BPTreeSync = class extends BPTree {
1135
1219
  const endNode = this.verifierEndNode[key](value);
1136
1220
  const direction = this.verifierDirection[key];
1137
1221
  const comparator = this.verifierMap[key];
1138
- const pairs = this.getPairs(value, startNode, endNode, comparator, direction);
1222
+ const earlyTerminate = this.verifierEarlyTerminate[key];
1223
+ const pairs = this.getPairs(value, startNode, endNode, comparator, direction, earlyTerminate);
1139
1224
  if (result === null) {
1140
1225
  result = pairs;
1141
1226
  } else {
@@ -1519,10 +1604,11 @@ var BPTreeAsync = class extends BPTree {
1519
1604
  this.lock.writeUnlock(lockId);
1520
1605
  });
1521
1606
  }
1522
- async getPairsRightToLeft(value, startNode, endNode, comparator) {
1607
+ async getPairsRightToLeft(value, startNode, endNode, comparator, earlyTerminate) {
1523
1608
  const pairs = [];
1524
1609
  let node = startNode;
1525
1610
  let done = false;
1611
+ let hasMatched = false;
1526
1612
  while (!done) {
1527
1613
  if (endNode && node.id === endNode.id) {
1528
1614
  done = true;
@@ -1533,12 +1619,17 @@ var BPTreeAsync = class extends BPTree {
1533
1619
  const nValue = node.values[i];
1534
1620
  const keys = node.keys[i];
1535
1621
  if (comparator(nValue, value)) {
1622
+ hasMatched = true;
1536
1623
  let j = keys.length;
1537
1624
  while (j--) {
1538
1625
  pairs.push([keys[j], nValue]);
1539
1626
  }
1627
+ } else if (earlyTerminate && hasMatched) {
1628
+ done = true;
1629
+ break;
1540
1630
  }
1541
1631
  }
1632
+ if (done) break;
1542
1633
  if (!node.prev) {
1543
1634
  done = true;
1544
1635
  break;
@@ -1547,10 +1638,11 @@ var BPTreeAsync = class extends BPTree {
1547
1638
  }
1548
1639
  return new Map(pairs.reverse());
1549
1640
  }
1550
- async getPairsLeftToRight(value, startNode, endNode, comparator) {
1641
+ async getPairsLeftToRight(value, startNode, endNode, comparator, earlyTerminate) {
1551
1642
  const pairs = [];
1552
1643
  let node = startNode;
1553
1644
  let done = false;
1645
+ let hasMatched = false;
1554
1646
  while (!done) {
1555
1647
  if (endNode && node.id === endNode.id) {
1556
1648
  done = true;
@@ -1560,12 +1652,17 @@ var BPTreeAsync = class extends BPTree {
1560
1652
  const nValue = node.values[i];
1561
1653
  const keys = node.keys[i];
1562
1654
  if (comparator(nValue, value)) {
1655
+ hasMatched = true;
1563
1656
  for (let j = 0, len2 = keys.length; j < len2; j++) {
1564
1657
  const key = keys[j];
1565
1658
  pairs.push([key, nValue]);
1566
1659
  }
1660
+ } else if (earlyTerminate && hasMatched) {
1661
+ done = true;
1662
+ break;
1567
1663
  }
1568
1664
  }
1665
+ if (done) break;
1569
1666
  if (!node.next) {
1570
1667
  done = true;
1571
1668
  break;
@@ -1574,12 +1671,12 @@ var BPTreeAsync = class extends BPTree {
1574
1671
  }
1575
1672
  return new Map(pairs);
1576
1673
  }
1577
- async getPairs(value, startNode, endNode, comparator, direction) {
1674
+ async getPairs(value, startNode, endNode, comparator, direction, earlyTerminate) {
1578
1675
  switch (direction) {
1579
1676
  case -1:
1580
- return await this.getPairsRightToLeft(value, startNode, endNode, comparator);
1677
+ return await this.getPairsRightToLeft(value, startNode, endNode, comparator, earlyTerminate);
1581
1678
  case 1:
1582
- return await this.getPairsLeftToRight(value, startNode, endNode, comparator);
1679
+ return await this.getPairsLeftToRight(value, startNode, endNode, comparator, earlyTerminate);
1583
1680
  default:
1584
1681
  throw new Error(`Direction must be -1 or 1. but got a ${direction}`);
1585
1682
  }
@@ -1928,6 +2025,30 @@ var BPTreeAsync = class extends BPTree {
1928
2025
  }
1929
2026
  return node;
1930
2027
  }
2028
+ /**
2029
+ * Find the insertable node using primaryAsc comparison.
2030
+ * This allows finding nodes by primary value only, ignoring unique identifiers.
2031
+ */
2032
+ async insertableNodeByPrimary(value) {
2033
+ let node = await this.getNode(this.root.id);
2034
+ while (!node.leaf) {
2035
+ for (let i = 0, len = node.values.length; i < len; i++) {
2036
+ const nValue = node.values[i];
2037
+ const k = node.keys;
2038
+ if (this.comparator.isPrimarySame(value, nValue)) {
2039
+ node = await this.getNode(k[i]);
2040
+ break;
2041
+ } else if (this.comparator.isPrimaryLower(value, nValue)) {
2042
+ node = await this.getNode(k[i]);
2043
+ break;
2044
+ } else if (i + 1 === node.values.length) {
2045
+ node = await this.getNode(k[i + 1]);
2046
+ break;
2047
+ }
2048
+ }
2049
+ }
2050
+ return node;
2051
+ }
1931
2052
  async insertableEndNode(value, direction) {
1932
2053
  const insertableNode = await this.insertableNode(value);
1933
2054
  let key;
@@ -1997,7 +2118,8 @@ var BPTreeAsync = class extends BPTree {
1997
2118
  const endNode = await this.verifierEndNode[key](value);
1998
2119
  const direction = this.verifierDirection[key];
1999
2120
  const comparator = this.verifierMap[key];
2000
- const pairs = await this.getPairs(value, startNode, endNode, comparator, direction);
2121
+ const earlyTerminate = this.verifierEarlyTerminate[key];
2122
+ const pairs = await this.getPairs(value, startNode, endNode, comparator, direction, earlyTerminate);
2001
2123
  if (!filterValues) {
2002
2124
  filterValues = new Set(pairs.keys());
2003
2125
  } else {
@@ -2024,7 +2146,8 @@ var BPTreeAsync = class extends BPTree {
2024
2146
  const endNode = await this.verifierEndNode[key](value);
2025
2147
  const direction = this.verifierDirection[key];
2026
2148
  const comparator = this.verifierMap[key];
2027
- const pairs = await this.getPairs(value, startNode, endNode, comparator, direction);
2149
+ const earlyTerminate = this.verifierEarlyTerminate[key];
2150
+ const pairs = await this.getPairs(value, startNode, endNode, comparator, direction, earlyTerminate);
2028
2151
  if (result === null) {
2029
2152
  result = pairs;
2030
2153
  } else {
@@ -5422,24 +5545,13 @@ var TextCodec = class _TextCodec {
5422
5545
  }
5423
5546
  };
5424
5547
 
5425
- // src/core/transaction/TxContext.ts
5426
- var import_node_async_hooks = require("node:async_hooks");
5427
- var storage = new import_node_async_hooks.AsyncLocalStorage();
5428
- var TxContext = {
5429
- run: (tx, callback) => {
5430
- return storage.run(tx, callback);
5431
- },
5432
- get: () => {
5433
- return storage.getStore();
5434
- }
5435
- };
5436
-
5437
5548
  // src/core/RowIndexStrategy.ts
5438
5549
  var RowIdentifierStrategy = class extends SerializeStrategyAsync {
5439
- constructor(order, pfs) {
5550
+ constructor(order, pfs, txContext) {
5440
5551
  super(order);
5441
5552
  this.order = order;
5442
5553
  this.pfs = pfs;
5554
+ this.txContext = txContext;
5443
5555
  this.factory = new PageManagerFactory();
5444
5556
  this.indexPageManger = this.factory.getManagerFromType(PageManager.CONSTANT.PAGE_TYPE_INDEX);
5445
5557
  this.codec = new TextCodec();
@@ -5449,12 +5561,12 @@ var RowIdentifierStrategy = class extends SerializeStrategyAsync {
5449
5561
  indexPageManger;
5450
5562
  codec;
5451
5563
  async id(isLeaf) {
5452
- const tx = TxContext.get();
5564
+ const tx = this.txContext.get();
5453
5565
  const pageId = await this.pfs.appendNewPage(PageManager.CONSTANT.PAGE_TYPE_INDEX, tx);
5454
5566
  return pageId.toString();
5455
5567
  }
5456
5568
  async read(id) {
5457
- const tx = TxContext.get();
5569
+ const tx = this.txContext.get();
5458
5570
  const pageId = +id;
5459
5571
  const page = await this.pfs.get(pageId, tx);
5460
5572
  const indexId = this.indexPageManger.getIndexId(page);
@@ -5486,7 +5598,7 @@ var RowIdentifierStrategy = class extends SerializeStrategyAsync {
5486
5598
  return res;
5487
5599
  }
5488
5600
  async write(id, node) {
5489
- const tx = TxContext.get();
5601
+ const tx = this.txContext.get();
5490
5602
  const pageId = +id;
5491
5603
  const page = await this.pfs.get(pageId, tx);
5492
5604
  if (node.leaf) {
@@ -5521,7 +5633,7 @@ var RowIdentifierStrategy = class extends SerializeStrategyAsync {
5521
5633
  await this.pfs.setPage(pageId, page, tx);
5522
5634
  }
5523
5635
  async delete(id) {
5524
- const tx = TxContext.get();
5636
+ const tx = this.txContext.get();
5525
5637
  const manager = this.factory.getManagerFromType(PageManager.CONSTANT.PAGE_TYPE_INDEX);
5526
5638
  let pageId = +id;
5527
5639
  while (true) {
@@ -5535,7 +5647,7 @@ var RowIdentifierStrategy = class extends SerializeStrategyAsync {
5535
5647
  }
5536
5648
  }
5537
5649
  async readHead() {
5538
- const tx = TxContext.get();
5650
+ const tx = this.txContext.get();
5539
5651
  const metadataPage = await this.pfs.getMetadata(tx);
5540
5652
  const manager = this.factory.getManager(metadataPage);
5541
5653
  const rootIndexPageId = manager.getRootIndexPageId(metadataPage);
@@ -5549,7 +5661,7 @@ var RowIdentifierStrategy = class extends SerializeStrategyAsync {
5549
5661
  };
5550
5662
  }
5551
5663
  async writeHead(head) {
5552
- const tx = TxContext.get();
5664
+ const tx = this.txContext.get();
5553
5665
  const { root, order } = head;
5554
5666
  if (root === null) {
5555
5667
  throw new Error("");
@@ -5617,8 +5729,9 @@ var KeyManager = class {
5617
5729
 
5618
5730
  // src/core/RowTableEngine.ts
5619
5731
  var RowTableEngine = class {
5620
- constructor(pfs, options) {
5732
+ constructor(pfs, txContext, options) {
5621
5733
  this.pfs = pfs;
5734
+ this.txContext = txContext;
5622
5735
  this.options = options;
5623
5736
  this.factory = new PageManagerFactory();
5624
5737
  this.metadataPageManager = this.factory.getManagerFromType(MetadataPageManager.CONSTANT.PAGE_TYPE_METADATA);
@@ -5631,7 +5744,7 @@ var RowTableEngine = class {
5631
5744
  this.maxBodySize = this.pfs.pageSize - DataPageManager.CONSTANT.SIZE_PAGE_HEADER;
5632
5745
  this.order = this.getOptimalOrder(pfs.pageSize, IndexPageManager.CONSTANT.SIZE_KEY, IndexPageManager.CONSTANT.SIZE_VALUE);
5633
5746
  this.bptree = new BPTreeAsync(
5634
- new RowIdentifierStrategy(this.order, pfs),
5747
+ new RowIdentifierStrategy(this.order, pfs, txContext),
5635
5748
  new NumericComparator(),
5636
5749
  {
5637
5750
  capacity: this.options.pageCacheCapacity
@@ -6061,7 +6174,8 @@ var Transaction = class {
6061
6174
  * @param vfs VFS instance
6062
6175
  * @param lockManager LockManager instance
6063
6176
  */
6064
- constructor(id, vfs, lockManager) {
6177
+ constructor(id, context, vfs, lockManager) {
6178
+ this.context = context;
6065
6179
  this.vfs = vfs;
6066
6180
  this.lockManager = lockManager;
6067
6181
  this.id = id;
@@ -6170,7 +6284,7 @@ var Transaction = class {
6170
6284
  async commit() {
6171
6285
  await this.vfs.prepareCommit(this);
6172
6286
  await this.vfs.finalizeCommit(this);
6173
- await TxContext.run(this, async () => {
6287
+ await this.context.run(this, async () => {
6174
6288
  for (const hook of this.commitHooks) {
6175
6289
  await hook();
6176
6290
  }
@@ -6211,6 +6325,18 @@ var Transaction = class {
6211
6325
  }
6212
6326
  };
6213
6327
 
6328
+ // src/core/transaction/TxContext.ts
6329
+ var import_node_async_hooks = require("node:async_hooks");
6330
+ var TransactionContext = class {
6331
+ storage = new import_node_async_hooks.AsyncLocalStorage();
6332
+ run(tx, callback) {
6333
+ return this.storage.run(tx, callback);
6334
+ }
6335
+ get() {
6336
+ return this.storage.getStore();
6337
+ }
6338
+ };
6339
+
6214
6340
  // src/core/DataplyAPI.ts
6215
6341
  var DataplyAPI = class {
6216
6342
  constructor(file, options) {
@@ -6226,8 +6352,9 @@ var DataplyAPI = class {
6226
6352
  this.options.wal
6227
6353
  );
6228
6354
  this.textCodec = new TextCodec();
6355
+ this.txContext = new TransactionContext();
6229
6356
  this.lockManager = new LockManager();
6230
- this.rowTableEngine = new RowTableEngine(this.pfs, this.options);
6357
+ this.rowTableEngine = new RowTableEngine(this.pfs, this.txContext, this.options);
6231
6358
  this.initialized = false;
6232
6359
  this.txIdCounter = 0;
6233
6360
  }
@@ -6247,6 +6374,8 @@ var DataplyAPI = class {
6247
6374
  lockManager;
6248
6375
  /** Text codec. Used for encoding and decoding text data */
6249
6376
  textCodec;
6377
+ /** Transaction context */
6378
+ txContext;
6250
6379
  /** Hook */
6251
6380
  hook;
6252
6381
  /** Whether the database was initialized via `init()` */
@@ -6389,7 +6518,12 @@ var DataplyAPI = class {
6389
6518
  * @returns Transaction object
6390
6519
  */
6391
6520
  createTransaction() {
6392
- return new Transaction(++this.txIdCounter, this.pfs.vfsInstance, this.lockManager);
6521
+ return new Transaction(
6522
+ ++this.txIdCounter,
6523
+ this.txContext,
6524
+ this.pfs.vfsInstance,
6525
+ this.lockManager
6526
+ );
6393
6527
  }
6394
6528
  /**
6395
6529
  * Runs a callback function within a transaction context.
@@ -6405,7 +6539,7 @@ var DataplyAPI = class {
6405
6539
  if (!tx) {
6406
6540
  tx = this.createTransaction();
6407
6541
  }
6408
- const [error, result] = await catchPromise(TxContext.run(tx, () => callback(tx)));
6542
+ const [error, result] = await catchPromise(this.txContext.run(tx, () => callback(tx)));
6409
6543
  if (error) {
6410
6544
  if (isInternalTx) {
6411
6545
  await tx.rollback();
@@ -5,6 +5,7 @@ import { RowTableEngine } from './RowTableEngine';
5
5
  import { TextCodec } from '../utils/TextCodec';
6
6
  import { LockManager } from './transaction/LockManager';
7
7
  import { Transaction } from './transaction/Transaction';
8
+ import { TransactionContext } from './transaction/TxContext';
8
9
  interface DataplyAPIAsyncHook {
9
10
  init: (tx: Transaction, isNewlyCreated: boolean) => Promise<Transaction>;
10
11
  close: () => Promise<void>;
@@ -30,6 +31,8 @@ export declare class DataplyAPI {
30
31
  protected readonly lockManager: LockManager;
31
32
  /** Text codec. Used for encoding and decoding text data */
32
33
  protected readonly textCodec: TextCodec;
34
+ /** Transaction context */
35
+ protected readonly txContext: TransactionContext;
33
36
  /** Hook */
34
37
  protected readonly hook: IHookall<DataplyAPIAsyncHook>;
35
38
  /** Whether the database was initialized via `init()` */
@@ -3,14 +3,16 @@ import { SerializeStrategyAsync } from 'serializable-bptree';
3
3
  import { PageFileSystem } from './PageFileSystem';
4
4
  import { IndexPageManager, PageManagerFactory } from './Page';
5
5
  import { TextCodec } from '../utils/TextCodec';
6
+ import { TransactionContext } from './transaction/TxContext';
6
7
  export declare class RowIdentifierStrategy extends SerializeStrategyAsync<number, number> {
7
8
  readonly order: number;
8
9
  protected readonly pfs: PageFileSystem;
10
+ protected readonly txContext: TransactionContext;
9
11
  protected rootPageId: number;
10
12
  protected factory: PageManagerFactory;
11
13
  protected indexPageManger: IndexPageManager;
12
14
  protected codec: TextCodec;
13
- constructor(order: number, pfs: PageFileSystem);
15
+ constructor(order: number, pfs: PageFileSystem, txContext: TransactionContext);
14
16
  id(isLeaf: boolean): Promise<string>;
15
17
  read(id: string): Promise<BPTreeNode<number, number>>;
16
18
  write(id: string, node: BPTreeNode<number, number>): Promise<void>;
@@ -5,8 +5,10 @@ import { Row } from './Row';
5
5
  import { KeyManager } from './KeyManager';
6
6
  import { PageManagerFactory } from './Page';
7
7
  import { Transaction } from './transaction/Transaction';
8
+ import { TransactionContext } from './transaction/TxContext';
8
9
  export declare class RowTableEngine {
9
10
  protected readonly pfs: PageFileSystem;
11
+ protected readonly txContext: TransactionContext;
10
12
  protected readonly options: Required<DataplyOptions>;
11
13
  protected readonly bptree: BPTreeAsync<number, number>;
12
14
  protected readonly order: number;
@@ -20,7 +22,7 @@ export declare class RowTableEngine {
20
22
  private readonly ridBuffer;
21
23
  private readonly pageIdBuffer;
22
24
  private initialized;
23
- constructor(pfs: PageFileSystem, options: Required<DataplyOptions>);
25
+ constructor(pfs: PageFileSystem, txContext: TransactionContext, options: Required<DataplyOptions>);
24
26
  /**
25
27
  * Initializes the B+ Tree.
26
28
  */
@@ -1,12 +1,14 @@
1
1
  import { LockManager } from './LockManager';
2
2
  import { VirtualFileSystem } from '../VirtualFileSystem';
3
+ import { TransactionContext } from './TxContext';
3
4
  /**
4
5
  * Transaction class.
5
6
  * Manages the lifecycle and resources of a database transaction.
6
7
  */
7
8
  export declare class Transaction {
8
- private vfs;
9
- private lockManager;
9
+ readonly context: TransactionContext;
10
+ private readonly vfs;
11
+ private readonly lockManager;
10
12
  /** Transaction ID */
11
13
  readonly id: number;
12
14
  /** List of held lock IDs (LOCK_ID) */
@@ -26,7 +28,7 @@ export declare class Transaction {
26
28
  * @param vfs VFS instance
27
29
  * @param lockManager LockManager instance
28
30
  */
29
- constructor(id: number, vfs: VirtualFileSystem, lockManager: LockManager);
31
+ constructor(id: number, context: TransactionContext, vfs: VirtualFileSystem, lockManager: LockManager);
30
32
  /**
31
33
  * Registers a commit hook.
32
34
  * @param hook Function to execute
@@ -1,5 +1,6 @@
1
1
  import { Transaction } from './Transaction';
2
- export declare const TxContext: {
3
- run: <T>(tx: Transaction, callback: () => T) => T;
4
- get: () => Transaction | undefined;
5
- };
2
+ export declare class TransactionContext {
3
+ private readonly storage;
4
+ run<T>(tx: Transaction, callback: () => T): T;
5
+ get(): Transaction | undefined;
6
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dataply",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "A lightweight storage engine for Node.js with support for MVCC, WAL.",
5
5
  "license": "MIT",
6
6
  "author": "izure <admin@izure.org>",
@@ -47,6 +47,6 @@
47
47
  "cache-entanglement": "^1.7.1",
48
48
  "hookall": "^2.2.0",
49
49
  "ryoiki": "^1.2.0",
50
- "serializable-bptree": "^6.0.2"
50
+ "serializable-bptree": "^6.1.0"
51
51
  }
52
52
  }
package/readme.md CHANGED
@@ -15,6 +15,7 @@
15
15
  - **📝 WAL (Write-Ahead Logging)**: Ensures data integrity and provides recovery capabilities in case of system failures.
16
16
  - **💼 Transaction Mechanism**: Supports Commit and Rollback for atomic operations.
17
17
  - **📦 Page-Based Storage**: Efficient page caching and disk I/O optimization through Virtual File System (VFS).
18
+ - **📉 Bitmap Space Optimization**: Uses bitmapped management to efficiently track page usage and maximize disk space utilization.
18
19
  - **⌨️ TypeScript Support**: Provides comprehensive type definitions for all APIs.
19
20
 
20
21
  ## Installation
@@ -224,6 +225,7 @@ graph TD
224
225
  - **Fixed-size Pages**: All data is managed in fixed-size units (default 8KB) called pages.
225
226
  - **VFS Cache**: Minimizes disk I/O by caching frequently accessed pages in memory.
226
227
  - **Dirty Page Tracking**: Tracks modified pages (Dirty) to synchronize them with disk efficiently only at the time of commit.
228
+ - **Bitmap Management**: Efficiently tracks the allocation and deallocation of pages using a bitmap structure, facilitating fast space reclamation and reuse. For more details on this mechanism, see [Page Reclamation and Reuse Guide](docs/page_reclamation.md).
227
229
  - **Detailed Structure**: For technical details on the physical layout, see [structure.md](docs/structure.md).
228
230
 
229
231
  #### Page & Row Layout