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