serializable-bptree 6.2.3 → 7.0.0

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.
@@ -21,7 +21,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var src_exports = {};
22
22
  __export(src_exports, {
23
23
  BPTreeAsync: () => BPTreeAsync,
24
+ BPTreeAsyncTransaction: () => BPTreeAsyncTransaction,
24
25
  BPTreeSync: () => BPTreeSync,
26
+ BPTreeSyncTransaction: () => BPTreeSyncTransaction,
25
27
  InMemoryStoreStrategyAsync: () => InMemoryStoreStrategyAsync,
26
28
  InMemoryStoreStrategySync: () => InMemoryStoreStrategySync,
27
29
  NumericComparator: () => NumericComparator,
@@ -761,8 +763,8 @@ var BPTree = class _BPTree {
761
763
  }
762
764
  };
763
765
 
764
- // src/BPTreeSync.ts
765
- var BPTreeSync = class extends BPTree {
766
+ // src/base/BPTreeSyncBase.ts
767
+ var BPTreeSyncBase = class extends BPTree {
766
768
  constructor(strategy, comparator, option) {
767
769
  super(strategy, comparator, option);
768
770
  this.nodes = this._createCachedNode();
@@ -871,7 +873,7 @@ var BPTreeSync = class extends BPTree {
871
873
  }
872
874
  }
873
875
  }
874
- if (this.rootId === node.id && node.keys.length === 1) {
876
+ if (this.rootId === node.id && node.keys.length === 1 && !node.leaf) {
875
877
  const keys = node.keys;
876
878
  this.bufferForNodeDelete(node);
877
879
  const newRoot = this.getNode(keys[0]);
@@ -940,20 +942,11 @@ var BPTreeSync = class extends BPTree {
940
942
  pointer.values.push(guess);
941
943
  } else {
942
944
  pointer.next = node.next;
943
- pointer.prev = node.id;
944
945
  if (pointer.next) {
945
- const n = this.getNode(node.next);
946
+ const n = this.getNode(pointer.next);
946
947
  n.prev = pointer.id;
947
948
  this.bufferForNodeUpdate(n);
948
949
  }
949
- if (pointer.prev) {
950
- const n = this.getNode(node.id);
951
- n.next = pointer.id;
952
- this.bufferForNodeUpdate(n);
953
- }
954
- if (isPredecessor) {
955
- pointer.prev = null;
956
- }
957
950
  }
958
951
  pointer.values.push(...node.values);
959
952
  if (!pointer.leaf) {
@@ -1178,10 +1171,6 @@ var BPTreeSync = class extends BPTree {
1178
1171
  }
1179
1172
  return node;
1180
1173
  }
1181
- /**
1182
- * Find the insertable node using primaryAsc comparison.
1183
- * This allows finding nodes by primary value only, ignoring unique identifiers.
1184
- */
1185
1174
  insertableNodeByPrimary(value) {
1186
1175
  let node = this.getNode(this.rootId);
1187
1176
  while (!node.leaf) {
@@ -1262,6 +1251,33 @@ var BPTreeSync = class extends BPTree {
1262
1251
  }
1263
1252
  return node;
1264
1253
  }
1254
+ exists(key, value) {
1255
+ const node = this.insertableNode(value);
1256
+ for (let i = 0, len = node.values.length; i < len; i++) {
1257
+ if (this.comparator.isSame(value, node.values[i])) {
1258
+ const keys = node.keys[i];
1259
+ if (keys.includes(key)) {
1260
+ return true;
1261
+ }
1262
+ }
1263
+ }
1264
+ return false;
1265
+ }
1266
+ forceUpdate(id) {
1267
+ if (id) {
1268
+ this.nodes.delete(id);
1269
+ this.getNode(id);
1270
+ return 1;
1271
+ }
1272
+ const keys = Array.from(this.nodes.keys());
1273
+ for (const key of keys) {
1274
+ this.nodes.delete(key);
1275
+ }
1276
+ for (const key of keys) {
1277
+ this.getNode(key);
1278
+ }
1279
+ return keys.length;
1280
+ }
1265
1281
  commitHeadBuffer() {
1266
1282
  if (!this._strategyDirty) {
1267
1283
  return;
@@ -1292,24 +1308,14 @@ var BPTreeSync = class extends BPTree {
1292
1308
  }
1293
1309
  this._nodeDeleteBuffer.clear();
1294
1310
  }
1295
- /**
1296
- * Retrieves the value associated with the given key (PK).
1297
- * Note: This method performs a full scan of leaf nodes as the tree is ordered by Value, not Key.
1298
- *
1299
- * @param key The key to search for.
1300
- * @returns The value associated with the key, or undefined if not found.
1301
- */
1302
1311
  get(key) {
1303
1312
  let node = this.leftestNode();
1304
1313
  while (true) {
1305
- if (node.values) {
1306
- const len = node.values.length;
1307
- for (let i = 0; i < len; i++) {
1308
- const keys = node.keys[i];
1309
- for (let j = 0; j < keys.length; j++) {
1310
- if (keys[j] === key) {
1311
- return node.values[i];
1312
- }
1314
+ for (let i = 0, len = node.values.length; i < len; i++) {
1315
+ const keys = node.keys[i];
1316
+ for (let j = 0, kLen = keys.length; j < kLen; j++) {
1317
+ if (keys[j] === key) {
1318
+ return node.values[i];
1313
1319
  }
1314
1320
  }
1315
1321
  }
@@ -1428,21 +1434,15 @@ var BPTreeSync = class extends BPTree {
1428
1434
  const nValue = node.values[i];
1429
1435
  if (this.comparator.isSame(value, nValue)) {
1430
1436
  const keys = node.keys[i];
1431
- if (keys.includes(key)) {
1432
- if (keys.length > 1) {
1433
- keys.splice(keys.indexOf(key), 1);
1434
- this.bufferForNodeUpdate(node);
1435
- } else if (node.id === this.rootId) {
1436
- node.values.splice(i, 1);
1437
- node.keys.splice(i, 1);
1438
- this.bufferForNodeUpdate(node);
1439
- } else {
1440
- keys.splice(keys.indexOf(key), 1);
1437
+ const keyIndex = keys.indexOf(key);
1438
+ if (keyIndex !== -1) {
1439
+ keys.splice(keyIndex, 1);
1440
+ if (keys.length === 0) {
1441
1441
  node.keys.splice(i, 1);
1442
- node.values.splice(node.values.indexOf(value), 1);
1443
- this._deleteEntry(node, key, value);
1444
- this.bufferForNodeUpdate(node);
1442
+ node.values.splice(i, 1);
1445
1443
  }
1444
+ this._deleteEntry(node, key, value);
1445
+ break;
1446
1446
  }
1447
1447
  }
1448
1448
  }
@@ -1451,298 +1451,356 @@ var BPTreeSync = class extends BPTree {
1451
1451
  this.commitNodeUpdateBuffer();
1452
1452
  this.commitNodeDeleteBuffer();
1453
1453
  }
1454
- exists(key, value) {
1455
- const node = this.insertableNode(value);
1456
- for (let i = 0, len = node.values.length; i < len; i++) {
1457
- const nValue = node.values[i];
1458
- if (this.comparator.isSame(value, nValue)) {
1459
- const keys = node.keys[i];
1460
- return keys.includes(key);
1461
- }
1462
- }
1463
- return false;
1454
+ getHeadData() {
1455
+ return this.strategy.head.data;
1464
1456
  }
1465
1457
  setHeadData(data) {
1466
1458
  this.strategy.head.data = data;
1467
- this._strategyDirty = true;
1468
- this.commitHeadBuffer();
1459
+ this.strategy.writeHead(this.strategy.head);
1469
1460
  }
1470
- forceUpdate() {
1471
- const keys = [...this.nodes.keys()];
1472
- this.nodes.clear();
1473
- this.init();
1474
- for (const key of keys) {
1475
- this.getNode(key);
1476
- }
1477
- return keys.length;
1461
+ };
1462
+
1463
+ // src/base/SerializeStrategy.ts
1464
+ var SerializeStrategy = class {
1465
+ order;
1466
+ head;
1467
+ constructor(order) {
1468
+ this.order = order;
1469
+ this.head = {
1470
+ order,
1471
+ root: null,
1472
+ data: {}
1473
+ };
1478
1474
  }
1479
1475
  };
1480
1476
 
1481
- // node_modules/ryoiki/dist/esm/index.mjs
1482
- var Ryoiki = class _Ryoiki {
1483
- readings;
1484
- writings;
1485
- readQueue;
1486
- writeQueue;
1487
- static async CatchError(promise) {
1488
- return await promise.then((v) => [void 0, v]).catch((err) => [err]);
1489
- }
1490
- static IsRangeOverlap(a, b) {
1491
- const [start1, end1] = a;
1492
- const [start2, end2] = b;
1493
- if (end1 <= start2 || end2 <= start1) {
1477
+ // src/SerializeStrategySync.ts
1478
+ var SerializeStrategySync = class extends SerializeStrategy {
1479
+ getHeadData(key, defaultValue) {
1480
+ if (!Object.hasOwn(this.head.data, key)) {
1481
+ this.setHeadData(key, defaultValue);
1482
+ }
1483
+ return this.head.data[key];
1484
+ }
1485
+ setHeadData(key, data) {
1486
+ this.head.data[key] = data;
1487
+ this.writeHead(this.head);
1488
+ }
1489
+ autoIncrement(key, defaultValue) {
1490
+ const current = this.getHeadData(key, defaultValue);
1491
+ const next = current + 1;
1492
+ this.setHeadData(key, next);
1493
+ return current;
1494
+ }
1495
+ compareAndSwapHead(oldRoot, newRoot) {
1496
+ if (this.head.root !== oldRoot) {
1494
1497
  return false;
1495
1498
  }
1499
+ this.head.root = newRoot;
1500
+ this.writeHead(this.head);
1496
1501
  return true;
1497
1502
  }
1498
- static ERR_ALREADY_EXISTS(lockId) {
1499
- return new Error(`The '${lockId}' task already existing in queue or running.`);
1503
+ };
1504
+ var InMemoryStoreStrategySync = class extends SerializeStrategySync {
1505
+ node;
1506
+ constructor(order) {
1507
+ super(order);
1508
+ this.node = {};
1500
1509
  }
1501
- static ERR_NOT_EXISTS(lockId) {
1502
- return new Error(`The '${lockId}' task not existing in task queue.`);
1510
+ id(isLeaf) {
1511
+ return this.autoIncrement("index", 1).toString();
1503
1512
  }
1504
- static ERR_TIMEOUT(lockId, timeout) {
1505
- return new Error(`The task with ID '${lockId}' failed to acquire the lock within the timeout(${timeout}ms).`);
1513
+ read(id) {
1514
+ if (!Object.hasOwn(this.node, id)) {
1515
+ throw new Error(`The tree attempted to reference node '${id}', but couldn't find the corresponding node.`);
1516
+ }
1517
+ const node = this.node[id];
1518
+ return JSON.parse(JSON.stringify(node));
1506
1519
  }
1507
- /**
1508
- * Constructs a new instance of the Ryoiki class.
1509
- */
1510
- constructor() {
1511
- this.readings = /* @__PURE__ */ new Map();
1512
- this.writings = /* @__PURE__ */ new Map();
1513
- this.readQueue = /* @__PURE__ */ new Map();
1514
- this.writeQueue = /* @__PURE__ */ new Map();
1520
+ write(id, node) {
1521
+ this.node[id] = node;
1515
1522
  }
1516
- /**
1517
- * Creates a range based on a start value and length.
1518
- * @param start - The starting value of the range.
1519
- * @param length - The length of the range.
1520
- * @returns A range tuple [start, start + length].
1521
- */
1522
- range(start, length) {
1523
- return [start, start + length];
1523
+ delete(id) {
1524
+ delete this.node[id];
1524
1525
  }
1525
- rangeOverlapping(tasks, range) {
1526
- return Array.from(tasks.values()).some((t) => _Ryoiki.IsRangeOverlap(t.range, range));
1526
+ readHead() {
1527
+ if (this.head.root === null) {
1528
+ return null;
1529
+ }
1530
+ return this.head;
1527
1531
  }
1528
- isSameRange(a, b) {
1529
- const [a1, a2] = a;
1530
- const [b1, b2] = b;
1531
- return a1 === b1 && a2 === b2;
1532
+ writeHead(head) {
1533
+ this.head = head;
1532
1534
  }
1533
- fetchUnitAndRun(queue, workspaces) {
1534
- for (const [id, unit] of queue) {
1535
- if (!unit.condition()) {
1536
- continue;
1537
- }
1538
- this._alloc(queue, workspaces, id);
1539
- }
1535
+ };
1536
+
1537
+ // src/transaction/BPTreeSyncSnapshotStrategy.ts
1538
+ var BPTreeSyncSnapshotStrategy = class extends SerializeStrategySync {
1539
+ baseStrategy;
1540
+ snapshotHead;
1541
+ constructor(baseStrategy, root) {
1542
+ super(baseStrategy.order);
1543
+ this.baseStrategy = baseStrategy;
1544
+ this.snapshotHead = {
1545
+ ...baseStrategy.head,
1546
+ root,
1547
+ data: { ...baseStrategy.head.data }
1548
+ };
1549
+ this.head = this.snapshotHead;
1540
1550
  }
1541
- _handleOverload(args, handlers, argPatterns) {
1542
- for (const [key, pattern] of Object.entries(argPatterns)) {
1543
- if (this._matchArgs(args, pattern)) {
1544
- return handlers[key](...args);
1545
- }
1546
- }
1547
- throw new Error("Invalid arguments");
1551
+ id(isLeaf) {
1552
+ return this.baseStrategy.id(isLeaf);
1548
1553
  }
1549
- _matchArgs(args, pattern) {
1550
- return args.every((arg, index) => {
1551
- const expectedType = pattern[index];
1552
- if (expectedType === void 0) return typeof arg === "undefined";
1553
- if (expectedType === Function) return typeof arg === "function";
1554
- if (expectedType === Number) return typeof arg === "number";
1555
- if (expectedType === Array) return Array.isArray(arg);
1556
- return false;
1557
- });
1554
+ read(id) {
1555
+ return this.baseStrategy.read(id);
1558
1556
  }
1559
- _createRandomId() {
1560
- const timestamp = Date.now().toString(36);
1561
- const random = Math.random().toString(36).substring(2);
1562
- return `${timestamp}${random}`;
1563
- }
1564
- _alloc(queue, workspaces, lockId) {
1565
- const unit = queue.get(lockId);
1566
- if (!unit) {
1567
- throw _Ryoiki.ERR_NOT_EXISTS(lockId);
1568
- }
1569
- workspaces.set(lockId, unit);
1570
- queue.delete(lockId);
1571
- unit.alloc();
1572
- }
1573
- _free(workspaces, lockId) {
1574
- const unit = workspaces.get(lockId);
1575
- if (!unit) {
1576
- throw _Ryoiki.ERR_NOT_EXISTS(lockId);
1577
- }
1578
- workspaces.delete(lockId);
1579
- unit.free();
1580
- }
1581
- _lock(queue, range, timeout, task, condition) {
1582
- return new Promise((resolve, reject) => {
1583
- let timeoutId = null;
1584
- if (timeout >= 0) {
1585
- timeoutId = setTimeout(() => {
1586
- reject(_Ryoiki.ERR_TIMEOUT(id, timeout));
1587
- }, timeout);
1588
- }
1589
- const id = this._createRandomId();
1590
- const alloc = async () => {
1591
- if (timeoutId !== null) {
1592
- clearTimeout(timeoutId);
1593
- }
1594
- const [err, v] = await _Ryoiki.CatchError(task(id));
1595
- if (err) reject(err);
1596
- else resolve(v);
1597
- };
1598
- const fetch = () => {
1599
- this.fetchUnitAndRun(this.readQueue, this.readings);
1600
- this.fetchUnitAndRun(this.writeQueue, this.writings);
1601
- };
1602
- queue.set(id, { id, range, condition, alloc, free: fetch });
1603
- fetch();
1604
- });
1557
+ write(id, node) {
1558
+ this.baseStrategy.write(id, node);
1605
1559
  }
1606
- _checkWorking(range, workspaces) {
1607
- let isLocked = false;
1608
- for (const lock of workspaces.values()) {
1609
- if (_Ryoiki.IsRangeOverlap(range, lock.range)) {
1610
- isLocked = true;
1611
- break;
1612
- }
1613
- }
1614
- return isLocked;
1560
+ delete(id) {
1561
+ this.baseStrategy.delete(id);
1615
1562
  }
1616
- /**
1617
- * Checks if there is any active read lock within the specified range.
1618
- * @param range The range to check for active read locks.
1619
- * @returns `true` if there is an active read lock within the range, `false` otherwise.
1620
- */
1621
- isReading(range) {
1622
- return this._checkWorking(range, this.readings);
1563
+ readHead() {
1564
+ return this.snapshotHead;
1623
1565
  }
1624
- /**
1625
- * Checks if there is any active write lock within the specified range.
1626
- * @param range The range to check for active write locks.
1627
- * @returns `true` if there is an active write lock within the range, `false` otherwise.
1628
- */
1629
- isWriting(range) {
1630
- return this._checkWorking(range, this.writings);
1566
+ writeHead(head) {
1567
+ this.snapshotHead.root = head.root;
1568
+ this.snapshotHead.data = { ...head.data };
1631
1569
  }
1632
- /**
1633
- * Checks if a read lock can be acquired within the specified range.
1634
- * @param range The range to check for read lock availability.
1635
- * @returns `true` if a read lock can be acquired, `false` otherwise.
1636
- */
1637
- canRead(range) {
1638
- const writing = this.isWriting(range);
1639
- return !writing;
1570
+ compareAndSwapHead(oldRoot, newRoot) {
1571
+ return this.baseStrategy.compareAndSwapHead(oldRoot, newRoot);
1640
1572
  }
1641
- /**
1642
- * Checks if a write lock can be acquired within the specified range.
1643
- * @param range The range to check for write lock availability.
1644
- * @returns `true` if a write lock can be acquired, `false` otherwise.
1645
- */
1646
- canWrite(range) {
1647
- const reading = this.isReading(range);
1648
- const writing = this.isWriting(range);
1649
- return !reading && !writing;
1573
+ getHeadData(key, defaultValue) {
1574
+ return this.snapshotHead.data[key] ?? defaultValue;
1575
+ }
1576
+ setHeadData(key, data) {
1577
+ this.snapshotHead.data[key] = data;
1578
+ }
1579
+ autoIncrement(key, defaultValue) {
1580
+ return this.snapshotHead.data[key] ?? defaultValue;
1581
+ }
1582
+ };
1583
+
1584
+ // src/transaction/BPTreeSyncTransaction.ts
1585
+ var BPTreeSyncTransaction = class extends BPTreeSyncBase {
1586
+ realBaseTree;
1587
+ realBaseStrategy;
1588
+ txNodes = /* @__PURE__ */ new Map();
1589
+ dirtyIds = /* @__PURE__ */ new Set();
1590
+ createdInTx = /* @__PURE__ */ new Set();
1591
+ initialRootId;
1592
+ transactionRootId;
1593
+ constructor(baseTree) {
1594
+ super(baseTree.strategy, baseTree.comparator, baseTree.option);
1595
+ this.realBaseTree = baseTree;
1596
+ this.realBaseStrategy = baseTree.strategy;
1597
+ this.initialRootId = "";
1598
+ this.transactionRootId = "";
1650
1599
  }
1651
1600
  /**
1652
- * Internal implementation of the read lock. Handles both overloads.
1653
- * @template T - The return type of the task.
1654
- * @param arg0 - Either a range or a task callback.
1655
- * If a range is provided, the task is the second argument.
1656
- * @param arg1 - The task to execute, required if a range is provided.
1657
- * @param arg2 - The timeout for acquiring the lock.
1658
- * If the lock cannot be acquired within this period, an error will be thrown.
1659
- * If this value is not provided, no timeout will be set.
1660
- * @returns A promise resolving to the result of the task execution.
1601
+ * Initializes the transaction by capturing the current state of the tree.
1661
1602
  */
1662
- readLock(arg0, arg1, arg2) {
1663
- const [range, task, timeout] = this._handleOverload(
1664
- [arg0, arg1, arg2],
1665
- {
1666
- rangeTask: (range2, task2) => [range2, task2, -1],
1667
- rangeTaskTimeout: (range2, task2, timeout2) => [range2, task2, timeout2],
1668
- task: (task2) => [[-Infinity, Infinity], task2, -1],
1669
- taskTimeout: (task2, timeout2) => [[-Infinity, Infinity], task2, timeout2]
1670
- },
1671
- {
1672
- task: [Function],
1673
- taskTimeout: [Function, Number],
1674
- rangeTask: [Array, Function],
1675
- rangeTaskTimeout: [Array, Function, Number]
1603
+ initTransaction() {
1604
+ const head = this.realBaseStrategy.readHead();
1605
+ this.initialRootId = head?.root ?? this.realBaseTree.rootId;
1606
+ this.transactionRootId = this.initialRootId;
1607
+ this.rootId = this.transactionRootId;
1608
+ const snapshotStrategy = new BPTreeSyncSnapshotStrategy(this.realBaseStrategy, this.initialRootId);
1609
+ this.strategy = snapshotStrategy;
1610
+ this.txNodes.clear();
1611
+ this.dirtyIds.clear();
1612
+ this.createdInTx.clear();
1613
+ }
1614
+ getNode(id) {
1615
+ if (this.txNodes.has(id)) {
1616
+ return this.txNodes.get(id);
1617
+ }
1618
+ const baseNode = this.realBaseStrategy.read(id);
1619
+ const clone = JSON.parse(JSON.stringify(baseNode));
1620
+ this.txNodes.set(id, clone);
1621
+ return clone;
1622
+ }
1623
+ bufferForNodeUpdate(node) {
1624
+ this.txNodes.set(node.id, node);
1625
+ this.dirtyIds.add(node.id);
1626
+ this.markPathDirty(node);
1627
+ }
1628
+ bufferForNodeCreate(node) {
1629
+ this.txNodes.set(node.id, node);
1630
+ this.dirtyIds.add(node.id);
1631
+ this.createdInTx.add(node.id);
1632
+ this.markPathDirty(node);
1633
+ }
1634
+ bufferForNodeDelete(node) {
1635
+ this.txNodes.delete(node.id);
1636
+ this.dirtyIds.add(node.id);
1637
+ }
1638
+ markPathDirty(node) {
1639
+ let curr = node;
1640
+ while (curr.parent) {
1641
+ if (this.dirtyIds.has(curr.parent) && this.txNodes.has(curr.parent)) {
1642
+ break;
1676
1643
  }
1677
- );
1678
- return this._lock(
1679
- this.readQueue,
1680
- range,
1681
- timeout,
1682
- task,
1683
- () => !this.rangeOverlapping(this.writings, range)
1684
- );
1644
+ const parent = this.getNode(curr.parent);
1645
+ this.dirtyIds.add(parent.id);
1646
+ curr = parent;
1647
+ }
1648
+ if (!curr.parent) {
1649
+ this.transactionRootId = curr.id;
1650
+ }
1651
+ }
1652
+ _createNode(isLeaf, keys, values, leaf = false, parent = null, next = null, prev = null) {
1653
+ const id = this.realBaseStrategy.id(isLeaf);
1654
+ const node = {
1655
+ id,
1656
+ keys,
1657
+ values,
1658
+ leaf: isLeaf,
1659
+ parent,
1660
+ next,
1661
+ prev
1662
+ };
1663
+ this.bufferForNodeCreate(node);
1664
+ return node;
1685
1665
  }
1686
1666
  /**
1687
- * Internal implementation of the write lock. Handles both overloads.
1688
- * @template T - The return type of the task.
1689
- * @param arg0 - Either a range or a task callback.
1690
- * If a range is provided, the task is the second argument.
1691
- * @param arg1 - The task to execute, required if a range is provided.
1692
- * @param arg2 - The timeout for acquiring the lock.
1693
- * If the lock cannot be acquired within this period, an error will be thrown.
1694
- * If this value is not provided, no timeout will be set.
1695
- * @returns A promise resolving to the result of the task execution.
1667
+ * Attempts to commit the transaction.
1668
+ * Uses Optimistic Locking (Compare-And-Swap) on the root node ID to detect conflicts.
1669
+ *
1670
+ * @returns The transaction result.
1696
1671
  */
1697
- writeLock(arg0, arg1, arg2) {
1698
- const [range, task, timeout] = this._handleOverload(
1699
- [arg0, arg1, arg2],
1700
- {
1701
- rangeTask: (range2, task2) => [range2, task2, -1],
1702
- rangeTaskTimeout: (range2, task2, timeout2) => [range2, task2, timeout2],
1703
- task: (task2) => [[-Infinity, Infinity], task2, -1],
1704
- taskTimeout: (task2, timeout2) => [[-Infinity, Infinity], task2, timeout2]
1705
- },
1706
- {
1707
- task: [Function],
1708
- taskTimeout: [Function, Number],
1709
- rangeTask: [Array, Function],
1710
- rangeTaskTimeout: [Array, Function, Number]
1672
+ commit() {
1673
+ const idMapping = /* @__PURE__ */ new Map();
1674
+ const finalNodes = [];
1675
+ for (const oldId of this.dirtyIds) {
1676
+ if (this.createdInTx.has(oldId)) {
1677
+ idMapping.set(oldId, oldId);
1678
+ } else {
1679
+ const node = this.txNodes.get(oldId);
1680
+ if (node) {
1681
+ const newId = this.realBaseStrategy.id(node.leaf);
1682
+ idMapping.set(oldId, newId);
1683
+ }
1711
1684
  }
1712
- );
1713
- return this._lock(
1714
- this.writeQueue,
1715
- range,
1716
- timeout,
1717
- task,
1718
- () => {
1719
- return !this.rangeOverlapping(this.writings, range) && !this.rangeOverlapping(this.readings, range);
1685
+ }
1686
+ const newCreatedIds = [];
1687
+ for (const oldId of this.dirtyIds) {
1688
+ const node = this.txNodes.get(oldId);
1689
+ if (!node) continue;
1690
+ const newId = idMapping.get(oldId);
1691
+ node.id = newId;
1692
+ if (node.parent && idMapping.has(node.parent)) {
1693
+ node.parent = idMapping.get(node.parent);
1694
+ }
1695
+ if (!node.leaf) {
1696
+ const internal = node;
1697
+ for (let i = 0; i < internal.keys.length; i++) {
1698
+ const childId = internal.keys[i];
1699
+ if (idMapping.has(childId)) {
1700
+ internal.keys[i] = idMapping.get(childId);
1701
+ }
1702
+ }
1720
1703
  }
1721
- );
1704
+ if (node.leaf) {
1705
+ const leaf = node;
1706
+ if (leaf.next && idMapping.has(leaf.next)) {
1707
+ leaf.next = idMapping.get(leaf.next);
1708
+ }
1709
+ if (leaf.prev && idMapping.has(leaf.prev)) {
1710
+ leaf.prev = idMapping.get(leaf.prev);
1711
+ }
1712
+ }
1713
+ finalNodes.push(node);
1714
+ newCreatedIds.push(newId);
1715
+ }
1716
+ let newRootId = this.transactionRootId;
1717
+ if (idMapping.has(this.transactionRootId)) {
1718
+ newRootId = idMapping.get(this.transactionRootId);
1719
+ }
1720
+ for (const node of finalNodes) {
1721
+ this.realBaseStrategy.write(node.id, node);
1722
+ }
1723
+ const success = this.realBaseStrategy.compareAndSwapHead(this.initialRootId, newRootId);
1724
+ if (success) {
1725
+ const distinctObsolete = /* @__PURE__ */ new Set();
1726
+ for (const oldId of this.dirtyIds) {
1727
+ if (!this.createdInTx.has(oldId) && this.txNodes.has(oldId)) {
1728
+ distinctObsolete.add(oldId);
1729
+ }
1730
+ }
1731
+ return {
1732
+ success: true,
1733
+ createdIds: newCreatedIds,
1734
+ obsoleteIds: Array.from(distinctObsolete)
1735
+ };
1736
+ } else {
1737
+ this.rollback();
1738
+ return {
1739
+ success: false,
1740
+ createdIds: newCreatedIds,
1741
+ obsoleteIds: []
1742
+ };
1743
+ }
1722
1744
  }
1723
1745
  /**
1724
- * Releases a read lock by its lock ID.
1725
- * @param lockId - The unique identifier for the lock to release.
1746
+ * Rolls back the transaction by clearing all buffered changes.
1747
+ * Internal use only.
1726
1748
  */
1727
- readUnlock(lockId) {
1728
- this._free(this.readings, lockId);
1749
+ rollback() {
1750
+ this.txNodes.clear();
1751
+ this.dirtyIds.clear();
1752
+ this.createdInTx.clear();
1753
+ }
1754
+ // Override to do nothing, as transaction handles its own commits
1755
+ commitHeadBuffer() {
1756
+ }
1757
+ commitNodeCreateBuffer() {
1758
+ }
1759
+ commitNodeUpdateBuffer() {
1760
+ }
1761
+ commitNodeDeleteBuffer() {
1762
+ }
1763
+ };
1764
+
1765
+ // src/BPTreeSync.ts
1766
+ var BPTreeSync = class extends BPTreeSyncBase {
1767
+ constructor(strategy, comparator, option) {
1768
+ super(strategy, comparator, option);
1729
1769
  }
1730
1770
  /**
1731
- * Releases a write lock by its lock ID.
1732
- * @param lockId - The unique identifier for the lock to release.
1771
+ * Creates a new synchronous transaction.
1772
+ * @returns A new BPTreeSyncTransaction.
1733
1773
  */
1734
- writeUnlock(lockId) {
1735
- this._free(this.writings, lockId);
1774
+ createTransaction() {
1775
+ const tx = new BPTreeSyncTransaction(this);
1776
+ tx.initTransaction();
1777
+ return tx;
1778
+ }
1779
+ insert(key, value) {
1780
+ const tx = this.createTransaction();
1781
+ tx.insert(key, value);
1782
+ const { success } = tx.commit();
1783
+ this.init();
1784
+ if (!success) {
1785
+ throw new Error("Transaction failed: Commit failed due to conflict");
1786
+ }
1787
+ }
1788
+ delete(key, value) {
1789
+ const tx = this.createTransaction();
1790
+ tx.delete(key, value);
1791
+ const { success } = tx.commit();
1792
+ this.init();
1793
+ if (!success) {
1794
+ throw new Error("Transaction failed: Commit failed due to conflict");
1795
+ }
1736
1796
  }
1737
1797
  };
1738
1798
 
1739
- // src/BPTreeAsync.ts
1740
- var BPTreeAsync = class extends BPTree {
1741
- lock;
1799
+ // src/base/BPTreeAsyncBase.ts
1800
+ var BPTreeAsyncBase = class extends BPTree {
1742
1801
  constructor(strategy, comparator, option) {
1743
1802
  super(strategy, comparator, option);
1744
1803
  this.nodes = this._createCachedNode();
1745
- this.lock = new Ryoiki();
1746
1804
  }
1747
1805
  _createCachedNode() {
1748
1806
  return new CacheEntanglementAsync(async (key) => {
@@ -1751,24 +1809,6 @@ var BPTreeAsync = class extends BPTree {
1751
1809
  capacity: this.option.capacity ?? 1e3
1752
1810
  });
1753
1811
  }
1754
- async readLock(callback) {
1755
- let lockId;
1756
- return await this.lock.readLock(async (_lockId) => {
1757
- lockId = _lockId;
1758
- return await callback();
1759
- }).finally(() => {
1760
- this.lock.readUnlock(lockId);
1761
- });
1762
- }
1763
- async writeLock(callback) {
1764
- let lockId;
1765
- return await this.lock.writeLock(async (_lockId) => {
1766
- lockId = _lockId;
1767
- return await callback();
1768
- }).finally(() => {
1769
- this.lock.writeUnlock(lockId);
1770
- });
1771
- }
1772
1812
  async *getPairsGenerator(value, startNode, endNode, comparator, direction, earlyTerminate) {
1773
1813
  let node = startNode;
1774
1814
  let done = false;
@@ -1866,7 +1906,7 @@ var BPTreeAsync = class extends BPTree {
1866
1906
  }
1867
1907
  }
1868
1908
  }
1869
- if (this.rootId === node.id && node.keys.length === 1) {
1909
+ if (this.rootId === node.id && node.keys.length === 1 && !node.leaf) {
1870
1910
  const keys = node.keys;
1871
1911
  this.bufferForNodeDelete(node);
1872
1912
  const newRoot = await this.getNode(keys[0]);
@@ -1935,28 +1975,19 @@ var BPTreeAsync = class extends BPTree {
1935
1975
  pointer.values.push(guess);
1936
1976
  } else {
1937
1977
  pointer.next = node.next;
1938
- pointer.prev = node.id;
1939
1978
  if (pointer.next) {
1940
- const n = await this.getNode(node.next);
1979
+ const n = await this.getNode(pointer.next);
1941
1980
  n.prev = pointer.id;
1942
1981
  this.bufferForNodeUpdate(n);
1943
1982
  }
1944
- if (pointer.prev) {
1945
- const n = await this.getNode(node.id);
1946
- n.next = pointer.id;
1947
- this.bufferForNodeUpdate(n);
1948
- }
1949
- if (isPredecessor) {
1950
- pointer.prev = null;
1951
- }
1952
1983
  }
1953
1984
  pointer.values.push(...node.values);
1954
1985
  if (!pointer.leaf) {
1955
1986
  const keys = pointer.keys;
1956
1987
  for (const key2 of keys) {
1957
- const node2 = await this.getNode(key2);
1958
- node2.parent = pointer.id;
1959
- this.bufferForNodeUpdate(node2);
1988
+ const n = await this.getNode(key2);
1989
+ n.parent = pointer.id;
1990
+ this.bufferForNodeUpdate(n);
1960
1991
  }
1961
1992
  }
1962
1993
  await this._deleteEntry(await this.getNode(node.parent), node.id, guess);
@@ -2033,21 +2064,24 @@ var BPTreeAsync = class extends BPTree {
2033
2064
  this.bufferForNodeUpdate(pointer);
2034
2065
  }
2035
2066
  if (!pointer.leaf) {
2036
- for (const key2 of pointer.keys) {
2067
+ const keys = pointer.keys;
2068
+ for (const key2 of keys) {
2037
2069
  const n = await this.getNode(key2);
2038
2070
  n.parent = pointer.id;
2039
2071
  this.bufferForNodeUpdate(n);
2040
2072
  }
2041
2073
  }
2042
2074
  if (!node.leaf) {
2043
- for (const key2 of node.keys) {
2075
+ const keys = node.keys;
2076
+ for (const key2 of keys) {
2044
2077
  const n = await this.getNode(key2);
2045
2078
  n.parent = node.id;
2046
2079
  this.bufferForNodeUpdate(n);
2047
2080
  }
2048
2081
  }
2049
2082
  if (!parentNode.leaf) {
2050
- for (const key2 of parentNode.keys) {
2083
+ const keys = parentNode.keys;
2084
+ for (const key2 of keys) {
2051
2085
  const n = await this.getNode(key2);
2052
2086
  n.parent = parentNode.id;
2053
2087
  this.bufferForNodeUpdate(n);
@@ -2111,14 +2145,14 @@ var BPTreeAsync = class extends BPTree {
2111
2145
  parentNode.values = parentNode.values.slice(0, mid);
2112
2146
  parentNode.keys = parentNode.keys.slice(0, mid + 1);
2113
2147
  for (const k of parentNode.keys) {
2114
- const node2 = await this.getNode(k);
2115
- node2.parent = parentNode.id;
2116
- this.bufferForNodeUpdate(node2);
2148
+ const n = await this.getNode(k);
2149
+ n.parent = parentNode.id;
2150
+ this.bufferForNodeUpdate(n);
2117
2151
  }
2118
2152
  for (const k of parentPointer.keys) {
2119
- const node2 = await this.getNode(k);
2120
- node2.parent = parentPointer.id;
2121
- this.bufferForNodeUpdate(node2);
2153
+ const n = await this.getNode(k);
2154
+ n.parent = parentPointer.id;
2155
+ this.bufferForNodeUpdate(n);
2122
2156
  }
2123
2157
  await this._insertInParent(parentNode, midValue, parentPointer);
2124
2158
  this.bufferForNodeUpdate(parentNode);
@@ -2160,23 +2194,19 @@ var BPTreeAsync = class extends BPTree {
2160
2194
  const nValue = node.values[i];
2161
2195
  const k = node.keys;
2162
2196
  if (this.comparator.isSame(value, nValue)) {
2163
- node = await this.getNode(k[i + 1]);
2197
+ node = await this.getNode(node.keys[i + 1]);
2164
2198
  break;
2165
2199
  } else if (this.comparator.isLower(value, nValue)) {
2166
- node = await this.getNode(k[i]);
2200
+ node = await this.getNode(node.keys[i]);
2167
2201
  break;
2168
2202
  } else if (i + 1 === node.values.length) {
2169
- node = await this.getNode(k[i + 1]);
2203
+ node = await this.getNode(node.keys[i + 1]);
2170
2204
  break;
2171
2205
  }
2172
2206
  }
2173
2207
  }
2174
2208
  return node;
2175
2209
  }
2176
- /**
2177
- * Find the insertable node using primaryAsc comparison.
2178
- * This allows finding nodes by primary value only, ignoring unique identifiers.
2179
- */
2180
2210
  async insertableNodeByPrimary(value) {
2181
2211
  let node = await this.getNode(this.rootId);
2182
2212
  while (!node.leaf) {
@@ -2184,13 +2214,13 @@ var BPTreeAsync = class extends BPTree {
2184
2214
  const nValue = node.values[i];
2185
2215
  const k = node.keys;
2186
2216
  if (this.comparator.isPrimarySame(value, nValue)) {
2187
- node = await this.getNode(k[i]);
2217
+ node = await this.getNode(node.keys[i]);
2188
2218
  break;
2189
2219
  } else if (this.comparator.isPrimaryLower(value, nValue)) {
2190
- node = await this.getNode(k[i]);
2220
+ node = await this.getNode(node.keys[i]);
2191
2221
  break;
2192
2222
  } else if (i + 1 === node.values.length) {
2193
- node = await this.getNode(k[i + 1]);
2223
+ node = await this.getNode(node.keys[i + 1]);
2194
2224
  break;
2195
2225
  }
2196
2226
  }
@@ -2204,11 +2234,11 @@ var BPTreeAsync = class extends BPTree {
2204
2234
  const nValue = node.values[i];
2205
2235
  const k = node.keys;
2206
2236
  if (this.comparator.isPrimaryLower(value, nValue)) {
2207
- node = await this.getNode(k[i]);
2237
+ node = await this.getNode(node.keys[i]);
2208
2238
  break;
2209
2239
  }
2210
2240
  if (i + 1 === node.values.length) {
2211
- node = await this.getNode(k[i + 1]);
2241
+ node = await this.getNode(node.keys[i + 1]);
2212
2242
  break;
2213
2243
  }
2214
2244
  }
@@ -2245,7 +2275,7 @@ var BPTreeAsync = class extends BPTree {
2245
2275
  let node = await this.getNode(this.rootId);
2246
2276
  while (!node.leaf) {
2247
2277
  const keys = node.keys;
2248
- node = await this.getNode(keys[0]);
2278
+ node = await this.getNode(node.keys[0]);
2249
2279
  }
2250
2280
  return node;
2251
2281
  }
@@ -2253,10 +2283,37 @@ var BPTreeAsync = class extends BPTree {
2253
2283
  let node = await this.getNode(this.rootId);
2254
2284
  while (!node.leaf) {
2255
2285
  const keys = node.keys;
2256
- node = await this.getNode(keys[keys.length - 1]);
2286
+ node = await this.getNode(node.keys[node.keys.length - 1]);
2257
2287
  }
2258
2288
  return node;
2259
2289
  }
2290
+ async exists(key, value) {
2291
+ const node = await this.insertableNode(value);
2292
+ for (let i = 0, len = node.values.length; i < len; i++) {
2293
+ if (this.comparator.isSame(value, node.values[i])) {
2294
+ const keys = node.keys[i];
2295
+ if (keys.includes(key)) {
2296
+ return true;
2297
+ }
2298
+ }
2299
+ }
2300
+ return false;
2301
+ }
2302
+ async forceUpdate(id) {
2303
+ if (id) {
2304
+ this.nodes.delete(id);
2305
+ await this.getNode(id);
2306
+ return 1;
2307
+ }
2308
+ const keys = Array.from(this.nodes.keys());
2309
+ for (const key of keys) {
2310
+ this.nodes.delete(key);
2311
+ }
2312
+ for (const key of keys) {
2313
+ await this.getNode(key);
2314
+ }
2315
+ return keys.length;
2316
+ }
2260
2317
  async commitHeadBuffer() {
2261
2318
  if (!this._strategyDirty) {
2262
2319
  return;
@@ -2287,25 +2344,15 @@ var BPTreeAsync = class extends BPTree {
2287
2344
  }
2288
2345
  this._nodeDeleteBuffer.clear();
2289
2346
  }
2290
- /**
2291
- * Retrieves the value associated with the given key (PK).
2292
- * Note: This method performs a full scan of leaf nodes as the tree is ordered by Value, not Key.
2293
- *
2294
- * @param key The key to search for.
2295
- * @returns The value associated with the key, or undefined if not found.
2296
- */
2297
2347
  async get(key) {
2298
- return this.readLock(async () => {
2348
+ return await this.readLock(async () => {
2299
2349
  let node = await this.leftestNode();
2300
2350
  while (true) {
2301
- if (node.values) {
2302
- const len = node.values.length;
2303
- for (let i = 0; i < len; i++) {
2304
- const keys = node.keys[i];
2305
- for (let j = 0; j < keys.length; j++) {
2306
- if (keys[j] === key) {
2307
- return node.values[i];
2308
- }
2351
+ for (let i = 0, len = node.values.length; i < len; i++) {
2352
+ const keys = node.keys[i];
2353
+ for (let j = 0, kLen = keys.length; j < kLen; j++) {
2354
+ if (keys[j] === key) {
2355
+ return node.values[i];
2309
2356
  }
2310
2357
  }
2311
2358
  }
@@ -2380,25 +2427,21 @@ var BPTreeAsync = class extends BPTree {
2380
2427
  }
2381
2428
  }
2382
2429
  async keys(condition, filterValues) {
2383
- return this.readLock(async () => {
2384
- const set = /* @__PURE__ */ new Set();
2385
- for await (const key of this.keysStream(condition, filterValues)) {
2386
- set.add(key);
2387
- }
2388
- return set;
2389
- });
2430
+ const set = /* @__PURE__ */ new Set();
2431
+ for await (const key of this.keysStream(condition, filterValues)) {
2432
+ set.add(key);
2433
+ }
2434
+ return set;
2390
2435
  }
2391
2436
  async where(condition) {
2392
- return this.readLock(async () => {
2393
- const map = /* @__PURE__ */ new Map();
2394
- for await (const [key, value] of this.whereStream(condition)) {
2395
- map.set(key, value);
2396
- }
2397
- return map;
2398
- });
2437
+ const map = /* @__PURE__ */ new Map();
2438
+ for await (const [key, value] of this.whereStream(condition)) {
2439
+ map.set(key, value);
2440
+ }
2441
+ return map;
2399
2442
  }
2400
2443
  async insert(key, value) {
2401
- return this.writeLock(async () => {
2444
+ await this.writeLock(async () => {
2402
2445
  const before = await this.insertableNode(value);
2403
2446
  this._insertAtLeaf(before, key, value);
2404
2447
  if (before.values.length === this.order) {
@@ -2425,182 +2468,368 @@ var BPTreeAsync = class extends BPTree {
2425
2468
  });
2426
2469
  }
2427
2470
  async delete(key, value) {
2428
- return this.writeLock(async () => {
2471
+ await this.writeLock(async () => {
2429
2472
  const node = await this.insertableNode(value);
2430
2473
  let i = node.values.length;
2431
2474
  while (i--) {
2432
2475
  const nValue = node.values[i];
2433
2476
  if (this.comparator.isSame(value, nValue)) {
2434
2477
  const keys = node.keys[i];
2435
- if (keys.includes(key)) {
2436
- if (keys.length > 1) {
2437
- keys.splice(keys.indexOf(key), 1);
2438
- this.bufferForNodeUpdate(node);
2439
- } else if (node.id === this.rootId) {
2440
- node.values.splice(i, 1);
2441
- node.keys.splice(i, 1);
2442
- this.bufferForNodeUpdate(node);
2443
- } else {
2444
- keys.splice(keys.indexOf(key), 1);
2478
+ const keyIndex = keys.indexOf(key);
2479
+ if (keyIndex !== -1) {
2480
+ keys.splice(keyIndex, 1);
2481
+ if (keys.length === 0) {
2445
2482
  node.keys.splice(i, 1);
2446
- node.values.splice(node.values.indexOf(value), 1);
2447
- await this._deleteEntry(node, key, value);
2448
- this.bufferForNodeUpdate(node);
2483
+ node.values.splice(i, 1);
2449
2484
  }
2485
+ await this._deleteEntry(node, key, value);
2486
+ break;
2450
2487
  }
2451
2488
  }
2452
2489
  }
2453
2490
  await this.commitHeadBuffer();
2454
- await this.commitNodeCreateBuffer();
2455
2491
  await this.commitNodeUpdateBuffer();
2456
2492
  await this.commitNodeDeleteBuffer();
2457
2493
  });
2458
2494
  }
2459
- async exists(key, value) {
2460
- return this.readLock(async () => {
2461
- const node = await this.insertableNode(value);
2462
- for (let i = 0, len = node.values.length; i < len; i++) {
2463
- const nValue = node.values[i];
2464
- if (this.comparator.isSame(value, nValue)) {
2465
- const keys = node.keys[i];
2466
- return keys.includes(key);
2467
- }
2468
- }
2469
- return false;
2470
- });
2495
+ getHeadData() {
2496
+ return this.strategy.head.data;
2471
2497
  }
2472
2498
  async setHeadData(data) {
2473
- return this.writeLock(async () => {
2474
- this.strategy.head.data = data;
2475
- this._strategyDirty = true;
2476
- await this.commitHeadBuffer();
2477
- });
2478
- }
2479
- async forceUpdate() {
2480
- return this.readLock(async () => {
2481
- const keys = [...this.nodes.keys()];
2482
- this.nodes.clear();
2483
- await this.init();
2484
- for (const key of keys) {
2485
- await this.getNode(key);
2486
- }
2487
- return keys.length;
2488
- });
2489
- }
2490
- };
2491
-
2492
- // src/base/SerializeStrategy.ts
2493
- var SerializeStrategy = class {
2494
- order;
2495
- head;
2496
- constructor(order) {
2497
- this.order = order;
2498
- this.head = {
2499
- order,
2500
- root: null,
2501
- data: {}
2502
- };
2499
+ this.strategy.head.data = data;
2500
+ await this.strategy.writeHead(this.strategy.head);
2503
2501
  }
2504
2502
  };
2505
2503
 
2506
- // src/SerializeStrategySync.ts
2507
- var SerializeStrategySync = class extends SerializeStrategy {
2508
- getHeadData(key, defaultValue) {
2504
+ // src/SerializeStrategyAsync.ts
2505
+ var SerializeStrategyAsync = class extends SerializeStrategy {
2506
+ async getHeadData(key, defaultValue) {
2509
2507
  if (!Object.hasOwn(this.head.data, key)) {
2510
- this.setHeadData(key, defaultValue);
2508
+ await this.setHeadData(key, defaultValue);
2511
2509
  }
2512
2510
  return this.head.data[key];
2513
2511
  }
2514
- setHeadData(key, data) {
2512
+ async setHeadData(key, data) {
2515
2513
  this.head.data[key] = data;
2516
- this.writeHead(this.head);
2514
+ await this.writeHead(this.head);
2517
2515
  }
2518
- autoIncrement(key, defaultValue) {
2519
- const current = this.getHeadData(key, defaultValue);
2516
+ async autoIncrement(key, defaultValue) {
2517
+ const current = await this.getHeadData(key, defaultValue);
2520
2518
  const next = current + 1;
2521
- this.setHeadData(key, next);
2519
+ await this.setHeadData(key, next);
2522
2520
  return current;
2523
2521
  }
2522
+ async compareAndSwapHead(oldRoot, newRoot) {
2523
+ if (this.head.root !== oldRoot) {
2524
+ return false;
2525
+ }
2526
+ this.head.root = newRoot;
2527
+ await this.writeHead(this.head);
2528
+ return true;
2529
+ }
2524
2530
  };
2525
- var InMemoryStoreStrategySync = class extends SerializeStrategySync {
2531
+ var InMemoryStoreStrategyAsync = class extends SerializeStrategyAsync {
2526
2532
  node;
2527
2533
  constructor(order) {
2528
2534
  super(order);
2529
2535
  this.node = {};
2530
2536
  }
2531
- id(isLeaf) {
2532
- return this.autoIncrement("index", 1).toString();
2537
+ async id(isLeaf) {
2538
+ return (await this.autoIncrement("index", 1)).toString();
2533
2539
  }
2534
- read(id) {
2540
+ async read(id) {
2535
2541
  if (!Object.hasOwn(this.node, id)) {
2536
2542
  throw new Error(`The tree attempted to reference node '${id}', but couldn't find the corresponding node.`);
2537
2543
  }
2538
- return this.node[id];
2544
+ const node = this.node[id];
2545
+ return JSON.parse(JSON.stringify(node));
2539
2546
  }
2540
- write(id, node) {
2547
+ async write(id, node) {
2541
2548
  this.node[id] = node;
2542
2549
  }
2543
- delete(id) {
2550
+ async delete(id) {
2544
2551
  delete this.node[id];
2545
2552
  }
2546
- readHead() {
2553
+ async readHead() {
2547
2554
  if (this.head.root === null) {
2548
2555
  return null;
2549
2556
  }
2550
2557
  return this.head;
2551
2558
  }
2552
- writeHead(head) {
2559
+ async writeHead(head) {
2553
2560
  this.head = head;
2554
2561
  }
2555
2562
  };
2556
2563
 
2557
- // src/SerializeStrategyAsync.ts
2558
- var SerializeStrategyAsync = class extends SerializeStrategy {
2564
+ // src/transaction/BPTreeAsyncSnapshotStrategy.ts
2565
+ var BPTreeAsyncSnapshotStrategy = class extends SerializeStrategyAsync {
2566
+ baseStrategy;
2567
+ snapshotHead;
2568
+ constructor(baseStrategy, root) {
2569
+ super(baseStrategy.order);
2570
+ this.baseStrategy = baseStrategy;
2571
+ this.snapshotHead = {
2572
+ ...baseStrategy.head,
2573
+ root,
2574
+ data: { ...baseStrategy.head.data }
2575
+ };
2576
+ this.head = this.snapshotHead;
2577
+ }
2578
+ async id(isLeaf) {
2579
+ return await this.baseStrategy.id(isLeaf);
2580
+ }
2581
+ async read(id) {
2582
+ return await this.baseStrategy.read(id);
2583
+ }
2584
+ async write(id, node) {
2585
+ await this.baseStrategy.write(id, node);
2586
+ }
2587
+ async delete(id) {
2588
+ await this.baseStrategy.delete(id);
2589
+ }
2590
+ async readHead() {
2591
+ return this.snapshotHead;
2592
+ }
2593
+ async writeHead(head) {
2594
+ this.snapshotHead.root = head.root;
2595
+ this.snapshotHead.data = { ...head.data };
2596
+ }
2597
+ async compareAndSwapHead(oldRoot, newRoot) {
2598
+ return await this.baseStrategy.compareAndSwapHead(oldRoot, newRoot);
2599
+ }
2559
2600
  async getHeadData(key, defaultValue) {
2560
- if (!Object.hasOwn(this.head.data, key)) {
2561
- await this.setHeadData(key, defaultValue);
2562
- }
2563
- return this.head.data[key];
2601
+ return this.snapshotHead.data[key] ?? defaultValue;
2564
2602
  }
2565
2603
  async setHeadData(key, data) {
2566
- this.head.data[key] = data;
2567
- await this.writeHead(this.head);
2604
+ this.snapshotHead.data[key] = data;
2568
2605
  }
2569
2606
  async autoIncrement(key, defaultValue) {
2570
- const current = await this.getHeadData(key, defaultValue);
2571
- const next = current + 1;
2572
- await this.setHeadData(key, next);
2573
- return current;
2607
+ return this.snapshotHead.data[key] ?? defaultValue;
2574
2608
  }
2575
2609
  };
2576
- var InMemoryStoreStrategyAsync = class extends SerializeStrategyAsync {
2577
- node;
2578
- constructor(order) {
2579
- super(order);
2580
- this.node = {};
2610
+
2611
+ // src/transaction/BPTreeAsyncTransaction.ts
2612
+ var BPTreeAsyncTransaction = class extends BPTreeAsyncBase {
2613
+ realBaseTree;
2614
+ realBaseStrategy;
2615
+ txNodes = /* @__PURE__ */ new Map();
2616
+ dirtyIds = /* @__PURE__ */ new Set();
2617
+ createdInTx = /* @__PURE__ */ new Set();
2618
+ initialRootId;
2619
+ transactionRootId;
2620
+ constructor(baseTree) {
2621
+ super(baseTree.strategy, baseTree.comparator, baseTree.option);
2622
+ this.realBaseTree = baseTree;
2623
+ this.realBaseStrategy = baseTree.strategy;
2624
+ this.initialRootId = "";
2625
+ this.transactionRootId = "";
2581
2626
  }
2582
- async id(isLeaf) {
2583
- return (await this.autoIncrement("index", 1)).toString();
2627
+ /**
2628
+ * Initializes the transaction by capturing the current state of the tree.
2629
+ */
2630
+ async initTransaction() {
2631
+ const head = await this.realBaseStrategy.readHead();
2632
+ this.initialRootId = head?.root ?? this.realBaseTree.rootId;
2633
+ this.transactionRootId = this.initialRootId;
2634
+ this.rootId = this.transactionRootId;
2635
+ const snapshotStrategy = new BPTreeAsyncSnapshotStrategy(this.realBaseStrategy, this.initialRootId);
2636
+ this.strategy = snapshotStrategy;
2637
+ this.txNodes.clear();
2638
+ this.dirtyIds.clear();
2639
+ this.createdInTx.clear();
2584
2640
  }
2585
- async read(id) {
2586
- if (!Object.hasOwn(this.node, id)) {
2587
- throw new Error(`The tree attempted to reference node '${id}', but couldn't find the corresponding node.`);
2641
+ async getNode(id) {
2642
+ if (this.txNodes.has(id)) {
2643
+ return this.txNodes.get(id);
2644
+ }
2645
+ const baseNode = await this.realBaseStrategy.read(id);
2646
+ const clone = JSON.parse(JSON.stringify(baseNode));
2647
+ this.txNodes.set(id, clone);
2648
+ return clone;
2649
+ }
2650
+ async bufferForNodeUpdate(node) {
2651
+ this.txNodes.set(node.id, node);
2652
+ this.dirtyIds.add(node.id);
2653
+ await this.markPathDirty(node);
2654
+ }
2655
+ async bufferForNodeCreate(node) {
2656
+ this.txNodes.set(node.id, node);
2657
+ this.dirtyIds.add(node.id);
2658
+ this.createdInTx.add(node.id);
2659
+ await this.markPathDirty(node);
2660
+ }
2661
+ async bufferForNodeDelete(node) {
2662
+ this.txNodes.delete(node.id);
2663
+ this.dirtyIds.add(node.id);
2664
+ }
2665
+ async markPathDirty(node) {
2666
+ let curr = node;
2667
+ while (curr.parent) {
2668
+ if (this.dirtyIds.has(curr.parent) && this.txNodes.has(curr.parent)) {
2669
+ break;
2670
+ }
2671
+ const parent = await this.getNode(curr.parent);
2672
+ this.dirtyIds.add(parent.id);
2673
+ curr = parent;
2674
+ }
2675
+ if (!curr.parent) {
2676
+ this.transactionRootId = curr.id;
2588
2677
  }
2589
- return this.node[id];
2590
2678
  }
2591
- async write(id, node) {
2592
- this.node[id] = node;
2679
+ async _createNode(isLeaf, keys, values, leaf = false, parent = null, next = null, prev = null) {
2680
+ const id = await this.realBaseStrategy.id(isLeaf);
2681
+ const node = {
2682
+ id,
2683
+ keys,
2684
+ values,
2685
+ leaf: isLeaf,
2686
+ parent,
2687
+ next,
2688
+ prev
2689
+ };
2690
+ await this.bufferForNodeCreate(node);
2691
+ return node;
2593
2692
  }
2594
- async delete(id) {
2595
- delete this.node[id];
2693
+ /**
2694
+ * Attempts to commit the transaction.
2695
+ * Uses Optimistic Locking (Compare-And-Swap) on the root node ID to detect conflicts.
2696
+ *
2697
+ * @returns A promise that resolves to the transaction result.
2698
+ */
2699
+ async commit() {
2700
+ const idMapping = /* @__PURE__ */ new Map();
2701
+ const finalNodes = [];
2702
+ for (const oldId of this.dirtyIds) {
2703
+ if (this.createdInTx.has(oldId)) {
2704
+ idMapping.set(oldId, oldId);
2705
+ } else {
2706
+ const node = this.txNodes.get(oldId);
2707
+ if (node) {
2708
+ const newId = await this.realBaseStrategy.id(node.leaf);
2709
+ idMapping.set(oldId, newId);
2710
+ }
2711
+ }
2712
+ }
2713
+ const newCreatedIds = [];
2714
+ for (const oldId of this.dirtyIds) {
2715
+ const node = this.txNodes.get(oldId);
2716
+ if (!node) continue;
2717
+ const newId = idMapping.get(oldId);
2718
+ node.id = newId;
2719
+ if (node.parent && idMapping.has(node.parent)) {
2720
+ node.parent = idMapping.get(node.parent);
2721
+ }
2722
+ if (!node.leaf) {
2723
+ const internal = node;
2724
+ for (let i = 0; i < internal.keys.length; i++) {
2725
+ const childId = internal.keys[i];
2726
+ if (idMapping.has(childId)) {
2727
+ internal.keys[i] = idMapping.get(childId);
2728
+ }
2729
+ }
2730
+ }
2731
+ if (node.leaf) {
2732
+ const leaf = node;
2733
+ if (leaf.next && idMapping.has(leaf.next)) {
2734
+ leaf.next = idMapping.get(leaf.next);
2735
+ }
2736
+ if (leaf.prev && idMapping.has(leaf.prev)) {
2737
+ leaf.prev = idMapping.get(leaf.prev);
2738
+ }
2739
+ }
2740
+ finalNodes.push(node);
2741
+ newCreatedIds.push(newId);
2742
+ }
2743
+ let newRootId = this.transactionRootId;
2744
+ if (idMapping.has(this.transactionRootId)) {
2745
+ newRootId = idMapping.get(this.transactionRootId);
2746
+ }
2747
+ for (const node of finalNodes) {
2748
+ await this.realBaseStrategy.write(node.id, node);
2749
+ }
2750
+ const success = await this.realBaseStrategy.compareAndSwapHead(this.initialRootId, newRootId);
2751
+ if (success) {
2752
+ const distinctObsolete = /* @__PURE__ */ new Set();
2753
+ for (const oldId of this.dirtyIds) {
2754
+ if (!this.createdInTx.has(oldId) && this.txNodes.has(oldId)) {
2755
+ distinctObsolete.add(oldId);
2756
+ }
2757
+ }
2758
+ return {
2759
+ success: true,
2760
+ createdIds: newCreatedIds,
2761
+ obsoleteIds: Array.from(distinctObsolete)
2762
+ };
2763
+ } else {
2764
+ await this.rollback();
2765
+ return {
2766
+ success: false,
2767
+ createdIds: newCreatedIds,
2768
+ obsoleteIds: []
2769
+ };
2770
+ }
2596
2771
  }
2597
- async readHead() {
2598
- if (this.head.root === null) {
2599
- return null;
2772
+ /**
2773
+ * Rolls back the transaction by clearing all buffered changes.
2774
+ * Internal use only.
2775
+ */
2776
+ async rollback() {
2777
+ this.txNodes.clear();
2778
+ this.dirtyIds.clear();
2779
+ this.createdInTx.clear();
2780
+ }
2781
+ async readLock(fn) {
2782
+ return await fn();
2783
+ }
2784
+ async writeLock(fn) {
2785
+ return await fn();
2786
+ }
2787
+ async commitHeadBuffer() {
2788
+ }
2789
+ async commitNodeCreateBuffer() {
2790
+ }
2791
+ async commitNodeUpdateBuffer() {
2792
+ }
2793
+ async commitNodeDeleteBuffer() {
2794
+ }
2795
+ };
2796
+
2797
+ // src/BPTreeAsync.ts
2798
+ var BPTreeAsync = class extends BPTreeAsyncBase {
2799
+ constructor(strategy, comparator, option) {
2800
+ super(strategy, comparator, option);
2801
+ }
2802
+ /**
2803
+ * Creates a new asynchronous transaction.
2804
+ * @returns A promise that resolves to a new BPTreeAsyncTransaction.
2805
+ */
2806
+ async createTransaction() {
2807
+ const tx = new BPTreeAsyncTransaction(this);
2808
+ await tx.initTransaction();
2809
+ return tx;
2810
+ }
2811
+ async insert(key, value) {
2812
+ const tx = await this.createTransaction();
2813
+ await tx.insert(key, value);
2814
+ const { success } = await tx.commit();
2815
+ await this.init();
2816
+ if (!success) {
2817
+ throw new Error("Transaction failed: Commit failed due to conflict");
2600
2818
  }
2601
- return this.head;
2602
2819
  }
2603
- async writeHead(head) {
2604
- this.head = head;
2820
+ async delete(key, value) {
2821
+ const tx = await this.createTransaction();
2822
+ await tx.delete(key, value);
2823
+ const { success } = await tx.commit();
2824
+ await this.init();
2825
+ if (!success) {
2826
+ throw new Error("Transaction failed: Commit failed due to conflict");
2827
+ }
2828
+ }
2829
+ async readLock(fn) {
2830
+ return await fn();
2831
+ }
2832
+ async writeLock(fn) {
2833
+ return await fn();
2605
2834
  }
2606
2835
  };