reptree 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -124,7 +124,7 @@ declare class RepTree {
124
124
  private static NULL_VERTEX_ID;
125
125
  private static DEFAULT_MAX_DEPTH;
126
126
  readonly peerId: string;
127
- readonly rootVertexId: string;
127
+ private rootVertexId;
128
128
  private lamportClock;
129
129
  private state;
130
130
  private moveOps;
@@ -139,15 +139,16 @@ declare class RepTree {
139
139
  private opAppliedCallbacks;
140
140
  private maxDepth;
141
141
  private stateVector;
142
+ private _stateVectorEnabled;
142
143
  /**
143
- * @param peerId - The peer ID of the current client
144
- * @param ops - The operations to replicate an existing tree, if null - a new tree will be created
144
+ * @param peerId - The peer ID of the current client. Should be unique across all peers.
145
+ * @param ops - The operations to replicate an existing tree, if not provided - an empty tree will be created without a root vertex
145
146
  */
146
- constructor(peerId: string, ops?: ReadonlyArray<VertexOperation> | null);
147
+ constructor(peerId: string, ops?: ReadonlyArray<VertexOperation>);
148
+ get root(): Vertex | undefined;
147
149
  getMoveOps(): ReadonlyArray<MoveVertex>;
148
150
  getAllOps(): ReadonlyArray<VertexOperation>;
149
151
  getVertex(vertexId: string): Vertex | undefined;
150
- get rootVertex(): Vertex;
151
152
  getAllVertices(): ReadonlyArray<Vertex>;
152
153
  getParent(vertexId: string): Vertex | undefined;
153
154
  getChildren(vertexId: string): Vertex[];
@@ -157,6 +158,7 @@ declare class RepTree {
157
158
  getVertexProperties(vertexId: string): Readonly<TreeVertexProperty[]>;
158
159
  popLocalOps(): VertexOperation[];
159
160
  setMaxDepth(maxDepth: number): void;
161
+ createRoot(): Vertex;
160
162
  newVertex(parentId: string, props?: Record<string, VertexPropertyType> | object | null): Vertex;
161
163
  newNamedVertex(parentId: string, name: string, props?: Record<string, VertexPropertyType> | object | null): Vertex;
162
164
  moveVertex(vertexId: string, parentId: string): void;
@@ -193,26 +195,11 @@ declare class RepTree {
193
195
  private reportOpAsApplied;
194
196
  private tryToMove;
195
197
  private undoMove;
196
- /**
197
- * Updates the state vector with a newly applied operation.
198
- * Assumes ranges are sorted and non-overlapping.
199
- *
200
- * @param op The operation that was just applied
201
- */
202
- private updateStateVector;
203
198
  /**
204
199
  * Returns the current state vector.
205
200
  * Returns a readonly reference to the internal state vector.
206
201
  */
207
- getStateVector(): Readonly<Record<string, number[][]>>;
208
- /**
209
- * Calculates which operation ranges we have that the other peer is missing
210
- * by comparing state vectors.
211
- *
212
- * @param theirStateVector The state vector from another peer
213
- * @returns Array of operation ID ranges that we have but they don't
214
- */
215
- private diffStateVectors;
202
+ getStateVector(): Readonly<Record<string, number[][]>> | null;
216
203
  /**
217
204
  * Determines which operations are needed to synchronize
218
205
  * with the provided state vector.
@@ -221,6 +208,15 @@ declare class RepTree {
221
208
  * @returns Operations that should be sent to the other peer, sorted by OpId.
222
209
  */
223
210
  getMissingOps(theirStateVector: Record<string, number[][]>): VertexOperation[];
211
+ /**
212
+ * Gets or sets whether state vector tracking is enabled
213
+ */
214
+ get stateVectorEnabled(): boolean;
215
+ /**
216
+ * Sets the state vector enabled status
217
+ * When enabled, rebuilds the state vector from existing operations if needed
218
+ */
219
+ set stateVectorEnabled(value: boolean);
224
220
  }
225
221
 
226
222
  declare class TreeState {
@@ -247,6 +243,63 @@ declare class TreeState {
247
243
  printTree(vertexId: TreeVertexId, indent?: string, isLast?: boolean): string;
248
244
  }
249
245
 
246
+ /**
247
+ * StateVector tracks operations that have been applied using a range-based representation.
248
+ * It's used for synchronization between peers to determine which operations need to be sent.
249
+ */
250
+ declare class StateVector {
251
+ private ranges;
252
+ /**
253
+ * Creates a new StateVector.
254
+ * @param initialState Optional initial state to copy from
255
+ */
256
+ constructor(initialState?: Record<string, number[][]>);
257
+ /**
258
+ * Updates the state vector with a newly applied operation.
259
+ * Assumes ranges are sorted and non-overlapping.
260
+ *
261
+ * @param peerId The peer ID of the operation
262
+ * @param counter The counter value of the operation
263
+ */
264
+ update(peerId: string, counter: number): void;
265
+ /**
266
+ * Updates the state vector with a newly applied operation.
267
+ *
268
+ * @param op The operation that was just applied
269
+ */
270
+ updateFromOp(op: VertexOperation): void;
271
+ /**
272
+ * Returns the current state vector.
273
+ * Returns a readonly reference to the internal state.
274
+ */
275
+ getState(): Readonly<Record<string, number[][]>>;
276
+ /**
277
+ * Calculates which operation ranges we have that the other state vector is missing
278
+ * by comparing state vectors.
279
+ *
280
+ * @param other The other state vector to compare against
281
+ * @returns Array of operation ID ranges that we have but they don't
282
+ */
283
+ diff(other: StateVector): OpIdRange[];
284
+ /**
285
+ * Checks if the state vector contains the given operation ID
286
+ *
287
+ * @param opId The operation ID to check
288
+ * @returns true if the operation is in the state vector, false otherwise
289
+ */
290
+ contains(opId: OpId): boolean;
291
+ /**
292
+ * Creates a copy of this state vector
293
+ */
294
+ clone(): StateVector;
295
+ /**
296
+ * Builds a state vector from an array of operations
297
+ * @param operations The operations to build the state vector from
298
+ * @returns A new StateVector instance
299
+ */
300
+ static fromOperations(operations: ReadonlyArray<VertexOperation>): StateVector;
301
+ }
302
+
250
303
  declare function uuid(): string;
251
304
 
252
- export { type MoveVertex, OpId, type OpIdRange, RepTree, type SetVertexProperty, TreeState, type TreeVertexId, type TreeVertexProperty, Vertex, type VertexChangeEvent, type VertexChildrenChangeEvent, type VertexMoveEvent, type VertexOperation, type VertexPropertyChangeEvent, type VertexPropertyType, VertexState, isMoveVertexOp, isSetPropertyOp, newMoveVertexOp, newSetTransientVertexPropertyOp, newSetVertexPropertyOp, uuid };
305
+ export { type MoveVertex, OpId, type OpIdRange, RepTree, type SetVertexProperty, StateVector, TreeState, type TreeVertexId, type TreeVertexProperty, Vertex, type VertexChangeEvent, type VertexChildrenChangeEvent, type VertexMoveEvent, type VertexOperation, type VertexPropertyChangeEvent, type VertexPropertyType, VertexState, isMoveVertexOp, isSetPropertyOp, newMoveVertexOp, newSetTransientVertexPropertyOp, newSetVertexPropertyOp, uuid };
package/dist/index.js CHANGED
@@ -324,9 +324,21 @@ var TreeState = class {
324
324
  }
325
325
  }
326
326
  const children = this.getChildrenIds(vertexId);
327
- for (let i = 0; i < children.length; i++) {
328
- const childId = children[i];
329
- const isLastChild = i === children.length - 1;
327
+ const sortedChildren = [...children].sort((a, b) => {
328
+ const vertexA = this.getVertex(a);
329
+ const vertexB = this.getVertex(b);
330
+ const nameA = vertexA?.getProperty("_n");
331
+ const nameB = vertexB?.getProperty("_n");
332
+ if (nameA && nameB) {
333
+ return nameA.localeCompare(nameB);
334
+ }
335
+ if (nameA) return -1;
336
+ if (nameB) return 1;
337
+ return a.localeCompare(b);
338
+ });
339
+ for (let i = 0; i < sortedChildren.length; i++) {
340
+ const childId = sortedChildren[i];
341
+ const isLastChild = i === sortedChildren.length - 1;
330
342
  result += this.printTree(childId, indent + (isLast ? " " : "\u2502 "), isLastChild);
331
343
  }
332
344
  return result;
@@ -459,7 +471,7 @@ var Vertex = class {
459
471
  }
460
472
  };
461
473
 
462
- // src/RepTree.ts
474
+ // src/StateVector.ts
463
475
  function subtractRanges(rangesA, rangesB) {
464
476
  if (rangesB.length === 0) return rangesA.map((r) => [...r]);
465
477
  if (rangesA.length === 0) return [];
@@ -492,12 +504,165 @@ function subtractRanges(rangesA, rangesB) {
492
504
  }
493
505
  return result;
494
506
  }
507
+ var StateVector = class _StateVector {
508
+ /**
509
+ * Creates a new StateVector.
510
+ * @param initialState Optional initial state to copy from
511
+ */
512
+ constructor(initialState = {}) {
513
+ this.ranges = {};
514
+ for (const [peerId, peerRanges] of Object.entries(initialState)) {
515
+ this.ranges[peerId] = peerRanges.map((range) => [...range]);
516
+ }
517
+ }
518
+ /**
519
+ * Updates the state vector with a newly applied operation.
520
+ * Assumes ranges are sorted and non-overlapping.
521
+ *
522
+ * @param peerId The peer ID of the operation
523
+ * @param counter The counter value of the operation
524
+ */
525
+ update(peerId, counter) {
526
+ if (!this.ranges[peerId]) {
527
+ this.ranges[peerId] = [];
528
+ }
529
+ const ranges = this.ranges[peerId];
530
+ if (ranges.length === 0) {
531
+ ranges.push([counter, counter]);
532
+ return;
533
+ }
534
+ let rangeExtendedOrMerged = false;
535
+ let insertIndex = -1;
536
+ for (let i = 0; i < ranges.length; i++) {
537
+ const range = ranges[i];
538
+ if (counter >= range[0] && counter <= range[1]) {
539
+ rangeExtendedOrMerged = true;
540
+ break;
541
+ }
542
+ if (counter === range[0] - 1) {
543
+ range[0] = counter;
544
+ rangeExtendedOrMerged = true;
545
+ if (i > 0 && range[0] === ranges[i - 1][1] + 1) {
546
+ ranges[i - 1][1] = range[1];
547
+ ranges.splice(i, 1);
548
+ }
549
+ break;
550
+ }
551
+ if (counter === range[1] + 1) {
552
+ range[1] = counter;
553
+ rangeExtendedOrMerged = true;
554
+ if (i < ranges.length - 1 && range[1] + 1 === ranges[i + 1][0]) {
555
+ range[1] = ranges[i + 1][1];
556
+ ranges.splice(i + 1, 1);
557
+ }
558
+ break;
559
+ }
560
+ if (counter < range[0] && insertIndex === -1) {
561
+ insertIndex = i;
562
+ }
563
+ }
564
+ if (!rangeExtendedOrMerged) {
565
+ if (insertIndex === -1) {
566
+ insertIndex = ranges.length;
567
+ }
568
+ ranges.splice(insertIndex, 0, [counter, counter]);
569
+ if (insertIndex > 0 && ranges[insertIndex][0] === ranges[insertIndex - 1][1] + 1) {
570
+ ranges[insertIndex - 1][1] = ranges[insertIndex][1];
571
+ ranges.splice(insertIndex, 1);
572
+ insertIndex--;
573
+ }
574
+ if (insertIndex < ranges.length - 1 && ranges[insertIndex][1] + 1 === ranges[insertIndex + 1][0]) {
575
+ ranges[insertIndex][1] = ranges[insertIndex + 1][1];
576
+ ranges.splice(insertIndex + 1, 1);
577
+ }
578
+ }
579
+ }
580
+ /**
581
+ * Updates the state vector with a newly applied operation.
582
+ *
583
+ * @param op The operation that was just applied
584
+ */
585
+ updateFromOp(op) {
586
+ this.update(op.id.peerId, op.id.counter);
587
+ }
588
+ /**
589
+ * Returns the current state vector.
590
+ * Returns a readonly reference to the internal state.
591
+ */
592
+ getState() {
593
+ return this.ranges;
594
+ }
595
+ /**
596
+ * Calculates which operation ranges we have that the other state vector is missing
597
+ * by comparing state vectors.
598
+ *
599
+ * @param other The other state vector to compare against
600
+ * @returns Array of operation ID ranges that we have but they don't
601
+ */
602
+ diff(other) {
603
+ const missingRanges = [];
604
+ const theirState = other.getState();
605
+ for (const [peerId, ourRanges] of Object.entries(this.ranges)) {
606
+ const theirRanges = theirState[peerId] || [];
607
+ const missing = subtractRanges(ourRanges, theirRanges);
608
+ for (const [start, end] of missing) {
609
+ if (start <= end) {
610
+ missingRanges.push({
611
+ peerId,
612
+ start,
613
+ end
614
+ });
615
+ }
616
+ }
617
+ }
618
+ return missingRanges;
619
+ }
620
+ /**
621
+ * Checks if the state vector contains the given operation ID
622
+ *
623
+ * @param opId The operation ID to check
624
+ * @returns true if the operation is in the state vector, false otherwise
625
+ */
626
+ contains(opId) {
627
+ const peerId = opId.peerId;
628
+ const counter = opId.counter;
629
+ if (!this.ranges[peerId]) {
630
+ return false;
631
+ }
632
+ for (const [start, end] of this.ranges[peerId]) {
633
+ if (counter >= start && counter <= end) {
634
+ return true;
635
+ }
636
+ }
637
+ return false;
638
+ }
639
+ /**
640
+ * Creates a copy of this state vector
641
+ */
642
+ clone() {
643
+ return new _StateVector(this.ranges);
644
+ }
645
+ /**
646
+ * Builds a state vector from an array of operations
647
+ * @param operations The operations to build the state vector from
648
+ * @returns A new StateVector instance
649
+ */
650
+ static fromOperations(operations) {
651
+ const stateVector = new _StateVector();
652
+ for (const op of operations) {
653
+ stateVector.updateFromOp(op);
654
+ }
655
+ return stateVector;
656
+ }
657
+ };
658
+
659
+ // src/RepTree.ts
495
660
  var _RepTree = class _RepTree {
496
661
  /**
497
- * @param peerId - The peer ID of the current client
498
- * @param ops - The operations to replicate an existing tree, if null - a new tree will be created
662
+ * @param peerId - The peer ID of the current client. Should be unique across all peers.
663
+ * @param ops - The operations to replicate an existing tree, if not provided - an empty tree will be created without a root vertex
499
664
  */
500
- constructor(peerId, ops = null) {
665
+ constructor(peerId, ops) {
501
666
  this.lamportClock = 0;
502
667
  this.moveOps = [];
503
668
  this.setPropertyOps = [];
@@ -510,30 +675,37 @@ var _RepTree = class _RepTree {
510
675
  this.parentIdBeforeMove = /* @__PURE__ */ new Map();
511
676
  this.opAppliedCallbacks = [];
512
677
  this.maxDepth = _RepTree.DEFAULT_MAX_DEPTH;
513
- // State vector tracking operations from each peer
514
- this.stateVector = {};
678
+ this._stateVectorEnabled = true;
515
679
  this.peerId = peerId;
516
680
  this.state = new TreeState();
517
- if (ops != null && ops.length > 0) {
518
- let rootMoveOp;
519
- for (let i = 0; i < ops.length; i++) {
520
- if (isMoveVertexOp(ops[i]) && ops[i].parentId === null) {
521
- rootMoveOp = ops[i];
522
- break;
523
- }
524
- }
525
- if (rootMoveOp) {
526
- this.rootVertexId = rootMoveOp.targetId;
527
- } else {
528
- throw new Error("The operations has to contain a move operation with a parentId as null to set the root vertex");
529
- }
681
+ this.stateVector = new StateVector();
682
+ if (ops && ops.length > 0) {
530
683
  this.applyOps(ops);
531
- this.ensureNullVertex();
684
+ const root = this.root;
685
+ if (!root) {
686
+ throw new Error("There has to be a root vertex in the operations");
687
+ }
532
688
  } else {
533
- this.rootVertexId = this.newVertexInternalWithUUID(null);
534
689
  this.ensureNullVertex();
535
690
  }
536
691
  }
692
+ get root() {
693
+ if (!this.rootVertexId) {
694
+ const vertices = this.state.getAllVertices();
695
+ for (const vertex of vertices) {
696
+ if (vertex.parentId === null && vertex.id !== _RepTree.NULL_VERTEX_ID) {
697
+ this.rootVertexId = vertex.id;
698
+ return new Vertex(this, vertex);
699
+ }
700
+ }
701
+ return void 0;
702
+ }
703
+ const rootVertex = this.state.getVertex(this.rootVertexId);
704
+ if (!rootVertex) {
705
+ throw new Error("Root vertex not found");
706
+ }
707
+ return new Vertex(this, rootVertex);
708
+ }
537
709
  getMoveOps() {
538
710
  return this.moveOps;
539
711
  }
@@ -544,13 +716,6 @@ var _RepTree = class _RepTree {
544
716
  const vertex = this.state.getVertex(vertexId);
545
717
  return vertex ? new Vertex(this, vertex) : void 0;
546
718
  }
547
- get rootVertex() {
548
- const rootVertex = this.state.getVertex(this.rootVertexId);
549
- if (!rootVertex) {
550
- throw new Error("Root vertex not found");
551
- }
552
- return new Vertex(this, rootVertex);
553
- }
554
719
  getAllVertices() {
555
720
  return this.state.getAllVertices().map((v) => new Vertex(this, v));
556
721
  }
@@ -601,6 +766,17 @@ var _RepTree = class _RepTree {
601
766
  setMaxDepth(maxDepth) {
602
767
  this.maxDepth = maxDepth;
603
768
  }
769
+ createRoot() {
770
+ if (this.rootVertexId) {
771
+ throw new Error("Root vertex already exists");
772
+ }
773
+ this.rootVertexId = this.newVertexInternalWithUUID(null);
774
+ const rootVertex = this.state.getVertex(this.rootVertexId);
775
+ if (!rootVertex) {
776
+ throw new Error("Root vertex not found");
777
+ }
778
+ return new Vertex(this, rootVertex);
779
+ }
604
780
  newVertex(parentId, props = null) {
605
781
  const typedProps = props;
606
782
  const vertexId = this.newVertexInternalWithUUID(parentId);
@@ -657,6 +833,9 @@ var _RepTree = class _RepTree {
657
833
  path = path.replace(/^\/+/, "");
658
834
  path = path.replace(/\/+$/, "");
659
835
  const pathParts = path.split("/");
836
+ if (!this.rootVertexId) {
837
+ return void 0;
838
+ }
660
839
  const root = this.state.getVertex(this.rootVertexId);
661
840
  if (!root) {
662
841
  throw new Error("The root vertex is not found");
@@ -678,6 +857,9 @@ var _RepTree = class _RepTree {
678
857
  return void 0;
679
858
  }
680
859
  printTree() {
860
+ if (!this.rootVertexId) {
861
+ return "";
862
+ }
681
863
  return this.state.printTree(this.rootVertexId);
682
864
  }
683
865
  merge(ops) {
@@ -701,6 +883,12 @@ var _RepTree = class _RepTree {
701
883
  }
702
884
  }
703
885
  compareStructure(other) {
886
+ if (this.root?.id !== other.root?.id) {
887
+ return false;
888
+ }
889
+ if (!this.rootVertexId) {
890
+ return true;
891
+ }
704
892
  return _RepTree.compareVertices(this.rootVertexId, this, other);
705
893
  }
706
894
  compareMoveOps(other) {
@@ -927,7 +1115,9 @@ var _RepTree = class _RepTree {
927
1115
  }
928
1116
  reportOpAsApplied(op) {
929
1117
  this.knownOps.add(op.id.toString());
930
- this.updateStateVector(op);
1118
+ if (this._stateVectorEnabled) {
1119
+ this.stateVector.updateFromOp(op);
1120
+ }
931
1121
  for (const callback of this.opAppliedCallbacks) {
932
1122
  callback(op);
933
1123
  }
@@ -961,99 +1151,15 @@ var _RepTree = class _RepTree {
961
1151
  this.state.moveVertex(op.targetId, prevParentId);
962
1152
  }
963
1153
  // --- Range-Based State Vector Methods ---
964
- /**
965
- * Updates the state vector with a newly applied operation.
966
- * Assumes ranges are sorted and non-overlapping.
967
- *
968
- * @param op The operation that was just applied
969
- */
970
- updateStateVector(op) {
971
- const peerId = op.id.peerId;
972
- const counter = op.id.counter;
973
- if (!this.stateVector[peerId]) {
974
- this.stateVector[peerId] = [];
975
- }
976
- const ranges = this.stateVector[peerId];
977
- if (ranges.length === 0) {
978
- ranges.push([counter, counter]);
979
- return;
980
- }
981
- let rangeExtendedOrMerged = false;
982
- let insertIndex = -1;
983
- for (let i = 0; i < ranges.length; i++) {
984
- const range = ranges[i];
985
- if (counter >= range[0] && counter <= range[1]) {
986
- rangeExtendedOrMerged = true;
987
- break;
988
- }
989
- if (counter === range[0] - 1) {
990
- range[0] = counter;
991
- rangeExtendedOrMerged = true;
992
- if (i > 0 && range[0] === ranges[i - 1][1] + 1) {
993
- ranges[i - 1][1] = range[1];
994
- ranges.splice(i, 1);
995
- }
996
- break;
997
- }
998
- if (counter === range[1] + 1) {
999
- range[1] = counter;
1000
- rangeExtendedOrMerged = true;
1001
- if (i < ranges.length - 1 && range[1] + 1 === ranges[i + 1][0]) {
1002
- range[1] = ranges[i + 1][1];
1003
- ranges.splice(i + 1, 1);
1004
- }
1005
- break;
1006
- }
1007
- if (counter < range[0] && insertIndex === -1) {
1008
- insertIndex = i;
1009
- }
1010
- }
1011
- if (!rangeExtendedOrMerged) {
1012
- if (insertIndex === -1) {
1013
- insertIndex = ranges.length;
1014
- }
1015
- ranges.splice(insertIndex, 0, [counter, counter]);
1016
- if (insertIndex > 0 && ranges[insertIndex][0] === ranges[insertIndex - 1][1] + 1) {
1017
- ranges[insertIndex - 1][1] = ranges[insertIndex][1];
1018
- ranges.splice(insertIndex, 1);
1019
- insertIndex--;
1020
- }
1021
- if (insertIndex < ranges.length - 1 && ranges[insertIndex][1] + 1 === ranges[insertIndex + 1][0]) {
1022
- ranges[insertIndex][1] = ranges[insertIndex + 1][1];
1023
- ranges.splice(insertIndex + 1, 1);
1024
- }
1025
- }
1026
- }
1027
1154
  /**
1028
1155
  * Returns the current state vector.
1029
1156
  * Returns a readonly reference to the internal state vector.
1030
1157
  */
1031
1158
  getStateVector() {
1032
- return this.stateVector;
1033
- }
1034
- /**
1035
- * Calculates which operation ranges we have that the other peer is missing
1036
- * by comparing state vectors.
1037
- *
1038
- * @param theirStateVector The state vector from another peer
1039
- * @returns Array of operation ID ranges that we have but they don't
1040
- */
1041
- diffStateVectors(theirStateVector) {
1042
- const missingRanges = [];
1043
- for (const [peerId, ourRanges] of Object.entries(this.stateVector)) {
1044
- const theirRanges = theirStateVector[peerId] || [];
1045
- const missing = subtractRanges(ourRanges, theirRanges);
1046
- for (const [start, end] of missing) {
1047
- if (start <= end) {
1048
- missingRanges.push({
1049
- peerId,
1050
- start,
1051
- end
1052
- });
1053
- }
1054
- }
1159
+ if (!this._stateVectorEnabled) {
1160
+ return null;
1055
1161
  }
1056
- return missingRanges;
1162
+ return this.stateVector.getState();
1057
1163
  }
1058
1164
  /**
1059
1165
  * Determines which operations are needed to synchronize
@@ -1063,7 +1169,11 @@ var _RepTree = class _RepTree {
1063
1169
  * @returns Operations that should be sent to the other peer, sorted by OpId.
1064
1170
  */
1065
1171
  getMissingOps(theirStateVector) {
1066
- const missingRanges = this.diffStateVectors(theirStateVector);
1172
+ if (!this._stateVectorEnabled) {
1173
+ return [...this.moveOps, ...this.setPropertyOps];
1174
+ }
1175
+ const otherStateVector = new StateVector(theirStateVector);
1176
+ const missingRanges = this.stateVector.diff(otherStateVector);
1067
1177
  const missingOps = [];
1068
1178
  const allOps = [...this.moveOps, ...this.setPropertyOps];
1069
1179
  for (const op of allOps) {
@@ -1077,6 +1187,26 @@ var _RepTree = class _RepTree {
1077
1187
  missingOps.sort((a, b) => OpId.compare(a.id, b.id));
1078
1188
  return missingOps;
1079
1189
  }
1190
+ /**
1191
+ * Gets or sets whether state vector tracking is enabled
1192
+ */
1193
+ get stateVectorEnabled() {
1194
+ return this._stateVectorEnabled;
1195
+ }
1196
+ /**
1197
+ * Sets the state vector enabled status
1198
+ * When enabled, rebuilds the state vector from existing operations if needed
1199
+ */
1200
+ set stateVectorEnabled(value) {
1201
+ if (value === this._stateVectorEnabled) return;
1202
+ if (value) {
1203
+ this._stateVectorEnabled = true;
1204
+ this.stateVector = StateVector.fromOperations([...this.moveOps, ...this.setPropertyOps]);
1205
+ } else {
1206
+ this._stateVectorEnabled = false;
1207
+ this.stateVector = new StateVector();
1208
+ }
1209
+ }
1080
1210
  };
1081
1211
  _RepTree.NULL_VERTEX_ID = "0";
1082
1212
  _RepTree.DEFAULT_MAX_DEPTH = 1e5;
@@ -1084,6 +1214,7 @@ var RepTree = _RepTree;
1084
1214
  export {
1085
1215
  OpId,
1086
1216
  RepTree,
1217
+ StateVector,
1087
1218
  TreeState,
1088
1219
  Vertex,
1089
1220
  VertexState,