reptree 0.1.5 → 0.2.1

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
@@ -1,3 +1,5 @@
1
+ import * as Y from 'yjs';
2
+
1
3
  declare class OpId {
2
4
  readonly counter: number;
3
5
  readonly peerId: string;
@@ -25,7 +27,21 @@ declare class VertexState {
25
27
  }
26
28
 
27
29
  type TreeVertexId = string;
28
- type VertexPropertyType = string | number | boolean | string[] | number[] | boolean[] | undefined;
30
+ /**
31
+ * Serializable CRDT data for operations
32
+ */
33
+ interface CRDTType {
34
+ type: string;
35
+ value: Uint8Array;
36
+ }
37
+ /**
38
+ * Property type for state - includes Y.Doc for runtime usage
39
+ */
40
+ type VertexPropertyType = string | number | boolean | string[] | number[] | boolean[] | undefined | Y.Doc;
41
+ /**
42
+ * Property type for operations - includes CRDTType instead of Y.Doc
43
+ */
44
+ type VertexPropertyTypeInOperation = string | number | boolean | string[] | number[] | boolean[] | undefined | CRDTType;
29
45
  type TreeVertexProperty = {
30
46
  readonly key: string;
31
47
  readonly value: VertexPropertyType;
@@ -67,15 +83,17 @@ interface SetVertexProperty {
67
83
  id: OpId;
68
84
  targetId: string;
69
85
  key: string;
70
- value: VertexPropertyType;
86
+ value: VertexPropertyTypeInOperation;
71
87
  transient: boolean;
72
88
  }
73
89
  type VertexOperation = MoveVertex | SetVertexProperty;
74
90
  declare function isMoveVertexOp(op: VertexOperation): op is MoveVertex;
75
- declare function isSetPropertyOp(op: VertexOperation): op is SetVertexProperty;
91
+ declare function isAnyPropertyOp(op: VertexOperation): op is SetVertexProperty;
92
+ declare function isLWWPropertyOp(op: VertexOperation): op is SetVertexProperty;
93
+ declare function isModifyPropertyOp(op: VertexOperation): op is SetVertexProperty;
76
94
  declare function newMoveVertexOp(clock: number, peerId: string, targetId: string, parentId: string | null): MoveVertex;
77
- declare function newSetVertexPropertyOp(clock: number, peerId: string, targetId: string, key: string, value: VertexPropertyType): SetVertexProperty;
78
- declare function newSetTransientVertexPropertyOp(clock: number, peerId: string, targetId: string, key: string, value: VertexPropertyType): SetVertexProperty;
95
+ declare function newSetVertexPropertyOp(clock: number, peerId: string, targetId: string, key: string, value: VertexPropertyTypeInOperation): SetVertexProperty;
96
+ declare function newSetTransientVertexPropertyOp(clock: number, peerId: string, targetId: string, key: string, value: VertexPropertyTypeInOperation): SetVertexProperty;
79
97
 
80
98
  /**
81
99
  * A wrapper class for VertexState that provides a more convenient API
@@ -122,7 +140,6 @@ declare class Vertex {
122
140
  */
123
141
  declare class RepTree {
124
142
  private static NULL_VERTEX_ID;
125
- private static DEFAULT_MAX_DEPTH;
126
143
  readonly peerId: string;
127
144
  private rootVertexId;
128
145
  private lamportClock;
@@ -131,13 +148,13 @@ declare class RepTree {
131
148
  private setPropertyOps;
132
149
  private propertiesAndTheirOpIds;
133
150
  private transientPropertiesAndTheirOpIds;
151
+ private yjsObservers;
134
152
  private localOps;
135
153
  private pendingMovesWithMissingParent;
136
154
  private pendingPropertiesWithMissingVertex;
137
155
  private knownOps;
138
156
  private parentIdBeforeMove;
139
157
  private opAppliedCallbacks;
140
- private maxDepth;
141
158
  private stateVector;
142
159
  private _stateVectorEnabled;
143
160
  /**
@@ -146,6 +163,7 @@ declare class RepTree {
146
163
  */
147
164
  constructor(peerId: string, ops?: ReadonlyArray<VertexOperation>);
148
165
  get root(): Vertex | undefined;
166
+ replicate(newPeerId: string): RepTree;
149
167
  getMoveOps(): ReadonlyArray<MoveVertex>;
150
168
  getAllOps(): ReadonlyArray<VertexOperation>;
151
169
  getVertex(vertexId: string): Vertex | undefined;
@@ -156,8 +174,11 @@ declare class RepTree {
156
174
  getAncestors(vertexId: string): Vertex[];
157
175
  getVertexProperty(vertexId: string, key: string, includingTransient?: boolean): VertexPropertyType | undefined;
158
176
  getVertexProperties(vertexId: string): Readonly<TreeVertexProperty[]>;
177
+ /**
178
+ * Returns all local operations and clears the local operations list.
179
+ * Can be used to get all operations that were generated from this peer and need to be sent to other peers.
180
+ */
159
181
  popLocalOps(): VertexOperation[];
160
- setMaxDepth(maxDepth: number): void;
161
182
  createRoot(): Vertex;
162
183
  newVertex(parentId: string, props?: Record<string, VertexPropertyType> | object | null): Vertex;
163
184
  newNamedVertex(parentId: string, name: string, props?: Record<string, VertexPropertyType> | object | null): Vertex;
@@ -170,6 +191,7 @@ declare class RepTree {
170
191
  private getVertexByPathArray;
171
192
  printTree(): string;
172
193
  merge(ops: ReadonlyArray<VertexOperation>): void;
194
+ private applyOps;
173
195
  /** Applies operations in an optimized way, sorting move ops by OpId to avoid undo-do-redo cycles */
174
196
  private applyOpsOptimizedForLotsOfMoves;
175
197
  compareStructure(other: RepTree): boolean;
@@ -188,10 +210,13 @@ declare class RepTree {
188
210
  private updateLamportClock;
189
211
  private applyPendingMovesForParent;
190
212
  private applyMove;
191
- private setPropertyAndItsOpId;
213
+ private setLLWPropertyAndItsOpId;
192
214
  private setTransientPropertyAndItsOpId;
215
+ private setupYjsObserver;
193
216
  private applyProperty;
194
- private applyOps;
217
+ private applyLLWProperty;
218
+ private applyModifyProperty;
219
+ private applyOperation;
195
220
  private reportOpAsApplied;
196
221
  private tryToMove;
197
222
  private undoMove;
@@ -302,4 +327,4 @@ declare class StateVector {
302
327
 
303
328
  declare function uuid(): string;
304
329
 
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 };
330
+ export { type CRDTType, 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, type VertexPropertyTypeInOperation, VertexState, isAnyPropertyOp, isLWWPropertyOp, isModifyPropertyOp, isMoveVertexOp, newMoveVertexOp, newSetTransientVertexPropertyOp, newSetVertexPropertyOp, uuid };
package/dist/index.js CHANGED
@@ -52,9 +52,15 @@ var OpId = class _OpId {
52
52
  function isMoveVertexOp(op) {
53
53
  return "parentId" in op;
54
54
  }
55
- function isSetPropertyOp(op) {
55
+ function isAnyPropertyOp(op) {
56
56
  return "key" in op;
57
57
  }
58
+ function isLWWPropertyOp(op) {
59
+ return "key" in op && "value" in op && (!op.value || typeof op.value !== "object" || !("type" in op.value));
60
+ }
61
+ function isModifyPropertyOp(op) {
62
+ return "key" in op && "value" in op && typeof op.value === "object" && op.value !== null && "type" in op.value;
63
+ }
58
64
  function newMoveVertexOp(clock, peerId, targetId, parentId) {
59
65
  return { id: new OpId(clock, peerId), targetId, parentId };
60
66
  }
@@ -657,6 +663,7 @@ var StateVector = class _StateVector {
657
663
  };
658
664
 
659
665
  // src/RepTree.ts
666
+ import * as Y from "yjs";
660
667
  var _RepTree = class _RepTree {
661
668
  /**
662
669
  * @param peerId - The peer ID of the current client. Should be unique across all peers.
@@ -668,13 +675,13 @@ var _RepTree = class _RepTree {
668
675
  this.setPropertyOps = [];
669
676
  this.propertiesAndTheirOpIds = /* @__PURE__ */ new Map();
670
677
  this.transientPropertiesAndTheirOpIds = /* @__PURE__ */ new Map();
678
+ this.yjsObservers = /* @__PURE__ */ new Map();
671
679
  this.localOps = [];
672
680
  this.pendingMovesWithMissingParent = /* @__PURE__ */ new Map();
673
681
  this.pendingPropertiesWithMissingVertex = /* @__PURE__ */ new Map();
674
682
  this.knownOps = /* @__PURE__ */ new Set();
675
683
  this.parentIdBeforeMove = /* @__PURE__ */ new Map();
676
684
  this.opAppliedCallbacks = [];
677
- this.maxDepth = _RepTree.DEFAULT_MAX_DEPTH;
678
685
  this._stateVectorEnabled = true;
679
686
  this.peerId = peerId;
680
687
  this.state = new TreeState();
@@ -706,6 +713,9 @@ var _RepTree = class _RepTree {
706
713
  }
707
714
  return new Vertex(this, rootVertex);
708
715
  }
716
+ replicate(newPeerId) {
717
+ return new _RepTree(newPeerId, this.getAllOps());
718
+ }
709
719
  getMoveOps() {
710
720
  return this.moveOps;
711
721
  }
@@ -758,14 +768,15 @@ var _RepTree = class _RepTree {
758
768
  }
759
769
  return vertex.getAllProperties();
760
770
  }
771
+ /**
772
+ * Returns all local operations and clears the local operations list.
773
+ * Can be used to get all operations that were generated from this peer and need to be sent to other peers.
774
+ */
761
775
  popLocalOps() {
762
776
  const ops = this.localOps;
763
777
  this.localOps = [];
764
778
  return ops;
765
779
  }
766
- setMaxDepth(maxDepth) {
767
- this.maxDepth = maxDepth;
768
- }
769
780
  createRoot() {
770
781
  if (this.rootVertexId) {
771
782
  throw new Error("Root vertex already exists");
@@ -813,13 +824,35 @@ var _RepTree = class _RepTree {
813
824
  }
814
825
  setTransientVertexProperty(vertexId, key, value) {
815
826
  this.lamportClock++;
816
- const op = newSetTransientVertexPropertyOp(this.lamportClock, this.peerId, vertexId, key, value);
827
+ let opValue;
828
+ if (value instanceof Y.Doc) {
829
+ const state = Y.encodeStateAsUpdate(value);
830
+ opValue = {
831
+ type: "yjs",
832
+ value: state
833
+ };
834
+ this.setupYjsObserver(value, vertexId, key);
835
+ } else {
836
+ opValue = value;
837
+ }
838
+ const op = newSetTransientVertexPropertyOp(this.lamportClock, this.peerId, vertexId, key, opValue);
817
839
  this.localOps.push(op);
818
840
  this.applyProperty(op);
819
841
  }
820
842
  setVertexProperty(vertexId, key, value) {
821
843
  this.lamportClock++;
822
- const op = newSetVertexPropertyOp(this.lamportClock, this.peerId, vertexId, key, value);
844
+ let opValue;
845
+ if (value instanceof Y.Doc) {
846
+ const state = Y.encodeStateAsUpdate(value);
847
+ opValue = {
848
+ type: "yjs",
849
+ value: state
850
+ };
851
+ this.setupYjsObserver(value, vertexId, key);
852
+ } else {
853
+ opValue = value;
854
+ }
855
+ const op = newSetVertexPropertyOp(this.lamportClock, this.peerId, vertexId, key, opValue);
823
856
  this.localOps.push(op);
824
857
  this.applyProperty(op);
825
858
  }
@@ -865,6 +898,14 @@ var _RepTree = class _RepTree {
865
898
  merge(ops) {
866
899
  this.applyOps(ops);
867
900
  }
901
+ applyOps(ops) {
902
+ for (const op of ops) {
903
+ if (this.knownOps.has(op.id.toString())) {
904
+ continue;
905
+ }
906
+ this.applyOperation(op);
907
+ }
908
+ }
868
909
  /** Applies operations in an optimized way, sorting move ops by OpId to avoid undo-do-redo cycles */
869
910
  applyOpsOptimizedForLotsOfMoves(ops) {
870
911
  const newMoveOps = ops.filter((op) => isMoveVertexOp(op) && !this.knownOps.has(op.id.toString()));
@@ -876,7 +917,7 @@ var _RepTree = class _RepTree {
876
917
  this.applyMove(op);
877
918
  }
878
919
  }
879
- const propertyOps = ops.filter((op) => isSetPropertyOp(op) && !this.knownOps.has(op.id.toString()));
920
+ const propertyOps = ops.filter((op) => isAnyPropertyOp(op) && !this.knownOps.has(op.id.toString()));
880
921
  for (let i = 0, len = propertyOps.length; i < len; i++) {
881
922
  const op = propertyOps[i];
882
923
  this.applyProperty(op);
@@ -908,16 +949,16 @@ var _RepTree = class _RepTree {
908
949
  isAncestor(childId, ancestorId) {
909
950
  let targetId = childId;
910
951
  let vertex;
911
- let depth = 0;
952
+ const visitedVertices = /* @__PURE__ */ new Set();
912
953
  while (vertex = this.state.getVertex(targetId)) {
913
954
  if (vertex.parentId === ancestorId) return true;
914
955
  if (!vertex.parentId) return false;
915
- if (depth > this.maxDepth) {
916
- console.error(`isAncestor: max depth of ${this.maxDepth} reached. Perhaps, we have an infinite loop here.`);
917
- return true;
956
+ if (visitedVertices.has(targetId)) {
957
+ console.error(`isAncestor: cycle detected in the tree structure.`);
958
+ return false;
918
959
  }
960
+ visitedVertices.add(targetId);
919
961
  targetId = vertex.parentId;
920
- depth++;
921
962
  }
922
963
  return false;
923
964
  }
@@ -971,7 +1012,16 @@ var _RepTree = class _RepTree {
971
1012
  }
972
1013
  for (const propA of propertiesA) {
973
1014
  const propB = propertiesB.find((p) => p.key === propA.key);
974
- if (!propB || propA.value !== propB.value) {
1015
+ if (!propB) {
1016
+ return false;
1017
+ }
1018
+ if (propA.value instanceof Y.Doc && propB.value instanceof Y.Doc) {
1019
+ const snapshotA = Y.snapshot(propA.value);
1020
+ const snapshotB = Y.snapshot(propB.value);
1021
+ if (!Y.equalSnapshots(snapshotA, snapshotB)) {
1022
+ return false;
1023
+ }
1024
+ } else if (propA.value !== propB.value) {
975
1025
  return false;
976
1026
  }
977
1027
  }
@@ -1058,7 +1108,7 @@ var _RepTree = class _RepTree {
1058
1108
  }
1059
1109
  this.applyPendingMovesForParent(op.targetId);
1060
1110
  }
1061
- setPropertyAndItsOpId(op) {
1111
+ setLLWPropertyAndItsOpId(op) {
1062
1112
  this.propertiesAndTheirOpIds.set(`${op.key}@${op.targetId}`, op.id);
1063
1113
  this.state.setProperty(op.targetId, op.key, op.value);
1064
1114
  this.reportOpAsApplied(op);
@@ -1068,6 +1118,40 @@ var _RepTree = class _RepTree {
1068
1118
  this.state.setTransientProperty(op.targetId, op.key, op.value);
1069
1119
  this.reportOpAsApplied(op);
1070
1120
  }
1121
+ setupYjsObserver(doc, vertexId, key) {
1122
+ const propertyKey = `${key}@${vertexId}`;
1123
+ if (this.yjsObservers.has(propertyKey)) {
1124
+ const existingDoc = this.getVertexProperty(vertexId, key);
1125
+ if (existingDoc instanceof Y.Doc) {
1126
+ existingDoc.off("update", this.yjsObservers.get(propertyKey));
1127
+ }
1128
+ this.yjsObservers.delete(propertyKey);
1129
+ }
1130
+ const ydocObserver = (update, origin, doc2, transaction) => {
1131
+ if (!transaction.local) {
1132
+ return;
1133
+ }
1134
+ const crdtValue = {
1135
+ type: "yjs",
1136
+ value: update
1137
+ };
1138
+ this.lamportClock++;
1139
+ const op = newSetVertexPropertyOp(
1140
+ this.lamportClock,
1141
+ this.peerId,
1142
+ vertexId,
1143
+ key,
1144
+ crdtValue
1145
+ );
1146
+ this.localOps.push(op);
1147
+ this.applyProperty(op);
1148
+ if (this._stateVectorEnabled) {
1149
+ this.stateVector.updateFromOp(op);
1150
+ }
1151
+ };
1152
+ doc.on("update", ydocObserver);
1153
+ this.yjsObservers.set(propertyKey, ydocObserver);
1154
+ }
1071
1155
  applyProperty(op) {
1072
1156
  const targetVertex = this.state.getVertex(op.targetId);
1073
1157
  if (!targetVertex) {
@@ -1081,13 +1165,19 @@ var _RepTree = class _RepTree {
1081
1165
  return;
1082
1166
  }
1083
1167
  this.updateLamportClock(op);
1168
+ if (isModifyPropertyOp(op)) {
1169
+ this.applyModifyProperty(op, targetVertex);
1170
+ } else {
1171
+ this.applyLLWProperty(op, targetVertex);
1172
+ }
1173
+ }
1174
+ applyLLWProperty(op, targetVertex) {
1084
1175
  const prevTransientOpId = this.transientPropertiesAndTheirOpIds.get(`${op.key}@${op.targetId}`);
1085
- const prevProp = targetVertex.getProperty(op.key);
1086
1176
  const prevOpId = this.propertiesAndTheirOpIds.get(`${op.key}@${op.targetId}`);
1087
1177
  if (!op.transient) {
1088
1178
  this.setPropertyOps.push(op);
1089
- if (!prevProp || !prevOpId || op.id.isGreaterThan(prevOpId)) {
1090
- this.setPropertyAndItsOpId(op);
1179
+ if (!prevOpId || op.id.isGreaterThan(prevOpId)) {
1180
+ this.setLLWPropertyAndItsOpId(op);
1091
1181
  } else {
1092
1182
  this.knownOps.add(op.id.toString());
1093
1183
  }
@@ -1101,16 +1191,33 @@ var _RepTree = class _RepTree {
1101
1191
  }
1102
1192
  }
1103
1193
  }
1104
- applyOps(ops) {
1105
- for (const op of ops) {
1106
- if (this.knownOps.has(op.id.toString())) {
1107
- continue;
1108
- }
1109
- if (isMoveVertexOp(op)) {
1110
- this.applyMove(op);
1111
- } else if (isSetPropertyOp(op)) {
1112
- this.applyProperty(op);
1113
- }
1194
+ applyModifyProperty(op, targetVertex) {
1195
+ if (op.transient) {
1196
+ console.warn("Not implemented: transient non LWW property");
1197
+ return;
1198
+ }
1199
+ this.setPropertyOps.push(op);
1200
+ const crdtValue = op.value;
1201
+ if (crdtValue.type !== "yjs") {
1202
+ throw new Error("Unknown CRDT type");
1203
+ }
1204
+ const ydoc = targetVertex.getProperty(op.key);
1205
+ if (ydoc instanceof Y.Doc) {
1206
+ Y.applyUpdate(ydoc, crdtValue.value);
1207
+ } else {
1208
+ const newDoc = new Y.Doc();
1209
+ this.setupYjsObserver(newDoc, op.targetId, op.key);
1210
+ this.state.setProperty(op.targetId, op.key, newDoc);
1211
+ Y.applyUpdate(newDoc, crdtValue.value);
1212
+ }
1213
+ this.propertiesAndTheirOpIds.set(`${op.key}@${op.targetId}`, op.id);
1214
+ this.reportOpAsApplied(op);
1215
+ }
1216
+ applyOperation(op) {
1217
+ if (isMoveVertexOp(op)) {
1218
+ this.applyMove(op);
1219
+ } else if (isAnyPropertyOp(op)) {
1220
+ this.applyProperty(op);
1114
1221
  }
1115
1222
  }
1116
1223
  reportOpAsApplied(op) {
@@ -1209,7 +1316,6 @@ var _RepTree = class _RepTree {
1209
1316
  }
1210
1317
  };
1211
1318
  _RepTree.NULL_VERTEX_ID = "0";
1212
- _RepTree.DEFAULT_MAX_DEPTH = 1e5;
1213
1319
  var RepTree = _RepTree;
1214
1320
  export {
1215
1321
  OpId,
@@ -1218,8 +1324,10 @@ export {
1218
1324
  TreeState,
1219
1325
  Vertex,
1220
1326
  VertexState,
1327
+ isAnyPropertyOp,
1328
+ isLWWPropertyOp,
1329
+ isModifyPropertyOp,
1221
1330
  isMoveVertexOp,
1222
- isSetPropertyOp,
1223
1331
  newMoveVertexOp,
1224
1332
  newSetTransientVertexPropertyOp,
1225
1333
  newSetVertexPropertyOp,