@yorkie-js/sdk 0.7.10 → 0.7.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6571,447 +6571,432 @@
6571
6571
  this.executedAt = executedAt;
6572
6572
  }
6573
6573
  }
6574
- class SplayNode {
6574
+ class TreeListNode {
6575
6575
  value;
6576
6576
  left;
6577
6577
  right;
6578
6578
  parent;
6579
6579
  weight;
6580
+ count;
6581
+ red;
6580
6582
  constructor(value) {
6581
6583
  this.value = value;
6582
- this.initWeight();
6583
- }
6584
- /**
6585
- * `getNodeString` returns a string of weight and value of this node.
6586
- */
6587
- getNodeString() {
6588
- return `${this.weight}${this.value}`;
6584
+ this.red = true;
6585
+ this.weight = this.size();
6586
+ this.count = 1;
6589
6587
  }
6590
6588
  /**
6591
- * `getValue` returns value of this node.
6589
+ * `getValue` returns the value of this node.
6592
6590
  */
6593
6591
  getValue() {
6594
6592
  return this.value;
6595
6593
  }
6596
6594
  /**
6597
- * `getLeftWeight` returns left weight of this node.
6595
+ * `size` returns 1 if the node is live, 0 if removed (tombstone).
6598
6596
  */
6599
- getLeftWeight() {
6600
- return !this.hasLeft() ? 0 : this.left.getWeight();
6597
+ size() {
6598
+ return this.value.isRemoved() ? 0 : 1;
6601
6599
  }
6602
6600
  /**
6603
- * `getRightWeight` returns right weight of this node.
6601
+ * `getLeft` returns the left child.
6604
6602
  */
6605
- getRightWeight() {
6606
- return !this.hasRight() ? 0 : this.right.getWeight();
6603
+ getLeft() {
6604
+ return this.left;
6607
6605
  }
6608
6606
  /**
6609
- * `getWeight` returns weight of this node.
6607
+ * `setLeft` sets the left child.
6610
6608
  */
6611
- getWeight() {
6612
- return this.weight;
6609
+ setLeft(node) {
6610
+ this.left = node;
6613
6611
  }
6614
6612
  /**
6615
- * `getLeft` returns a left node.
6613
+ * `getRight` returns the right child.
6616
6614
  */
6617
- getLeft() {
6618
- return this.left;
6615
+ getRight() {
6616
+ return this.right;
6619
6617
  }
6620
6618
  /**
6621
- * `getRight` returns a right node.
6619
+ * `setRight` sets the right child.
6622
6620
  */
6623
- getRight() {
6624
- return this.right;
6621
+ setRight(node) {
6622
+ this.right = node;
6625
6623
  }
6626
6624
  /**
6627
- * `getParent` returns parent of this node.
6625
+ * `getParent` returns the parent.
6628
6626
  */
6629
6627
  getParent() {
6630
6628
  return this.parent;
6631
6629
  }
6632
6630
  /**
6633
- * `hasLeft` check if the left node exists
6631
+ * `setParent` sets the parent.
6634
6632
  */
6635
- hasLeft() {
6636
- return !!this.left;
6633
+ setParent(node) {
6634
+ this.parent = node;
6637
6635
  }
6638
6636
  /**
6639
- * `hasRight` check if the right node exists
6637
+ * `getWeight` returns the cached live-node weight of this subtree.
6640
6638
  */
6641
- hasRight() {
6642
- return !!this.right;
6639
+ getWeight() {
6640
+ return this.weight;
6643
6641
  }
6644
6642
  /**
6645
- * `hasParent` check if the parent node exists
6643
+ * `setWeight` sets the cached live-node weight of this subtree.
6646
6644
  */
6647
- hasParent() {
6648
- return !!this.parent;
6645
+ setWeight(w) {
6646
+ this.weight = w;
6649
6647
  }
6650
6648
  /**
6651
- * `setLeft` sets a left node.
6649
+ * `getCount` returns the cached total node count of this subtree.
6652
6650
  */
6653
- setLeft(left) {
6654
- this.left = left;
6651
+ getCount() {
6652
+ return this.count;
6655
6653
  }
6656
6654
  /**
6657
- * `setRight` sets a right node.
6655
+ * `setCount` sets the cached total node count of this subtree.
6658
6656
  */
6659
- setRight(right) {
6660
- this.right = right;
6657
+ setCount(c) {
6658
+ this.count = c;
6661
6659
  }
6662
6660
  /**
6663
- * `setParent` sets a parent node.
6661
+ * `isRed` reports whether this node is colored red.
6664
6662
  */
6665
- setParent(parent) {
6666
- this.parent = parent;
6663
+ isRed() {
6664
+ return this.red;
6667
6665
  }
6668
6666
  /**
6669
- * `unlink` unlink parent, right and left node.
6667
+ * `setRed` sets the color of this node.
6670
6668
  */
6671
- unlink() {
6672
- this.parent = void 0;
6673
- this.right = void 0;
6674
- this.left = void 0;
6669
+ setRed(red) {
6670
+ this.red = red;
6675
6671
  }
6676
6672
  /**
6677
- * `hasLinks` checks if parent, right and left node exists.
6673
+ * `leftWeight` returns the live-node weight of the left subtree, or 0 if absent.
6678
6674
  */
6679
- hasLinks() {
6680
- return this.hasParent() || this.hasLeft() || this.hasRight();
6675
+ leftWeight() {
6676
+ return this.left ? this.left.weight : 0;
6681
6677
  }
6682
6678
  /**
6683
- * `increaseWeight` increases weight.
6679
+ * `rightWeight` returns the live-node weight of the right subtree, or 0 if absent.
6684
6680
  */
6685
- increaseWeight(weight) {
6686
- this.weight += weight;
6681
+ rightWeight() {
6682
+ return this.right ? this.right.weight : 0;
6687
6683
  }
6688
6684
  /**
6689
- * `initWeight` sets initial weight of this node.
6685
+ * `leftCount` returns the total node count of the left subtree, or 0 if absent.
6690
6686
  */
6691
- initWeight() {
6692
- this.weight = this.getLength();
6687
+ leftCount() {
6688
+ return this.left ? this.left.count : 0;
6689
+ }
6690
+ /**
6691
+ * `rightCount` returns the total node count of the right subtree, or 0 if absent.
6692
+ */
6693
+ rightCount() {
6694
+ return this.right ? this.right.count : 0;
6693
6695
  }
6694
6696
  }
6695
- class SplayTree {
6697
+ function isRed(node) {
6698
+ return !!node && node.isRed();
6699
+ }
6700
+ function updateNode(node) {
6701
+ node.setWeight(node.leftWeight() + node.size() + node.rightWeight());
6702
+ node.setCount(node.leftCount() + 1 + node.rightCount());
6703
+ }
6704
+ function rotateLeft(node) {
6705
+ const right = node.getRight();
6706
+ node.setRight(right.getLeft());
6707
+ if (node.getRight()) {
6708
+ node.getRight().setParent(node);
6709
+ }
6710
+ right.setLeft(node);
6711
+ right.setParent(node.getParent());
6712
+ node.setParent(right);
6713
+ right.setRed(node.isRed());
6714
+ node.setRed(true);
6715
+ updateNode(node);
6716
+ updateNode(right);
6717
+ return right;
6718
+ }
6719
+ function rotateRight(node) {
6720
+ const left = node.getLeft();
6721
+ node.setLeft(left.getRight());
6722
+ if (node.getLeft()) {
6723
+ node.getLeft().setParent(node);
6724
+ }
6725
+ left.setRight(node);
6726
+ left.setParent(node.getParent());
6727
+ node.setParent(left);
6728
+ left.setRed(node.isRed());
6729
+ node.setRed(true);
6730
+ updateNode(node);
6731
+ updateNode(left);
6732
+ return left;
6733
+ }
6734
+ function flipColors(node) {
6735
+ node.setRed(!node.isRed());
6736
+ node.getLeft().setRed(!node.getLeft().isRed());
6737
+ node.getRight().setRed(!node.getRight().isRed());
6738
+ }
6739
+ function moveRedLeft(node) {
6740
+ flipColors(node);
6741
+ if (isRed(node.getRight().getLeft())) {
6742
+ node.setRight(rotateRight(node.getRight()));
6743
+ node.getRight().setParent(node);
6744
+ node = rotateLeft(node);
6745
+ flipColors(node);
6746
+ }
6747
+ return node;
6748
+ }
6749
+ function moveRedRight(node) {
6750
+ flipColors(node);
6751
+ if (isRed(node.getLeft().getLeft())) {
6752
+ node = rotateRight(node);
6753
+ flipColors(node);
6754
+ }
6755
+ return node;
6756
+ }
6757
+ function removeMin(node) {
6758
+ if (!node.getLeft()) {
6759
+ return void 0;
6760
+ }
6761
+ if (!isRed(node.getLeft()) && !isRed(node.getLeft().getLeft())) {
6762
+ node = moveRedLeft(node);
6763
+ }
6764
+ node.setLeft(removeMin(node.getLeft()));
6765
+ if (node.getLeft()) {
6766
+ node.getLeft().setParent(node);
6767
+ }
6768
+ return fixUp(node);
6769
+ }
6770
+ function minNode(node) {
6771
+ while (node.getLeft()) {
6772
+ node = node.getLeft();
6773
+ }
6774
+ return node;
6775
+ }
6776
+ function fixUp(node) {
6777
+ if (isRed(node.getRight()) && !isRed(node.getLeft())) {
6778
+ node = rotateLeft(node);
6779
+ }
6780
+ if (isRed(node.getLeft()) && isRed(node.getLeft().getLeft())) {
6781
+ node = rotateRight(node);
6782
+ }
6783
+ if (isRed(node.getLeft()) && isRed(node.getRight())) {
6784
+ flipColors(node);
6785
+ }
6786
+ updateNode(node);
6787
+ return node;
6788
+ }
6789
+ function traverseInOrder(node, cb) {
6790
+ if (!node) {
6791
+ return;
6792
+ }
6793
+ traverseInOrder(node.getLeft(), cb);
6794
+ cb(node);
6795
+ traverseInOrder(node.getRight(), cb);
6796
+ }
6797
+ class TreeList {
6696
6798
  root;
6697
6799
  constructor(root) {
6800
+ if (root) {
6801
+ root.setRed(false);
6802
+ }
6698
6803
  this.root = root;
6699
6804
  }
6700
6805
  /**
6701
- * `length` returns the size of this tree.
6806
+ * `length` returns the number of non-removed (live) nodes.
6702
6807
  */
6703
6808
  get length() {
6704
6809
  return this.root ? this.root.getWeight() : 0;
6705
6810
  }
6706
6811
  /**
6707
- * `findForText` returns the Node and offset of the given position (cursor).
6708
- * Used for Text where cursor placed between characters.
6812
+ * `insertAfter` inserts the target node right after prev in the in-order
6813
+ * traversal. It uses structural (count-based) indexing to correctly handle
6814
+ * tombstone nodes.
6709
6815
  */
6710
- findForText(pos) {
6711
- if (!this.root || pos < 0) {
6712
- return [void 0, 0];
6816
+ insertAfter(prev, target) {
6817
+ if (!prev || !target) {
6818
+ return;
6713
6819
  }
6714
- let node = this.root;
6715
- for (; ; ) {
6716
- if (node.hasLeft() && pos <= node.getLeftWeight()) {
6717
- node = node.getLeft();
6718
- } else if (node.hasRight() && node.getLeftWeight() + node.getLength() < pos) {
6719
- pos -= node.getLeftWeight() + node.getLength();
6720
- node = node.getRight();
6721
- } else {
6722
- pos -= node.getLeftWeight();
6723
- break;
6724
- }
6820
+ target.setLeft(void 0);
6821
+ target.setRight(void 0);
6822
+ target.setParent(void 0);
6823
+ target.setRed(true);
6824
+ target.setWeight(target.size());
6825
+ target.setCount(1);
6826
+ const idx = this.structuralIndexOf(prev);
6827
+ this.root = this.insertByCount(this.root, idx + 1, target);
6828
+ this.root.setRed(false);
6829
+ this.root.setParent(void 0);
6830
+ }
6831
+ /**
6832
+ * `insertByCount` inserts newNode at the given structural index within the
6833
+ * subtree rooted at node, descending the tree using each node's left count
6834
+ * (tombstones included) and rebalancing on the way back up.
6835
+ */
6836
+ insertByCount(node, index, newNode) {
6837
+ if (!node) {
6838
+ return newNode;
6725
6839
  }
6726
- if (pos > node.getLength()) {
6727
- throw new YorkieError(
6728
- Code.ErrInvalidArgument,
6729
- `out of index range: pos: ${pos} > node.length: ${node.getLength()}`
6840
+ if (index <= node.leftCount()) {
6841
+ node.setLeft(this.insertByCount(node.getLeft(), index, newNode));
6842
+ node.getLeft().setParent(node);
6843
+ } else {
6844
+ node.setRight(
6845
+ this.insertByCount(
6846
+ node.getRight(),
6847
+ index - node.leftCount() - 1,
6848
+ newNode
6849
+ )
6730
6850
  );
6851
+ node.getRight().setParent(node);
6731
6852
  }
6732
- this.splayNode(node);
6733
- return [node, pos];
6853
+ return fixUp(node);
6734
6854
  }
6735
6855
  /**
6736
- * `findForArray` returns the Node of the given position (index).
6737
- * Used for Array where index points to the element.
6856
+ * `find` returns the node at the given logical index (among non-removed
6857
+ * nodes). Throws when the index is out of range.
6738
6858
  */
6739
- findForArray(idx) {
6740
- if (!this.root) {
6741
- return void 0;
6742
- }
6743
- if (idx < 0 || idx >= this.length) {
6859
+ find(index) {
6860
+ if (!this.root || index < 0 || index >= this.length) {
6744
6861
  throw new YorkieError(
6745
6862
  Code.ErrInvalidArgument,
6746
- `out of index range: idx: ${idx}, length: ${this.length}`
6863
+ `out of index: tree size ${this.length}, index ${index}`
6747
6864
  );
6748
6865
  }
6749
6866
  let node = this.root;
6750
6867
  for (; ; ) {
6751
- if (node.hasLeft() && idx < node.getLeftWeight()) {
6868
+ if (index < node.leftWeight()) {
6752
6869
  node = node.getLeft();
6753
- } else if (node.hasRight() && node.getLeftWeight() + node.getLength() <= idx) {
6754
- idx -= node.getLeftWeight() + node.getLength();
6755
- node = node.getRight();
6756
- } else {
6870
+ } else if (index < node.leftWeight() + node.size()) {
6757
6871
  break;
6872
+ } else {
6873
+ index -= node.leftWeight() + node.size();
6874
+ node = node.getRight();
6758
6875
  }
6759
6876
  }
6760
- this.splayNode(node);
6761
6877
  return node;
6762
6878
  }
6763
6879
  /**
6764
- * Find the index of the given node in BST.
6765
- *
6766
- * @param node - the given node
6767
- * @returns the index of given node
6880
+ * `delete` physically removes a node from the tree. Unlike tombstoning,
6881
+ * this completely removes the node from the tree structure. It uses
6882
+ * structural (count-based) indexing and swaps the node structure (not
6883
+ * values) with its successor to preserve node identity.
6768
6884
  */
6769
- indexOf(node) {
6770
- if (!node || node !== this.root && !node.hasLinks()) {
6771
- return -1;
6885
+ delete(node) {
6886
+ if (!node || !this.root) {
6887
+ return;
6888
+ }
6889
+ if (!isRed(this.root.getLeft()) && !isRed(this.root.getRight())) {
6890
+ this.root.setRed(true);
6891
+ }
6892
+ const idx = this.structuralIndexOf(node);
6893
+ this.root = this.deleteByCount(this.root, idx);
6894
+ if (this.root) {
6895
+ this.root.setRed(false);
6896
+ this.root.setParent(void 0);
6772
6897
  }
6773
- this.splayNode(node);
6774
- return this.root.getLeftWeight();
6775
- }
6776
- /**
6777
- * `getRoot` returns root of this tree.
6778
- */
6779
- getRoot() {
6780
- return this.root;
6781
- }
6782
- /**
6783
- * `insert` inserts the node at the last.
6784
- */
6785
- insert(newNode) {
6786
- return this.insertAfter(this.root, newNode);
6787
6898
  }
6788
6899
  /**
6789
- * `insertAfter` inserts the node after the given previous node.
6900
+ * `deleteByCount` removes the node at the given structural index within the
6901
+ * subtree rooted at node. When deleting an internal node, it swaps in the
6902
+ * in-order successor by re-parenting rather than copying values so external
6903
+ * references to the surviving node remain valid.
6790
6904
  */
6791
- insertAfter(target, newNode) {
6792
- if (!target) {
6793
- this.root = newNode;
6794
- return newNode;
6795
- }
6796
- this.splayNode(target);
6797
- this.root = newNode;
6798
- newNode.setRight(target.getRight());
6799
- if (target.hasRight()) {
6800
- target.getRight().setParent(newNode);
6905
+ deleteByCount(node, index) {
6906
+ if (index < node.leftCount()) {
6907
+ if (!isRed(node.getLeft()) && !isRed(node.getLeft().getLeft())) {
6908
+ node = moveRedLeft(node);
6909
+ }
6910
+ node.setLeft(this.deleteByCount(node.getLeft(), index));
6911
+ if (node.getLeft()) {
6912
+ node.getLeft().setParent(node);
6913
+ }
6914
+ } else {
6915
+ if (isRed(node.getLeft())) {
6916
+ node = rotateRight(node);
6917
+ }
6918
+ if (index === node.leftCount() && !node.getRight()) {
6919
+ return void 0;
6920
+ }
6921
+ if (!isRed(node.getRight()) && !isRed(node.getRight().getLeft())) {
6922
+ node = moveRedRight(node);
6923
+ }
6924
+ if (index === node.leftCount()) {
6925
+ const successor = minNode(node.getRight());
6926
+ const newRight = removeMin(node.getRight());
6927
+ successor.setLeft(node.getLeft());
6928
+ successor.setRight(newRight);
6929
+ successor.setRed(node.isRed());
6930
+ if (successor.getLeft()) {
6931
+ successor.getLeft().setParent(successor);
6932
+ }
6933
+ if (successor.getRight()) {
6934
+ successor.getRight().setParent(successor);
6935
+ }
6936
+ node.setLeft(void 0);
6937
+ node.setRight(void 0);
6938
+ node.setParent(void 0);
6939
+ node = successor;
6940
+ } else {
6941
+ node.setRight(
6942
+ this.deleteByCount(node.getRight(), index - node.leftCount() - 1)
6943
+ );
6944
+ if (node.getRight()) {
6945
+ node.getRight().setParent(node);
6946
+ }
6947
+ }
6801
6948
  }
6802
- newNode.setLeft(target);
6803
- target.setParent(newNode);
6804
- target.setRight();
6805
- this.updateWeight(target);
6806
- this.updateWeight(newNode);
6807
- return newNode;
6949
+ return fixUp(node);
6808
6950
  }
6809
6951
  /**
6810
- * `updateWeight` recalculates the weight of this node with the value and children.
6952
+ * `updateWeight` propagates weight changes from the given node up to the
6953
+ * root. Call this after a node's isRemoved() status changes (i.e., after
6954
+ * tombstoning).
6811
6955
  */
6812
6956
  updateWeight(node) {
6813
- node.initWeight();
6814
- if (node.hasLeft()) {
6815
- node.increaseWeight(node.getLeftWeight());
6816
- }
6817
- if (node.hasRight()) {
6818
- node.increaseWeight(node.getRightWeight());
6957
+ for (let cur = node; cur !== void 0; cur = cur.getParent()) {
6958
+ cur.setWeight(cur.leftWeight() + cur.size() + cur.rightWeight());
6819
6959
  }
6820
6960
  }
6821
- updateTreeWeight(node) {
6822
- while (node) {
6823
- this.updateWeight(node);
6824
- node = node.getParent();
6825
- }
6961
+ /**
6962
+ * `toTestString` returns a string containing the metadata of the node for
6963
+ * debugging purpose.
6964
+ */
6965
+ toTestString() {
6966
+ let s = "";
6967
+ traverseInOrder(this.root, (node) => {
6968
+ s += `[${node.getWeight()},${node.size()}]${node.getValue().toString()}`;
6969
+ });
6970
+ return s;
6826
6971
  }
6827
6972
  /**
6828
- * `splayNode` moves the given node to the root.
6973
+ * `indexOf` returns the logical (live-node) index of the given node, or -1
6974
+ * if the node is a tombstone.
6829
6975
  */
6830
- splayNode(node) {
6831
- if (!node) {
6832
- return;
6976
+ indexOf(node) {
6977
+ if (node.size() === 0) {
6978
+ return -1;
6833
6979
  }
6834
- for (; ; ) {
6835
- if (this.isLeftChild(node.getParent()) && this.isRightChild(node)) {
6836
- this.rotateLeft(node);
6837
- this.rotateRight(node);
6838
- } else if (this.isRightChild(node.getParent()) && this.isLeftChild(node)) {
6839
- this.rotateRight(node);
6840
- this.rotateLeft(node);
6841
- } else if (this.isLeftChild(node.getParent()) && this.isLeftChild(node)) {
6842
- this.rotateRight(node.getParent());
6843
- this.rotateRight(node);
6844
- } else if (this.isRightChild(node.getParent()) && this.isRightChild(node)) {
6845
- this.rotateLeft(node.getParent());
6846
- this.rotateLeft(node);
6847
- } else {
6848
- if (this.isLeftChild(node)) {
6849
- this.rotateRight(node);
6850
- } else if (this.isRightChild(node)) {
6851
- this.rotateLeft(node);
6852
- }
6853
- this.updateWeight(node);
6854
- return;
6980
+ let index = node.leftWeight();
6981
+ for (let cur = node; cur.getParent() !== void 0; cur = cur.getParent()) {
6982
+ if (cur === cur.getParent().getRight()) {
6983
+ index += cur.getParent().leftWeight() + cur.getParent().size();
6855
6984
  }
6856
6985
  }
6986
+ return index;
6857
6987
  }
6858
6988
  /**
6859
- * `delete` deletes target node of this tree.
6989
+ * `structuralIndexOf` returns the structural position of the node, counting
6990
+ * all nodes including tombstones.
6860
6991
  */
6861
- delete(node) {
6862
- this.splayNode(node);
6863
- const leftTree = new SplayTree(node.getLeft());
6864
- if (leftTree.root) {
6865
- leftTree.root.setParent();
6866
- }
6867
- const rightTree = new SplayTree(node.getRight());
6868
- if (rightTree.root) {
6869
- rightTree.root.setParent();
6992
+ structuralIndexOf(node) {
6993
+ let index = node.leftCount();
6994
+ for (let cur = node; cur.getParent() !== void 0; cur = cur.getParent()) {
6995
+ if (cur === cur.getParent().getRight()) {
6996
+ index += cur.getParent().leftCount() + 1;
6997
+ }
6870
6998
  }
6871
- if (leftTree.root) {
6872
- const rightmostNode = leftTree.getRightmost();
6873
- leftTree.splayNode(rightmostNode);
6874
- leftTree.root.setRight(rightTree.root);
6875
- if (rightTree.root) {
6876
- rightTree.root.setParent(leftTree.root);
6877
- }
6878
- this.root = leftTree.root;
6879
- } else {
6880
- this.root = rightTree.root;
6881
- }
6882
- node.unlink();
6883
- if (this.root) {
6884
- this.updateWeight(this.root);
6885
- }
6886
- }
6887
- /**
6888
- * `deleteRange` separates the range between given 2 boundaries from this Tree.
6889
- * This function separates the range to delete as a subtree
6890
- * by splaying outer boundary nodes.
6891
- * leftBoundary must exist because of 0-indexed initial dummy node of tree,
6892
- * but rightBoundary can be nil means range to delete includes the end of tree.
6893
- * Refer to the design document in https://github.com/yorkie-team/yorkie/tree/main/design
6894
- */
6895
- deleteRange(leftBoundary, rightBoundary) {
6896
- if (!rightBoundary) {
6897
- this.splayNode(leftBoundary);
6898
- this.cutOffRight(leftBoundary);
6899
- return;
6900
- }
6901
- this.splayNode(leftBoundary);
6902
- this.splayNode(rightBoundary);
6903
- if (rightBoundary.getLeft() != leftBoundary) {
6904
- this.rotateRight(leftBoundary);
6905
- }
6906
- this.cutOffRight(leftBoundary);
6907
- }
6908
- cutOffRight(root) {
6909
- const nodesToFreeWeight = [];
6910
- this.traversePostorder(root.getRight(), nodesToFreeWeight);
6911
- for (const node of nodesToFreeWeight) {
6912
- node.initWeight();
6913
- }
6914
- this.updateTreeWeight(root);
6915
- }
6916
- /**
6917
- * `toTestString` returns a string containing the meta data of the Node
6918
- * for debugging purpose.
6919
- */
6920
- toTestString() {
6921
- const metaString = [];
6922
- this.traverseInorder(this.root, metaString);
6923
- return metaString.map((n) => `[${n.getWeight()},${n.getLength()}]${n.getValue() || ""}`).join("");
6924
- }
6925
- /**
6926
- * `checkWeight` returns false when there is an incorrect weight node.
6927
- * for debugging purpose.
6928
- */
6929
- checkWeight() {
6930
- const nodes = [];
6931
- this.traverseInorder(this.root, nodes);
6932
- for (const node of nodes) {
6933
- if (node.getWeight() != node.getLength() + node.getLeftWeight() + node.getRightWeight()) {
6934
- return false;
6935
- }
6936
- }
6937
- return true;
6938
- }
6939
- getRightmost() {
6940
- let node = this.root;
6941
- while (node.hasRight()) {
6942
- node = node.getRight();
6943
- }
6944
- return node;
6945
- }
6946
- traverseInorder(node, stack) {
6947
- if (!node) {
6948
- return;
6949
- }
6950
- this.traverseInorder(node.getLeft(), stack);
6951
- stack.push(node);
6952
- this.traverseInorder(node.getRight(), stack);
6953
- }
6954
- traversePostorder(node, stack) {
6955
- if (!node) {
6956
- return;
6957
- }
6958
- this.traversePostorder(node.getLeft(), stack);
6959
- this.traversePostorder(node.getRight(), stack);
6960
- stack.push(node);
6961
- }
6962
- rotateLeft(pivot) {
6963
- const root = pivot.getParent();
6964
- if (root.hasParent()) {
6965
- if (root === root.getParent().getLeft()) {
6966
- root.getParent().setLeft(pivot);
6967
- } else {
6968
- root.getParent().setRight(pivot);
6969
- }
6970
- } else {
6971
- this.root = pivot;
6972
- }
6973
- pivot.setParent(root.getParent());
6974
- root.setRight(pivot.getLeft());
6975
- if (root.hasRight()) {
6976
- root.getRight().setParent(root);
6977
- }
6978
- pivot.setLeft(root);
6979
- pivot.getLeft().setParent(pivot);
6980
- this.updateWeight(root);
6981
- this.updateWeight(pivot);
6982
- }
6983
- rotateRight(pivot) {
6984
- const root = pivot.getParent();
6985
- if (root.hasParent()) {
6986
- if (root === root.getParent().getLeft()) {
6987
- root.getParent().setLeft(pivot);
6988
- } else {
6989
- root.getParent().setRight(pivot);
6990
- }
6991
- } else {
6992
- this.root = pivot;
6993
- }
6994
- pivot.setParent(root.getParent());
6995
- root.setLeft(pivot.getRight());
6996
- if (root.hasLeft()) {
6997
- root.getLeft().setParent(root);
6998
- }
6999
- pivot.setRight(root);
7000
- pivot.getRight().setParent(pivot);
7001
- this.updateWeight(root);
7002
- this.updateWeight(pivot);
7003
- }
7004
- isLeftChild(node) {
7005
- if (node && node.hasParent()) {
7006
- return node.getParent().getLeft() === node;
7007
- }
7008
- return false;
7009
- }
7010
- isRightChild(node) {
7011
- if (node && node.hasParent()) {
7012
- return node.getParent().getRight() === node;
7013
- }
7014
- return false;
6999
+ return index;
7015
7000
  }
7016
7001
  }
7017
7002
  const removeDecimal = (number) => number < 0 ? Math.ceil(number) : Math.floor(number);
@@ -7301,14 +7286,14 @@
7301
7286
  this.elem = elem;
7302
7287
  }
7303
7288
  }
7304
- class RGATreeListNode extends SplayNode {
7289
+ class RGATreeListNode {
7290
+ indexNode;
7305
7291
  _elementEntry;
7306
7292
  _createdAt;
7307
7293
  _removedAt;
7308
7294
  prev;
7309
7295
  next;
7310
- constructor(elem, createdAt) {
7311
- super(elem);
7296
+ constructor(createdAt) {
7312
7297
  this._createdAt = createdAt;
7313
7298
  }
7314
7299
  /**
@@ -7316,9 +7301,10 @@
7316
7301
  */
7317
7302
  static createWithElement(elem) {
7318
7303
  const entry = new ElementEntry(elem);
7319
- const node = new RGATreeListNode(elem, elem.getCreatedAt());
7320
- entry.positionNode = node;
7304
+ const node = new RGATreeListNode(elem.getCreatedAt());
7321
7305
  node._elementEntry = entry;
7306
+ entry.positionNode = node;
7307
+ node.indexNode = new TreeListNode(node);
7322
7308
  return node;
7323
7309
  }
7324
7310
  /**
@@ -7326,7 +7312,9 @@
7326
7312
  * (used for move).
7327
7313
  */
7328
7314
  static createBarePosition(createdAt) {
7329
- return new RGATreeListNode(void 0, createdAt);
7315
+ const node = new RGATreeListNode(createdAt);
7316
+ node.indexNode = new TreeListNode(node);
7317
+ return node;
7330
7318
  }
7331
7319
  /**
7332
7320
  * `createAfter` creates a new node with the given element after
@@ -7398,14 +7386,14 @@
7398
7386
  this.next = void 0;
7399
7387
  }
7400
7388
  /**
7401
- * `getLength` returns the length of this node.
7402
- * Dead nodes (no element) return 0, removed elements return 0.
7389
+ * `toString` returns a string representation of this node's value, used by
7390
+ * TreeList for debugging.
7403
7391
  */
7404
- getLength() {
7405
- if (!this._elementEntry || this.isRemoved()) {
7406
- return 0;
7392
+ toString() {
7393
+ if (!this._elementEntry) {
7394
+ return "";
7407
7395
  }
7408
- return 1;
7396
+ return this._elementEntry.elem.toJSON();
7409
7397
  }
7410
7398
  /**
7411
7399
  * `getPrev` returns a previous node.
@@ -7423,9 +7411,6 @@
7423
7411
  * `getValue` returns the element value.
7424
7412
  */
7425
7413
  getValue() {
7426
- if (!this._elementEntry) {
7427
- return this.value;
7428
- }
7429
7414
  return this._elementEntry.elem;
7430
7415
  }
7431
7416
  /**
@@ -7514,10 +7499,11 @@
7514
7499
  dummyValue.setRemovedAt(InitialTimeTicket);
7515
7500
  this.dummyHead = RGATreeListNode.createWithElement(dummyValue);
7516
7501
  this.last = this.dummyHead;
7517
- this.nodeMapByIndex = new SplayTree();
7502
+ this.nodeMapByIndex = new TreeList(
7503
+ this.dummyHead.indexNode
7504
+ );
7518
7505
  this.nodeMapByCreatedAt = /* @__PURE__ */ new Map();
7519
7506
  this.elementMapByCreatedAt = /* @__PURE__ */ new Map();
7520
- this.nodeMapByIndex.insert(this.dummyHead);
7521
7507
  this.nodeMapByCreatedAt.set(
7522
7508
  this.dummyHead.getCreatedAt().toIDString(),
7523
7509
  this.dummyHead
@@ -7550,7 +7536,7 @@
7550
7536
  this.last = node.getPrev();
7551
7537
  }
7552
7538
  node.release();
7553
- this.nodeMapByIndex.delete(node);
7539
+ this.nodeMapByIndex.delete(node.indexNode);
7554
7540
  this.nodeMapByCreatedAt.delete(node.getPositionCreatedAt().toIDString());
7555
7541
  }
7556
7542
  /**
@@ -7578,7 +7564,7 @@
7578
7564
  if (prevNode === this.last) {
7579
7565
  this.last = newNode;
7580
7566
  }
7581
- this.nodeMapByIndex.insertAfter(prevNode, newNode);
7567
+ this.nodeMapByIndex.insertAfter(prevNode.indexNode, newNode.indexNode);
7582
7568
  this.nodeMapByCreatedAt.set(value.getCreatedAt().toIDString(), newNode);
7583
7569
  this.elementMapByCreatedAt.set(
7584
7570
  value.getCreatedAt().toIDString(),
@@ -7605,7 +7591,7 @@
7605
7591
  if (prevNode === this.last) {
7606
7592
  this.last = newNode;
7607
7593
  }
7608
- this.nodeMapByIndex.insertAfter(prevNode, newNode);
7594
+ this.nodeMapByIndex.insertAfter(prevNode.indexNode, newNode.indexNode);
7609
7595
  this.nodeMapByCreatedAt.set(executedAt.toIDString(), newNode);
7610
7596
  return newNode;
7611
7597
  }
@@ -7634,19 +7620,19 @@
7634
7620
  }
7635
7621
  const deadPosNode = this.insertPositionAfter(prevCreatedAt, executedAt);
7636
7622
  deadPosNode.setRemovedAt(executedAt);
7637
- this.nodeMapByIndex.splayNode(deadPosNode);
7623
+ this.nodeMapByIndex.updateWeight(deadPosNode.indexNode);
7638
7624
  return deadPosNode;
7639
7625
  }
7640
7626
  const newPosNode = this.insertPositionAfter(prevCreatedAt, executedAt);
7641
7627
  const oldPosNode = entry.positionNode;
7642
7628
  oldPosNode.setElementEntry(void 0);
7643
7629
  oldPosNode.setRemovedAt(executedAt);
7644
- this.nodeMapByIndex.splayNode(oldPosNode);
7630
+ this.nodeMapByIndex.updateWeight(oldPosNode.indexNode);
7645
7631
  newPosNode.setElementEntry(entry);
7646
7632
  entry.positionNode = newPosNode;
7647
7633
  entry.posMovedAt = executedAt;
7648
7634
  entry.elem.setMovedAt(executedAt);
7649
- this.nodeMapByIndex.splayNode(newPosNode);
7635
+ this.nodeMapByIndex.updateWeight(newPosNode.indexNode);
7650
7636
  return oldPosNode;
7651
7637
  }
7652
7638
  /**
@@ -7677,9 +7663,9 @@
7677
7663
  if (!node) {
7678
7664
  return;
7679
7665
  }
7680
- return String(this.nodeMapByIndex.indexOf(node));
7666
+ return String(this.nodeMapByIndex.indexOf(node.indexNode));
7681
7667
  }
7682
- return String(this.nodeMapByIndex.indexOf(entry.positionNode));
7668
+ return String(this.nodeMapByIndex.indexOf(entry.positionNode.indexNode));
7683
7669
  }
7684
7670
  /**
7685
7671
  * `purge` physically purges the given child. Handles both dead
@@ -7712,8 +7698,7 @@
7712
7698
  if (idx >= this.length) {
7713
7699
  return;
7714
7700
  }
7715
- const node = this.nodeMapByIndex.findForArray(idx);
7716
- return node;
7701
+ return this.nodeMapByIndex.find(idx).getValue();
7717
7702
  }
7718
7703
  /**
7719
7704
  * `findPrevCreatedAt` returns the position node's createdAt of the
@@ -7761,7 +7746,7 @@
7761
7746
  const node = entry.positionNode;
7762
7747
  const alreadyRemoved = node.isRemoved();
7763
7748
  if (entry.elem.remove(editedAt) && !alreadyRemoved) {
7764
- this.nodeMapByIndex.splayNode(node);
7749
+ this.nodeMapByIndex.updateWeight(node.indexNode);
7765
7750
  }
7766
7751
  return entry.elem;
7767
7752
  }
@@ -7787,7 +7772,7 @@
7787
7772
  return;
7788
7773
  }
7789
7774
  if (node.remove(editedAt)) {
7790
- this.nodeMapByIndex.splayNode(node);
7775
+ this.nodeMapByIndex.updateWeight(node.indexNode);
7791
7776
  }
7792
7777
  return node.getValue();
7793
7778
  }
@@ -7798,10 +7783,15 @@
7798
7783
  return this.dummyHead.getValue();
7799
7784
  }
7800
7785
  /**
7801
- * `getLast` returns the value of last elements.
7786
+ * `getLast` returns the value of last elements. Skips bare position nodes
7787
+ * (created by moveAfter/addDeadPosition) that have no element.
7802
7788
  */
7803
7789
  getLast() {
7804
- return this.last.getValue();
7790
+ let node = this.last;
7791
+ while (!node.getElementEntry() && node !== this.dummyHead) {
7792
+ node = node.getPrev();
7793
+ }
7794
+ return node.getValue();
7805
7795
  }
7806
7796
  /**
7807
7797
  * `getLastCreatedAt` returns the position node's createdAt of the
@@ -7836,7 +7826,7 @@
7836
7826
  const prevNode = this.last;
7837
7827
  RGATreeListNode.insertNodeAfter(prevNode, node);
7838
7828
  this.last = node;
7839
- this.nodeMapByIndex.insertAfter(prevNode, node);
7829
+ this.nodeMapByIndex.insertAfter(prevNode.indexNode, node.indexNode);
7840
7830
  this.nodeMapByCreatedAt.set(posCreatedAt.toIDString(), node);
7841
7831
  }
7842
7832
  /**
@@ -7852,7 +7842,7 @@
7852
7842
  const prevNode = this.last;
7853
7843
  RGATreeListNode.insertNodeAfter(prevNode, node);
7854
7844
  this.last = node;
7855
- this.nodeMapByIndex.insertAfter(prevNode, node);
7845
+ this.nodeMapByIndex.insertAfter(prevNode.indexNode, node.indexNode);
7856
7846
  this.nodeMapByCreatedAt.set(posCreatedAt.toIDString(), node);
7857
7847
  this.elementMapByCreatedAt.set(elem.getCreatedAt().toIDString(), entry);
7858
7848
  }
@@ -8018,9 +8008,10 @@
8018
8008
  */
8019
8009
  *[Symbol.iterator]() {
8020
8010
  for (const node of this.elements) {
8021
- if (node.getElementEntry() && !node.isRemoved()) {
8022
- yield node.getValue();
8011
+ if (node.isRemoved()) {
8012
+ continue;
8023
8013
  }
8014
+ yield node.getValue();
8024
8015
  }
8025
8016
  }
8026
8017
  /**
@@ -8445,127 +8436,570 @@
8445
8436
  return `${this.getParentCreatedAt().toTestString()}.SET.${this.key}=${this.value.toSortedJSON()}`;
8446
8437
  }
8447
8438
  /**
8448
- * `getKey` returns the key of this operation.
8439
+ * `getKey` returns the key of this operation.
8440
+ */
8441
+ getKey() {
8442
+ return this.key;
8443
+ }
8444
+ /**
8445
+ * `getValue` returns the value of this operation.
8446
+ */
8447
+ getValue() {
8448
+ return this.value;
8449
+ }
8450
+ }
8451
+ class MoveOperation extends Operation {
8452
+ prevCreatedAt;
8453
+ createdAt;
8454
+ constructor(parentCreatedAt, prevCreatedAt, createdAt, executedAt) {
8455
+ super(parentCreatedAt, executedAt);
8456
+ this.prevCreatedAt = prevCreatedAt;
8457
+ this.createdAt = createdAt;
8458
+ }
8459
+ /**
8460
+ * `create` creates a new instance of MoveOperation.
8461
+ */
8462
+ static create(parentCreatedAt, prevCreatedAt, createdAt, executedAt) {
8463
+ return new MoveOperation(
8464
+ parentCreatedAt,
8465
+ prevCreatedAt,
8466
+ createdAt,
8467
+ executedAt
8468
+ );
8469
+ }
8470
+ /**
8471
+ * `execute` executes this operation on the given `CRDTRoot`.
8472
+ */
8473
+ execute(root) {
8474
+ const parentObject = root.findByCreatedAt(this.getParentCreatedAt());
8475
+ if (!parentObject) {
8476
+ throw new YorkieError(
8477
+ Code.ErrInvalidArgument,
8478
+ `fail to find ${this.getParentCreatedAt()}`
8479
+ );
8480
+ }
8481
+ if (!(parentObject instanceof CRDTArray)) {
8482
+ throw new YorkieError(
8483
+ Code.ErrInvalidArgument,
8484
+ `fail to execute, only array can execute move`
8485
+ );
8486
+ }
8487
+ const array = parentObject;
8488
+ const reverseOp = this.toReverseOperation(array);
8489
+ const previousIndex = Number(array.subPathOf(this.createdAt));
8490
+ const deadNode = array.moveAfter(
8491
+ this.prevCreatedAt,
8492
+ this.createdAt,
8493
+ this.getExecutedAt()
8494
+ );
8495
+ if (deadNode) {
8496
+ root.registerGCPair({
8497
+ parent: array.getRGATreeList(),
8498
+ child: deadNode
8499
+ });
8500
+ }
8501
+ const index = Number(array.subPathOf(this.createdAt));
8502
+ return {
8503
+ opInfos: [
8504
+ {
8505
+ type: "move",
8506
+ path: root.createPath(this.getParentCreatedAt()),
8507
+ index,
8508
+ previousIndex
8509
+ }
8510
+ ],
8511
+ reverseOp
8512
+ };
8513
+ }
8514
+ toReverseOperation(array) {
8515
+ const preservePrevCreatedAt = array.getPrevCreatedAt(this.createdAt);
8516
+ return MoveOperation.create(
8517
+ this.getParentCreatedAt(),
8518
+ preservePrevCreatedAt,
8519
+ this.createdAt
8520
+ );
8521
+ }
8522
+ /**
8523
+ * `getEffectedCreatedAt` returns the creation time of the
8524
+ * effected element.
8525
+ */
8526
+ getEffectedCreatedAt() {
8527
+ return this.createdAt;
8528
+ }
8529
+ /**
8530
+ * `toTestString` returns a string containing the meta data.
8531
+ */
8532
+ toTestString() {
8533
+ return `${this.getParentCreatedAt().toTestString()}.MOVE`;
8534
+ }
8535
+ /**
8536
+ * `getPrevCreatedAt` returns the creation time of previous
8537
+ * element.
8538
+ */
8539
+ getPrevCreatedAt() {
8540
+ return this.prevCreatedAt;
8541
+ }
8542
+ /**
8543
+ * `getCreatedAt` returns the creation time of the target element.
8544
+ */
8545
+ getCreatedAt() {
8546
+ return this.createdAt;
8547
+ }
8548
+ /**
8549
+ * `setPrevCreatedAt` sets the creation time of the previous
8550
+ * element.
8551
+ */
8552
+ setPrevCreatedAt(createdAt) {
8553
+ this.prevCreatedAt = createdAt;
8554
+ }
8555
+ /**
8556
+ * `setCreatedAt` sets the creation time of the target element.
8557
+ */
8558
+ setCreatedAt(createdAt) {
8559
+ this.createdAt = createdAt;
8560
+ }
8561
+ }
8562
+ class SplayNode {
8563
+ value;
8564
+ left;
8565
+ right;
8566
+ parent;
8567
+ weight;
8568
+ constructor(value) {
8569
+ this.value = value;
8570
+ this.initWeight();
8571
+ }
8572
+ /**
8573
+ * `getNodeString` returns a string of weight and value of this node.
8574
+ */
8575
+ getNodeString() {
8576
+ return `${this.weight}${this.value}`;
8577
+ }
8578
+ /**
8579
+ * `getValue` returns value of this node.
8580
+ */
8581
+ getValue() {
8582
+ return this.value;
8583
+ }
8584
+ /**
8585
+ * `getLeftWeight` returns left weight of this node.
8586
+ */
8587
+ getLeftWeight() {
8588
+ return !this.hasLeft() ? 0 : this.left.getWeight();
8589
+ }
8590
+ /**
8591
+ * `getRightWeight` returns right weight of this node.
8592
+ */
8593
+ getRightWeight() {
8594
+ return !this.hasRight() ? 0 : this.right.getWeight();
8595
+ }
8596
+ /**
8597
+ * `getWeight` returns weight of this node.
8598
+ */
8599
+ getWeight() {
8600
+ return this.weight;
8601
+ }
8602
+ /**
8603
+ * `getLeft` returns a left node.
8604
+ */
8605
+ getLeft() {
8606
+ return this.left;
8607
+ }
8608
+ /**
8609
+ * `getRight` returns a right node.
8610
+ */
8611
+ getRight() {
8612
+ return this.right;
8613
+ }
8614
+ /**
8615
+ * `getParent` returns parent of this node.
8616
+ */
8617
+ getParent() {
8618
+ return this.parent;
8619
+ }
8620
+ /**
8621
+ * `hasLeft` check if the left node exists
8622
+ */
8623
+ hasLeft() {
8624
+ return !!this.left;
8625
+ }
8626
+ /**
8627
+ * `hasRight` check if the right node exists
8628
+ */
8629
+ hasRight() {
8630
+ return !!this.right;
8631
+ }
8632
+ /**
8633
+ * `hasParent` check if the parent node exists
8634
+ */
8635
+ hasParent() {
8636
+ return !!this.parent;
8637
+ }
8638
+ /**
8639
+ * `setLeft` sets a left node.
8640
+ */
8641
+ setLeft(left) {
8642
+ this.left = left;
8643
+ }
8644
+ /**
8645
+ * `setRight` sets a right node.
8646
+ */
8647
+ setRight(right) {
8648
+ this.right = right;
8649
+ }
8650
+ /**
8651
+ * `setParent` sets a parent node.
8652
+ */
8653
+ setParent(parent) {
8654
+ this.parent = parent;
8655
+ }
8656
+ /**
8657
+ * `unlink` unlink parent, right and left node.
8658
+ */
8659
+ unlink() {
8660
+ this.parent = void 0;
8661
+ this.right = void 0;
8662
+ this.left = void 0;
8663
+ }
8664
+ /**
8665
+ * `hasLinks` checks if parent, right and left node exists.
8666
+ */
8667
+ hasLinks() {
8668
+ return this.hasParent() || this.hasLeft() || this.hasRight();
8669
+ }
8670
+ /**
8671
+ * `increaseWeight` increases weight.
8672
+ */
8673
+ increaseWeight(weight) {
8674
+ this.weight += weight;
8675
+ }
8676
+ /**
8677
+ * `initWeight` sets initial weight of this node.
8678
+ */
8679
+ initWeight() {
8680
+ this.weight = this.getLength();
8681
+ }
8682
+ }
8683
+ class SplayTree {
8684
+ root;
8685
+ constructor(root) {
8686
+ this.root = root;
8687
+ }
8688
+ /**
8689
+ * `length` returns the size of this tree.
8690
+ */
8691
+ get length() {
8692
+ return this.root ? this.root.getWeight() : 0;
8693
+ }
8694
+ /**
8695
+ * `findForText` returns the Node and offset of the given position (cursor).
8696
+ * Used for Text where cursor placed between characters.
8697
+ */
8698
+ findForText(pos) {
8699
+ if (!this.root || pos < 0) {
8700
+ return [void 0, 0];
8701
+ }
8702
+ let node = this.root;
8703
+ for (; ; ) {
8704
+ if (node.hasLeft() && pos <= node.getLeftWeight()) {
8705
+ node = node.getLeft();
8706
+ } else if (node.hasRight() && node.getLeftWeight() + node.getLength() < pos) {
8707
+ pos -= node.getLeftWeight() + node.getLength();
8708
+ node = node.getRight();
8709
+ } else {
8710
+ pos -= node.getLeftWeight();
8711
+ break;
8712
+ }
8713
+ }
8714
+ if (pos > node.getLength()) {
8715
+ throw new YorkieError(
8716
+ Code.ErrInvalidArgument,
8717
+ `out of index range: pos: ${pos} > node.length: ${node.getLength()}`
8718
+ );
8719
+ }
8720
+ this.splayNode(node);
8721
+ return [node, pos];
8722
+ }
8723
+ /**
8724
+ * `findForArray` returns the Node of the given position (index).
8725
+ * Used for Array where index points to the element.
8726
+ */
8727
+ findForArray(idx) {
8728
+ if (!this.root) {
8729
+ return void 0;
8730
+ }
8731
+ if (idx < 0 || idx >= this.length) {
8732
+ throw new YorkieError(
8733
+ Code.ErrInvalidArgument,
8734
+ `out of index range: idx: ${idx}, length: ${this.length}`
8735
+ );
8736
+ }
8737
+ let node = this.root;
8738
+ for (; ; ) {
8739
+ if (node.hasLeft() && idx < node.getLeftWeight()) {
8740
+ node = node.getLeft();
8741
+ } else if (node.hasRight() && node.getLeftWeight() + node.getLength() <= idx) {
8742
+ idx -= node.getLeftWeight() + node.getLength();
8743
+ node = node.getRight();
8744
+ } else {
8745
+ break;
8746
+ }
8747
+ }
8748
+ this.splayNode(node);
8749
+ return node;
8750
+ }
8751
+ /**
8752
+ * Find the index of the given node in BST.
8753
+ *
8754
+ * @param node - the given node
8755
+ * @returns the index of given node
8756
+ */
8757
+ indexOf(node) {
8758
+ if (!node || node !== this.root && !node.hasLinks()) {
8759
+ return -1;
8760
+ }
8761
+ this.splayNode(node);
8762
+ return this.root.getLeftWeight();
8763
+ }
8764
+ /**
8765
+ * `getRoot` returns root of this tree.
8766
+ */
8767
+ getRoot() {
8768
+ return this.root;
8769
+ }
8770
+ /**
8771
+ * `insert` inserts the node at the last.
8772
+ */
8773
+ insert(newNode) {
8774
+ return this.insertAfter(this.root, newNode);
8775
+ }
8776
+ /**
8777
+ * `insertAfter` inserts the node after the given previous node.
8778
+ */
8779
+ insertAfter(target, newNode) {
8780
+ if (!target) {
8781
+ this.root = newNode;
8782
+ return newNode;
8783
+ }
8784
+ this.splayNode(target);
8785
+ this.root = newNode;
8786
+ newNode.setRight(target.getRight());
8787
+ if (target.hasRight()) {
8788
+ target.getRight().setParent(newNode);
8789
+ }
8790
+ newNode.setLeft(target);
8791
+ target.setParent(newNode);
8792
+ target.setRight();
8793
+ this.updateWeight(target);
8794
+ this.updateWeight(newNode);
8795
+ return newNode;
8796
+ }
8797
+ /**
8798
+ * `updateWeight` recalculates the weight of this node with the value and children.
8799
+ */
8800
+ updateWeight(node) {
8801
+ node.initWeight();
8802
+ if (node.hasLeft()) {
8803
+ node.increaseWeight(node.getLeftWeight());
8804
+ }
8805
+ if (node.hasRight()) {
8806
+ node.increaseWeight(node.getRightWeight());
8807
+ }
8808
+ }
8809
+ updateTreeWeight(node) {
8810
+ while (node) {
8811
+ this.updateWeight(node);
8812
+ node = node.getParent();
8813
+ }
8814
+ }
8815
+ /**
8816
+ * `splayNode` moves the given node to the root.
8817
+ */
8818
+ splayNode(node) {
8819
+ if (!node) {
8820
+ return;
8821
+ }
8822
+ for (; ; ) {
8823
+ if (this.isLeftChild(node.getParent()) && this.isRightChild(node)) {
8824
+ this.rotateLeft(node);
8825
+ this.rotateRight(node);
8826
+ } else if (this.isRightChild(node.getParent()) && this.isLeftChild(node)) {
8827
+ this.rotateRight(node);
8828
+ this.rotateLeft(node);
8829
+ } else if (this.isLeftChild(node.getParent()) && this.isLeftChild(node)) {
8830
+ this.rotateRight(node.getParent());
8831
+ this.rotateRight(node);
8832
+ } else if (this.isRightChild(node.getParent()) && this.isRightChild(node)) {
8833
+ this.rotateLeft(node.getParent());
8834
+ this.rotateLeft(node);
8835
+ } else {
8836
+ if (this.isLeftChild(node)) {
8837
+ this.rotateRight(node);
8838
+ } else if (this.isRightChild(node)) {
8839
+ this.rotateLeft(node);
8840
+ }
8841
+ this.updateWeight(node);
8842
+ return;
8843
+ }
8844
+ }
8845
+ }
8846
+ /**
8847
+ * `delete` deletes target node of this tree.
8449
8848
  */
8450
- getKey() {
8451
- return this.key;
8849
+ delete(node) {
8850
+ this.splayNode(node);
8851
+ const leftTree = new SplayTree(node.getLeft());
8852
+ if (leftTree.root) {
8853
+ leftTree.root.setParent();
8854
+ }
8855
+ const rightTree = new SplayTree(node.getRight());
8856
+ if (rightTree.root) {
8857
+ rightTree.root.setParent();
8858
+ }
8859
+ if (leftTree.root) {
8860
+ const rightmostNode = leftTree.getRightmost();
8861
+ leftTree.splayNode(rightmostNode);
8862
+ leftTree.root.setRight(rightTree.root);
8863
+ if (rightTree.root) {
8864
+ rightTree.root.setParent(leftTree.root);
8865
+ }
8866
+ this.root = leftTree.root;
8867
+ } else {
8868
+ this.root = rightTree.root;
8869
+ }
8870
+ node.unlink();
8871
+ if (this.root) {
8872
+ this.updateWeight(this.root);
8873
+ }
8452
8874
  }
8453
8875
  /**
8454
- * `getValue` returns the value of this operation.
8876
+ * `deleteRange` separates the range between given 2 boundaries from this Tree.
8877
+ * This function separates the range to delete as a subtree
8878
+ * by splaying outer boundary nodes.
8879
+ * leftBoundary must exist because of 0-indexed initial dummy node of tree,
8880
+ * but rightBoundary can be nil means range to delete includes the end of tree.
8881
+ * Refer to the design document in https://github.com/yorkie-team/yorkie/tree/main/design
8455
8882
  */
8456
- getValue() {
8457
- return this.value;
8883
+ deleteRange(leftBoundary, rightBoundary) {
8884
+ if (!rightBoundary) {
8885
+ this.splayNode(leftBoundary);
8886
+ this.cutOffRight(leftBoundary);
8887
+ return;
8888
+ }
8889
+ this.splayNode(leftBoundary);
8890
+ this.splayNode(rightBoundary);
8891
+ if (rightBoundary.getLeft() != leftBoundary) {
8892
+ this.rotateRight(leftBoundary);
8893
+ }
8894
+ this.cutOffRight(leftBoundary);
8458
8895
  }
8459
- }
8460
- class MoveOperation extends Operation {
8461
- prevCreatedAt;
8462
- createdAt;
8463
- constructor(parentCreatedAt, prevCreatedAt, createdAt, executedAt) {
8464
- super(parentCreatedAt, executedAt);
8465
- this.prevCreatedAt = prevCreatedAt;
8466
- this.createdAt = createdAt;
8896
+ cutOffRight(root) {
8897
+ const nodesToFreeWeight = [];
8898
+ this.traversePostorder(root.getRight(), nodesToFreeWeight);
8899
+ for (const node of nodesToFreeWeight) {
8900
+ node.initWeight();
8901
+ }
8902
+ this.updateTreeWeight(root);
8467
8903
  }
8468
8904
  /**
8469
- * `create` creates a new instance of MoveOperation.
8905
+ * `toTestString` returns a string containing the meta data of the Node
8906
+ * for debugging purpose.
8470
8907
  */
8471
- static create(parentCreatedAt, prevCreatedAt, createdAt, executedAt) {
8472
- return new MoveOperation(
8473
- parentCreatedAt,
8474
- prevCreatedAt,
8475
- createdAt,
8476
- executedAt
8477
- );
8908
+ toTestString() {
8909
+ const metaString = [];
8910
+ this.traverseInorder(this.root, metaString);
8911
+ return metaString.map((n) => `[${n.getWeight()},${n.getLength()}]${n.getValue() || ""}`).join("");
8478
8912
  }
8479
8913
  /**
8480
- * `execute` executes this operation on the given `CRDTRoot`.
8914
+ * `checkWeight` returns false when there is an incorrect weight node.
8915
+ * for debugging purpose.
8481
8916
  */
8482
- execute(root) {
8483
- const parentObject = root.findByCreatedAt(this.getParentCreatedAt());
8484
- if (!parentObject) {
8485
- throw new YorkieError(
8486
- Code.ErrInvalidArgument,
8487
- `fail to find ${this.getParentCreatedAt()}`
8488
- );
8489
- }
8490
- if (!(parentObject instanceof CRDTArray)) {
8491
- throw new YorkieError(
8492
- Code.ErrInvalidArgument,
8493
- `fail to execute, only array can execute move`
8494
- );
8495
- }
8496
- const array = parentObject;
8497
- const reverseOp = this.toReverseOperation(array);
8498
- const previousIndex = Number(array.subPathOf(this.createdAt));
8499
- const deadNode = array.moveAfter(
8500
- this.prevCreatedAt,
8501
- this.createdAt,
8502
- this.getExecutedAt()
8503
- );
8504
- if (deadNode) {
8505
- root.registerGCPair({
8506
- parent: array.getRGATreeList(),
8507
- child: deadNode
8508
- });
8917
+ checkWeight() {
8918
+ const nodes = [];
8919
+ this.traverseInorder(this.root, nodes);
8920
+ for (const node of nodes) {
8921
+ if (node.getWeight() != node.getLength() + node.getLeftWeight() + node.getRightWeight()) {
8922
+ return false;
8923
+ }
8509
8924
  }
8510
- const index = Number(array.subPathOf(this.createdAt));
8511
- return {
8512
- opInfos: [
8513
- {
8514
- type: "move",
8515
- path: root.createPath(this.getParentCreatedAt()),
8516
- index,
8517
- previousIndex
8518
- }
8519
- ],
8520
- reverseOp
8521
- };
8925
+ return true;
8522
8926
  }
8523
- toReverseOperation(array) {
8524
- const preservePrevCreatedAt = array.getPrevCreatedAt(this.createdAt);
8525
- return MoveOperation.create(
8526
- this.getParentCreatedAt(),
8527
- preservePrevCreatedAt,
8528
- this.createdAt
8529
- );
8927
+ getRightmost() {
8928
+ let node = this.root;
8929
+ while (node.hasRight()) {
8930
+ node = node.getRight();
8931
+ }
8932
+ return node;
8530
8933
  }
8531
- /**
8532
- * `getEffectedCreatedAt` returns the creation time of the
8533
- * effected element.
8534
- */
8535
- getEffectedCreatedAt() {
8536
- return this.createdAt;
8934
+ traverseInorder(node, stack) {
8935
+ if (!node) {
8936
+ return;
8937
+ }
8938
+ this.traverseInorder(node.getLeft(), stack);
8939
+ stack.push(node);
8940
+ this.traverseInorder(node.getRight(), stack);
8537
8941
  }
8538
- /**
8539
- * `toTestString` returns a string containing the meta data.
8540
- */
8541
- toTestString() {
8542
- return `${this.getParentCreatedAt().toTestString()}.MOVE`;
8942
+ traversePostorder(node, stack) {
8943
+ if (!node) {
8944
+ return;
8945
+ }
8946
+ this.traversePostorder(node.getLeft(), stack);
8947
+ this.traversePostorder(node.getRight(), stack);
8948
+ stack.push(node);
8543
8949
  }
8544
- /**
8545
- * `getPrevCreatedAt` returns the creation time of previous
8546
- * element.
8547
- */
8548
- getPrevCreatedAt() {
8549
- return this.prevCreatedAt;
8950
+ rotateLeft(pivot) {
8951
+ const root = pivot.getParent();
8952
+ if (root.hasParent()) {
8953
+ if (root === root.getParent().getLeft()) {
8954
+ root.getParent().setLeft(pivot);
8955
+ } else {
8956
+ root.getParent().setRight(pivot);
8957
+ }
8958
+ } else {
8959
+ this.root = pivot;
8960
+ }
8961
+ pivot.setParent(root.getParent());
8962
+ root.setRight(pivot.getLeft());
8963
+ if (root.hasRight()) {
8964
+ root.getRight().setParent(root);
8965
+ }
8966
+ pivot.setLeft(root);
8967
+ pivot.getLeft().setParent(pivot);
8968
+ this.updateWeight(root);
8969
+ this.updateWeight(pivot);
8550
8970
  }
8551
- /**
8552
- * `getCreatedAt` returns the creation time of the target element.
8553
- */
8554
- getCreatedAt() {
8555
- return this.createdAt;
8971
+ rotateRight(pivot) {
8972
+ const root = pivot.getParent();
8973
+ if (root.hasParent()) {
8974
+ if (root === root.getParent().getLeft()) {
8975
+ root.getParent().setLeft(pivot);
8976
+ } else {
8977
+ root.getParent().setRight(pivot);
8978
+ }
8979
+ } else {
8980
+ this.root = pivot;
8981
+ }
8982
+ pivot.setParent(root.getParent());
8983
+ root.setLeft(pivot.getRight());
8984
+ if (root.hasLeft()) {
8985
+ root.getLeft().setParent(root);
8986
+ }
8987
+ pivot.setRight(root);
8988
+ pivot.getRight().setParent(pivot);
8989
+ this.updateWeight(root);
8990
+ this.updateWeight(pivot);
8556
8991
  }
8557
- /**
8558
- * `setPrevCreatedAt` sets the creation time of the previous
8559
- * element.
8560
- */
8561
- setPrevCreatedAt(createdAt) {
8562
- this.prevCreatedAt = createdAt;
8992
+ isLeftChild(node) {
8993
+ if (node && node.hasParent()) {
8994
+ return node.getParent().getLeft() === node;
8995
+ }
8996
+ return false;
8563
8997
  }
8564
- /**
8565
- * `setCreatedAt` sets the creation time of the target element.
8566
- */
8567
- setCreatedAt(createdAt) {
8568
- this.createdAt = createdAt;
8998
+ isRightChild(node) {
8999
+ if (node && node.hasParent()) {
9000
+ return node.getParent().getRight() === node;
9001
+ }
9002
+ return false;
8569
9003
  }
8570
9004
  }
8571
9005
  const DefaultComparator = (a, b) => {
@@ -8583,10 +9017,10 @@
8583
9017
  left;
8584
9018
  right;
8585
9019
  isRed;
8586
- constructor(key, value, isRed) {
9020
+ constructor(key, value, isRed2) {
8587
9021
  this.key = key;
8588
9022
  this.value = value;
8589
- this.isRed = isRed;
9023
+ this.isRed = isRed2;
8590
9024
  }
8591
9025
  }
8592
9026
  class SortedMapIterator {
@@ -13770,6 +14204,26 @@
13770
14204
  newID.versionVector.set(this.actor, lamport);
13771
14205
  return newID;
13772
14206
  }
14207
+ /**
14208
+ * `syncLamport` advances the lamport clock against the given ID without
14209
+ * merging its version vector into the receiver's. It is the counterpart
14210
+ * of `syncClocks` for attachments that have opted out of GC participation
14211
+ * (see docs/design/disable-gc-on-attach.md in the server repo): the
14212
+ * receiver does not need other actors' entries in its VV because it
14213
+ * never produces or consumes tombstones, and dropping them keeps each
14214
+ * subsequent local Change's VV at O(1) instead of O(num_actors).
14215
+ * Lamport must still advance so that TimeTickets produced locally
14216
+ * remain ordered against remote operations.
14217
+ */
14218
+ syncLamport(other) {
14219
+ if (!other.hasClocks()) {
14220
+ return this;
14221
+ }
14222
+ const lamport = other.lamport > this.lamport ? other.lamport + 1n : this.lamport + 1n;
14223
+ const vector = this.versionVector.deepcopy();
14224
+ vector.set(this.actor, lamport);
14225
+ return new ChangeID(this.clientSeq, lamport, this.actor, vector);
14226
+ }
13773
14227
  /**
13774
14228
  * `setClocks` sets the given clocks to this ID. This is used when the snapshot
13775
14229
  * is given from the server.
@@ -19646,6 +20100,12 @@
19646
20100
  onlineClients;
19647
20101
  eventStream;
19648
20102
  eventStreamObserver;
20103
+ // `disableGC`, when true, declares that this document does not produce or
20104
+ // consume tombstones (see disable-gc-on-attach in the server repo). It is
20105
+ // set by the client on Attach and consumed by applyChange to skip merging
20106
+ // remote actors' version vectors into changeID, keeping each subsequent
20107
+ // local Change's VV at O(1) for high-fan-out Counter workloads.
20108
+ disableGC;
19649
20109
  /**
19650
20110
  * `history` is exposed to the user to manage undo/redo operations.
19651
20111
  */
@@ -19659,6 +20119,7 @@
19659
20119
  this.changeID = InitialChangeID;
19660
20120
  this.checkpoint = InitialCheckpoint;
19661
20121
  this.localChanges = [];
20122
+ this.disableGC = false;
19662
20123
  this.root = CRDTRoot.create();
19663
20124
  this.presences = /* @__PURE__ */ new Map();
19664
20125
  this.onlineClients = /* @__PURE__ */ new Set();
@@ -20067,6 +20528,14 @@
20067
20528
  }
20068
20529
  this.changeID = this.changeID.setActor(actorID);
20069
20530
  }
20531
+ /**
20532
+ * `setDisableGC` records whether this document participates in GC. The
20533
+ * client calls this on attach so subsequent applyChange runs use the
20534
+ * lamport-only sync path.
20535
+ */
20536
+ setDisableGC(disableGC) {
20537
+ this.disableGC = disableGC;
20538
+ }
20070
20539
  /**
20071
20540
  * `isEnableDevtools` returns whether devtools is enabled or not.
20072
20541
  */
@@ -20305,7 +20774,7 @@
20305
20774
  );
20306
20775
  }
20307
20776
  }
20308
- this.changeID = this.changeID.syncClocks(change.getID());
20777
+ this.changeID = this.disableGC ? this.changeID.syncLamport(change.getID()) : this.changeID.syncClocks(change.getID());
20309
20778
  if (opInfos.length) {
20310
20779
  const rawChange = this.isEnableDevtools() ? change.toStruct() : void 0;
20311
20780
  events.push(
@@ -20988,7 +21457,7 @@
20988
21457
  };
20989
21458
  }
20990
21459
  const name = "@yorkie-js/sdk";
20991
- const version = "0.7.10";
21460
+ const version = "0.7.11";
20992
21461
  const pkg = {
20993
21462
  name,
20994
21463
  version
@@ -21549,6 +22018,7 @@
21549
22018
  doc.setSchemaRules(converter.fromSchemaRules(res.schemaRules));
21550
22019
  }
21551
22020
  const pack = converter.fromChangePack(res.changePack);
22021
+ doc.setDisableGC(opts.disableGC ?? false);
21552
22022
  doc.applyChangePack(pack);
21553
22023
  if (doc.getStatus() === DocStatus.Removed) {
21554
22024
  return doc;