reptree 1.0.1 → 1.0.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 +95 -80
- package/dist/index.cjs +93 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -5
- package/dist/index.d.ts +22 -5
- package/dist/index.js +93 -50
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -322,8 +322,6 @@ declare class RepTree {
|
|
|
322
322
|
printTree(): string;
|
|
323
323
|
merge(ops: ReadonlyArray<NodeOperation>): void;
|
|
324
324
|
private applyOps;
|
|
325
|
-
/** Applies operations in an optimized way, sorting move ops by OpId to avoid undo-do-redo cycles */
|
|
326
|
-
private applyOpsOptimizedForLotsOfMoves;
|
|
327
325
|
compareStructure(other: RepTree): boolean;
|
|
328
326
|
compareMoveOps(other: RepTree): boolean;
|
|
329
327
|
/** Checks whether moving `targetId` under `parentId` would create a cycle. */
|
|
@@ -354,12 +352,14 @@ declare class RepTree {
|
|
|
354
352
|
private applyLLWProperty;
|
|
355
353
|
private applyOperation;
|
|
356
354
|
private markOpSeen;
|
|
355
|
+
private reportMoveOpAsApplied;
|
|
356
|
+
private reportPropertyOpAsApplied;
|
|
357
357
|
private reportOpAsApplied;
|
|
358
358
|
private tryToMove;
|
|
359
359
|
private undoMove;
|
|
360
360
|
/**
|
|
361
361
|
* Returns the current state vectors for move and property streams.
|
|
362
|
-
* Returns
|
|
362
|
+
* Returns copies of the internal state vectors.
|
|
363
363
|
*/
|
|
364
364
|
getStateVectors(): {
|
|
365
365
|
move: Readonly<StateVectors["move"]>;
|
|
@@ -389,8 +389,9 @@ declare class RepTree {
|
|
|
389
389
|
parse: (data: unknown) => T;
|
|
390
390
|
}): T;
|
|
391
391
|
private getPropertyOps;
|
|
392
|
-
private
|
|
392
|
+
private recordRetainedPropertyOpInStateVector;
|
|
393
393
|
private getOpKey;
|
|
394
|
+
private getPropertyKey;
|
|
394
395
|
private filterOpsByRanges;
|
|
395
396
|
}
|
|
396
397
|
|
|
@@ -445,9 +446,25 @@ declare class StateVector {
|
|
|
445
446
|
* @param op The operation that was just applied
|
|
446
447
|
*/
|
|
447
448
|
updateFromOp(op: NodeOperation): void;
|
|
449
|
+
/**
|
|
450
|
+
* Removes a single operation ID from the state vector.
|
|
451
|
+
* Assumes ranges are sorted and non-overlapping.
|
|
452
|
+
*
|
|
453
|
+
* @param peerId The peer ID of the operation
|
|
454
|
+
* @param counter The counter value of the operation
|
|
455
|
+
* @returns true if a counter was removed, false if it was absent
|
|
456
|
+
*/
|
|
457
|
+
remove(peerId: string, counter: number): boolean;
|
|
458
|
+
/**
|
|
459
|
+
* Removes the operation ID from the state vector.
|
|
460
|
+
*
|
|
461
|
+
* @param op The operation to remove
|
|
462
|
+
* @returns true if the operation ID was removed, false if it was absent
|
|
463
|
+
*/
|
|
464
|
+
removeFromOp(op: NodeOperation): boolean;
|
|
448
465
|
/**
|
|
449
466
|
* Returns the current state vector.
|
|
450
|
-
* Returns a
|
|
467
|
+
* Returns a deep copy so callers cannot mutate internal state.
|
|
451
468
|
*/
|
|
452
469
|
getState(): Readonly<Record<string, number[][]>>;
|
|
453
470
|
/**
|
package/dist/index.js
CHANGED
|
@@ -750,12 +750,63 @@ var StateVector = class _StateVector {
|
|
|
750
750
|
updateFromOp(op) {
|
|
751
751
|
this.update(op.id.peerId, op.id.counter);
|
|
752
752
|
}
|
|
753
|
+
/**
|
|
754
|
+
* Removes a single operation ID from the state vector.
|
|
755
|
+
* Assumes ranges are sorted and non-overlapping.
|
|
756
|
+
*
|
|
757
|
+
* @param peerId The peer ID of the operation
|
|
758
|
+
* @param counter The counter value of the operation
|
|
759
|
+
* @returns true if a counter was removed, false if it was absent
|
|
760
|
+
*/
|
|
761
|
+
remove(peerId, counter) {
|
|
762
|
+
const ranges = this.ranges[peerId];
|
|
763
|
+
if (!ranges) {
|
|
764
|
+
return false;
|
|
765
|
+
}
|
|
766
|
+
for (let i = 0; i < ranges.length; i++) {
|
|
767
|
+
const range = ranges[i];
|
|
768
|
+
const [start, end] = range;
|
|
769
|
+
if (counter < start) {
|
|
770
|
+
return false;
|
|
771
|
+
}
|
|
772
|
+
if (counter > end) {
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
775
|
+
if (start === end) {
|
|
776
|
+
ranges.splice(i, 1);
|
|
777
|
+
} else if (counter === start) {
|
|
778
|
+
range[0] = start + 1;
|
|
779
|
+
} else if (counter === end) {
|
|
780
|
+
range[1] = end - 1;
|
|
781
|
+
} else {
|
|
782
|
+
ranges.splice(i, 1, [start, counter - 1], [counter + 1, end]);
|
|
783
|
+
}
|
|
784
|
+
if (ranges.length === 0) {
|
|
785
|
+
delete this.ranges[peerId];
|
|
786
|
+
}
|
|
787
|
+
return true;
|
|
788
|
+
}
|
|
789
|
+
return false;
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Removes the operation ID from the state vector.
|
|
793
|
+
*
|
|
794
|
+
* @param op The operation to remove
|
|
795
|
+
* @returns true if the operation ID was removed, false if it was absent
|
|
796
|
+
*/
|
|
797
|
+
removeFromOp(op) {
|
|
798
|
+
return this.remove(op.id.peerId, op.id.counter);
|
|
799
|
+
}
|
|
753
800
|
/**
|
|
754
801
|
* Returns the current state vector.
|
|
755
|
-
* Returns a
|
|
802
|
+
* Returns a deep copy so callers cannot mutate internal state.
|
|
756
803
|
*/
|
|
757
804
|
getState() {
|
|
758
|
-
|
|
805
|
+
const state = {};
|
|
806
|
+
for (const [peerId, peerRanges] of Object.entries(this.ranges)) {
|
|
807
|
+
state[peerId] = peerRanges.map((range) => [...range]);
|
|
808
|
+
}
|
|
809
|
+
return state;
|
|
759
810
|
}
|
|
760
811
|
/**
|
|
761
812
|
* Calculates which operation ranges we have that the other state vector is missing
|
|
@@ -766,9 +817,8 @@ var StateVector = class _StateVector {
|
|
|
766
817
|
*/
|
|
767
818
|
diff(other) {
|
|
768
819
|
const missingRanges = [];
|
|
769
|
-
const theirState = other.getState();
|
|
770
820
|
for (const [peerId, ourRanges] of Object.entries(this.ranges)) {
|
|
771
|
-
const theirRanges =
|
|
821
|
+
const theirRanges = other.ranges[peerId] || [];
|
|
772
822
|
const missing = subtractRanges(ourRanges, theirRanges);
|
|
773
823
|
for (const [start, end] of missing) {
|
|
774
824
|
if (start <= end) {
|
|
@@ -1138,23 +1188,6 @@ var _RepTree = class _RepTree {
|
|
|
1138
1188
|
this.applyOperation(op);
|
|
1139
1189
|
}
|
|
1140
1190
|
}
|
|
1141
|
-
/** Applies operations in an optimized way, sorting move ops by OpId to avoid undo-do-redo cycles */
|
|
1142
|
-
applyOpsOptimizedForLotsOfMoves(ops) {
|
|
1143
|
-
const newMoveOps = ops.filter((op) => isMoveNodeOp(op) && !this.knownOps.has(this.getOpKey(op)));
|
|
1144
|
-
if (newMoveOps.length > 0) {
|
|
1145
|
-
const allMoveOps = [...this.moveOps, ...newMoveOps];
|
|
1146
|
-
allMoveOps.sort((a, b) => compareOpId(a.id, b.id));
|
|
1147
|
-
for (let i = 0, len = allMoveOps.length; i < len; i++) {
|
|
1148
|
-
const op = allMoveOps[i];
|
|
1149
|
-
this.applyMove(op);
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
const propertyOps = ops.filter((op) => isAnyPropertyOp(op) && !this.knownOps.has(this.getOpKey(op)));
|
|
1153
|
-
for (let i = 0, len = propertyOps.length; i < len; i++) {
|
|
1154
|
-
const op = propertyOps[i];
|
|
1155
|
-
this.applyProperty(op);
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
1191
|
compareStructure(other) {
|
|
1159
1192
|
if (this.root?.id !== other.root?.id) {
|
|
1160
1193
|
return false;
|
|
@@ -1320,14 +1353,14 @@ var _RepTree = class _RepTree {
|
|
|
1320
1353
|
this.pendingMovesWithMissingParent.set(op.parentId, []);
|
|
1321
1354
|
}
|
|
1322
1355
|
this.pendingMovesWithMissingParent.get(op.parentId).push(op);
|
|
1323
|
-
this.markOpSeen(op
|
|
1356
|
+
this.markOpSeen(op);
|
|
1324
1357
|
return;
|
|
1325
1358
|
}
|
|
1326
1359
|
this.updateMoveClock(op);
|
|
1327
1360
|
const lastOp = this.moveOps.length > 0 ? this.moveOps[this.moveOps.length - 1] : null;
|
|
1328
1361
|
if (lastOp === null || isOpIdGreaterThan(op.id, lastOp.id)) {
|
|
1329
1362
|
this.moveOps.push(op);
|
|
1330
|
-
this.
|
|
1363
|
+
this.reportMoveOpAsApplied(op);
|
|
1331
1364
|
this.tryToMove(op);
|
|
1332
1365
|
} else {
|
|
1333
1366
|
let targetIndex = this.moveOps.length;
|
|
@@ -1341,7 +1374,7 @@ var _RepTree = class _RepTree {
|
|
|
1341
1374
|
}
|
|
1342
1375
|
}
|
|
1343
1376
|
this.moveOps.splice(targetIndex + 1, 0, op);
|
|
1344
|
-
this.
|
|
1377
|
+
this.reportMoveOpAsApplied(op);
|
|
1345
1378
|
this.tryToMove(op);
|
|
1346
1379
|
for (let i = targetIndex + 2; i < this.moveOps.length; i++) {
|
|
1347
1380
|
this.tryToMove(this.moveOps[i]);
|
|
@@ -1349,16 +1382,16 @@ var _RepTree = class _RepTree {
|
|
|
1349
1382
|
}
|
|
1350
1383
|
this.applyPendingMovesForParent(op.targetId);
|
|
1351
1384
|
}
|
|
1352
|
-
setLLWPropertyAndItsOpId(op) {
|
|
1353
|
-
this.propertyOpsByKey.set(
|
|
1385
|
+
setLLWPropertyAndItsOpId(op, previousOp) {
|
|
1386
|
+
this.propertyOpsByKey.set(this.getPropertyKey(op), op);
|
|
1354
1387
|
this.state.setProperty(op.targetId, op.key, op.value);
|
|
1355
|
-
this.
|
|
1356
|
-
this.
|
|
1388
|
+
this.recordRetainedPropertyOpInStateVector(op, previousOp);
|
|
1389
|
+
this.reportPropertyOpAsApplied(op);
|
|
1357
1390
|
}
|
|
1358
1391
|
setTransientPropertyAndItsOpId(op) {
|
|
1359
|
-
this.transientPropertiesAndTheirOpIds.set(
|
|
1392
|
+
this.transientPropertiesAndTheirOpIds.set(this.getPropertyKey(op), op.id);
|
|
1360
1393
|
this.state.setTransientProperty(op.targetId, op.key, op.value);
|
|
1361
|
-
this.
|
|
1394
|
+
this.reportPropertyOpAsApplied(op);
|
|
1362
1395
|
}
|
|
1363
1396
|
applyProperty(op) {
|
|
1364
1397
|
const targetNode = this.state.getNode(op.targetId);
|
|
@@ -1370,30 +1403,31 @@ var _RepTree = class _RepTree {
|
|
|
1370
1403
|
this.pendingPropertiesWithMissingNode.set(op.targetId, []);
|
|
1371
1404
|
}
|
|
1372
1405
|
this.pendingPropertiesWithMissingNode.get(op.targetId).push(op);
|
|
1373
|
-
this.markOpSeen(op
|
|
1406
|
+
this.markOpSeen(op);
|
|
1374
1407
|
return;
|
|
1375
1408
|
}
|
|
1376
1409
|
this.updatePropClock(op);
|
|
1377
1410
|
this.applyLLWProperty(op, targetNode);
|
|
1378
1411
|
}
|
|
1379
1412
|
applyLLWProperty(op, targetNode) {
|
|
1380
|
-
const
|
|
1381
|
-
const
|
|
1413
|
+
const propertyKey = this.getPropertyKey(op);
|
|
1414
|
+
const prevTransientOpId = this.transientPropertiesAndTheirOpIds.get(propertyKey);
|
|
1415
|
+
const previousOp = this.propertyOpsByKey.get(propertyKey);
|
|
1382
1416
|
if (!op.transient) {
|
|
1383
|
-
if (!
|
|
1384
|
-
this.setLLWPropertyAndItsOpId(op);
|
|
1417
|
+
if (!previousOp || isOpIdGreaterThan(op.id, previousOp.id)) {
|
|
1418
|
+
this.setLLWPropertyAndItsOpId(op, previousOp);
|
|
1385
1419
|
} else {
|
|
1386
|
-
this.markOpSeen(op
|
|
1420
|
+
this.markOpSeen(op);
|
|
1387
1421
|
}
|
|
1388
1422
|
if (prevTransientOpId && isOpIdGreaterThan(op.id, prevTransientOpId)) {
|
|
1389
|
-
this.transientPropertiesAndTheirOpIds.delete(
|
|
1423
|
+
this.transientPropertiesAndTheirOpIds.delete(propertyKey);
|
|
1390
1424
|
targetNode.removeTransientProperty(op.key);
|
|
1391
1425
|
}
|
|
1392
1426
|
} else {
|
|
1393
1427
|
if (!prevTransientOpId || isOpIdGreaterThan(op.id, prevTransientOpId)) {
|
|
1394
1428
|
this.setTransientPropertyAndItsOpId(op);
|
|
1395
1429
|
} else {
|
|
1396
|
-
this.markOpSeen(op
|
|
1430
|
+
this.markOpSeen(op);
|
|
1397
1431
|
}
|
|
1398
1432
|
}
|
|
1399
1433
|
}
|
|
@@ -1404,18 +1438,21 @@ var _RepTree = class _RepTree {
|
|
|
1404
1438
|
this.applyProperty(op);
|
|
1405
1439
|
}
|
|
1406
1440
|
}
|
|
1407
|
-
markOpSeen(op
|
|
1441
|
+
markOpSeen(op) {
|
|
1408
1442
|
this.knownOps.add(this.getOpKey(op));
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
}
|
|
1443
|
+
}
|
|
1444
|
+
reportMoveOpAsApplied(op) {
|
|
1445
|
+
this.markOpSeen(op);
|
|
1446
|
+
if (this._stateVectorEnabled) {
|
|
1447
|
+
this.moveStateVector.updateFromOp(op);
|
|
1415
1448
|
}
|
|
1449
|
+
this.reportOpAsApplied(op);
|
|
1450
|
+
}
|
|
1451
|
+
reportPropertyOpAsApplied(op) {
|
|
1452
|
+
this.markOpSeen(op);
|
|
1453
|
+
this.reportOpAsApplied(op);
|
|
1416
1454
|
}
|
|
1417
|
-
reportOpAsApplied(op
|
|
1418
|
-
this.markOpSeen(op, includeInStateVector);
|
|
1455
|
+
reportOpAsApplied(op) {
|
|
1419
1456
|
for (const callback of this.opAppliedCallbacks) {
|
|
1420
1457
|
callback(op);
|
|
1421
1458
|
}
|
|
@@ -1450,7 +1487,7 @@ var _RepTree = class _RepTree {
|
|
|
1450
1487
|
// --- Range-Based State Vector Methods ---
|
|
1451
1488
|
/**
|
|
1452
1489
|
* Returns the current state vectors for move and property streams.
|
|
1453
|
-
* Returns
|
|
1490
|
+
* Returns copies of the internal state vectors.
|
|
1454
1491
|
*/
|
|
1455
1492
|
getStateVectors() {
|
|
1456
1493
|
if (!this._stateVectorEnabled) {
|
|
@@ -1518,16 +1555,22 @@ var _RepTree = class _RepTree {
|
|
|
1518
1555
|
getPropertyOps() {
|
|
1519
1556
|
return Array.from(this.propertyOpsByKey.values());
|
|
1520
1557
|
}
|
|
1521
|
-
|
|
1558
|
+
recordRetainedPropertyOpInStateVector(op, previousOp) {
|
|
1522
1559
|
if (!this._stateVectorEnabled) {
|
|
1523
1560
|
return;
|
|
1524
1561
|
}
|
|
1525
|
-
|
|
1562
|
+
if (previousOp) {
|
|
1563
|
+
this.propStateVector.removeFromOp(previousOp);
|
|
1564
|
+
}
|
|
1565
|
+
this.propStateVector.updateFromOp(op);
|
|
1526
1566
|
}
|
|
1527
1567
|
getOpKey(op) {
|
|
1528
1568
|
const stream = isMoveNodeOp(op) ? "move" : "prop";
|
|
1529
1569
|
return `${stream}:${opIdToString(op.id)}`;
|
|
1530
1570
|
}
|
|
1571
|
+
getPropertyKey(op) {
|
|
1572
|
+
return `${op.key}@${op.targetId}`;
|
|
1573
|
+
}
|
|
1531
1574
|
filterOpsByRanges(ops, ranges) {
|
|
1532
1575
|
const missingOps = [];
|
|
1533
1576
|
for (const op of ops) {
|