reptree 0.1.1 → 0.1.2
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/README.md +41 -13
- package/dist/index.cjs +228 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -16
- package/dist/index.d.ts +48 -16
- package/dist/index.js +228 -73
- package/dist/index.js.map +1 -1
- package/package.json +12 -5
package/README.md
CHANGED
|
@@ -8,11 +8,12 @@ A tree data structure using CRDTs for seamless replication between peers.
|
|
|
8
8
|
|
|
9
9
|
## Description
|
|
10
10
|
|
|
11
|
-
RepTree
|
|
12
|
-
It uses 2 conflict-free replicated data types (CRDTs) to manage seamless replication between peers:
|
|
11
|
+
RepTree uses 2 conflict-free replicated data types (CRDTs) to manage seamless replication between peers:
|
|
13
12
|
- A move tree CRDT is used for the tree structure (https://martin.kleppmann.com/papers/move-op.pdf).
|
|
14
13
|
- A last writer wins (LWW) CRDT is used for properties.
|
|
15
14
|
|
|
15
|
+
RepTree can also be viewed as a hierarchical, distributed database. For more details on its database capabilities, see [RepTree as a Database](docs/database.md).
|
|
16
|
+
|
|
16
17
|
## Installation
|
|
17
18
|
|
|
18
19
|
```bash
|
|
@@ -29,17 +30,44 @@ const tree = new RepTree('peer1');
|
|
|
29
30
|
|
|
30
31
|
// Root vertex is created automatically
|
|
31
32
|
const rootVertex = tree.rootVertex;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
//
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
33
|
+
rootVertex.name = 'Project';
|
|
34
|
+
|
|
35
|
+
// Create a folder structure with properties
|
|
36
|
+
const docsFolder = rootVertex.newNamedChild('Docs');
|
|
37
|
+
docsFolder.setProperties({
|
|
38
|
+
type: 'folder',
|
|
39
|
+
icon: 'folder-icon'
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const imagesFolder = rootVertex.newNamedChild('Images');
|
|
43
|
+
imagesFolder.setProperties({
|
|
44
|
+
type: 'folder',
|
|
45
|
+
icon: 'image-icon'
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Add files to folders
|
|
49
|
+
const readmeFile = docsFolder.newNamedChild('README.md');
|
|
50
|
+
readmeFile.setProperties({
|
|
51
|
+
type: 'file',
|
|
52
|
+
size: 2048,
|
|
53
|
+
lastModified: '2023-10-15T14:22:10Z',
|
|
54
|
+
s3Path: 's3://my-bucket/docs/README.md'
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const logoFile = imagesFolder.newNamedChild('logo.png');
|
|
58
|
+
logoFile.setProperties({
|
|
59
|
+
type: 'file',
|
|
60
|
+
size: 15360,
|
|
61
|
+
dimensions: '512x512',
|
|
62
|
+
format: 'png',
|
|
63
|
+
s3Path: 's3://my-bucket/images/logo.png'
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Move a file to a different folder
|
|
67
|
+
logoFile.moveTo(docsFolder);
|
|
68
|
+
|
|
69
|
+
// Get children of a folder
|
|
70
|
+
const docsFolderContents = docsFolder.children;
|
|
43
71
|
|
|
44
72
|
// Syncing between trees
|
|
45
73
|
const otherTree = new RepTree('peer2');
|
package/dist/index.cjs
CHANGED
|
@@ -496,6 +496,38 @@ var Vertex = class {
|
|
|
496
496
|
};
|
|
497
497
|
|
|
498
498
|
// src/RepTree.ts
|
|
499
|
+
function subtractRanges(rangesA, rangesB) {
|
|
500
|
+
if (rangesB.length === 0) return rangesA.map((r) => [...r]);
|
|
501
|
+
if (rangesA.length === 0) return [];
|
|
502
|
+
const result = [];
|
|
503
|
+
let indexB = 0;
|
|
504
|
+
for (const rangeA of rangesA) {
|
|
505
|
+
let currentStart = rangeA[0];
|
|
506
|
+
const endA = rangeA[1];
|
|
507
|
+
while (indexB < rangesB.length && rangesB[indexB][1] < currentStart) {
|
|
508
|
+
indexB++;
|
|
509
|
+
}
|
|
510
|
+
while (indexB < rangesB.length && rangesB[indexB][0] <= endA) {
|
|
511
|
+
const startB = rangesB[indexB][0];
|
|
512
|
+
const endB = rangesB[indexB][1];
|
|
513
|
+
if (currentStart < startB) {
|
|
514
|
+
result.push([currentStart, Math.min(endA, startB - 1)]);
|
|
515
|
+
}
|
|
516
|
+
currentStart = Math.max(currentStart, endB + 1);
|
|
517
|
+
if (currentStart > endA) break;
|
|
518
|
+
if (endB >= endA) break;
|
|
519
|
+
if (endB < currentStart) {
|
|
520
|
+
indexB++;
|
|
521
|
+
} else if (startB >= currentStart) {
|
|
522
|
+
indexB++;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
if (currentStart <= endA) {
|
|
526
|
+
result.push([currentStart, endA]);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return result;
|
|
530
|
+
}
|
|
499
531
|
var _RepTree = class _RepTree {
|
|
500
532
|
/**
|
|
501
533
|
* @param peerId - The peer ID of the current client
|
|
@@ -510,10 +542,12 @@ var _RepTree = class _RepTree {
|
|
|
510
542
|
this.localOps = [];
|
|
511
543
|
this.pendingMovesWithMissingParent = /* @__PURE__ */ new Map();
|
|
512
544
|
this.pendingPropertiesWithMissingVertex = /* @__PURE__ */ new Map();
|
|
513
|
-
this.
|
|
545
|
+
this.knownOps = /* @__PURE__ */ new Set();
|
|
514
546
|
this.parentIdBeforeMove = /* @__PURE__ */ new Map();
|
|
515
547
|
this.opAppliedCallbacks = [];
|
|
516
548
|
this.maxDepth = _RepTree.DEFAULT_MAX_DEPTH;
|
|
549
|
+
// State vector tracking operations from each peer
|
|
550
|
+
this.stateVector = {};
|
|
517
551
|
this.peerId = peerId;
|
|
518
552
|
this.state = new TreeState();
|
|
519
553
|
if (ops != null && ops.length > 0) {
|
|
@@ -530,10 +564,10 @@ var _RepTree = class _RepTree {
|
|
|
530
564
|
throw new Error("The operations has to contain a move operation with a parentId as null to set the root vertex");
|
|
531
565
|
}
|
|
532
566
|
this.applyOps(ops);
|
|
533
|
-
this.
|
|
567
|
+
this.ensureNullVertex();
|
|
534
568
|
} else {
|
|
535
569
|
this.rootVertexId = this.newVertexInternalWithUUID(null);
|
|
536
|
-
this.
|
|
570
|
+
this.ensureNullVertex();
|
|
537
571
|
}
|
|
538
572
|
}
|
|
539
573
|
getMoveOps() {
|
|
@@ -635,7 +669,7 @@ var _RepTree = class _RepTree {
|
|
|
635
669
|
this.applyMove(op);
|
|
636
670
|
}
|
|
637
671
|
deleteVertex(vertexId) {
|
|
638
|
-
this.moveVertex(vertexId, _RepTree.
|
|
672
|
+
this.moveVertex(vertexId, _RepTree.NULL_VERTEX_ID);
|
|
639
673
|
}
|
|
640
674
|
setTransientVertexProperty(vertexId, key, value) {
|
|
641
675
|
this.lamportClock++;
|
|
@@ -685,6 +719,23 @@ var _RepTree = class _RepTree {
|
|
|
685
719
|
merge(ops) {
|
|
686
720
|
this.applyOps(ops);
|
|
687
721
|
}
|
|
722
|
+
/** Applies operations in an optimized way, sorting move ops by OpId to avoid undo-do-redo cycles */
|
|
723
|
+
applyOpsOptimizedForLotsOfMoves(ops) {
|
|
724
|
+
const newMoveOps = ops.filter((op) => isMoveVertexOp(op) && !this.knownOps.has(op.id.toString()));
|
|
725
|
+
if (newMoveOps.length > 0) {
|
|
726
|
+
const allMoveOps = [...this.moveOps, ...newMoveOps];
|
|
727
|
+
allMoveOps.sort((a, b) => OpId.compare(a.id, b.id));
|
|
728
|
+
for (let i = 0, len = allMoveOps.length; i < len; i++) {
|
|
729
|
+
const op = allMoveOps[i];
|
|
730
|
+
this.applyMove(op);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
const propertyOps = ops.filter((op) => isSetPropertyOp(op) && !this.knownOps.has(op.id.toString()));
|
|
734
|
+
for (let i = 0, len = propertyOps.length; i < len; i++) {
|
|
735
|
+
const op = propertyOps[i];
|
|
736
|
+
this.applyProperty(op);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
688
739
|
compareStructure(other) {
|
|
689
740
|
return _RepTree.compareVertices(this.rootVertexId, this, other);
|
|
690
741
|
}
|
|
@@ -795,8 +846,8 @@ var _RepTree = class _RepTree {
|
|
|
795
846
|
const vertexId = uuid();
|
|
796
847
|
return this.newVertexInternal(vertexId, parentId);
|
|
797
848
|
}
|
|
798
|
-
|
|
799
|
-
const vertexId = _RepTree.
|
|
849
|
+
ensureNullVertex() {
|
|
850
|
+
const vertexId = _RepTree.NULL_VERTEX_ID;
|
|
800
851
|
if (this.state.getVertex(vertexId)) {
|
|
801
852
|
return;
|
|
802
853
|
}
|
|
@@ -808,6 +859,19 @@ var _RepTree = class _RepTree {
|
|
|
808
859
|
this.lamportClock = operation.id.counter;
|
|
809
860
|
}
|
|
810
861
|
}
|
|
862
|
+
applyPendingMovesForParent(parentId) {
|
|
863
|
+
if (!this.state.getVertex(parentId)) {
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
const pendingMoves = this.pendingMovesWithMissingParent.get(parentId);
|
|
867
|
+
if (!pendingMoves) {
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
this.pendingMovesWithMissingParent.delete(parentId);
|
|
871
|
+
for (const pendingOp of pendingMoves) {
|
|
872
|
+
this.applyMove(pendingOp);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
811
875
|
applyMove(op) {
|
|
812
876
|
if (op.parentId !== null && !this.state.getVertex(op.parentId)) {
|
|
813
877
|
if (!this.pendingMovesWithMissingParent.has(op.parentId)) {
|
|
@@ -842,15 +906,52 @@ var _RepTree = class _RepTree {
|
|
|
842
906
|
}
|
|
843
907
|
this.applyPendingMovesForParent(op.targetId);
|
|
844
908
|
}
|
|
845
|
-
|
|
846
|
-
this.
|
|
847
|
-
|
|
848
|
-
|
|
909
|
+
setPropertyAndItsOpId(op) {
|
|
910
|
+
this.propertiesAndTheirOpIds.set(`${op.key}@${op.targetId}`, op.id);
|
|
911
|
+
this.state.setProperty(op.targetId, op.key, op.value);
|
|
912
|
+
this.reportOpAsApplied(op);
|
|
913
|
+
}
|
|
914
|
+
setTransientPropertyAndItsOpId(op) {
|
|
915
|
+
this.transientPropertiesAndTheirOpIds.set(`${op.key}@${op.targetId}`, op.id);
|
|
916
|
+
this.state.setTransientProperty(op.targetId, op.key, op.value);
|
|
917
|
+
this.reportOpAsApplied(op);
|
|
918
|
+
}
|
|
919
|
+
applyProperty(op) {
|
|
920
|
+
const targetVertex = this.state.getVertex(op.targetId);
|
|
921
|
+
if (!targetVertex) {
|
|
922
|
+
if (op.transient) {
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
if (!this.pendingPropertiesWithMissingVertex.has(op.targetId)) {
|
|
926
|
+
this.pendingPropertiesWithMissingVertex.set(op.targetId, []);
|
|
927
|
+
}
|
|
928
|
+
this.pendingPropertiesWithMissingVertex.get(op.targetId).push(op);
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
this.updateLamportClock(op);
|
|
932
|
+
const prevTransientOpId = this.transientPropertiesAndTheirOpIds.get(`${op.key}@${op.targetId}`);
|
|
933
|
+
const prevProp = targetVertex.getProperty(op.key);
|
|
934
|
+
const prevOpId = this.propertiesAndTheirOpIds.get(`${op.key}@${op.targetId}`);
|
|
935
|
+
if (!op.transient) {
|
|
936
|
+
this.setPropertyOps.push(op);
|
|
937
|
+
if (!prevProp || !prevOpId || op.id.isGreaterThan(prevOpId)) {
|
|
938
|
+
this.setPropertyAndItsOpId(op);
|
|
939
|
+
} else {
|
|
940
|
+
this.knownOps.add(op.id.toString());
|
|
941
|
+
}
|
|
942
|
+
if (prevTransientOpId && op.id.isGreaterThan(prevTransientOpId)) {
|
|
943
|
+
this.transientPropertiesAndTheirOpIds.delete(`${op.key}@${op.targetId}`);
|
|
944
|
+
targetVertex.removeTransientProperty(op.key);
|
|
945
|
+
}
|
|
946
|
+
} else {
|
|
947
|
+
if (!prevTransientOpId || op.id.isGreaterThan(prevTransientOpId)) {
|
|
948
|
+
this.setTransientPropertyAndItsOpId(op);
|
|
949
|
+
}
|
|
849
950
|
}
|
|
850
951
|
}
|
|
851
952
|
applyOps(ops) {
|
|
852
953
|
for (const op of ops) {
|
|
853
|
-
if (this.
|
|
954
|
+
if (this.knownOps.has(op.id.toString())) {
|
|
854
955
|
continue;
|
|
855
956
|
}
|
|
856
957
|
if (isMoveVertexOp(op)) {
|
|
@@ -860,34 +961,11 @@ var _RepTree = class _RepTree {
|
|
|
860
961
|
}
|
|
861
962
|
}
|
|
862
963
|
}
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
allMoveOps.sort((a, b) => OpId.compare(a.id, b.id));
|
|
869
|
-
for (let i = 0, len = allMoveOps.length; i < len; i++) {
|
|
870
|
-
const op = allMoveOps[i];
|
|
871
|
-
this.applyMove(op);
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
const propertyOps = ops.filter((op) => isSetPropertyOp(op) && !this.appliedOps.has(op.id.toString()));
|
|
875
|
-
for (let i = 0, len = propertyOps.length; i < len; i++) {
|
|
876
|
-
const op = propertyOps[i];
|
|
877
|
-
this.applyProperty(op);
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
applyPendingMovesForParent(parentId) {
|
|
881
|
-
if (!this.state.getVertex(parentId)) {
|
|
882
|
-
return;
|
|
883
|
-
}
|
|
884
|
-
const pendingMoves = this.pendingMovesWithMissingParent.get(parentId);
|
|
885
|
-
if (!pendingMoves) {
|
|
886
|
-
return;
|
|
887
|
-
}
|
|
888
|
-
this.pendingMovesWithMissingParent.delete(parentId);
|
|
889
|
-
for (const pendingOp of pendingMoves) {
|
|
890
|
-
this.applyMove(pendingOp);
|
|
964
|
+
reportOpAsApplied(op) {
|
|
965
|
+
this.knownOps.add(op.id.toString());
|
|
966
|
+
this.updateStateVector(op);
|
|
967
|
+
for (const callback of this.opAppliedCallbacks) {
|
|
968
|
+
callback(op);
|
|
891
969
|
}
|
|
892
970
|
}
|
|
893
971
|
tryToMove(op) {
|
|
@@ -900,6 +978,7 @@ var _RepTree = class _RepTree {
|
|
|
900
978
|
this.state.moveVertex(op.targetId, op.parentId);
|
|
901
979
|
if (!targetVertex) {
|
|
902
980
|
const pendingProperties = this.pendingPropertiesWithMissingVertex.get(op.targetId) || [];
|
|
981
|
+
this.pendingPropertiesWithMissingVertex.delete(op.targetId);
|
|
903
982
|
for (const prop of pendingProperties) {
|
|
904
983
|
this.setPropertyAndItsOpId(prop);
|
|
905
984
|
}
|
|
@@ -917,49 +996,125 @@ var _RepTree = class _RepTree {
|
|
|
917
996
|
}
|
|
918
997
|
this.state.moveVertex(op.targetId, prevParentId);
|
|
919
998
|
}
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
999
|
+
// --- Range-Based State Vector Methods ---
|
|
1000
|
+
/**
|
|
1001
|
+
* Updates the state vector with a newly applied operation.
|
|
1002
|
+
* Assumes ranges are sorted and non-overlapping.
|
|
1003
|
+
*
|
|
1004
|
+
* @param op The operation that was just applied
|
|
1005
|
+
*/
|
|
1006
|
+
updateStateVector(op) {
|
|
1007
|
+
const peerId = op.id.peerId;
|
|
1008
|
+
const counter = op.id.counter;
|
|
1009
|
+
if (!this.stateVector[peerId]) {
|
|
1010
|
+
this.stateVector[peerId] = [];
|
|
1011
|
+
}
|
|
1012
|
+
const ranges = this.stateVector[peerId];
|
|
1013
|
+
if (ranges.length === 0) {
|
|
1014
|
+
ranges.push([counter, counter]);
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
let rangeExtendedOrMerged = false;
|
|
1018
|
+
let insertIndex = -1;
|
|
1019
|
+
for (let i = 0; i < ranges.length; i++) {
|
|
1020
|
+
const range = ranges[i];
|
|
1021
|
+
if (counter >= range[0] && counter <= range[1]) {
|
|
1022
|
+
rangeExtendedOrMerged = true;
|
|
1023
|
+
break;
|
|
935
1024
|
}
|
|
936
|
-
if (
|
|
937
|
-
|
|
1025
|
+
if (counter === range[0] - 1) {
|
|
1026
|
+
range[0] = counter;
|
|
1027
|
+
rangeExtendedOrMerged = true;
|
|
1028
|
+
if (i > 0 && range[0] === ranges[i - 1][1] + 1) {
|
|
1029
|
+
ranges[i - 1][1] = range[1];
|
|
1030
|
+
ranges.splice(i, 1);
|
|
1031
|
+
}
|
|
1032
|
+
break;
|
|
1033
|
+
}
|
|
1034
|
+
if (counter === range[1] + 1) {
|
|
1035
|
+
range[1] = counter;
|
|
1036
|
+
rangeExtendedOrMerged = true;
|
|
1037
|
+
if (i < ranges.length - 1 && range[1] + 1 === ranges[i + 1][0]) {
|
|
1038
|
+
range[1] = ranges[i + 1][1];
|
|
1039
|
+
ranges.splice(i + 1, 1);
|
|
1040
|
+
}
|
|
1041
|
+
break;
|
|
1042
|
+
}
|
|
1043
|
+
if (counter < range[0] && insertIndex === -1) {
|
|
1044
|
+
insertIndex = i;
|
|
938
1045
|
}
|
|
939
|
-
this.pendingPropertiesWithMissingVertex.get(op.targetId).push(op);
|
|
940
|
-
return;
|
|
941
1046
|
}
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
const prevOpId = this.propertiesAndTheirOpIds.get(`${op.key}@${op.targetId}`);
|
|
946
|
-
if (!op.transient) {
|
|
947
|
-
this.setPropertyOps.push(op);
|
|
948
|
-
if (!prevProp || !prevOpId || op.id.isGreaterThan(prevOpId)) {
|
|
949
|
-
this.setPropertyAndItsOpId(op);
|
|
1047
|
+
if (!rangeExtendedOrMerged) {
|
|
1048
|
+
if (insertIndex === -1) {
|
|
1049
|
+
insertIndex = ranges.length;
|
|
950
1050
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
1051
|
+
ranges.splice(insertIndex, 0, [counter, counter]);
|
|
1052
|
+
if (insertIndex > 0 && ranges[insertIndex][0] === ranges[insertIndex - 1][1] + 1) {
|
|
1053
|
+
ranges[insertIndex - 1][1] = ranges[insertIndex][1];
|
|
1054
|
+
ranges.splice(insertIndex, 1);
|
|
1055
|
+
insertIndex--;
|
|
954
1056
|
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1057
|
+
if (insertIndex < ranges.length - 1 && ranges[insertIndex][1] + 1 === ranges[insertIndex + 1][0]) {
|
|
1058
|
+
ranges[insertIndex][1] = ranges[insertIndex + 1][1];
|
|
1059
|
+
ranges.splice(insertIndex + 1, 1);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Returns the current state vector.
|
|
1065
|
+
* Returns a readonly reference to the internal state vector.
|
|
1066
|
+
*/
|
|
1067
|
+
getStateVector() {
|
|
1068
|
+
return this.stateVector;
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Calculates which operation ranges we have that the other peer is missing
|
|
1072
|
+
* by comparing state vectors.
|
|
1073
|
+
*
|
|
1074
|
+
* @param theirStateVector The state vector from another peer
|
|
1075
|
+
* @returns Array of operation ID ranges that we have but they don't
|
|
1076
|
+
*/
|
|
1077
|
+
diffStateVectors(theirStateVector) {
|
|
1078
|
+
const missingRanges = [];
|
|
1079
|
+
for (const [peerId, ourRanges] of Object.entries(this.stateVector)) {
|
|
1080
|
+
const theirRanges = theirStateVector[peerId] || [];
|
|
1081
|
+
const missing = subtractRanges(ourRanges, theirRanges);
|
|
1082
|
+
for (const [start, end] of missing) {
|
|
1083
|
+
if (start <= end) {
|
|
1084
|
+
missingRanges.push({
|
|
1085
|
+
peerId,
|
|
1086
|
+
start,
|
|
1087
|
+
end
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
return missingRanges;
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Determines which operations are needed to synchronize
|
|
1096
|
+
* with the provided state vector.
|
|
1097
|
+
*
|
|
1098
|
+
* @param theirStateVector The state vector from another peer
|
|
1099
|
+
* @returns Operations that should be sent to the other peer, sorted by OpId.
|
|
1100
|
+
*/
|
|
1101
|
+
getMissingOps(theirStateVector) {
|
|
1102
|
+
const missingRanges = this.diffStateVectors(theirStateVector);
|
|
1103
|
+
const missingOps = [];
|
|
1104
|
+
const allOps = [...this.moveOps, ...this.setPropertyOps];
|
|
1105
|
+
for (const op of allOps) {
|
|
1106
|
+
for (const range of missingRanges) {
|
|
1107
|
+
if (op.id.peerId === range.peerId && op.id.counter >= range.start && op.id.counter <= range.end) {
|
|
1108
|
+
missingOps.push(op);
|
|
1109
|
+
break;
|
|
1110
|
+
}
|
|
958
1111
|
}
|
|
959
1112
|
}
|
|
1113
|
+
missingOps.sort((a, b) => OpId.compare(a.id, b.id));
|
|
1114
|
+
return missingOps;
|
|
960
1115
|
}
|
|
961
1116
|
};
|
|
962
|
-
_RepTree.
|
|
1117
|
+
_RepTree.NULL_VERTEX_ID = "0";
|
|
963
1118
|
_RepTree.DEFAULT_MAX_DEPTH = 1e5;
|
|
964
1119
|
var RepTree = _RepTree;
|
|
965
1120
|
// Annotate the CommonJS export names for ESM import in node:
|