dataply 0.0.16-alpha.6 → 0.0.16-alpha.8
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 +464 -67
- package/dist/types/core/PageFileSystem.d.ts +5 -0
- package/dist/types/core/VirtualFileSystem.d.ts +2 -3
- package/package.json +3 -3
- package/readme.md +22 -19
package/dist/cjs/index.js
CHANGED
|
@@ -52,7 +52,7 @@ __export(src_exports, {
|
|
|
52
52
|
OverflowPageManager: () => OverflowPageManager,
|
|
53
53
|
PageManager: () => PageManager,
|
|
54
54
|
PageManagerFactory: () => PageManagerFactory,
|
|
55
|
-
Ryoiki: () =>
|
|
55
|
+
Ryoiki: () => Ryoiki2,
|
|
56
56
|
SerializeStrategyAsync: () => SerializeStrategyAsync,
|
|
57
57
|
SerializeStrategySync: () => SerializeStrategySync,
|
|
58
58
|
StringComparator: () => StringComparator,
|
|
@@ -499,24 +499,13 @@ var BPTree = class _BPTree {
|
|
|
499
499
|
option;
|
|
500
500
|
order;
|
|
501
501
|
rootId;
|
|
502
|
-
/**
|
|
503
|
-
* Returns the ID of the root node.
|
|
504
|
-
* @returns The root node ID.
|
|
505
|
-
*/
|
|
506
|
-
getRootId() {
|
|
507
|
-
return this.rootId;
|
|
508
|
-
}
|
|
509
|
-
/**
|
|
510
|
-
* Returns the order of the B+Tree.
|
|
511
|
-
* @returns The order of the tree.
|
|
512
|
-
*/
|
|
513
|
-
getOrder() {
|
|
514
|
-
return this.order;
|
|
515
|
-
}
|
|
516
502
|
_strategyDirty;
|
|
517
503
|
_nodeCreateBuffer;
|
|
518
504
|
_nodeUpdateBuffer;
|
|
519
505
|
_nodeDeleteBuffer;
|
|
506
|
+
sharedDeleteCache = /* @__PURE__ */ new Map();
|
|
507
|
+
activeTransactions = /* @__PURE__ */ new Set();
|
|
508
|
+
lastTransactionId = 0;
|
|
520
509
|
verifierMap = {
|
|
521
510
|
gt: (nv, v) => this.comparator.isHigher(nv, v),
|
|
522
511
|
gte: (nv, v) => this.comparator.isHigher(nv, v) || this.comparator.isSame(nv, v),
|
|
@@ -677,6 +666,20 @@ var BPTree = class _BPTree {
|
|
|
677
666
|
}
|
|
678
667
|
return best;
|
|
679
668
|
}
|
|
669
|
+
/**
|
|
670
|
+
* Returns the ID of the root node.
|
|
671
|
+
* @returns The root node ID.
|
|
672
|
+
*/
|
|
673
|
+
getRootId() {
|
|
674
|
+
return this.rootId;
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Returns the order of the B+Tree.
|
|
678
|
+
* @returns The order of the tree.
|
|
679
|
+
*/
|
|
680
|
+
getOrder() {
|
|
681
|
+
return this.order;
|
|
682
|
+
}
|
|
680
683
|
/**
|
|
681
684
|
* Verified if the value satisfies the condition.
|
|
682
685
|
*
|
|
@@ -796,6 +799,38 @@ var BPTree = class _BPTree {
|
|
|
796
799
|
this._cachedRegexp.clear();
|
|
797
800
|
this.nodes.clear();
|
|
798
801
|
}
|
|
802
|
+
registerTransaction(txId) {
|
|
803
|
+
this.activeTransactions.add(txId);
|
|
804
|
+
}
|
|
805
|
+
unregisterTransaction(txId) {
|
|
806
|
+
this.activeTransactions.delete(txId);
|
|
807
|
+
}
|
|
808
|
+
pruneObsoleteNodes() {
|
|
809
|
+
if (this.activeTransactions.size === 0) {
|
|
810
|
+
this.sharedDeleteCache.clear();
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
const minActiveTxId = Math.min(...this.activeTransactions);
|
|
814
|
+
for (const [id, entry] of this.sharedDeleteCache) {
|
|
815
|
+
if (entry.obsoleteAt < minActiveTxId) {
|
|
816
|
+
this.sharedDeleteCache.delete(id);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
getObsoleteNode(id) {
|
|
821
|
+
return this.sharedDeleteCache.get(id)?.node;
|
|
822
|
+
}
|
|
823
|
+
addObsoleteNode(node, obsoleteAt) {
|
|
824
|
+
this.sharedDeleteCache.set(node.id, { node, obsoleteAt });
|
|
825
|
+
}
|
|
826
|
+
getNextTransactionId() {
|
|
827
|
+
let nextId = Date.now();
|
|
828
|
+
if (nextId <= this.lastTransactionId) {
|
|
829
|
+
nextId = this.lastTransactionId + 1e-3;
|
|
830
|
+
}
|
|
831
|
+
this.lastTransactionId = nextId;
|
|
832
|
+
return nextId;
|
|
833
|
+
}
|
|
799
834
|
};
|
|
800
835
|
var BPTreeSyncBase = class extends BPTree {
|
|
801
836
|
constructor(strategy, comparator, option) {
|
|
@@ -1382,7 +1417,9 @@ var BPTreeSyncBase = class extends BPTree {
|
|
|
1382
1417
|
this._nodeUpdateBuffer.clear();
|
|
1383
1418
|
}
|
|
1384
1419
|
commitNodeDeleteBuffer() {
|
|
1420
|
+
const obsoleteAt = this.getNextTransactionId();
|
|
1385
1421
|
for (const node of this._nodeDeleteBuffer.values()) {
|
|
1422
|
+
this.addObsoleteNode(node, obsoleteAt);
|
|
1386
1423
|
this.strategy.delete(node.id);
|
|
1387
1424
|
this.nodes.delete(node.id);
|
|
1388
1425
|
}
|
|
@@ -1542,6 +1579,7 @@ var BPTreeSyncBase = class extends BPTree {
|
|
|
1542
1579
|
var SerializeStrategy = class {
|
|
1543
1580
|
order;
|
|
1544
1581
|
head;
|
|
1582
|
+
lastCommittedTransactionId = 0;
|
|
1545
1583
|
constructor(order) {
|
|
1546
1584
|
this.order = order;
|
|
1547
1585
|
this.head = {
|
|
@@ -1568,13 +1606,13 @@ var SerializeStrategySync = class extends SerializeStrategy {
|
|
|
1568
1606
|
this.setHeadData(key, next);
|
|
1569
1607
|
return current;
|
|
1570
1608
|
}
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1609
|
+
getLastCommittedTransactionId() {
|
|
1610
|
+
return this.lastCommittedTransactionId;
|
|
1611
|
+
}
|
|
1612
|
+
compareAndSwapHead(newRoot, newTxId) {
|
|
1575
1613
|
this.head.root = newRoot;
|
|
1614
|
+
this.lastCommittedTransactionId = newTxId;
|
|
1576
1615
|
this.writeHead(this.head);
|
|
1577
|
-
return true;
|
|
1578
1616
|
}
|
|
1579
1617
|
};
|
|
1580
1618
|
var InMemoryStoreStrategySync = class extends SerializeStrategySync {
|
|
@@ -1641,8 +1679,11 @@ var BPTreeSyncSnapshotStrategy = class extends SerializeStrategySync {
|
|
|
1641
1679
|
this.snapshotHead.root = head.root;
|
|
1642
1680
|
this.snapshotHead.data = { ...head.data };
|
|
1643
1681
|
}
|
|
1644
|
-
compareAndSwapHead(
|
|
1645
|
-
|
|
1682
|
+
compareAndSwapHead(newRoot, newTxId) {
|
|
1683
|
+
this.baseStrategy.compareAndSwapHead(newRoot, newTxId);
|
|
1684
|
+
}
|
|
1685
|
+
getLastCommittedTransactionId() {
|
|
1686
|
+
return this.baseStrategy.getLastCommittedTransactionId();
|
|
1646
1687
|
}
|
|
1647
1688
|
getHeadData(key, defaultValue) {
|
|
1648
1689
|
return this.snapshotHead.data[key] ?? defaultValue;
|
|
@@ -1661,8 +1702,11 @@ var BPTreeSyncTransaction = class extends BPTreeSyncBase {
|
|
|
1661
1702
|
dirtyIds;
|
|
1662
1703
|
createdInTx;
|
|
1663
1704
|
deletedIds;
|
|
1705
|
+
originalNodes = /* @__PURE__ */ new Map();
|
|
1664
1706
|
initialRootId;
|
|
1665
1707
|
transactionRootId;
|
|
1708
|
+
transactionId;
|
|
1709
|
+
initialLastCommittedTransactionId = 0;
|
|
1666
1710
|
constructor(baseTree) {
|
|
1667
1711
|
super(baseTree.strategy, baseTree.comparator, baseTree.option);
|
|
1668
1712
|
this.realBaseTree = baseTree;
|
|
@@ -1673,6 +1717,7 @@ var BPTreeSyncTransaction = class extends BPTreeSyncBase {
|
|
|
1673
1717
|
this.dirtyIds = /* @__PURE__ */ new Set();
|
|
1674
1718
|
this.createdInTx = /* @__PURE__ */ new Set();
|
|
1675
1719
|
this.deletedIds = /* @__PURE__ */ new Set();
|
|
1720
|
+
this.transactionId = Date.now() + Math.random();
|
|
1676
1721
|
}
|
|
1677
1722
|
/**
|
|
1678
1723
|
* Initializes the transaction by capturing the current state of the tree.
|
|
@@ -1689,6 +1734,7 @@ var BPTreeSyncTransaction = class extends BPTreeSyncBase {
|
|
|
1689
1734
|
const root = this._createNode(true, [], [], true);
|
|
1690
1735
|
this.initialRootId = root.id;
|
|
1691
1736
|
}
|
|
1737
|
+
this.initialLastCommittedTransactionId = this.realBaseStrategy.getLastCommittedTransactionId();
|
|
1692
1738
|
this.transactionRootId = this.initialRootId;
|
|
1693
1739
|
this.rootId = this.transactionRootId;
|
|
1694
1740
|
const snapshotStrategy = new BPTreeSyncSnapshotStrategy(this.realBaseStrategy, this.initialRootId);
|
|
@@ -1697,6 +1743,7 @@ var BPTreeSyncTransaction = class extends BPTreeSyncBase {
|
|
|
1697
1743
|
this.dirtyIds.clear();
|
|
1698
1744
|
this.createdInTx.clear();
|
|
1699
1745
|
this.deletedIds.clear();
|
|
1746
|
+
this.realBaseTree.registerTransaction(this.transactionId);
|
|
1700
1747
|
}
|
|
1701
1748
|
getNode(id) {
|
|
1702
1749
|
if (this.txNodes.has(id)) {
|
|
@@ -1705,7 +1752,13 @@ var BPTreeSyncTransaction = class extends BPTreeSyncBase {
|
|
|
1705
1752
|
if (this.deletedIds.has(id)) {
|
|
1706
1753
|
throw new Error(`The tree attempted to reference deleted node '${id}'`);
|
|
1707
1754
|
}
|
|
1708
|
-
|
|
1755
|
+
let baseNode = this.realBaseTree.getObsoleteNode(id);
|
|
1756
|
+
if (!baseNode) {
|
|
1757
|
+
baseNode = this.realBaseStrategy.read(id);
|
|
1758
|
+
}
|
|
1759
|
+
if (!this.originalNodes.has(id) && !this.createdInTx.has(id)) {
|
|
1760
|
+
this.originalNodes.set(id, JSON.parse(JSON.stringify(baseNode)));
|
|
1761
|
+
}
|
|
1709
1762
|
const clone = JSON.parse(JSON.stringify(baseNode));
|
|
1710
1763
|
this.txNodes.set(id, clone);
|
|
1711
1764
|
return clone;
|
|
@@ -1795,9 +1848,10 @@ var BPTreeSyncTransaction = class extends BPTreeSyncBase {
|
|
|
1795
1848
|
* Attempts to commit the transaction.
|
|
1796
1849
|
* Uses Optimistic Locking (Compare-And-Swap) on the root node ID to detect conflicts.
|
|
1797
1850
|
*
|
|
1851
|
+
* @param cleanup Whether to clean up obsolete nodes after commit. Defaults to true.
|
|
1798
1852
|
* @returns The transaction result.
|
|
1799
1853
|
*/
|
|
1800
|
-
commit() {
|
|
1854
|
+
commit(cleanup = true) {
|
|
1801
1855
|
const idMapping = /* @__PURE__ */ new Map();
|
|
1802
1856
|
const finalNodes = [];
|
|
1803
1857
|
for (const oldId of this.dirtyIds) {
|
|
@@ -1845,24 +1899,46 @@ var BPTreeSyncTransaction = class extends BPTreeSyncBase {
|
|
|
1845
1899
|
if (idMapping.has(this.rootId)) {
|
|
1846
1900
|
newRootId = idMapping.get(this.rootId);
|
|
1847
1901
|
}
|
|
1848
|
-
|
|
1849
|
-
|
|
1902
|
+
let success = false;
|
|
1903
|
+
if (finalNodes.length === 0) {
|
|
1904
|
+
success = true;
|
|
1905
|
+
} else if (this.realBaseStrategy.getLastCommittedTransactionId() === this.initialLastCommittedTransactionId) {
|
|
1906
|
+
for (const node of finalNodes) {
|
|
1907
|
+
this.realBaseStrategy.write(node.id, node);
|
|
1908
|
+
}
|
|
1909
|
+
this.realBaseStrategy.compareAndSwapHead(newRootId, this.transactionId);
|
|
1910
|
+
success = true;
|
|
1850
1911
|
}
|
|
1851
|
-
const success = this.realBaseStrategy.compareAndSwapHead(this.initialRootId, newRootId);
|
|
1852
1912
|
if (success) {
|
|
1853
1913
|
const distinctObsolete = /* @__PURE__ */ new Set();
|
|
1854
1914
|
for (const oldId of this.dirtyIds) {
|
|
1855
|
-
if (
|
|
1915
|
+
if (this.createdInTx.has(oldId)) {
|
|
1916
|
+
continue;
|
|
1917
|
+
}
|
|
1918
|
+
if (this.txNodes.has(oldId) || this.deletedIds.has(oldId)) {
|
|
1856
1919
|
distinctObsolete.add(oldId);
|
|
1857
1920
|
}
|
|
1858
1921
|
}
|
|
1922
|
+
if (cleanup) {
|
|
1923
|
+
for (const obsoleteId of distinctObsolete) {
|
|
1924
|
+
if (this.originalNodes.has(obsoleteId)) {
|
|
1925
|
+
this.realBaseTree.addObsoleteNode(
|
|
1926
|
+
this.originalNodes.get(obsoleteId),
|
|
1927
|
+
this.transactionId
|
|
1928
|
+
);
|
|
1929
|
+
}
|
|
1930
|
+
this.realBaseStrategy.delete(obsoleteId);
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
this.realBaseTree.unregisterTransaction(this.transactionId);
|
|
1934
|
+
this.realBaseTree.pruneObsoleteNodes();
|
|
1859
1935
|
return {
|
|
1860
1936
|
success: true,
|
|
1861
1937
|
createdIds: newCreatedIds,
|
|
1862
1938
|
obsoleteIds: Array.from(distinctObsolete)
|
|
1863
1939
|
};
|
|
1864
1940
|
} else {
|
|
1865
|
-
this.rollback();
|
|
1941
|
+
this.rollback(cleanup);
|
|
1866
1942
|
return {
|
|
1867
1943
|
success: false,
|
|
1868
1944
|
createdIds: newCreatedIds,
|
|
@@ -1886,8 +1962,16 @@ var BPTreeSyncTransaction = class extends BPTreeSyncBase {
|
|
|
1886
1962
|
this.realBaseStrategy.delete(id);
|
|
1887
1963
|
}
|
|
1888
1964
|
}
|
|
1965
|
+
this.realBaseTree.unregisterTransaction(this.transactionId);
|
|
1966
|
+
this.realBaseTree.pruneObsoleteNodes();
|
|
1889
1967
|
return createdIds;
|
|
1890
1968
|
}
|
|
1969
|
+
readLock(fn) {
|
|
1970
|
+
return fn();
|
|
1971
|
+
}
|
|
1972
|
+
writeLock(fn) {
|
|
1973
|
+
return fn();
|
|
1974
|
+
}
|
|
1891
1975
|
// Override to do nothing, as transaction handles its own commits
|
|
1892
1976
|
commitHeadBuffer() {
|
|
1893
1977
|
}
|
|
@@ -2521,7 +2605,9 @@ var BPTreeAsyncBase = class extends BPTree {
|
|
|
2521
2605
|
this._nodeUpdateBuffer.clear();
|
|
2522
2606
|
}
|
|
2523
2607
|
async commitNodeDeleteBuffer() {
|
|
2608
|
+
const obsoleteAt = this.getNextTransactionId();
|
|
2524
2609
|
for (const node of this._nodeDeleteBuffer.values()) {
|
|
2610
|
+
this.addObsoleteNode(node, obsoleteAt);
|
|
2525
2611
|
await this.strategy.delete(node.id);
|
|
2526
2612
|
this.nodes.delete(node.id);
|
|
2527
2613
|
}
|
|
@@ -2685,7 +2771,273 @@ var BPTreeAsyncBase = class extends BPTree {
|
|
|
2685
2771
|
await this.strategy.writeHead(this.strategy.head);
|
|
2686
2772
|
}
|
|
2687
2773
|
};
|
|
2774
|
+
var Ryoiki = class _Ryoiki {
|
|
2775
|
+
readings;
|
|
2776
|
+
writings;
|
|
2777
|
+
readQueue;
|
|
2778
|
+
writeQueue;
|
|
2779
|
+
static async CatchError(promise) {
|
|
2780
|
+
return await promise.then((v) => [void 0, v]).catch((err) => [err]);
|
|
2781
|
+
}
|
|
2782
|
+
static IsRangeOverlap(a, b) {
|
|
2783
|
+
const [start1, end1] = a;
|
|
2784
|
+
const [start2, end2] = b;
|
|
2785
|
+
if (end1 <= start2 || end2 <= start1) {
|
|
2786
|
+
return false;
|
|
2787
|
+
}
|
|
2788
|
+
return true;
|
|
2789
|
+
}
|
|
2790
|
+
static ERR_ALREADY_EXISTS(lockId) {
|
|
2791
|
+
return new Error(`The '${lockId}' task already existing in queue or running.`);
|
|
2792
|
+
}
|
|
2793
|
+
static ERR_NOT_EXISTS(lockId) {
|
|
2794
|
+
return new Error(`The '${lockId}' task not existing in task queue.`);
|
|
2795
|
+
}
|
|
2796
|
+
static ERR_TIMEOUT(lockId, timeout) {
|
|
2797
|
+
return new Error(`The task with ID '${lockId}' failed to acquire the lock within the timeout(${timeout}ms).`);
|
|
2798
|
+
}
|
|
2799
|
+
/**
|
|
2800
|
+
* Constructs a new instance of the Ryoiki class.
|
|
2801
|
+
*/
|
|
2802
|
+
constructor() {
|
|
2803
|
+
this.readings = /* @__PURE__ */ new Map();
|
|
2804
|
+
this.writings = /* @__PURE__ */ new Map();
|
|
2805
|
+
this.readQueue = /* @__PURE__ */ new Map();
|
|
2806
|
+
this.writeQueue = /* @__PURE__ */ new Map();
|
|
2807
|
+
}
|
|
2808
|
+
/**
|
|
2809
|
+
* Creates a range based on a start value and length.
|
|
2810
|
+
* @param start - The starting value of the range.
|
|
2811
|
+
* @param length - The length of the range.
|
|
2812
|
+
* @returns A range tuple [start, start + length].
|
|
2813
|
+
*/
|
|
2814
|
+
range(start, length) {
|
|
2815
|
+
return [start, start + length];
|
|
2816
|
+
}
|
|
2817
|
+
rangeOverlapping(tasks, range) {
|
|
2818
|
+
return Array.from(tasks.values()).some((t) => _Ryoiki.IsRangeOverlap(t.range, range));
|
|
2819
|
+
}
|
|
2820
|
+
isSameRange(a, b) {
|
|
2821
|
+
const [a1, a2] = a;
|
|
2822
|
+
const [b1, b2] = b;
|
|
2823
|
+
return a1 === b1 && a2 === b2;
|
|
2824
|
+
}
|
|
2825
|
+
fetchUnitAndRun(queue, workspaces) {
|
|
2826
|
+
for (const [id, unit] of queue) {
|
|
2827
|
+
if (!unit.condition()) {
|
|
2828
|
+
continue;
|
|
2829
|
+
}
|
|
2830
|
+
this._alloc(queue, workspaces, id);
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
_handleOverload(args, handlers, argPatterns) {
|
|
2834
|
+
for (const [key, pattern] of Object.entries(argPatterns)) {
|
|
2835
|
+
if (this._matchArgs(args, pattern)) {
|
|
2836
|
+
return handlers[key](...args);
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
throw new Error("Invalid arguments");
|
|
2840
|
+
}
|
|
2841
|
+
_matchArgs(args, pattern) {
|
|
2842
|
+
return args.every((arg, index) => {
|
|
2843
|
+
const expectedType = pattern[index];
|
|
2844
|
+
if (expectedType === void 0) return typeof arg === "undefined";
|
|
2845
|
+
if (expectedType === Function) return typeof arg === "function";
|
|
2846
|
+
if (expectedType === Number) return typeof arg === "number";
|
|
2847
|
+
if (expectedType === Array) return Array.isArray(arg);
|
|
2848
|
+
return false;
|
|
2849
|
+
});
|
|
2850
|
+
}
|
|
2851
|
+
_createRandomId() {
|
|
2852
|
+
const timestamp = Date.now().toString(36);
|
|
2853
|
+
const random = Math.random().toString(36).substring(2);
|
|
2854
|
+
return `${timestamp}${random}`;
|
|
2855
|
+
}
|
|
2856
|
+
_alloc(queue, workspaces, lockId) {
|
|
2857
|
+
const unit = queue.get(lockId);
|
|
2858
|
+
if (!unit) {
|
|
2859
|
+
throw _Ryoiki.ERR_NOT_EXISTS(lockId);
|
|
2860
|
+
}
|
|
2861
|
+
workspaces.set(lockId, unit);
|
|
2862
|
+
queue.delete(lockId);
|
|
2863
|
+
unit.alloc();
|
|
2864
|
+
}
|
|
2865
|
+
_free(workspaces, lockId) {
|
|
2866
|
+
const unit = workspaces.get(lockId);
|
|
2867
|
+
if (!unit) {
|
|
2868
|
+
throw _Ryoiki.ERR_NOT_EXISTS(lockId);
|
|
2869
|
+
}
|
|
2870
|
+
workspaces.delete(lockId);
|
|
2871
|
+
unit.free();
|
|
2872
|
+
}
|
|
2873
|
+
_lock(queue, range, timeout, task, condition) {
|
|
2874
|
+
return new Promise((resolve, reject) => {
|
|
2875
|
+
let timeoutId = null;
|
|
2876
|
+
if (timeout >= 0) {
|
|
2877
|
+
timeoutId = setTimeout(() => {
|
|
2878
|
+
reject(_Ryoiki.ERR_TIMEOUT(id, timeout));
|
|
2879
|
+
}, timeout);
|
|
2880
|
+
}
|
|
2881
|
+
const id = this._createRandomId();
|
|
2882
|
+
const alloc = async () => {
|
|
2883
|
+
if (timeoutId !== null) {
|
|
2884
|
+
clearTimeout(timeoutId);
|
|
2885
|
+
}
|
|
2886
|
+
const [err, v] = await _Ryoiki.CatchError(task(id));
|
|
2887
|
+
if (err) reject(err);
|
|
2888
|
+
else resolve(v);
|
|
2889
|
+
};
|
|
2890
|
+
const fetch = () => {
|
|
2891
|
+
this.fetchUnitAndRun(this.readQueue, this.readings);
|
|
2892
|
+
this.fetchUnitAndRun(this.writeQueue, this.writings);
|
|
2893
|
+
};
|
|
2894
|
+
queue.set(id, { id, range, condition, alloc, free: fetch });
|
|
2895
|
+
fetch();
|
|
2896
|
+
});
|
|
2897
|
+
}
|
|
2898
|
+
_checkWorking(range, workspaces) {
|
|
2899
|
+
let isLocked = false;
|
|
2900
|
+
for (const lock of workspaces.values()) {
|
|
2901
|
+
if (_Ryoiki.IsRangeOverlap(range, lock.range)) {
|
|
2902
|
+
isLocked = true;
|
|
2903
|
+
break;
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
return isLocked;
|
|
2907
|
+
}
|
|
2908
|
+
/**
|
|
2909
|
+
* Checks if there is any active read lock within the specified range.
|
|
2910
|
+
* @param range The range to check for active read locks.
|
|
2911
|
+
* @returns `true` if there is an active read lock within the range, `false` otherwise.
|
|
2912
|
+
*/
|
|
2913
|
+
isReading(range) {
|
|
2914
|
+
return this._checkWorking(range, this.readings);
|
|
2915
|
+
}
|
|
2916
|
+
/**
|
|
2917
|
+
* Checks if there is any active write lock within the specified range.
|
|
2918
|
+
* @param range The range to check for active write locks.
|
|
2919
|
+
* @returns `true` if there is an active write lock within the range, `false` otherwise.
|
|
2920
|
+
*/
|
|
2921
|
+
isWriting(range) {
|
|
2922
|
+
return this._checkWorking(range, this.writings);
|
|
2923
|
+
}
|
|
2924
|
+
/**
|
|
2925
|
+
* Checks if a read lock can be acquired within the specified range.
|
|
2926
|
+
* @param range The range to check for read lock availability.
|
|
2927
|
+
* @returns `true` if a read lock can be acquired, `false` otherwise.
|
|
2928
|
+
*/
|
|
2929
|
+
canRead(range) {
|
|
2930
|
+
const writing = this.isWriting(range);
|
|
2931
|
+
return !writing;
|
|
2932
|
+
}
|
|
2933
|
+
/**
|
|
2934
|
+
* Checks if a write lock can be acquired within the specified range.
|
|
2935
|
+
* @param range The range to check for write lock availability.
|
|
2936
|
+
* @returns `true` if a write lock can be acquired, `false` otherwise.
|
|
2937
|
+
*/
|
|
2938
|
+
canWrite(range) {
|
|
2939
|
+
const reading = this.isReading(range);
|
|
2940
|
+
const writing = this.isWriting(range);
|
|
2941
|
+
return !reading && !writing;
|
|
2942
|
+
}
|
|
2943
|
+
/**
|
|
2944
|
+
* Internal implementation of the read lock. Handles both overloads.
|
|
2945
|
+
* @template T - The return type of the task.
|
|
2946
|
+
* @param arg0 - Either a range or a task callback.
|
|
2947
|
+
* If a range is provided, the task is the second argument.
|
|
2948
|
+
* @param arg1 - The task to execute, required if a range is provided.
|
|
2949
|
+
* @param arg2 - The timeout for acquiring the lock.
|
|
2950
|
+
* If the lock cannot be acquired within this period, an error will be thrown.
|
|
2951
|
+
* If this value is not provided, no timeout will be set.
|
|
2952
|
+
* @returns A promise resolving to the result of the task execution.
|
|
2953
|
+
*/
|
|
2954
|
+
readLock(arg0, arg1, arg2) {
|
|
2955
|
+
const [range, task, timeout] = this._handleOverload(
|
|
2956
|
+
[arg0, arg1, arg2],
|
|
2957
|
+
{
|
|
2958
|
+
rangeTask: (range2, task2) => [range2, task2, -1],
|
|
2959
|
+
rangeTaskTimeout: (range2, task2, timeout2) => [range2, task2, timeout2],
|
|
2960
|
+
task: (task2) => [[-Infinity, Infinity], task2, -1],
|
|
2961
|
+
taskTimeout: (task2, timeout2) => [[-Infinity, Infinity], task2, timeout2]
|
|
2962
|
+
},
|
|
2963
|
+
{
|
|
2964
|
+
task: [Function],
|
|
2965
|
+
taskTimeout: [Function, Number],
|
|
2966
|
+
rangeTask: [Array, Function],
|
|
2967
|
+
rangeTaskTimeout: [Array, Function, Number]
|
|
2968
|
+
}
|
|
2969
|
+
);
|
|
2970
|
+
return this._lock(
|
|
2971
|
+
this.readQueue,
|
|
2972
|
+
range,
|
|
2973
|
+
timeout,
|
|
2974
|
+
task,
|
|
2975
|
+
() => !this.rangeOverlapping(this.writings, range)
|
|
2976
|
+
);
|
|
2977
|
+
}
|
|
2978
|
+
/**
|
|
2979
|
+
* Internal implementation of the write lock. Handles both overloads.
|
|
2980
|
+
* @template T - The return type of the task.
|
|
2981
|
+
* @param arg0 - Either a range or a task callback.
|
|
2982
|
+
* If a range is provided, the task is the second argument.
|
|
2983
|
+
* @param arg1 - The task to execute, required if a range is provided.
|
|
2984
|
+
* @param arg2 - The timeout for acquiring the lock.
|
|
2985
|
+
* If the lock cannot be acquired within this period, an error will be thrown.
|
|
2986
|
+
* If this value is not provided, no timeout will be set.
|
|
2987
|
+
* @returns A promise resolving to the result of the task execution.
|
|
2988
|
+
*/
|
|
2989
|
+
writeLock(arg0, arg1, arg2) {
|
|
2990
|
+
const [range, task, timeout] = this._handleOverload(
|
|
2991
|
+
[arg0, arg1, arg2],
|
|
2992
|
+
{
|
|
2993
|
+
rangeTask: (range2, task2) => [range2, task2, -1],
|
|
2994
|
+
rangeTaskTimeout: (range2, task2, timeout2) => [range2, task2, timeout2],
|
|
2995
|
+
task: (task2) => [[-Infinity, Infinity], task2, -1],
|
|
2996
|
+
taskTimeout: (task2, timeout2) => [[-Infinity, Infinity], task2, timeout2]
|
|
2997
|
+
},
|
|
2998
|
+
{
|
|
2999
|
+
task: [Function],
|
|
3000
|
+
taskTimeout: [Function, Number],
|
|
3001
|
+
rangeTask: [Array, Function],
|
|
3002
|
+
rangeTaskTimeout: [Array, Function, Number]
|
|
3003
|
+
}
|
|
3004
|
+
);
|
|
3005
|
+
return this._lock(
|
|
3006
|
+
this.writeQueue,
|
|
3007
|
+
range,
|
|
3008
|
+
timeout,
|
|
3009
|
+
task,
|
|
3010
|
+
() => {
|
|
3011
|
+
return !this.rangeOverlapping(this.writings, range) && !this.rangeOverlapping(this.readings, range);
|
|
3012
|
+
}
|
|
3013
|
+
);
|
|
3014
|
+
}
|
|
3015
|
+
/**
|
|
3016
|
+
* Releases a read lock by its lock ID.
|
|
3017
|
+
* @param lockId - The unique identifier for the lock to release.
|
|
3018
|
+
*/
|
|
3019
|
+
readUnlock(lockId) {
|
|
3020
|
+
this._free(this.readings, lockId);
|
|
3021
|
+
}
|
|
3022
|
+
/**
|
|
3023
|
+
* Releases a write lock by its lock ID.
|
|
3024
|
+
* @param lockId - The unique identifier for the lock to release.
|
|
3025
|
+
*/
|
|
3026
|
+
writeUnlock(lockId) {
|
|
3027
|
+
this._free(this.writings, lockId);
|
|
3028
|
+
}
|
|
3029
|
+
};
|
|
2688
3030
|
var SerializeStrategyAsync = class extends SerializeStrategy {
|
|
3031
|
+
lock = new Ryoiki();
|
|
3032
|
+
async acquireLock(action) {
|
|
3033
|
+
let lockId;
|
|
3034
|
+
return this.lock.writeLock((_lockId) => {
|
|
3035
|
+
lockId = _lockId;
|
|
3036
|
+
return action();
|
|
3037
|
+
}).finally(() => {
|
|
3038
|
+
this.lock.writeUnlock(lockId);
|
|
3039
|
+
});
|
|
3040
|
+
}
|
|
2689
3041
|
async getHeadData(key, defaultValue) {
|
|
2690
3042
|
if (!Object.hasOwn(this.head.data, key)) {
|
|
2691
3043
|
await this.setHeadData(key, defaultValue);
|
|
@@ -2702,13 +3054,13 @@ var SerializeStrategyAsync = class extends SerializeStrategy {
|
|
|
2702
3054
|
await this.setHeadData(key, next);
|
|
2703
3055
|
return current;
|
|
2704
3056
|
}
|
|
2705
|
-
async
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
3057
|
+
async getLastCommittedTransactionId() {
|
|
3058
|
+
return this.lastCommittedTransactionId;
|
|
3059
|
+
}
|
|
3060
|
+
async compareAndSwapHead(newRoot, newTxId) {
|
|
2709
3061
|
this.head.root = newRoot;
|
|
3062
|
+
this.lastCommittedTransactionId = newTxId;
|
|
2710
3063
|
await this.writeHead(this.head);
|
|
2711
|
-
return true;
|
|
2712
3064
|
}
|
|
2713
3065
|
};
|
|
2714
3066
|
var InMemoryStoreStrategyAsync = class extends SerializeStrategyAsync {
|
|
@@ -2775,8 +3127,11 @@ var BPTreeAsyncSnapshotStrategy = class extends SerializeStrategyAsync {
|
|
|
2775
3127
|
this.snapshotHead.root = head.root;
|
|
2776
3128
|
this.snapshotHead.data = { ...head.data };
|
|
2777
3129
|
}
|
|
2778
|
-
async compareAndSwapHead(
|
|
2779
|
-
|
|
3130
|
+
async compareAndSwapHead(newRoot, newTxId) {
|
|
3131
|
+
await this.baseStrategy.compareAndSwapHead(newRoot, newTxId);
|
|
3132
|
+
}
|
|
3133
|
+
async getLastCommittedTransactionId() {
|
|
3134
|
+
return await this.baseStrategy.getLastCommittedTransactionId();
|
|
2780
3135
|
}
|
|
2781
3136
|
async getHeadData(key, defaultValue) {
|
|
2782
3137
|
return this.snapshotHead.data[key] ?? defaultValue;
|
|
@@ -2795,8 +3150,11 @@ var BPTreeAsyncTransaction = class extends BPTreeAsyncBase {
|
|
|
2795
3150
|
dirtyIds;
|
|
2796
3151
|
createdInTx;
|
|
2797
3152
|
deletedIds;
|
|
3153
|
+
originalNodes = /* @__PURE__ */ new Map();
|
|
2798
3154
|
initialRootId;
|
|
2799
3155
|
transactionRootId;
|
|
3156
|
+
transactionId;
|
|
3157
|
+
initialLastCommittedTransactionId = 0;
|
|
2800
3158
|
constructor(baseTree) {
|
|
2801
3159
|
super(baseTree.strategy, baseTree.comparator, baseTree.option);
|
|
2802
3160
|
this.realBaseTree = baseTree;
|
|
@@ -2807,6 +3165,7 @@ var BPTreeAsyncTransaction = class extends BPTreeAsyncBase {
|
|
|
2807
3165
|
this.dirtyIds = /* @__PURE__ */ new Set();
|
|
2808
3166
|
this.createdInTx = /* @__PURE__ */ new Set();
|
|
2809
3167
|
this.deletedIds = /* @__PURE__ */ new Set();
|
|
3168
|
+
this.transactionId = Date.now() + Math.random();
|
|
2810
3169
|
}
|
|
2811
3170
|
/**
|
|
2812
3171
|
* Initializes the transaction by capturing the current state of the tree.
|
|
@@ -2823,6 +3182,7 @@ var BPTreeAsyncTransaction = class extends BPTreeAsyncBase {
|
|
|
2823
3182
|
const root = await this._createNode(true, [], [], true);
|
|
2824
3183
|
this.initialRootId = root.id;
|
|
2825
3184
|
}
|
|
3185
|
+
this.initialLastCommittedTransactionId = await this.realBaseStrategy.getLastCommittedTransactionId();
|
|
2826
3186
|
this.transactionRootId = this.initialRootId;
|
|
2827
3187
|
this.rootId = this.transactionRootId;
|
|
2828
3188
|
const snapshotStrategy = new BPTreeAsyncSnapshotStrategy(this.realBaseStrategy, this.initialRootId);
|
|
@@ -2831,6 +3191,7 @@ var BPTreeAsyncTransaction = class extends BPTreeAsyncBase {
|
|
|
2831
3191
|
this.dirtyIds.clear();
|
|
2832
3192
|
this.createdInTx.clear();
|
|
2833
3193
|
this.deletedIds.clear();
|
|
3194
|
+
this.realBaseTree.registerTransaction(this.transactionId);
|
|
2834
3195
|
}
|
|
2835
3196
|
async getNode(id) {
|
|
2836
3197
|
if (this.txNodes.has(id)) {
|
|
@@ -2839,7 +3200,13 @@ var BPTreeAsyncTransaction = class extends BPTreeAsyncBase {
|
|
|
2839
3200
|
if (this.deletedIds.has(id)) {
|
|
2840
3201
|
throw new Error(`The tree attempted to reference deleted node '${id}'`);
|
|
2841
3202
|
}
|
|
2842
|
-
|
|
3203
|
+
let baseNode = this.realBaseTree.getObsoleteNode(id);
|
|
3204
|
+
if (!baseNode) {
|
|
3205
|
+
baseNode = await this.realBaseStrategy.read(id);
|
|
3206
|
+
}
|
|
3207
|
+
if (!this.originalNodes.has(id) && !this.createdInTx.has(id)) {
|
|
3208
|
+
this.originalNodes.set(id, JSON.parse(JSON.stringify(baseNode)));
|
|
3209
|
+
}
|
|
2843
3210
|
const clone = JSON.parse(JSON.stringify(baseNode));
|
|
2844
3211
|
this.txNodes.set(id, clone);
|
|
2845
3212
|
return clone;
|
|
@@ -2929,9 +3296,10 @@ var BPTreeAsyncTransaction = class extends BPTreeAsyncBase {
|
|
|
2929
3296
|
* Attempts to commit the transaction.
|
|
2930
3297
|
* Uses Optimistic Locking (Compare-And-Swap) on the root node ID to detect conflicts.
|
|
2931
3298
|
*
|
|
3299
|
+
* @param cleanup Whether to clean up obsolete nodes after commit. Defaults to true.
|
|
2932
3300
|
* @returns A promise that resolves to the transaction result.
|
|
2933
3301
|
*/
|
|
2934
|
-
async commit() {
|
|
3302
|
+
async commit(cleanup = true) {
|
|
2935
3303
|
const idMapping = /* @__PURE__ */ new Map();
|
|
2936
3304
|
const finalNodes = [];
|
|
2937
3305
|
for (const oldId of this.dirtyIds) {
|
|
@@ -2979,24 +3347,51 @@ var BPTreeAsyncTransaction = class extends BPTreeAsyncBase {
|
|
|
2979
3347
|
if (idMapping.has(this.rootId)) {
|
|
2980
3348
|
newRootId = idMapping.get(this.rootId);
|
|
2981
3349
|
}
|
|
2982
|
-
|
|
2983
|
-
|
|
3350
|
+
let success = false;
|
|
3351
|
+
if (finalNodes.length === 0) {
|
|
3352
|
+
success = true;
|
|
3353
|
+
} else {
|
|
3354
|
+
success = await this.realBaseStrategy.acquireLock(async () => {
|
|
3355
|
+
if (await this.realBaseStrategy.getLastCommittedTransactionId() === this.initialLastCommittedTransactionId) {
|
|
3356
|
+
for (const node of finalNodes) {
|
|
3357
|
+
await this.realBaseStrategy.write(node.id, node);
|
|
3358
|
+
}
|
|
3359
|
+
await this.realBaseStrategy.compareAndSwapHead(newRootId, this.transactionId);
|
|
3360
|
+
return true;
|
|
3361
|
+
}
|
|
3362
|
+
return false;
|
|
3363
|
+
});
|
|
2984
3364
|
}
|
|
2985
|
-
const success = await this.realBaseStrategy.compareAndSwapHead(this.initialRootId, newRootId);
|
|
2986
3365
|
if (success) {
|
|
2987
3366
|
const distinctObsolete = /* @__PURE__ */ new Set();
|
|
2988
3367
|
for (const oldId of this.dirtyIds) {
|
|
2989
|
-
if (
|
|
3368
|
+
if (this.createdInTx.has(oldId)) {
|
|
3369
|
+
continue;
|
|
3370
|
+
}
|
|
3371
|
+
if (this.txNodes.has(oldId) || this.deletedIds.has(oldId)) {
|
|
2990
3372
|
distinctObsolete.add(oldId);
|
|
2991
3373
|
}
|
|
2992
3374
|
}
|
|
3375
|
+
if (cleanup) {
|
|
3376
|
+
for (const obsoleteId of distinctObsolete) {
|
|
3377
|
+
if (this.originalNodes.has(obsoleteId)) {
|
|
3378
|
+
this.realBaseTree.addObsoleteNode(
|
|
3379
|
+
this.originalNodes.get(obsoleteId),
|
|
3380
|
+
this.transactionId
|
|
3381
|
+
);
|
|
3382
|
+
}
|
|
3383
|
+
await this.realBaseStrategy.delete(obsoleteId);
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
this.realBaseTree.unregisterTransaction(this.transactionId);
|
|
3387
|
+
this.realBaseTree.pruneObsoleteNodes();
|
|
2993
3388
|
return {
|
|
2994
3389
|
success: true,
|
|
2995
3390
|
createdIds: newCreatedIds,
|
|
2996
3391
|
obsoleteIds: Array.from(distinctObsolete)
|
|
2997
3392
|
};
|
|
2998
3393
|
} else {
|
|
2999
|
-
await this.rollback();
|
|
3394
|
+
await this.rollback(cleanup);
|
|
3000
3395
|
return {
|
|
3001
3396
|
success: false,
|
|
3002
3397
|
createdIds: newCreatedIds,
|
|
@@ -3020,6 +3415,8 @@ var BPTreeAsyncTransaction = class extends BPTreeAsyncBase {
|
|
|
3020
3415
|
await this.realBaseStrategy.delete(id);
|
|
3021
3416
|
}
|
|
3022
3417
|
}
|
|
3418
|
+
this.realBaseTree.unregisterTransaction(this.transactionId);
|
|
3419
|
+
this.realBaseTree.pruneObsoleteNodes();
|
|
3023
3420
|
return createdIds;
|
|
3024
3421
|
}
|
|
3025
3422
|
async readLock(fn) {
|
|
@@ -3077,7 +3474,7 @@ var BPTreeAsync = class extends BPTreeAsyncBase {
|
|
|
3077
3474
|
};
|
|
3078
3475
|
|
|
3079
3476
|
// node_modules/ryoiki/dist/esm/index.mjs
|
|
3080
|
-
var
|
|
3477
|
+
var Ryoiki2 = class _Ryoiki2 {
|
|
3081
3478
|
readings;
|
|
3082
3479
|
writings;
|
|
3083
3480
|
readQueue;
|
|
@@ -3121,7 +3518,7 @@ var Ryoiki = class _Ryoiki {
|
|
|
3121
3518
|
return [start, start + length];
|
|
3122
3519
|
}
|
|
3123
3520
|
rangeOverlapping(tasks, range) {
|
|
3124
|
-
return Array.from(tasks.values()).some((t) =>
|
|
3521
|
+
return Array.from(tasks.values()).some((t) => _Ryoiki2.IsRangeOverlap(t.range, range));
|
|
3125
3522
|
}
|
|
3126
3523
|
isSameRange(a, b) {
|
|
3127
3524
|
const [a1, a2] = a;
|
|
@@ -3162,7 +3559,7 @@ var Ryoiki = class _Ryoiki {
|
|
|
3162
3559
|
_alloc(queue, workspaces, lockId) {
|
|
3163
3560
|
const unit = queue.get(lockId);
|
|
3164
3561
|
if (!unit) {
|
|
3165
|
-
throw
|
|
3562
|
+
throw _Ryoiki2.ERR_NOT_EXISTS(lockId);
|
|
3166
3563
|
}
|
|
3167
3564
|
workspaces.set(lockId, unit);
|
|
3168
3565
|
queue.delete(lockId);
|
|
@@ -3171,7 +3568,7 @@ var Ryoiki = class _Ryoiki {
|
|
|
3171
3568
|
_free(workspaces, lockId) {
|
|
3172
3569
|
const unit = workspaces.get(lockId);
|
|
3173
3570
|
if (!unit) {
|
|
3174
|
-
throw
|
|
3571
|
+
throw _Ryoiki2.ERR_NOT_EXISTS(lockId);
|
|
3175
3572
|
}
|
|
3176
3573
|
workspaces.delete(lockId);
|
|
3177
3574
|
unit.free();
|
|
@@ -3181,7 +3578,7 @@ var Ryoiki = class _Ryoiki {
|
|
|
3181
3578
|
let timeoutId = null;
|
|
3182
3579
|
if (timeout >= 0) {
|
|
3183
3580
|
timeoutId = setTimeout(() => {
|
|
3184
|
-
reject(
|
|
3581
|
+
reject(_Ryoiki2.ERR_TIMEOUT(id, timeout));
|
|
3185
3582
|
}, timeout);
|
|
3186
3583
|
}
|
|
3187
3584
|
const id = this._createRandomId();
|
|
@@ -3189,7 +3586,7 @@ var Ryoiki = class _Ryoiki {
|
|
|
3189
3586
|
if (timeoutId !== null) {
|
|
3190
3587
|
clearTimeout(timeoutId);
|
|
3191
3588
|
}
|
|
3192
|
-
const [err, v] = await
|
|
3589
|
+
const [err, v] = await _Ryoiki2.CatchError(task(id));
|
|
3193
3590
|
if (err) reject(err);
|
|
3194
3591
|
else resolve(v);
|
|
3195
3592
|
};
|
|
@@ -3204,7 +3601,7 @@ var Ryoiki = class _Ryoiki {
|
|
|
3204
3601
|
_checkWorking(range, workspaces) {
|
|
3205
3602
|
let isLocked = false;
|
|
3206
3603
|
for (const lock of workspaces.values()) {
|
|
3207
|
-
if (
|
|
3604
|
+
if (_Ryoiki2.IsRangeOverlap(range, lock.range)) {
|
|
3208
3605
|
isLocked = true;
|
|
3209
3606
|
break;
|
|
3210
3607
|
}
|
|
@@ -5614,7 +6011,6 @@ var VirtualFileSystem = class {
|
|
|
5614
6011
|
this.fileSize = import_node_fs2.default.fstatSync(fileHandle).size;
|
|
5615
6012
|
if (walPath) {
|
|
5616
6013
|
this.logManager = new LogManager(walPath, pageSize);
|
|
5617
|
-
this.recover();
|
|
5618
6014
|
}
|
|
5619
6015
|
}
|
|
5620
6016
|
/** Cache list (Page ID -> Data Buffer) */
|
|
@@ -5633,10 +6029,9 @@ var VirtualFileSystem = class {
|
|
|
5633
6029
|
activeTransactions = /* @__PURE__ */ new Map();
|
|
5634
6030
|
/**
|
|
5635
6031
|
* Performs recovery (Redo) using WAL logs.
|
|
5636
|
-
* Called
|
|
5637
|
-
* Actual disk sync and log clearing are performed during future transactions or closure.
|
|
6032
|
+
* Called during initialization (DataplyAPI.init), ensuring data is fully restored before operations start.
|
|
5638
6033
|
*/
|
|
5639
|
-
recover() {
|
|
6034
|
+
async recover() {
|
|
5640
6035
|
if (!this.logManager) return;
|
|
5641
6036
|
this.logManager.open();
|
|
5642
6037
|
const restoredPages = this.logManager.readAllSync();
|
|
@@ -5669,11 +6064,10 @@ var VirtualFileSystem = class {
|
|
|
5669
6064
|
this.fileSize = endPos;
|
|
5670
6065
|
}
|
|
5671
6066
|
}
|
|
5672
|
-
Promise.all(promises)
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
});
|
|
6067
|
+
await Promise.all(promises);
|
|
6068
|
+
if (this.logManager && restoredPages.size > 0) {
|
|
6069
|
+
await this.logManager.clear();
|
|
6070
|
+
}
|
|
5677
6071
|
}
|
|
5678
6072
|
/**
|
|
5679
6073
|
* Prepares the transaction for commit (Phase 1).
|
|
@@ -5940,6 +6334,13 @@ var PageFileSystem = class {
|
|
|
5940
6334
|
pageFactory = new PageManagerFactory();
|
|
5941
6335
|
vfs;
|
|
5942
6336
|
pageManagerFactory;
|
|
6337
|
+
/**
|
|
6338
|
+
* Initializes the page file system.
|
|
6339
|
+
* Performs VFS recovery if necessary.
|
|
6340
|
+
*/
|
|
6341
|
+
async init() {
|
|
6342
|
+
await this.vfs.recover();
|
|
6343
|
+
}
|
|
5943
6344
|
/**
|
|
5944
6345
|
* Updates the bitmap status for a specific page.
|
|
5945
6346
|
* @param pageId The ID of the page to update
|
|
@@ -6481,13 +6882,8 @@ var RowTableEngine = class {
|
|
|
6481
6882
|
}
|
|
6482
6883
|
if (!btx) return;
|
|
6483
6884
|
const result = await btx.commit();
|
|
6484
|
-
if (result.success) {
|
|
6485
|
-
|
|
6486
|
-
for (const id of result.obsoleteIds) {
|
|
6487
|
-
await this.strategy.delete(id);
|
|
6488
|
-
}
|
|
6489
|
-
} else {
|
|
6490
|
-
throw new Error(`BPTree transaction commit failed. Current Root: ${this.bptree.getRootId()}`);
|
|
6885
|
+
if (!result.success) {
|
|
6886
|
+
throw new Error(`BPTree transaction commit failed. Current TxID: ${btx.transactionId}`);
|
|
6491
6887
|
}
|
|
6492
6888
|
});
|
|
6493
6889
|
}
|
|
@@ -6854,7 +7250,7 @@ var LockManager = class {
|
|
|
6854
7250
|
lock;
|
|
6855
7251
|
unlockMap = /* @__PURE__ */ new Map();
|
|
6856
7252
|
constructor() {
|
|
6857
|
-
this.lock = new
|
|
7253
|
+
this.lock = new Ryoiki2();
|
|
6858
7254
|
}
|
|
6859
7255
|
/**
|
|
6860
7256
|
* Requests a read (Shared) lock for a page.
|
|
@@ -7266,6 +7662,7 @@ var DataplyAPI = class {
|
|
|
7266
7662
|
}
|
|
7267
7663
|
await this.runWithDefault(async (tx) => {
|
|
7268
7664
|
await this.hook.trigger("init", tx, async (tx2) => {
|
|
7665
|
+
await this.pfs.init();
|
|
7269
7666
|
await this.rowTableEngine.init();
|
|
7270
7667
|
this.initialized = true;
|
|
7271
7668
|
return tx2;
|
|
@@ -21,6 +21,11 @@ export declare class PageFileSystem {
|
|
|
21
21
|
* @param walPath WAL 파일 경로 (기본값: null)
|
|
22
22
|
*/
|
|
23
23
|
constructor(fileHandle: number, pageSize: number, pageCacheCapacity: number, walPath?: string | undefined | null);
|
|
24
|
+
/**
|
|
25
|
+
* Initializes the page file system.
|
|
26
|
+
* Performs VFS recovery if necessary.
|
|
27
|
+
*/
|
|
28
|
+
init(): Promise<void>;
|
|
24
29
|
/**
|
|
25
30
|
* Updates the bitmap status for a specific page.
|
|
26
31
|
* @param pageId The ID of the page to update
|
|
@@ -22,10 +22,9 @@ export declare class VirtualFileSystem {
|
|
|
22
22
|
constructor(fileHandle: number, pageSize: number, pageCacheCapacity: number, walPath?: string | undefined | null);
|
|
23
23
|
/**
|
|
24
24
|
* Performs recovery (Redo) using WAL logs.
|
|
25
|
-
* Called
|
|
26
|
-
* Actual disk sync and log clearing are performed during future transactions or closure.
|
|
25
|
+
* Called during initialization (DataplyAPI.init), ensuring data is fully restored before operations start.
|
|
27
26
|
*/
|
|
28
|
-
|
|
27
|
+
recover(): Promise<void>;
|
|
29
28
|
/**
|
|
30
29
|
* Prepares the transaction for commit (Phase 1).
|
|
31
30
|
* Writes dirty pages to WAL but does not update the main database file.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dataply",
|
|
3
|
-
"version": "0.0.16-alpha.
|
|
3
|
+
"version": "0.0.16-alpha.8",
|
|
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>",
|
|
@@ -48,6 +48,6 @@
|
|
|
48
48
|
"cache-entanglement": "^1.7.1",
|
|
49
49
|
"hookall": "^2.2.0",
|
|
50
50
|
"ryoiki": "^1.2.0",
|
|
51
|
-
"serializable-bptree": "^7.0.
|
|
51
|
+
"serializable-bptree": "^7.0.4"
|
|
52
52
|
}
|
|
53
|
-
}
|
|
53
|
+
}
|
package/readme.md
CHANGED
|
@@ -9,14 +9,15 @@
|
|
|
9
9
|
|
|
10
10
|
## Key Features
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
12
|
+
Dataply provides essential features for high-performance data management:
|
|
13
|
+
|
|
14
|
+
- **Identity-Based Access**: Manage records through auto-generated Primary Keys for ultra-fast retrieval.
|
|
15
|
+
- **High-Performance B+Tree**: Asynchronous B+Tree structure optimizes both lookups and insertions.
|
|
16
|
+
- **MVCC & Isolation**: Snapshot isolation via Multi-Version Concurrency Control (MVCC) enables non-blocking reads.
|
|
17
|
+
- **Reliability (WAL)**: Write-Ahead Logging (WAL) ensures data integrity and automatic crash recovery.
|
|
18
|
+
- **Atomic Transactions**: Full support for ACID-compliant Commit and Rollback operations.
|
|
19
|
+
- **Efficient Storage**: Fixed-size page management with VFS-based caching and Bitmap space optimization.
|
|
20
|
+
- **Type Safety**: Comprehensive TypeScript definitions for a seamless developer experience.
|
|
20
21
|
|
|
21
22
|
## Installation
|
|
22
23
|
|
|
@@ -148,10 +149,10 @@ try {
|
|
|
148
149
|
```
|
|
149
150
|
|
|
150
151
|
### Auto-Transaction
|
|
151
|
-
If you omit the `tx` argument
|
|
152
|
+
If you omit the `tx` argument, Dataply creates an internal transaction for each operation.
|
|
152
153
|
|
|
153
|
-
- **
|
|
154
|
-
- **
|
|
154
|
+
- **Security**: Atomicity is guaranteed even for single operations.
|
|
155
|
+
- **Optimization Tip**: For bulk operations, use an **explicit transaction** to significantly reduce I/O overhead and increase performance.
|
|
155
156
|
|
|
156
157
|
## API Reference
|
|
157
158
|
|
|
@@ -201,13 +202,13 @@ Cancels all changes made during the transaction and restores the original state.
|
|
|
201
202
|
### GlobalTransaction Class
|
|
202
203
|
|
|
203
204
|
#### `add(tx: Transaction): void`
|
|
204
|
-
|
|
205
|
+
Registers a transaction from a Dataply instance to the global unit.
|
|
205
206
|
|
|
206
207
|
#### `async commit(): Promise<void>`
|
|
207
|
-
|
|
208
|
+
Executes an atomic commit across all registered transactions via a 2-Phase Commit (2PC) protocol.
|
|
208
209
|
|
|
209
210
|
#### `async rollback(): Promise<void>`
|
|
210
|
-
Rolls back all
|
|
211
|
+
Rolls back all registered transactions simultaneously.
|
|
211
212
|
|
|
212
213
|
## Extending Dataply
|
|
213
214
|
|
|
@@ -331,12 +332,14 @@ As **Dataply** is currently in Alpha, there are several limitations to keep in m
|
|
|
331
332
|
|
|
332
333
|
### Q: Why should I use Dataply instead of a simple JSON file?
|
|
333
334
|
|
|
334
|
-
|
|
335
|
+
While JSON is simple, Dataply is designed for scalable and reliable data management:
|
|
335
336
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
337
|
+
| Feature | JSON File Approach | Dataply Record Store |
|
|
338
|
+
| :--- | :--- | :--- |
|
|
339
|
+
| **Memory usage** | Loads entire file into RAM | Constant memory via page-based I/O |
|
|
340
|
+
| **Search speed** | Linear scan (O(N)) | B+Tree Index lookups (O(log N)) |
|
|
341
|
+
| **Integrity** | High risk of corruption on crash | Protected by WAL and Transactions |
|
|
342
|
+
| **Concurrency** | Single-user only | Multi-user via MVCC & Locking |
|
|
340
343
|
|
|
341
344
|
### Q: What can I build with Dataply?
|
|
342
345
|
|