reptree 0.2.1 → 0.2.3

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.js CHANGED
@@ -1,52 +1,49 @@
1
1
  // src/OpId.ts
2
- var OpId = class _OpId {
3
- constructor(counter, peerId) {
4
- this.counter = counter;
5
- this.peerId = peerId;
6
- }
7
- static compare(opIdA, opIdB) {
8
- if (!(opIdA instanceof _OpId)) {
9
- const parsedA = _OpId.tryParseStr(opIdA);
10
- if (!parsedA) throw new Error(`Invalid OpId string: ${opIdA}`);
11
- opIdA = parsedA;
12
- }
13
- if (!(opIdB instanceof _OpId)) {
14
- const parsedB = _OpId.tryParseStr(opIdB);
15
- if (!parsedB) throw new Error(`Invalid OpId string: ${opIdB}`);
16
- opIdB = parsedB;
17
- }
18
- const counterA = opIdA.counter;
19
- const counterB = opIdB.counter;
20
- if (counterA > counterB) {
21
- return 1;
22
- } else if (counterA < counterB) {
23
- return -1;
24
- } else {
25
- return opIdA.peerId.localeCompare(opIdB.peerId);
26
- }
27
- }
28
- static equals(opIdA, opIdB) {
29
- if (opIdA === opIdB) {
30
- return true;
31
- } else if (!opIdA || !opIdB) {
32
- return false;
33
- }
34
- return _OpId.compare(opIdA, opIdB) === 0;
35
- }
36
- static tryParseStr(opIdStr) {
37
- const parts = opIdStr.split("@");
38
- if (parts.length !== 2) {
39
- throw new Error(`Invalid OpId string: ${opIdStr}`);
40
- }
41
- return new _OpId(parseInt(parts[0], 10), parts[1]);
2
+ function createOpId(counter, peerId) {
3
+ return { counter, peerId };
4
+ }
5
+ function compareOpId(opIdA, opIdB) {
6
+ if (typeof opIdA === "string") {
7
+ const parsedA = tryParseOpIdStr(opIdA);
8
+ if (!parsedA) throw new Error(`Invalid OpId string: ${opIdA}`);
9
+ opIdA = parsedA;
10
+ }
11
+ if (typeof opIdB === "string") {
12
+ const parsedB = tryParseOpIdStr(opIdB);
13
+ if (!parsedB) throw new Error(`Invalid OpId string: ${opIdB}`);
14
+ opIdB = parsedB;
15
+ }
16
+ const counterA = opIdA.counter;
17
+ const counterB = opIdB.counter;
18
+ if (counterA > counterB) {
19
+ return 1;
20
+ } else if (counterA < counterB) {
21
+ return -1;
22
+ } else {
23
+ return opIdA.peerId.localeCompare(opIdB.peerId);
42
24
  }
43
- isGreaterThan(opId) {
44
- return _OpId.compare(this, opId) === 1;
25
+ }
26
+ function equalsOpId(opIdA, opIdB) {
27
+ if (opIdA === opIdB) {
28
+ return true;
29
+ } else if (!opIdA || !opIdB) {
30
+ return false;
45
31
  }
46
- toString() {
47
- return `${this.counter}@${this.peerId}`;
32
+ return compareOpId(opIdA, opIdB) === 0;
33
+ }
34
+ function tryParseOpIdStr(opIdStr) {
35
+ const parts = opIdStr.split("@");
36
+ if (parts.length !== 2) {
37
+ throw new Error(`Invalid OpId string: ${opIdStr}`);
48
38
  }
49
- };
39
+ return createOpId(parseInt(parts[0], 10), parts[1]);
40
+ }
41
+ function isOpIdGreaterThan(opIdA, opIdB) {
42
+ return compareOpId(opIdA, opIdB) === 1;
43
+ }
44
+ function opIdToString(opId) {
45
+ return `${opId.counter}@${opId.peerId}`;
46
+ }
50
47
 
51
48
  // src/operations.ts
52
49
  function isMoveVertexOp(op) {
@@ -62,13 +59,13 @@ function isModifyPropertyOp(op) {
62
59
  return "key" in op && "value" in op && typeof op.value === "object" && op.value !== null && "type" in op.value;
63
60
  }
64
61
  function newMoveVertexOp(clock, peerId, targetId, parentId) {
65
- return { id: new OpId(clock, peerId), targetId, parentId };
62
+ return { id: createOpId(clock, peerId), targetId, parentId };
66
63
  }
67
64
  function newSetVertexPropertyOp(clock, peerId, targetId, key, value) {
68
- return { id: new OpId(clock, peerId), targetId, key, value, transient: false };
65
+ return { id: createOpId(clock, peerId), targetId, key, value, transient: false };
69
66
  }
70
67
  function newSetTransientVertexPropertyOp(clock, peerId, targetId, key, value) {
71
- return { id: new OpId(clock, peerId), targetId, key, value, transient: true };
68
+ return { id: createOpId(clock, peerId), targetId, key, value, transient: true };
72
69
  }
73
70
 
74
71
  // src/VertexState.ts
@@ -357,6 +354,57 @@ function uuid() {
357
354
  return removeDashes(crypto.randomUUID());
358
355
  }
359
356
 
357
+ // src/reactive.ts
358
+ function toObject(tree, id) {
359
+ const obj = {};
360
+ for (const { key, value } of tree.getVertexProperties(id)) obj[key] = value;
361
+ return obj;
362
+ }
363
+ function bindVertex(tree, id, schema) {
364
+ return new Proxy({}, {
365
+ get(_target, prop) {
366
+ if (typeof prop !== "string") return void 0;
367
+ return tree.getVertexProperty(id, prop);
368
+ },
369
+ set(_target, prop, value) {
370
+ if (typeof prop !== "string") return true;
371
+ if (schema?.shape && schema.shape[prop]) {
372
+ const field = schema.shape[prop];
373
+ if (field.safeParse) {
374
+ const res = field.safeParse(value);
375
+ if (!res.success) throw new Error(`Invalid value for ${String(prop)}`);
376
+ value = res.data ?? value;
377
+ }
378
+ } else if (schema?.safeParse) {
379
+ const next = { ...toObject(tree, id), [prop]: value };
380
+ const res = schema.safeParse(next);
381
+ if (!res.success) throw new Error(`Invalid value for ${String(prop)}`);
382
+ const parsed = res.data;
383
+ if (parsed && Object.prototype.hasOwnProperty.call(parsed, prop)) {
384
+ value = parsed[prop];
385
+ }
386
+ }
387
+ tree.setVertexProperty(id, prop, value);
388
+ return true;
389
+ },
390
+ deleteProperty(_target, prop) {
391
+ if (typeof prop !== "string") return true;
392
+ tree.setVertexProperty(id, prop, void 0);
393
+ return true;
394
+ },
395
+ has(_target, prop) {
396
+ if (typeof prop !== "string") return false;
397
+ return !!schema?.shape && Object.prototype.hasOwnProperty.call(schema.shape, prop);
398
+ },
399
+ ownKeys() {
400
+ return Object.keys(schema?.shape ?? {});
401
+ },
402
+ getOwnPropertyDescriptor() {
403
+ return { enumerable: true, configurable: true };
404
+ }
405
+ });
406
+ }
407
+
360
408
  // src/Vertex.ts
361
409
  var Vertex = class {
362
410
  constructor(tree, state) {
@@ -475,6 +523,10 @@ var Vertex = class {
475
523
  moveTo(parent) {
476
524
  this.tree.moveVertex(this.id, parent.id);
477
525
  }
526
+ /** Returns a live reactive object bound to this vertex. Optional schema validates writes. */
527
+ bind(schema) {
528
+ return bindVertex(this.tree, this.id, schema);
529
+ }
478
530
  };
479
531
 
480
532
  // src/StateVector.ts
@@ -900,7 +952,7 @@ var _RepTree = class _RepTree {
900
952
  }
901
953
  applyOps(ops) {
902
954
  for (const op of ops) {
903
- if (this.knownOps.has(op.id.toString())) {
955
+ if (this.knownOps.has(opIdToString(op.id))) {
904
956
  continue;
905
957
  }
906
958
  this.applyOperation(op);
@@ -908,16 +960,16 @@ var _RepTree = class _RepTree {
908
960
  }
909
961
  /** Applies operations in an optimized way, sorting move ops by OpId to avoid undo-do-redo cycles */
910
962
  applyOpsOptimizedForLotsOfMoves(ops) {
911
- const newMoveOps = ops.filter((op) => isMoveVertexOp(op) && !this.knownOps.has(op.id.toString()));
963
+ const newMoveOps = ops.filter((op) => isMoveVertexOp(op) && !this.knownOps.has(opIdToString(op.id)));
912
964
  if (newMoveOps.length > 0) {
913
965
  const allMoveOps = [...this.moveOps, ...newMoveOps];
914
- allMoveOps.sort((a, b) => OpId.compare(a.id, b.id));
966
+ allMoveOps.sort((a, b) => compareOpId(a.id, b.id));
915
967
  for (let i = 0, len = allMoveOps.length; i < len; i++) {
916
968
  const op = allMoveOps[i];
917
969
  this.applyMove(op);
918
970
  }
919
971
  }
920
- const propertyOps = ops.filter((op) => isAnyPropertyOp(op) && !this.knownOps.has(op.id.toString()));
972
+ const propertyOps = ops.filter((op) => isAnyPropertyOp(op) && !this.knownOps.has(opIdToString(op.id)));
921
973
  for (let i = 0, len = propertyOps.length; i < len; i++) {
922
974
  const op = propertyOps[i];
923
975
  this.applyProperty(op);
@@ -939,7 +991,7 @@ var _RepTree = class _RepTree {
939
991
  return false;
940
992
  }
941
993
  for (let i = 0; i < movesA.length; i++) {
942
- if (!OpId.equals(movesA[i].id, movesB[i].id)) {
994
+ if (!equalsOpId(movesA[i].id, movesB[i].id)) {
943
995
  return false;
944
996
  }
945
997
  }
@@ -1084,7 +1136,7 @@ var _RepTree = class _RepTree {
1084
1136
  }
1085
1137
  this.updateLamportClock(op);
1086
1138
  const lastOp = this.moveOps.length > 0 ? this.moveOps[this.moveOps.length - 1] : null;
1087
- if (lastOp === null || op.id.isGreaterThan(lastOp.id)) {
1139
+ if (lastOp === null || isOpIdGreaterThan(op.id, lastOp.id)) {
1088
1140
  this.moveOps.push(op);
1089
1141
  this.reportOpAsApplied(op);
1090
1142
  this.tryToMove(op);
@@ -1093,7 +1145,7 @@ var _RepTree = class _RepTree {
1093
1145
  for (let i = this.moveOps.length - 1; i >= 0; i--) {
1094
1146
  const moveOp = this.moveOps[i];
1095
1147
  targetIndex = i;
1096
- if (op.id.isGreaterThan(moveOp.id)) {
1148
+ if (isOpIdGreaterThan(op.id, moveOp.id)) {
1097
1149
  break;
1098
1150
  } else {
1099
1151
  this.undoMove(moveOp);
@@ -1176,17 +1228,17 @@ var _RepTree = class _RepTree {
1176
1228
  const prevOpId = this.propertiesAndTheirOpIds.get(`${op.key}@${op.targetId}`);
1177
1229
  if (!op.transient) {
1178
1230
  this.setPropertyOps.push(op);
1179
- if (!prevOpId || op.id.isGreaterThan(prevOpId)) {
1231
+ if (!prevOpId || isOpIdGreaterThan(op.id, prevOpId)) {
1180
1232
  this.setLLWPropertyAndItsOpId(op);
1181
1233
  } else {
1182
- this.knownOps.add(op.id.toString());
1234
+ this.knownOps.add(opIdToString(op.id));
1183
1235
  }
1184
- if (prevTransientOpId && op.id.isGreaterThan(prevTransientOpId)) {
1236
+ if (prevTransientOpId && isOpIdGreaterThan(op.id, prevTransientOpId)) {
1185
1237
  this.transientPropertiesAndTheirOpIds.delete(`${op.key}@${op.targetId}`);
1186
1238
  targetVertex.removeTransientProperty(op.key);
1187
1239
  }
1188
1240
  } else {
1189
- if (!prevTransientOpId || op.id.isGreaterThan(prevTransientOpId)) {
1241
+ if (!prevTransientOpId || isOpIdGreaterThan(op.id, prevTransientOpId)) {
1190
1242
  this.setTransientPropertyAndItsOpId(op);
1191
1243
  }
1192
1244
  }
@@ -1221,7 +1273,7 @@ var _RepTree = class _RepTree {
1221
1273
  }
1222
1274
  }
1223
1275
  reportOpAsApplied(op) {
1224
- this.knownOps.add(op.id.toString());
1276
+ this.knownOps.add(opIdToString(op.id));
1225
1277
  if (this._stateVectorEnabled) {
1226
1278
  this.stateVector.updateFromOp(op);
1227
1279
  }
@@ -1248,7 +1300,7 @@ var _RepTree = class _RepTree {
1248
1300
  undoMove(op) {
1249
1301
  const targetVertex = this.state.getVertex(op.targetId);
1250
1302
  if (!targetVertex) {
1251
- console.error(`An attempt to undo move operation ${op.id.toString()} failed because the target vertex ${op.targetId} not found`);
1303
+ console.error(`An attempt to undo move operation ${opIdToString(op.id)} failed because the target vertex ${op.targetId} not found`);
1252
1304
  return;
1253
1305
  }
1254
1306
  const prevParentId = this.parentIdBeforeMove.get(op.id);
@@ -1291,7 +1343,7 @@ var _RepTree = class _RepTree {
1291
1343
  }
1292
1344
  }
1293
1345
  }
1294
- missingOps.sort((a, b) => OpId.compare(a.id, b.id));
1346
+ missingOps.sort((a, b) => compareOpId(a.id, b.id));
1295
1347
  return missingOps;
1296
1348
  }
1297
1349
  /**
@@ -1314,16 +1366,27 @@ var _RepTree = class _RepTree {
1314
1366
  this.stateVector = new StateVector();
1315
1367
  }
1316
1368
  }
1369
+ /**
1370
+ * Parses the vertex properties with a provided schema that has a `parse` method (e.g., Zod schema)
1371
+ */
1372
+ parseVertex(vertexId, schema) {
1373
+ const propsArray = this.getVertexProperties(vertexId);
1374
+ const propsObject = {};
1375
+ for (const { key, value } of propsArray) {
1376
+ propsObject[key] = value;
1377
+ }
1378
+ return schema.parse(propsObject);
1379
+ }
1317
1380
  };
1318
1381
  _RepTree.NULL_VERTEX_ID = "0";
1319
1382
  var RepTree = _RepTree;
1320
1383
  export {
1321
- OpId,
1322
1384
  RepTree,
1323
1385
  StateVector,
1324
1386
  TreeState,
1325
1387
  Vertex,
1326
1388
  VertexState,
1389
+ bindVertex,
1327
1390
  isAnyPropertyOp,
1328
1391
  isLWWPropertyOp,
1329
1392
  isModifyPropertyOp,