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.
@@ -727,8 +727,8 @@ var BPTree = class _BPTree {
727
727
  }
728
728
  };
729
729
 
730
- // src/BPTreeSync.ts
731
- var BPTreeSync = class extends BPTree {
730
+ // src/base/BPTreeSyncBase.ts
731
+ var BPTreeSyncBase = class extends BPTree {
732
732
  constructor(strategy, comparator, option) {
733
733
  super(strategy, comparator, option);
734
734
  this.nodes = this._createCachedNode();
@@ -837,7 +837,7 @@ var BPTreeSync = class extends BPTree {
837
837
  }
838
838
  }
839
839
  }
840
- if (this.rootId === node.id && node.keys.length === 1) {
840
+ if (this.rootId === node.id && node.keys.length === 1 && !node.leaf) {
841
841
  const keys = node.keys;
842
842
  this.bufferForNodeDelete(node);
843
843
  const newRoot = this.getNode(keys[0]);
@@ -906,20 +906,11 @@ var BPTreeSync = class extends BPTree {
906
906
  pointer.values.push(guess);
907
907
  } else {
908
908
  pointer.next = node.next;
909
- pointer.prev = node.id;
910
909
  if (pointer.next) {
911
- const n = this.getNode(node.next);
910
+ const n = this.getNode(pointer.next);
912
911
  n.prev = pointer.id;
913
912
  this.bufferForNodeUpdate(n);
914
913
  }
915
- if (pointer.prev) {
916
- const n = this.getNode(node.id);
917
- n.next = pointer.id;
918
- this.bufferForNodeUpdate(n);
919
- }
920
- if (isPredecessor) {
921
- pointer.prev = null;
922
- }
923
914
  }
924
915
  pointer.values.push(...node.values);
925
916
  if (!pointer.leaf) {
@@ -1144,10 +1135,6 @@ var BPTreeSync = class extends BPTree {
1144
1135
  }
1145
1136
  return node;
1146
1137
  }
1147
- /**
1148
- * Find the insertable node using primaryAsc comparison.
1149
- * This allows finding nodes by primary value only, ignoring unique identifiers.
1150
- */
1151
1138
  insertableNodeByPrimary(value) {
1152
1139
  let node = this.getNode(this.rootId);
1153
1140
  while (!node.leaf) {
@@ -1228,6 +1215,33 @@ var BPTreeSync = class extends BPTree {
1228
1215
  }
1229
1216
  return node;
1230
1217
  }
1218
+ exists(key, value) {
1219
+ const node = this.insertableNode(value);
1220
+ for (let i = 0, len = node.values.length; i < len; i++) {
1221
+ if (this.comparator.isSame(value, node.values[i])) {
1222
+ const keys = node.keys[i];
1223
+ if (keys.includes(key)) {
1224
+ return true;
1225
+ }
1226
+ }
1227
+ }
1228
+ return false;
1229
+ }
1230
+ forceUpdate(id) {
1231
+ if (id) {
1232
+ this.nodes.delete(id);
1233
+ this.getNode(id);
1234
+ return 1;
1235
+ }
1236
+ const keys = Array.from(this.nodes.keys());
1237
+ for (const key of keys) {
1238
+ this.nodes.delete(key);
1239
+ }
1240
+ for (const key of keys) {
1241
+ this.getNode(key);
1242
+ }
1243
+ return keys.length;
1244
+ }
1231
1245
  commitHeadBuffer() {
1232
1246
  if (!this._strategyDirty) {
1233
1247
  return;
@@ -1258,24 +1272,14 @@ var BPTreeSync = class extends BPTree {
1258
1272
  }
1259
1273
  this._nodeDeleteBuffer.clear();
1260
1274
  }
1261
- /**
1262
- * Retrieves the value associated with the given key (PK).
1263
- * Note: This method performs a full scan of leaf nodes as the tree is ordered by Value, not Key.
1264
- *
1265
- * @param key The key to search for.
1266
- * @returns The value associated with the key, or undefined if not found.
1267
- */
1268
1275
  get(key) {
1269
1276
  let node = this.leftestNode();
1270
1277
  while (true) {
1271
- if (node.values) {
1272
- const len = node.values.length;
1273
- for (let i = 0; i < len; i++) {
1274
- const keys = node.keys[i];
1275
- for (let j = 0; j < keys.length; j++) {
1276
- if (keys[j] === key) {
1277
- return node.values[i];
1278
- }
1278
+ for (let i = 0, len = node.values.length; i < len; i++) {
1279
+ const keys = node.keys[i];
1280
+ for (let j = 0, kLen = keys.length; j < kLen; j++) {
1281
+ if (keys[j] === key) {
1282
+ return node.values[i];
1279
1283
  }
1280
1284
  }
1281
1285
  }
@@ -1394,21 +1398,15 @@ var BPTreeSync = class extends BPTree {
1394
1398
  const nValue = node.values[i];
1395
1399
  if (this.comparator.isSame(value, nValue)) {
1396
1400
  const keys = node.keys[i];
1397
- if (keys.includes(key)) {
1398
- if (keys.length > 1) {
1399
- keys.splice(keys.indexOf(key), 1);
1400
- this.bufferForNodeUpdate(node);
1401
- } else if (node.id === this.rootId) {
1402
- node.values.splice(i, 1);
1403
- node.keys.splice(i, 1);
1404
- this.bufferForNodeUpdate(node);
1405
- } else {
1406
- keys.splice(keys.indexOf(key), 1);
1401
+ const keyIndex = keys.indexOf(key);
1402
+ if (keyIndex !== -1) {
1403
+ keys.splice(keyIndex, 1);
1404
+ if (keys.length === 0) {
1407
1405
  node.keys.splice(i, 1);
1408
- node.values.splice(node.values.indexOf(value), 1);
1409
- this._deleteEntry(node, key, value);
1410
- this.bufferForNodeUpdate(node);
1406
+ node.values.splice(i, 1);
1411
1407
  }
1408
+ this._deleteEntry(node, key, value);
1409
+ break;
1412
1410
  }
1413
1411
  }
1414
1412
  }
@@ -1417,298 +1415,356 @@ var BPTreeSync = class extends BPTree {
1417
1415
  this.commitNodeUpdateBuffer();
1418
1416
  this.commitNodeDeleteBuffer();
1419
1417
  }
1420
- exists(key, value) {
1421
- const node = this.insertableNode(value);
1422
- for (let i = 0, len = node.values.length; i < len; i++) {
1423
- const nValue = node.values[i];
1424
- if (this.comparator.isSame(value, nValue)) {
1425
- const keys = node.keys[i];
1426
- return keys.includes(key);
1427
- }
1428
- }
1429
- return false;
1418
+ getHeadData() {
1419
+ return this.strategy.head.data;
1430
1420
  }
1431
1421
  setHeadData(data) {
1432
1422
  this.strategy.head.data = data;
1433
- this._strategyDirty = true;
1434
- this.commitHeadBuffer();
1423
+ this.strategy.writeHead(this.strategy.head);
1435
1424
  }
1436
- forceUpdate() {
1437
- const keys = [...this.nodes.keys()];
1438
- this.nodes.clear();
1439
- this.init();
1440
- for (const key of keys) {
1441
- this.getNode(key);
1442
- }
1443
- return keys.length;
1425
+ };
1426
+
1427
+ // src/base/SerializeStrategy.ts
1428
+ var SerializeStrategy = class {
1429
+ order;
1430
+ head;
1431
+ constructor(order) {
1432
+ this.order = order;
1433
+ this.head = {
1434
+ order,
1435
+ root: null,
1436
+ data: {}
1437
+ };
1444
1438
  }
1445
1439
  };
1446
1440
 
1447
- // node_modules/ryoiki/dist/esm/index.mjs
1448
- var Ryoiki = class _Ryoiki {
1449
- readings;
1450
- writings;
1451
- readQueue;
1452
- writeQueue;
1453
- static async CatchError(promise) {
1454
- return await promise.then((v) => [void 0, v]).catch((err) => [err]);
1455
- }
1456
- static IsRangeOverlap(a, b) {
1457
- const [start1, end1] = a;
1458
- const [start2, end2] = b;
1459
- if (end1 <= start2 || end2 <= start1) {
1441
+ // src/SerializeStrategySync.ts
1442
+ var SerializeStrategySync = class extends SerializeStrategy {
1443
+ getHeadData(key, defaultValue) {
1444
+ if (!Object.hasOwn(this.head.data, key)) {
1445
+ this.setHeadData(key, defaultValue);
1446
+ }
1447
+ return this.head.data[key];
1448
+ }
1449
+ setHeadData(key, data) {
1450
+ this.head.data[key] = data;
1451
+ this.writeHead(this.head);
1452
+ }
1453
+ autoIncrement(key, defaultValue) {
1454
+ const current = this.getHeadData(key, defaultValue);
1455
+ const next = current + 1;
1456
+ this.setHeadData(key, next);
1457
+ return current;
1458
+ }
1459
+ compareAndSwapHead(oldRoot, newRoot) {
1460
+ if (this.head.root !== oldRoot) {
1460
1461
  return false;
1461
1462
  }
1463
+ this.head.root = newRoot;
1464
+ this.writeHead(this.head);
1462
1465
  return true;
1463
1466
  }
1464
- static ERR_ALREADY_EXISTS(lockId) {
1465
- return new Error(`The '${lockId}' task already existing in queue or running.`);
1467
+ };
1468
+ var InMemoryStoreStrategySync = class extends SerializeStrategySync {
1469
+ node;
1470
+ constructor(order) {
1471
+ super(order);
1472
+ this.node = {};
1466
1473
  }
1467
- static ERR_NOT_EXISTS(lockId) {
1468
- return new Error(`The '${lockId}' task not existing in task queue.`);
1474
+ id(isLeaf) {
1475
+ return this.autoIncrement("index", 1).toString();
1469
1476
  }
1470
- static ERR_TIMEOUT(lockId, timeout) {
1471
- return new Error(`The task with ID '${lockId}' failed to acquire the lock within the timeout(${timeout}ms).`);
1477
+ read(id) {
1478
+ if (!Object.hasOwn(this.node, id)) {
1479
+ throw new Error(`The tree attempted to reference node '${id}', but couldn't find the corresponding node.`);
1480
+ }
1481
+ const node = this.node[id];
1482
+ return JSON.parse(JSON.stringify(node));
1472
1483
  }
1473
- /**
1474
- * Constructs a new instance of the Ryoiki class.
1475
- */
1476
- constructor() {
1477
- this.readings = /* @__PURE__ */ new Map();
1478
- this.writings = /* @__PURE__ */ new Map();
1479
- this.readQueue = /* @__PURE__ */ new Map();
1480
- this.writeQueue = /* @__PURE__ */ new Map();
1484
+ write(id, node) {
1485
+ this.node[id] = node;
1481
1486
  }
1482
- /**
1483
- * Creates a range based on a start value and length.
1484
- * @param start - The starting value of the range.
1485
- * @param length - The length of the range.
1486
- * @returns A range tuple [start, start + length].
1487
- */
1488
- range(start, length) {
1489
- return [start, start + length];
1487
+ delete(id) {
1488
+ delete this.node[id];
1490
1489
  }
1491
- rangeOverlapping(tasks, range) {
1492
- return Array.from(tasks.values()).some((t) => _Ryoiki.IsRangeOverlap(t.range, range));
1490
+ readHead() {
1491
+ if (this.head.root === null) {
1492
+ return null;
1493
+ }
1494
+ return this.head;
1493
1495
  }
1494
- isSameRange(a, b) {
1495
- const [a1, a2] = a;
1496
- const [b1, b2] = b;
1497
- return a1 === b1 && a2 === b2;
1496
+ writeHead(head) {
1497
+ this.head = head;
1498
1498
  }
1499
- fetchUnitAndRun(queue, workspaces) {
1500
- for (const [id, unit] of queue) {
1501
- if (!unit.condition()) {
1502
- continue;
1503
- }
1504
- this._alloc(queue, workspaces, id);
1505
- }
1499
+ };
1500
+
1501
+ // src/transaction/BPTreeSyncSnapshotStrategy.ts
1502
+ var BPTreeSyncSnapshotStrategy = class extends SerializeStrategySync {
1503
+ baseStrategy;
1504
+ snapshotHead;
1505
+ constructor(baseStrategy, root) {
1506
+ super(baseStrategy.order);
1507
+ this.baseStrategy = baseStrategy;
1508
+ this.snapshotHead = {
1509
+ ...baseStrategy.head,
1510
+ root,
1511
+ data: { ...baseStrategy.head.data }
1512
+ };
1513
+ this.head = this.snapshotHead;
1506
1514
  }
1507
- _handleOverload(args, handlers, argPatterns) {
1508
- for (const [key, pattern] of Object.entries(argPatterns)) {
1509
- if (this._matchArgs(args, pattern)) {
1510
- return handlers[key](...args);
1511
- }
1512
- }
1513
- throw new Error("Invalid arguments");
1515
+ id(isLeaf) {
1516
+ return this.baseStrategy.id(isLeaf);
1514
1517
  }
1515
- _matchArgs(args, pattern) {
1516
- return args.every((arg, index) => {
1517
- const expectedType = pattern[index];
1518
- if (expectedType === void 0) return typeof arg === "undefined";
1519
- if (expectedType === Function) return typeof arg === "function";
1520
- if (expectedType === Number) return typeof arg === "number";
1521
- if (expectedType === Array) return Array.isArray(arg);
1522
- return false;
1523
- });
1518
+ read(id) {
1519
+ return this.baseStrategy.read(id);
1524
1520
  }
1525
- _createRandomId() {
1526
- const timestamp = Date.now().toString(36);
1527
- const random = Math.random().toString(36).substring(2);
1528
- return `${timestamp}${random}`;
1529
- }
1530
- _alloc(queue, workspaces, lockId) {
1531
- const unit = queue.get(lockId);
1532
- if (!unit) {
1533
- throw _Ryoiki.ERR_NOT_EXISTS(lockId);
1534
- }
1535
- workspaces.set(lockId, unit);
1536
- queue.delete(lockId);
1537
- unit.alloc();
1538
- }
1539
- _free(workspaces, lockId) {
1540
- const unit = workspaces.get(lockId);
1541
- if (!unit) {
1542
- throw _Ryoiki.ERR_NOT_EXISTS(lockId);
1543
- }
1544
- workspaces.delete(lockId);
1545
- unit.free();
1546
- }
1547
- _lock(queue, range, timeout, task, condition) {
1548
- return new Promise((resolve, reject) => {
1549
- let timeoutId = null;
1550
- if (timeout >= 0) {
1551
- timeoutId = setTimeout(() => {
1552
- reject(_Ryoiki.ERR_TIMEOUT(id, timeout));
1553
- }, timeout);
1554
- }
1555
- const id = this._createRandomId();
1556
- const alloc = async () => {
1557
- if (timeoutId !== null) {
1558
- clearTimeout(timeoutId);
1559
- }
1560
- const [err, v] = await _Ryoiki.CatchError(task(id));
1561
- if (err) reject(err);
1562
- else resolve(v);
1563
- };
1564
- const fetch = () => {
1565
- this.fetchUnitAndRun(this.readQueue, this.readings);
1566
- this.fetchUnitAndRun(this.writeQueue, this.writings);
1567
- };
1568
- queue.set(id, { id, range, condition, alloc, free: fetch });
1569
- fetch();
1570
- });
1521
+ write(id, node) {
1522
+ this.baseStrategy.write(id, node);
1571
1523
  }
1572
- _checkWorking(range, workspaces) {
1573
- let isLocked = false;
1574
- for (const lock of workspaces.values()) {
1575
- if (_Ryoiki.IsRangeOverlap(range, lock.range)) {
1576
- isLocked = true;
1577
- break;
1578
- }
1579
- }
1580
- return isLocked;
1524
+ delete(id) {
1525
+ this.baseStrategy.delete(id);
1581
1526
  }
1582
- /**
1583
- * Checks if there is any active read lock within the specified range.
1584
- * @param range The range to check for active read locks.
1585
- * @returns `true` if there is an active read lock within the range, `false` otherwise.
1586
- */
1587
- isReading(range) {
1588
- return this._checkWorking(range, this.readings);
1527
+ readHead() {
1528
+ return this.snapshotHead;
1589
1529
  }
1590
- /**
1591
- * Checks if there is any active write lock within the specified range.
1592
- * @param range The range to check for active write locks.
1593
- * @returns `true` if there is an active write lock within the range, `false` otherwise.
1594
- */
1595
- isWriting(range) {
1596
- return this._checkWorking(range, this.writings);
1530
+ writeHead(head) {
1531
+ this.snapshotHead.root = head.root;
1532
+ this.snapshotHead.data = { ...head.data };
1597
1533
  }
1598
- /**
1599
- * Checks if a read lock can be acquired within the specified range.
1600
- * @param range The range to check for read lock availability.
1601
- * @returns `true` if a read lock can be acquired, `false` otherwise.
1602
- */
1603
- canRead(range) {
1604
- const writing = this.isWriting(range);
1605
- return !writing;
1534
+ compareAndSwapHead(oldRoot, newRoot) {
1535
+ return this.baseStrategy.compareAndSwapHead(oldRoot, newRoot);
1606
1536
  }
1607
- /**
1608
- * Checks if a write lock can be acquired within the specified range.
1609
- * @param range The range to check for write lock availability.
1610
- * @returns `true` if a write lock can be acquired, `false` otherwise.
1611
- */
1612
- canWrite(range) {
1613
- const reading = this.isReading(range);
1614
- const writing = this.isWriting(range);
1615
- return !reading && !writing;
1537
+ getHeadData(key, defaultValue) {
1538
+ return this.snapshotHead.data[key] ?? defaultValue;
1539
+ }
1540
+ setHeadData(key, data) {
1541
+ this.snapshotHead.data[key] = data;
1542
+ }
1543
+ autoIncrement(key, defaultValue) {
1544
+ return this.snapshotHead.data[key] ?? defaultValue;
1545
+ }
1546
+ };
1547
+
1548
+ // src/transaction/BPTreeSyncTransaction.ts
1549
+ var BPTreeSyncTransaction = class extends BPTreeSyncBase {
1550
+ realBaseTree;
1551
+ realBaseStrategy;
1552
+ txNodes = /* @__PURE__ */ new Map();
1553
+ dirtyIds = /* @__PURE__ */ new Set();
1554
+ createdInTx = /* @__PURE__ */ new Set();
1555
+ initialRootId;
1556
+ transactionRootId;
1557
+ constructor(baseTree) {
1558
+ super(baseTree.strategy, baseTree.comparator, baseTree.option);
1559
+ this.realBaseTree = baseTree;
1560
+ this.realBaseStrategy = baseTree.strategy;
1561
+ this.initialRootId = "";
1562
+ this.transactionRootId = "";
1616
1563
  }
1617
1564
  /**
1618
- * Internal implementation of the read lock. Handles both overloads.
1619
- * @template T - The return type of the task.
1620
- * @param arg0 - Either a range or a task callback.
1621
- * If a range is provided, the task is the second argument.
1622
- * @param arg1 - The task to execute, required if a range is provided.
1623
- * @param arg2 - The timeout for acquiring the lock.
1624
- * If the lock cannot be acquired within this period, an error will be thrown.
1625
- * If this value is not provided, no timeout will be set.
1626
- * @returns A promise resolving to the result of the task execution.
1565
+ * Initializes the transaction by capturing the current state of the tree.
1627
1566
  */
1628
- readLock(arg0, arg1, arg2) {
1629
- const [range, task, timeout] = this._handleOverload(
1630
- [arg0, arg1, arg2],
1631
- {
1632
- rangeTask: (range2, task2) => [range2, task2, -1],
1633
- rangeTaskTimeout: (range2, task2, timeout2) => [range2, task2, timeout2],
1634
- task: (task2) => [[-Infinity, Infinity], task2, -1],
1635
- taskTimeout: (task2, timeout2) => [[-Infinity, Infinity], task2, timeout2]
1636
- },
1637
- {
1638
- task: [Function],
1639
- taskTimeout: [Function, Number],
1640
- rangeTask: [Array, Function],
1641
- rangeTaskTimeout: [Array, Function, Number]
1567
+ initTransaction() {
1568
+ const head = this.realBaseStrategy.readHead();
1569
+ this.initialRootId = head?.root ?? this.realBaseTree.rootId;
1570
+ this.transactionRootId = this.initialRootId;
1571
+ this.rootId = this.transactionRootId;
1572
+ const snapshotStrategy = new BPTreeSyncSnapshotStrategy(this.realBaseStrategy, this.initialRootId);
1573
+ this.strategy = snapshotStrategy;
1574
+ this.txNodes.clear();
1575
+ this.dirtyIds.clear();
1576
+ this.createdInTx.clear();
1577
+ }
1578
+ getNode(id) {
1579
+ if (this.txNodes.has(id)) {
1580
+ return this.txNodes.get(id);
1581
+ }
1582
+ const baseNode = this.realBaseStrategy.read(id);
1583
+ const clone = JSON.parse(JSON.stringify(baseNode));
1584
+ this.txNodes.set(id, clone);
1585
+ return clone;
1586
+ }
1587
+ bufferForNodeUpdate(node) {
1588
+ this.txNodes.set(node.id, node);
1589
+ this.dirtyIds.add(node.id);
1590
+ this.markPathDirty(node);
1591
+ }
1592
+ bufferForNodeCreate(node) {
1593
+ this.txNodes.set(node.id, node);
1594
+ this.dirtyIds.add(node.id);
1595
+ this.createdInTx.add(node.id);
1596
+ this.markPathDirty(node);
1597
+ }
1598
+ bufferForNodeDelete(node) {
1599
+ this.txNodes.delete(node.id);
1600
+ this.dirtyIds.add(node.id);
1601
+ }
1602
+ markPathDirty(node) {
1603
+ let curr = node;
1604
+ while (curr.parent) {
1605
+ if (this.dirtyIds.has(curr.parent) && this.txNodes.has(curr.parent)) {
1606
+ break;
1642
1607
  }
1643
- );
1644
- return this._lock(
1645
- this.readQueue,
1646
- range,
1647
- timeout,
1648
- task,
1649
- () => !this.rangeOverlapping(this.writings, range)
1650
- );
1608
+ const parent = this.getNode(curr.parent);
1609
+ this.dirtyIds.add(parent.id);
1610
+ curr = parent;
1611
+ }
1612
+ if (!curr.parent) {
1613
+ this.transactionRootId = curr.id;
1614
+ }
1615
+ }
1616
+ _createNode(isLeaf, keys, values, leaf = false, parent = null, next = null, prev = null) {
1617
+ const id = this.realBaseStrategy.id(isLeaf);
1618
+ const node = {
1619
+ id,
1620
+ keys,
1621
+ values,
1622
+ leaf: isLeaf,
1623
+ parent,
1624
+ next,
1625
+ prev
1626
+ };
1627
+ this.bufferForNodeCreate(node);
1628
+ return node;
1651
1629
  }
1652
1630
  /**
1653
- * Internal implementation of the write lock. Handles both overloads.
1654
- * @template T - The return type of the task.
1655
- * @param arg0 - Either a range or a task callback.
1656
- * If a range is provided, the task is the second argument.
1657
- * @param arg1 - The task to execute, required if a range is provided.
1658
- * @param arg2 - The timeout for acquiring the lock.
1659
- * If the lock cannot be acquired within this period, an error will be thrown.
1660
- * If this value is not provided, no timeout will be set.
1661
- * @returns A promise resolving to the result of the task execution.
1631
+ * Attempts to commit the transaction.
1632
+ * Uses Optimistic Locking (Compare-And-Swap) on the root node ID to detect conflicts.
1633
+ *
1634
+ * @returns The transaction result.
1662
1635
  */
1663
- writeLock(arg0, arg1, arg2) {
1664
- const [range, task, timeout] = this._handleOverload(
1665
- [arg0, arg1, arg2],
1666
- {
1667
- rangeTask: (range2, task2) => [range2, task2, -1],
1668
- rangeTaskTimeout: (range2, task2, timeout2) => [range2, task2, timeout2],
1669
- task: (task2) => [[-Infinity, Infinity], task2, -1],
1670
- taskTimeout: (task2, timeout2) => [[-Infinity, Infinity], task2, timeout2]
1671
- },
1672
- {
1673
- task: [Function],
1674
- taskTimeout: [Function, Number],
1675
- rangeTask: [Array, Function],
1676
- rangeTaskTimeout: [Array, Function, Number]
1636
+ commit() {
1637
+ const idMapping = /* @__PURE__ */ new Map();
1638
+ const finalNodes = [];
1639
+ for (const oldId of this.dirtyIds) {
1640
+ if (this.createdInTx.has(oldId)) {
1641
+ idMapping.set(oldId, oldId);
1642
+ } else {
1643
+ const node = this.txNodes.get(oldId);
1644
+ if (node) {
1645
+ const newId = this.realBaseStrategy.id(node.leaf);
1646
+ idMapping.set(oldId, newId);
1647
+ }
1677
1648
  }
1678
- );
1679
- return this._lock(
1680
- this.writeQueue,
1681
- range,
1682
- timeout,
1683
- task,
1684
- () => {
1685
- return !this.rangeOverlapping(this.writings, range) && !this.rangeOverlapping(this.readings, range);
1649
+ }
1650
+ const newCreatedIds = [];
1651
+ for (const oldId of this.dirtyIds) {
1652
+ const node = this.txNodes.get(oldId);
1653
+ if (!node) continue;
1654
+ const newId = idMapping.get(oldId);
1655
+ node.id = newId;
1656
+ if (node.parent && idMapping.has(node.parent)) {
1657
+ node.parent = idMapping.get(node.parent);
1658
+ }
1659
+ if (!node.leaf) {
1660
+ const internal = node;
1661
+ for (let i = 0; i < internal.keys.length; i++) {
1662
+ const childId = internal.keys[i];
1663
+ if (idMapping.has(childId)) {
1664
+ internal.keys[i] = idMapping.get(childId);
1665
+ }
1666
+ }
1686
1667
  }
1687
- );
1668
+ if (node.leaf) {
1669
+ const leaf = node;
1670
+ if (leaf.next && idMapping.has(leaf.next)) {
1671
+ leaf.next = idMapping.get(leaf.next);
1672
+ }
1673
+ if (leaf.prev && idMapping.has(leaf.prev)) {
1674
+ leaf.prev = idMapping.get(leaf.prev);
1675
+ }
1676
+ }
1677
+ finalNodes.push(node);
1678
+ newCreatedIds.push(newId);
1679
+ }
1680
+ let newRootId = this.transactionRootId;
1681
+ if (idMapping.has(this.transactionRootId)) {
1682
+ newRootId = idMapping.get(this.transactionRootId);
1683
+ }
1684
+ for (const node of finalNodes) {
1685
+ this.realBaseStrategy.write(node.id, node);
1686
+ }
1687
+ const success = this.realBaseStrategy.compareAndSwapHead(this.initialRootId, newRootId);
1688
+ if (success) {
1689
+ const distinctObsolete = /* @__PURE__ */ new Set();
1690
+ for (const oldId of this.dirtyIds) {
1691
+ if (!this.createdInTx.has(oldId) && this.txNodes.has(oldId)) {
1692
+ distinctObsolete.add(oldId);
1693
+ }
1694
+ }
1695
+ return {
1696
+ success: true,
1697
+ createdIds: newCreatedIds,
1698
+ obsoleteIds: Array.from(distinctObsolete)
1699
+ };
1700
+ } else {
1701
+ this.rollback();
1702
+ return {
1703
+ success: false,
1704
+ createdIds: newCreatedIds,
1705
+ obsoleteIds: []
1706
+ };
1707
+ }
1688
1708
  }
1689
1709
  /**
1690
- * Releases a read lock by its lock ID.
1691
- * @param lockId - The unique identifier for the lock to release.
1710
+ * Rolls back the transaction by clearing all buffered changes.
1711
+ * Internal use only.
1692
1712
  */
1693
- readUnlock(lockId) {
1694
- this._free(this.readings, lockId);
1713
+ rollback() {
1714
+ this.txNodes.clear();
1715
+ this.dirtyIds.clear();
1716
+ this.createdInTx.clear();
1717
+ }
1718
+ // Override to do nothing, as transaction handles its own commits
1719
+ commitHeadBuffer() {
1720
+ }
1721
+ commitNodeCreateBuffer() {
1722
+ }
1723
+ commitNodeUpdateBuffer() {
1724
+ }
1725
+ commitNodeDeleteBuffer() {
1726
+ }
1727
+ };
1728
+
1729
+ // src/BPTreeSync.ts
1730
+ var BPTreeSync = class extends BPTreeSyncBase {
1731
+ constructor(strategy, comparator, option) {
1732
+ super(strategy, comparator, option);
1695
1733
  }
1696
1734
  /**
1697
- * Releases a write lock by its lock ID.
1698
- * @param lockId - The unique identifier for the lock to release.
1735
+ * Creates a new synchronous transaction.
1736
+ * @returns A new BPTreeSyncTransaction.
1699
1737
  */
1700
- writeUnlock(lockId) {
1701
- this._free(this.writings, lockId);
1738
+ createTransaction() {
1739
+ const tx = new BPTreeSyncTransaction(this);
1740
+ tx.initTransaction();
1741
+ return tx;
1742
+ }
1743
+ insert(key, value) {
1744
+ const tx = this.createTransaction();
1745
+ tx.insert(key, value);
1746
+ const { success } = tx.commit();
1747
+ this.init();
1748
+ if (!success) {
1749
+ throw new Error("Transaction failed: Commit failed due to conflict");
1750
+ }
1751
+ }
1752
+ delete(key, value) {
1753
+ const tx = this.createTransaction();
1754
+ tx.delete(key, value);
1755
+ const { success } = tx.commit();
1756
+ this.init();
1757
+ if (!success) {
1758
+ throw new Error("Transaction failed: Commit failed due to conflict");
1759
+ }
1702
1760
  }
1703
1761
  };
1704
1762
 
1705
- // src/BPTreeAsync.ts
1706
- var BPTreeAsync = class extends BPTree {
1707
- lock;
1763
+ // src/base/BPTreeAsyncBase.ts
1764
+ var BPTreeAsyncBase = class extends BPTree {
1708
1765
  constructor(strategy, comparator, option) {
1709
1766
  super(strategy, comparator, option);
1710
1767
  this.nodes = this._createCachedNode();
1711
- this.lock = new Ryoiki();
1712
1768
  }
1713
1769
  _createCachedNode() {
1714
1770
  return new CacheEntanglementAsync(async (key) => {
@@ -1717,24 +1773,6 @@ var BPTreeAsync = class extends BPTree {
1717
1773
  capacity: this.option.capacity ?? 1e3
1718
1774
  });
1719
1775
  }
1720
- async readLock(callback) {
1721
- let lockId;
1722
- return await this.lock.readLock(async (_lockId) => {
1723
- lockId = _lockId;
1724
- return await callback();
1725
- }).finally(() => {
1726
- this.lock.readUnlock(lockId);
1727
- });
1728
- }
1729
- async writeLock(callback) {
1730
- let lockId;
1731
- return await this.lock.writeLock(async (_lockId) => {
1732
- lockId = _lockId;
1733
- return await callback();
1734
- }).finally(() => {
1735
- this.lock.writeUnlock(lockId);
1736
- });
1737
- }
1738
1776
  async *getPairsGenerator(value, startNode, endNode, comparator, direction, earlyTerminate) {
1739
1777
  let node = startNode;
1740
1778
  let done = false;
@@ -1832,7 +1870,7 @@ var BPTreeAsync = class extends BPTree {
1832
1870
  }
1833
1871
  }
1834
1872
  }
1835
- if (this.rootId === node.id && node.keys.length === 1) {
1873
+ if (this.rootId === node.id && node.keys.length === 1 && !node.leaf) {
1836
1874
  const keys = node.keys;
1837
1875
  this.bufferForNodeDelete(node);
1838
1876
  const newRoot = await this.getNode(keys[0]);
@@ -1901,28 +1939,19 @@ var BPTreeAsync = class extends BPTree {
1901
1939
  pointer.values.push(guess);
1902
1940
  } else {
1903
1941
  pointer.next = node.next;
1904
- pointer.prev = node.id;
1905
1942
  if (pointer.next) {
1906
- const n = await this.getNode(node.next);
1943
+ const n = await this.getNode(pointer.next);
1907
1944
  n.prev = pointer.id;
1908
1945
  this.bufferForNodeUpdate(n);
1909
1946
  }
1910
- if (pointer.prev) {
1911
- const n = await this.getNode(node.id);
1912
- n.next = pointer.id;
1913
- this.bufferForNodeUpdate(n);
1914
- }
1915
- if (isPredecessor) {
1916
- pointer.prev = null;
1917
- }
1918
1947
  }
1919
1948
  pointer.values.push(...node.values);
1920
1949
  if (!pointer.leaf) {
1921
1950
  const keys = pointer.keys;
1922
1951
  for (const key2 of keys) {
1923
- const node2 = await this.getNode(key2);
1924
- node2.parent = pointer.id;
1925
- this.bufferForNodeUpdate(node2);
1952
+ const n = await this.getNode(key2);
1953
+ n.parent = pointer.id;
1954
+ this.bufferForNodeUpdate(n);
1926
1955
  }
1927
1956
  }
1928
1957
  await this._deleteEntry(await this.getNode(node.parent), node.id, guess);
@@ -1999,21 +2028,24 @@ var BPTreeAsync = class extends BPTree {
1999
2028
  this.bufferForNodeUpdate(pointer);
2000
2029
  }
2001
2030
  if (!pointer.leaf) {
2002
- for (const key2 of pointer.keys) {
2031
+ const keys = pointer.keys;
2032
+ for (const key2 of keys) {
2003
2033
  const n = await this.getNode(key2);
2004
2034
  n.parent = pointer.id;
2005
2035
  this.bufferForNodeUpdate(n);
2006
2036
  }
2007
2037
  }
2008
2038
  if (!node.leaf) {
2009
- for (const key2 of node.keys) {
2039
+ const keys = node.keys;
2040
+ for (const key2 of keys) {
2010
2041
  const n = await this.getNode(key2);
2011
2042
  n.parent = node.id;
2012
2043
  this.bufferForNodeUpdate(n);
2013
2044
  }
2014
2045
  }
2015
2046
  if (!parentNode.leaf) {
2016
- for (const key2 of parentNode.keys) {
2047
+ const keys = parentNode.keys;
2048
+ for (const key2 of keys) {
2017
2049
  const n = await this.getNode(key2);
2018
2050
  n.parent = parentNode.id;
2019
2051
  this.bufferForNodeUpdate(n);
@@ -2077,14 +2109,14 @@ var BPTreeAsync = class extends BPTree {
2077
2109
  parentNode.values = parentNode.values.slice(0, mid);
2078
2110
  parentNode.keys = parentNode.keys.slice(0, mid + 1);
2079
2111
  for (const k of parentNode.keys) {
2080
- const node2 = await this.getNode(k);
2081
- node2.parent = parentNode.id;
2082
- this.bufferForNodeUpdate(node2);
2112
+ const n = await this.getNode(k);
2113
+ n.parent = parentNode.id;
2114
+ this.bufferForNodeUpdate(n);
2083
2115
  }
2084
2116
  for (const k of parentPointer.keys) {
2085
- const node2 = await this.getNode(k);
2086
- node2.parent = parentPointer.id;
2087
- this.bufferForNodeUpdate(node2);
2117
+ const n = await this.getNode(k);
2118
+ n.parent = parentPointer.id;
2119
+ this.bufferForNodeUpdate(n);
2088
2120
  }
2089
2121
  await this._insertInParent(parentNode, midValue, parentPointer);
2090
2122
  this.bufferForNodeUpdate(parentNode);
@@ -2126,23 +2158,19 @@ var BPTreeAsync = class extends BPTree {
2126
2158
  const nValue = node.values[i];
2127
2159
  const k = node.keys;
2128
2160
  if (this.comparator.isSame(value, nValue)) {
2129
- node = await this.getNode(k[i + 1]);
2161
+ node = await this.getNode(node.keys[i + 1]);
2130
2162
  break;
2131
2163
  } else if (this.comparator.isLower(value, nValue)) {
2132
- node = await this.getNode(k[i]);
2164
+ node = await this.getNode(node.keys[i]);
2133
2165
  break;
2134
2166
  } else if (i + 1 === node.values.length) {
2135
- node = await this.getNode(k[i + 1]);
2167
+ node = await this.getNode(node.keys[i + 1]);
2136
2168
  break;
2137
2169
  }
2138
2170
  }
2139
2171
  }
2140
2172
  return node;
2141
2173
  }
2142
- /**
2143
- * Find the insertable node using primaryAsc comparison.
2144
- * This allows finding nodes by primary value only, ignoring unique identifiers.
2145
- */
2146
2174
  async insertableNodeByPrimary(value) {
2147
2175
  let node = await this.getNode(this.rootId);
2148
2176
  while (!node.leaf) {
@@ -2150,13 +2178,13 @@ var BPTreeAsync = class extends BPTree {
2150
2178
  const nValue = node.values[i];
2151
2179
  const k = node.keys;
2152
2180
  if (this.comparator.isPrimarySame(value, nValue)) {
2153
- node = await this.getNode(k[i]);
2181
+ node = await this.getNode(node.keys[i]);
2154
2182
  break;
2155
2183
  } else if (this.comparator.isPrimaryLower(value, nValue)) {
2156
- node = await this.getNode(k[i]);
2184
+ node = await this.getNode(node.keys[i]);
2157
2185
  break;
2158
2186
  } else if (i + 1 === node.values.length) {
2159
- node = await this.getNode(k[i + 1]);
2187
+ node = await this.getNode(node.keys[i + 1]);
2160
2188
  break;
2161
2189
  }
2162
2190
  }
@@ -2170,11 +2198,11 @@ var BPTreeAsync = class extends BPTree {
2170
2198
  const nValue = node.values[i];
2171
2199
  const k = node.keys;
2172
2200
  if (this.comparator.isPrimaryLower(value, nValue)) {
2173
- node = await this.getNode(k[i]);
2201
+ node = await this.getNode(node.keys[i]);
2174
2202
  break;
2175
2203
  }
2176
2204
  if (i + 1 === node.values.length) {
2177
- node = await this.getNode(k[i + 1]);
2205
+ node = await this.getNode(node.keys[i + 1]);
2178
2206
  break;
2179
2207
  }
2180
2208
  }
@@ -2211,7 +2239,7 @@ var BPTreeAsync = class extends BPTree {
2211
2239
  let node = await this.getNode(this.rootId);
2212
2240
  while (!node.leaf) {
2213
2241
  const keys = node.keys;
2214
- node = await this.getNode(keys[0]);
2242
+ node = await this.getNode(node.keys[0]);
2215
2243
  }
2216
2244
  return node;
2217
2245
  }
@@ -2219,10 +2247,37 @@ var BPTreeAsync = class extends BPTree {
2219
2247
  let node = await this.getNode(this.rootId);
2220
2248
  while (!node.leaf) {
2221
2249
  const keys = node.keys;
2222
- node = await this.getNode(keys[keys.length - 1]);
2250
+ node = await this.getNode(node.keys[node.keys.length - 1]);
2223
2251
  }
2224
2252
  return node;
2225
2253
  }
2254
+ async exists(key, value) {
2255
+ const node = await this.insertableNode(value);
2256
+ for (let i = 0, len = node.values.length; i < len; i++) {
2257
+ if (this.comparator.isSame(value, node.values[i])) {
2258
+ const keys = node.keys[i];
2259
+ if (keys.includes(key)) {
2260
+ return true;
2261
+ }
2262
+ }
2263
+ }
2264
+ return false;
2265
+ }
2266
+ async forceUpdate(id) {
2267
+ if (id) {
2268
+ this.nodes.delete(id);
2269
+ await this.getNode(id);
2270
+ return 1;
2271
+ }
2272
+ const keys = Array.from(this.nodes.keys());
2273
+ for (const key of keys) {
2274
+ this.nodes.delete(key);
2275
+ }
2276
+ for (const key of keys) {
2277
+ await this.getNode(key);
2278
+ }
2279
+ return keys.length;
2280
+ }
2226
2281
  async commitHeadBuffer() {
2227
2282
  if (!this._strategyDirty) {
2228
2283
  return;
@@ -2253,25 +2308,15 @@ var BPTreeAsync = class extends BPTree {
2253
2308
  }
2254
2309
  this._nodeDeleteBuffer.clear();
2255
2310
  }
2256
- /**
2257
- * Retrieves the value associated with the given key (PK).
2258
- * Note: This method performs a full scan of leaf nodes as the tree is ordered by Value, not Key.
2259
- *
2260
- * @param key The key to search for.
2261
- * @returns The value associated with the key, or undefined if not found.
2262
- */
2263
2311
  async get(key) {
2264
- return this.readLock(async () => {
2312
+ return await this.readLock(async () => {
2265
2313
  let node = await this.leftestNode();
2266
2314
  while (true) {
2267
- if (node.values) {
2268
- const len = node.values.length;
2269
- for (let i = 0; i < len; i++) {
2270
- const keys = node.keys[i];
2271
- for (let j = 0; j < keys.length; j++) {
2272
- if (keys[j] === key) {
2273
- return node.values[i];
2274
- }
2315
+ for (let i = 0, len = node.values.length; i < len; i++) {
2316
+ const keys = node.keys[i];
2317
+ for (let j = 0, kLen = keys.length; j < kLen; j++) {
2318
+ if (keys[j] === key) {
2319
+ return node.values[i];
2275
2320
  }
2276
2321
  }
2277
2322
  }
@@ -2346,25 +2391,21 @@ var BPTreeAsync = class extends BPTree {
2346
2391
  }
2347
2392
  }
2348
2393
  async keys(condition, filterValues) {
2349
- return this.readLock(async () => {
2350
- const set = /* @__PURE__ */ new Set();
2351
- for await (const key of this.keysStream(condition, filterValues)) {
2352
- set.add(key);
2353
- }
2354
- return set;
2355
- });
2394
+ const set = /* @__PURE__ */ new Set();
2395
+ for await (const key of this.keysStream(condition, filterValues)) {
2396
+ set.add(key);
2397
+ }
2398
+ return set;
2356
2399
  }
2357
2400
  async where(condition) {
2358
- return this.readLock(async () => {
2359
- const map = /* @__PURE__ */ new Map();
2360
- for await (const [key, value] of this.whereStream(condition)) {
2361
- map.set(key, value);
2362
- }
2363
- return map;
2364
- });
2401
+ const map = /* @__PURE__ */ new Map();
2402
+ for await (const [key, value] of this.whereStream(condition)) {
2403
+ map.set(key, value);
2404
+ }
2405
+ return map;
2365
2406
  }
2366
2407
  async insert(key, value) {
2367
- return this.writeLock(async () => {
2408
+ await this.writeLock(async () => {
2368
2409
  const before = await this.insertableNode(value);
2369
2410
  this._insertAtLeaf(before, key, value);
2370
2411
  if (before.values.length === this.order) {
@@ -2391,188 +2432,376 @@ var BPTreeAsync = class extends BPTree {
2391
2432
  });
2392
2433
  }
2393
2434
  async delete(key, value) {
2394
- return this.writeLock(async () => {
2435
+ await this.writeLock(async () => {
2395
2436
  const node = await this.insertableNode(value);
2396
2437
  let i = node.values.length;
2397
2438
  while (i--) {
2398
2439
  const nValue = node.values[i];
2399
2440
  if (this.comparator.isSame(value, nValue)) {
2400
2441
  const keys = node.keys[i];
2401
- if (keys.includes(key)) {
2402
- if (keys.length > 1) {
2403
- keys.splice(keys.indexOf(key), 1);
2404
- this.bufferForNodeUpdate(node);
2405
- } else if (node.id === this.rootId) {
2406
- node.values.splice(i, 1);
2407
- node.keys.splice(i, 1);
2408
- this.bufferForNodeUpdate(node);
2409
- } else {
2410
- keys.splice(keys.indexOf(key), 1);
2442
+ const keyIndex = keys.indexOf(key);
2443
+ if (keyIndex !== -1) {
2444
+ keys.splice(keyIndex, 1);
2445
+ if (keys.length === 0) {
2411
2446
  node.keys.splice(i, 1);
2412
- node.values.splice(node.values.indexOf(value), 1);
2413
- await this._deleteEntry(node, key, value);
2414
- this.bufferForNodeUpdate(node);
2447
+ node.values.splice(i, 1);
2415
2448
  }
2449
+ await this._deleteEntry(node, key, value);
2450
+ break;
2416
2451
  }
2417
2452
  }
2418
2453
  }
2419
2454
  await this.commitHeadBuffer();
2420
- await this.commitNodeCreateBuffer();
2421
2455
  await this.commitNodeUpdateBuffer();
2422
2456
  await this.commitNodeDeleteBuffer();
2423
2457
  });
2424
2458
  }
2425
- async exists(key, value) {
2426
- return this.readLock(async () => {
2427
- const node = await this.insertableNode(value);
2428
- for (let i = 0, len = node.values.length; i < len; i++) {
2429
- const nValue = node.values[i];
2430
- if (this.comparator.isSame(value, nValue)) {
2431
- const keys = node.keys[i];
2432
- return keys.includes(key);
2433
- }
2434
- }
2435
- return false;
2436
- });
2459
+ getHeadData() {
2460
+ return this.strategy.head.data;
2437
2461
  }
2438
2462
  async setHeadData(data) {
2439
- return this.writeLock(async () => {
2440
- this.strategy.head.data = data;
2441
- this._strategyDirty = true;
2442
- await this.commitHeadBuffer();
2443
- });
2444
- }
2445
- async forceUpdate() {
2446
- return this.readLock(async () => {
2447
- const keys = [...this.nodes.keys()];
2448
- this.nodes.clear();
2449
- await this.init();
2450
- for (const key of keys) {
2451
- await this.getNode(key);
2452
- }
2453
- return keys.length;
2454
- });
2455
- }
2456
- };
2457
-
2458
- // src/base/SerializeStrategy.ts
2459
- var SerializeStrategy = class {
2460
- order;
2461
- head;
2462
- constructor(order) {
2463
- this.order = order;
2464
- this.head = {
2465
- order,
2466
- root: null,
2467
- data: {}
2468
- };
2463
+ this.strategy.head.data = data;
2464
+ await this.strategy.writeHead(this.strategy.head);
2469
2465
  }
2470
2466
  };
2471
2467
 
2472
- // src/SerializeStrategySync.ts
2473
- var SerializeStrategySync = class extends SerializeStrategy {
2474
- getHeadData(key, defaultValue) {
2468
+ // src/SerializeStrategyAsync.ts
2469
+ var SerializeStrategyAsync = class extends SerializeStrategy {
2470
+ async getHeadData(key, defaultValue) {
2475
2471
  if (!Object.hasOwn(this.head.data, key)) {
2476
- this.setHeadData(key, defaultValue);
2472
+ await this.setHeadData(key, defaultValue);
2477
2473
  }
2478
2474
  return this.head.data[key];
2479
2475
  }
2480
- setHeadData(key, data) {
2476
+ async setHeadData(key, data) {
2481
2477
  this.head.data[key] = data;
2482
- this.writeHead(this.head);
2478
+ await this.writeHead(this.head);
2483
2479
  }
2484
- autoIncrement(key, defaultValue) {
2485
- const current = this.getHeadData(key, defaultValue);
2480
+ async autoIncrement(key, defaultValue) {
2481
+ const current = await this.getHeadData(key, defaultValue);
2486
2482
  const next = current + 1;
2487
- this.setHeadData(key, next);
2483
+ await this.setHeadData(key, next);
2488
2484
  return current;
2489
2485
  }
2486
+ async compareAndSwapHead(oldRoot, newRoot) {
2487
+ if (this.head.root !== oldRoot) {
2488
+ return false;
2489
+ }
2490
+ this.head.root = newRoot;
2491
+ await this.writeHead(this.head);
2492
+ return true;
2493
+ }
2490
2494
  };
2491
- var InMemoryStoreStrategySync = class extends SerializeStrategySync {
2495
+ var InMemoryStoreStrategyAsync = class extends SerializeStrategyAsync {
2492
2496
  node;
2493
2497
  constructor(order) {
2494
2498
  super(order);
2495
2499
  this.node = {};
2496
2500
  }
2497
- id(isLeaf) {
2498
- return this.autoIncrement("index", 1).toString();
2501
+ async id(isLeaf) {
2502
+ return (await this.autoIncrement("index", 1)).toString();
2499
2503
  }
2500
- read(id) {
2504
+ async read(id) {
2501
2505
  if (!Object.hasOwn(this.node, id)) {
2502
2506
  throw new Error(`The tree attempted to reference node '${id}', but couldn't find the corresponding node.`);
2503
2507
  }
2504
- return this.node[id];
2508
+ const node = this.node[id];
2509
+ return JSON.parse(JSON.stringify(node));
2505
2510
  }
2506
- write(id, node) {
2511
+ async write(id, node) {
2507
2512
  this.node[id] = node;
2508
2513
  }
2509
- delete(id) {
2514
+ async delete(id) {
2510
2515
  delete this.node[id];
2511
2516
  }
2512
- readHead() {
2517
+ async readHead() {
2513
2518
  if (this.head.root === null) {
2514
2519
  return null;
2515
2520
  }
2516
2521
  return this.head;
2517
2522
  }
2518
- writeHead(head) {
2523
+ async writeHead(head) {
2519
2524
  this.head = head;
2520
2525
  }
2521
2526
  };
2522
2527
 
2523
- // src/SerializeStrategyAsync.ts
2524
- var SerializeStrategyAsync = class extends SerializeStrategy {
2528
+ // src/transaction/BPTreeAsyncSnapshotStrategy.ts
2529
+ var BPTreeAsyncSnapshotStrategy = class extends SerializeStrategyAsync {
2530
+ baseStrategy;
2531
+ snapshotHead;
2532
+ constructor(baseStrategy, root) {
2533
+ super(baseStrategy.order);
2534
+ this.baseStrategy = baseStrategy;
2535
+ this.snapshotHead = {
2536
+ ...baseStrategy.head,
2537
+ root,
2538
+ data: { ...baseStrategy.head.data }
2539
+ };
2540
+ this.head = this.snapshotHead;
2541
+ }
2542
+ async id(isLeaf) {
2543
+ return await this.baseStrategy.id(isLeaf);
2544
+ }
2545
+ async read(id) {
2546
+ return await this.baseStrategy.read(id);
2547
+ }
2548
+ async write(id, node) {
2549
+ await this.baseStrategy.write(id, node);
2550
+ }
2551
+ async delete(id) {
2552
+ await this.baseStrategy.delete(id);
2553
+ }
2554
+ async readHead() {
2555
+ return this.snapshotHead;
2556
+ }
2557
+ async writeHead(head) {
2558
+ this.snapshotHead.root = head.root;
2559
+ this.snapshotHead.data = { ...head.data };
2560
+ }
2561
+ async compareAndSwapHead(oldRoot, newRoot) {
2562
+ return await this.baseStrategy.compareAndSwapHead(oldRoot, newRoot);
2563
+ }
2525
2564
  async getHeadData(key, defaultValue) {
2526
- if (!Object.hasOwn(this.head.data, key)) {
2527
- await this.setHeadData(key, defaultValue);
2528
- }
2529
- return this.head.data[key];
2565
+ return this.snapshotHead.data[key] ?? defaultValue;
2530
2566
  }
2531
2567
  async setHeadData(key, data) {
2532
- this.head.data[key] = data;
2533
- await this.writeHead(this.head);
2568
+ this.snapshotHead.data[key] = data;
2534
2569
  }
2535
2570
  async autoIncrement(key, defaultValue) {
2536
- const current = await this.getHeadData(key, defaultValue);
2537
- const next = current + 1;
2538
- await this.setHeadData(key, next);
2539
- return current;
2571
+ return this.snapshotHead.data[key] ?? defaultValue;
2540
2572
  }
2541
2573
  };
2542
- var InMemoryStoreStrategyAsync = class extends SerializeStrategyAsync {
2543
- node;
2544
- constructor(order) {
2545
- super(order);
2546
- this.node = {};
2574
+
2575
+ // src/transaction/BPTreeAsyncTransaction.ts
2576
+ var BPTreeAsyncTransaction = class extends BPTreeAsyncBase {
2577
+ realBaseTree;
2578
+ realBaseStrategy;
2579
+ txNodes = /* @__PURE__ */ new Map();
2580
+ dirtyIds = /* @__PURE__ */ new Set();
2581
+ createdInTx = /* @__PURE__ */ new Set();
2582
+ initialRootId;
2583
+ transactionRootId;
2584
+ constructor(baseTree) {
2585
+ super(baseTree.strategy, baseTree.comparator, baseTree.option);
2586
+ this.realBaseTree = baseTree;
2587
+ this.realBaseStrategy = baseTree.strategy;
2588
+ this.initialRootId = "";
2589
+ this.transactionRootId = "";
2547
2590
  }
2548
- async id(isLeaf) {
2549
- return (await this.autoIncrement("index", 1)).toString();
2591
+ /**
2592
+ * Initializes the transaction by capturing the current state of the tree.
2593
+ */
2594
+ async initTransaction() {
2595
+ const head = await this.realBaseStrategy.readHead();
2596
+ this.initialRootId = head?.root ?? this.realBaseTree.rootId;
2597
+ this.transactionRootId = this.initialRootId;
2598
+ this.rootId = this.transactionRootId;
2599
+ const snapshotStrategy = new BPTreeAsyncSnapshotStrategy(this.realBaseStrategy, this.initialRootId);
2600
+ this.strategy = snapshotStrategy;
2601
+ this.txNodes.clear();
2602
+ this.dirtyIds.clear();
2603
+ this.createdInTx.clear();
2550
2604
  }
2551
- async read(id) {
2552
- if (!Object.hasOwn(this.node, id)) {
2553
- throw new Error(`The tree attempted to reference node '${id}', but couldn't find the corresponding node.`);
2605
+ async getNode(id) {
2606
+ if (this.txNodes.has(id)) {
2607
+ return this.txNodes.get(id);
2608
+ }
2609
+ const baseNode = await this.realBaseStrategy.read(id);
2610
+ const clone = JSON.parse(JSON.stringify(baseNode));
2611
+ this.txNodes.set(id, clone);
2612
+ return clone;
2613
+ }
2614
+ async bufferForNodeUpdate(node) {
2615
+ this.txNodes.set(node.id, node);
2616
+ this.dirtyIds.add(node.id);
2617
+ await this.markPathDirty(node);
2618
+ }
2619
+ async bufferForNodeCreate(node) {
2620
+ this.txNodes.set(node.id, node);
2621
+ this.dirtyIds.add(node.id);
2622
+ this.createdInTx.add(node.id);
2623
+ await this.markPathDirty(node);
2624
+ }
2625
+ async bufferForNodeDelete(node) {
2626
+ this.txNodes.delete(node.id);
2627
+ this.dirtyIds.add(node.id);
2628
+ }
2629
+ async markPathDirty(node) {
2630
+ let curr = node;
2631
+ while (curr.parent) {
2632
+ if (this.dirtyIds.has(curr.parent) && this.txNodes.has(curr.parent)) {
2633
+ break;
2634
+ }
2635
+ const parent = await this.getNode(curr.parent);
2636
+ this.dirtyIds.add(parent.id);
2637
+ curr = parent;
2638
+ }
2639
+ if (!curr.parent) {
2640
+ this.transactionRootId = curr.id;
2554
2641
  }
2555
- return this.node[id];
2556
2642
  }
2557
- async write(id, node) {
2558
- this.node[id] = node;
2643
+ async _createNode(isLeaf, keys, values, leaf = false, parent = null, next = null, prev = null) {
2644
+ const id = await this.realBaseStrategy.id(isLeaf);
2645
+ const node = {
2646
+ id,
2647
+ keys,
2648
+ values,
2649
+ leaf: isLeaf,
2650
+ parent,
2651
+ next,
2652
+ prev
2653
+ };
2654
+ await this.bufferForNodeCreate(node);
2655
+ return node;
2559
2656
  }
2560
- async delete(id) {
2561
- delete this.node[id];
2657
+ /**
2658
+ * Attempts to commit the transaction.
2659
+ * Uses Optimistic Locking (Compare-And-Swap) on the root node ID to detect conflicts.
2660
+ *
2661
+ * @returns A promise that resolves to the transaction result.
2662
+ */
2663
+ async commit() {
2664
+ const idMapping = /* @__PURE__ */ new Map();
2665
+ const finalNodes = [];
2666
+ for (const oldId of this.dirtyIds) {
2667
+ if (this.createdInTx.has(oldId)) {
2668
+ idMapping.set(oldId, oldId);
2669
+ } else {
2670
+ const node = this.txNodes.get(oldId);
2671
+ if (node) {
2672
+ const newId = await this.realBaseStrategy.id(node.leaf);
2673
+ idMapping.set(oldId, newId);
2674
+ }
2675
+ }
2676
+ }
2677
+ const newCreatedIds = [];
2678
+ for (const oldId of this.dirtyIds) {
2679
+ const node = this.txNodes.get(oldId);
2680
+ if (!node) continue;
2681
+ const newId = idMapping.get(oldId);
2682
+ node.id = newId;
2683
+ if (node.parent && idMapping.has(node.parent)) {
2684
+ node.parent = idMapping.get(node.parent);
2685
+ }
2686
+ if (!node.leaf) {
2687
+ const internal = node;
2688
+ for (let i = 0; i < internal.keys.length; i++) {
2689
+ const childId = internal.keys[i];
2690
+ if (idMapping.has(childId)) {
2691
+ internal.keys[i] = idMapping.get(childId);
2692
+ }
2693
+ }
2694
+ }
2695
+ if (node.leaf) {
2696
+ const leaf = node;
2697
+ if (leaf.next && idMapping.has(leaf.next)) {
2698
+ leaf.next = idMapping.get(leaf.next);
2699
+ }
2700
+ if (leaf.prev && idMapping.has(leaf.prev)) {
2701
+ leaf.prev = idMapping.get(leaf.prev);
2702
+ }
2703
+ }
2704
+ finalNodes.push(node);
2705
+ newCreatedIds.push(newId);
2706
+ }
2707
+ let newRootId = this.transactionRootId;
2708
+ if (idMapping.has(this.transactionRootId)) {
2709
+ newRootId = idMapping.get(this.transactionRootId);
2710
+ }
2711
+ for (const node of finalNodes) {
2712
+ await this.realBaseStrategy.write(node.id, node);
2713
+ }
2714
+ const success = await this.realBaseStrategy.compareAndSwapHead(this.initialRootId, newRootId);
2715
+ if (success) {
2716
+ const distinctObsolete = /* @__PURE__ */ new Set();
2717
+ for (const oldId of this.dirtyIds) {
2718
+ if (!this.createdInTx.has(oldId) && this.txNodes.has(oldId)) {
2719
+ distinctObsolete.add(oldId);
2720
+ }
2721
+ }
2722
+ return {
2723
+ success: true,
2724
+ createdIds: newCreatedIds,
2725
+ obsoleteIds: Array.from(distinctObsolete)
2726
+ };
2727
+ } else {
2728
+ await this.rollback();
2729
+ return {
2730
+ success: false,
2731
+ createdIds: newCreatedIds,
2732
+ obsoleteIds: []
2733
+ };
2734
+ }
2562
2735
  }
2563
- async readHead() {
2564
- if (this.head.root === null) {
2565
- return null;
2736
+ /**
2737
+ * Rolls back the transaction by clearing all buffered changes.
2738
+ * Internal use only.
2739
+ */
2740
+ async rollback() {
2741
+ this.txNodes.clear();
2742
+ this.dirtyIds.clear();
2743
+ this.createdInTx.clear();
2744
+ }
2745
+ async readLock(fn) {
2746
+ return await fn();
2747
+ }
2748
+ async writeLock(fn) {
2749
+ return await fn();
2750
+ }
2751
+ async commitHeadBuffer() {
2752
+ }
2753
+ async commitNodeCreateBuffer() {
2754
+ }
2755
+ async commitNodeUpdateBuffer() {
2756
+ }
2757
+ async commitNodeDeleteBuffer() {
2758
+ }
2759
+ };
2760
+
2761
+ // src/BPTreeAsync.ts
2762
+ var BPTreeAsync = class extends BPTreeAsyncBase {
2763
+ constructor(strategy, comparator, option) {
2764
+ super(strategy, comparator, option);
2765
+ }
2766
+ /**
2767
+ * Creates a new asynchronous transaction.
2768
+ * @returns A promise that resolves to a new BPTreeAsyncTransaction.
2769
+ */
2770
+ async createTransaction() {
2771
+ const tx = new BPTreeAsyncTransaction(this);
2772
+ await tx.initTransaction();
2773
+ return tx;
2774
+ }
2775
+ async insert(key, value) {
2776
+ const tx = await this.createTransaction();
2777
+ await tx.insert(key, value);
2778
+ const { success } = await tx.commit();
2779
+ await this.init();
2780
+ if (!success) {
2781
+ throw new Error("Transaction failed: Commit failed due to conflict");
2566
2782
  }
2567
- return this.head;
2568
2783
  }
2569
- async writeHead(head) {
2570
- this.head = head;
2784
+ async delete(key, value) {
2785
+ const tx = await this.createTransaction();
2786
+ await tx.delete(key, value);
2787
+ const { success } = await tx.commit();
2788
+ await this.init();
2789
+ if (!success) {
2790
+ throw new Error("Transaction failed: Commit failed due to conflict");
2791
+ }
2792
+ }
2793
+ async readLock(fn) {
2794
+ return await fn();
2795
+ }
2796
+ async writeLock(fn) {
2797
+ return await fn();
2571
2798
  }
2572
2799
  };
2573
2800
  export {
2574
2801
  BPTreeAsync,
2802
+ BPTreeAsyncTransaction,
2575
2803
  BPTreeSync,
2804
+ BPTreeSyncTransaction,
2576
2805
  InMemoryStoreStrategyAsync,
2577
2806
  InMemoryStoreStrategySync,
2578
2807
  NumericComparator,