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 +174 -40
- package/dist/types/core/DataplyAPI.d.ts +3 -0
- package/dist/types/core/RowIndexStrategy.d.ts +3 -1
- package/dist/types/core/RowTableEngine.d.ts +3 -1
- package/dist/types/core/transaction/Transaction.d.ts +5 -3
- package/dist/types/core/transaction/TxContext.d.ts +5 -4
- package/package.json +2 -2
- package/readme.md +2 -0
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
9
|
-
private
|
|
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
|
|
3
|
-
|
|
4
|
-
|
|
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.
|
|
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
|
|
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
|