@fluidframework/merge-tree 2.3.1 → 2.4.0-297027

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.
Files changed (118) hide show
  1. package/api-report/merge-tree.legacy.alpha.api.md +26 -7
  2. package/dist/attributionPolicy.d.ts.map +1 -1
  3. package/dist/attributionPolicy.js +10 -3
  4. package/dist/attributionPolicy.js.map +1 -1
  5. package/dist/client.d.ts +14 -4
  6. package/dist/client.d.ts.map +1 -1
  7. package/dist/client.js +97 -10
  8. package/dist/client.js.map +1 -1
  9. package/dist/index.d.ts +1 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/legacy.d.ts +1 -0
  13. package/dist/mergeTree.d.ts +15 -2
  14. package/dist/mergeTree.d.ts.map +1 -1
  15. package/dist/mergeTree.js +68 -48
  16. package/dist/mergeTree.js.map +1 -1
  17. package/dist/mergeTreeNodes.d.ts +6 -9
  18. package/dist/mergeTreeNodes.d.ts.map +1 -1
  19. package/dist/mergeTreeNodes.js +2 -1
  20. package/dist/mergeTreeNodes.js.map +1 -1
  21. package/dist/opBuilder.d.ts +15 -1
  22. package/dist/opBuilder.d.ts.map +1 -1
  23. package/dist/opBuilder.js +28 -1
  24. package/dist/opBuilder.js.map +1 -1
  25. package/dist/ops.d.ts +27 -1
  26. package/dist/ops.d.ts.map +1 -1
  27. package/dist/ops.js +1 -0
  28. package/dist/ops.js.map +1 -1
  29. package/dist/sequencePlace.d.ts +4 -0
  30. package/dist/sequencePlace.d.ts.map +1 -1
  31. package/dist/sequencePlace.js +17 -1
  32. package/dist/sequencePlace.js.map +1 -1
  33. package/dist/test/obliterate.concurrent.spec.js +18 -0
  34. package/dist/test/obliterate.concurrent.spec.js.map +1 -1
  35. package/dist/test/obliterate.partialLength.spec.js +8 -4
  36. package/dist/test/obliterate.partialLength.spec.js.map +1 -1
  37. package/dist/test/obliterate.rangeExpansion.spec.js +109 -53
  38. package/dist/test/obliterate.rangeExpansion.spec.js.map +1 -1
  39. package/dist/test/obliterate.spec.js +14 -8
  40. package/dist/test/obliterate.spec.js.map +1 -1
  41. package/dist/test/reconnectHelper.d.ts +8 -6
  42. package/dist/test/reconnectHelper.d.ts.map +1 -1
  43. package/dist/test/reconnectHelper.js +14 -13
  44. package/dist/test/reconnectHelper.js.map +1 -1
  45. package/dist/test/testClient.d.ts +1 -11
  46. package/dist/test/testClient.d.ts.map +1 -1
  47. package/dist/test/testClient.js +0 -3
  48. package/dist/test/testClient.js.map +1 -1
  49. package/dist/test/testClientLogger.d.ts.map +1 -1
  50. package/dist/test/testClientLogger.js +19 -8
  51. package/dist/test/testClientLogger.js.map +1 -1
  52. package/dist/test/testUtils.d.ts +10 -0
  53. package/dist/test/testUtils.d.ts.map +1 -1
  54. package/dist/test/testUtils.js +5 -1
  55. package/dist/test/testUtils.js.map +1 -1
  56. package/lib/attributionPolicy.d.ts.map +1 -1
  57. package/lib/attributionPolicy.js +10 -3
  58. package/lib/attributionPolicy.js.map +1 -1
  59. package/lib/client.d.ts +14 -4
  60. package/lib/client.d.ts.map +1 -1
  61. package/lib/client.js +98 -11
  62. package/lib/client.js.map +1 -1
  63. package/lib/index.d.ts +1 -1
  64. package/lib/index.d.ts.map +1 -1
  65. package/lib/index.js.map +1 -1
  66. package/lib/legacy.d.ts +1 -0
  67. package/lib/mergeTree.d.ts +15 -2
  68. package/lib/mergeTree.d.ts.map +1 -1
  69. package/lib/mergeTree.js +69 -49
  70. package/lib/mergeTree.js.map +1 -1
  71. package/lib/mergeTreeNodes.d.ts +6 -9
  72. package/lib/mergeTreeNodes.d.ts.map +1 -1
  73. package/lib/mergeTreeNodes.js +2 -1
  74. package/lib/mergeTreeNodes.js.map +1 -1
  75. package/lib/opBuilder.d.ts +15 -1
  76. package/lib/opBuilder.d.ts.map +1 -1
  77. package/lib/opBuilder.js +26 -0
  78. package/lib/opBuilder.js.map +1 -1
  79. package/lib/ops.d.ts +27 -1
  80. package/lib/ops.d.ts.map +1 -1
  81. package/lib/ops.js +1 -0
  82. package/lib/ops.js.map +1 -1
  83. package/lib/sequencePlace.d.ts +4 -0
  84. package/lib/sequencePlace.d.ts.map +1 -1
  85. package/lib/sequencePlace.js +15 -0
  86. package/lib/sequencePlace.js.map +1 -1
  87. package/lib/test/obliterate.concurrent.spec.js +18 -0
  88. package/lib/test/obliterate.concurrent.spec.js.map +1 -1
  89. package/lib/test/obliterate.partialLength.spec.js +9 -5
  90. package/lib/test/obliterate.partialLength.spec.js.map +1 -1
  91. package/lib/test/obliterate.rangeExpansion.spec.js +109 -53
  92. package/lib/test/obliterate.rangeExpansion.spec.js.map +1 -1
  93. package/lib/test/obliterate.spec.js +15 -9
  94. package/lib/test/obliterate.spec.js.map +1 -1
  95. package/lib/test/reconnectHelper.d.ts +8 -6
  96. package/lib/test/reconnectHelper.d.ts.map +1 -1
  97. package/lib/test/reconnectHelper.js +15 -14
  98. package/lib/test/reconnectHelper.js.map +1 -1
  99. package/lib/test/testClient.d.ts +1 -11
  100. package/lib/test/testClient.d.ts.map +1 -1
  101. package/lib/test/testClient.js +0 -3
  102. package/lib/test/testClient.js.map +1 -1
  103. package/lib/test/testClientLogger.d.ts.map +1 -1
  104. package/lib/test/testClientLogger.js +19 -8
  105. package/lib/test/testClientLogger.js.map +1 -1
  106. package/lib/test/testUtils.d.ts +10 -0
  107. package/lib/test/testUtils.d.ts.map +1 -1
  108. package/lib/test/testUtils.js +3 -0
  109. package/lib/test/testUtils.js.map +1 -1
  110. package/package.json +30 -17
  111. package/src/attributionPolicy.ts +5 -0
  112. package/src/client.ts +138 -20
  113. package/src/index.ts +1 -0
  114. package/src/mergeTree.ts +116 -77
  115. package/src/mergeTreeNodes.ts +9 -10
  116. package/src/opBuilder.ts +32 -0
  117. package/src/ops.ts +23 -1
  118. package/src/sequencePlace.ts +16 -0
package/dist/mergeTree.js CHANGED
@@ -765,13 +765,15 @@ class MergeTree {
765
765
  segment: pendingSegment,
766
766
  });
767
767
  });
768
- if (opArgs.op.type === ops_js_1.MergeTreeDeltaType.OBLITERATE) {
768
+ if (opArgs.op.type === ops_js_1.MergeTreeDeltaType.OBLITERATE ||
769
+ opArgs.op.type === ops_js_1.MergeTreeDeltaType.OBLITERATE_SIDED) {
769
770
  this.obliterates.addOrUpdate(pendingSegmentGroup.obliterateInfo);
770
771
  }
771
772
  // Perform slides after all segments have been acked, so that
772
773
  // positions after slide are final
773
774
  if (opArgs.op.type === ops_js_1.MergeTreeDeltaType.REMOVE ||
774
- opArgs.op.type === ops_js_1.MergeTreeDeltaType.OBLITERATE) {
775
+ opArgs.op.type === ops_js_1.MergeTreeDeltaType.OBLITERATE ||
776
+ opArgs.op.type === ops_js_1.MergeTreeDeltaType.OBLITERATE_SIDED) {
775
777
  this.slideAckedRemovedSegmentReferences(pendingSegmentGroup.segments);
776
778
  }
777
779
  this.mergeTreeMaintenanceCallback?.({
@@ -971,9 +973,9 @@ class MergeTree {
971
973
  });
972
974
  }
973
975
  this.updateRoot(splitNode);
974
- saveIfLocal(newSegment);
975
976
  insertPos += newSegment.cachedLength;
976
977
  if (!this.options?.mergeTreeEnableObliterate || this.obliterates.empty()) {
978
+ saveIfLocal(newSegment);
977
979
  continue;
978
980
  }
979
981
  // eslint-disable-next-line import/no-deprecated
@@ -1000,13 +1002,13 @@ class MergeTree {
1000
1002
  movedSeqs.unshift(ob.seq);
1001
1003
  }
1002
1004
  else {
1003
- if (newest === undefined || normalizedNewestSeq < normalizedObSeq) {
1004
- normalizedNewestSeq = normalizedObSeq;
1005
- newest = ob;
1006
- }
1007
1005
  movedClientIds.push(ob.clientId);
1008
1006
  movedSeqs.push(ob.seq);
1009
1007
  }
1008
+ if (newest === undefined || normalizedNewestSeq < normalizedObSeq) {
1009
+ normalizedNewestSeq = normalizedObSeq;
1010
+ newest = ob;
1011
+ }
1010
1012
  }
1011
1013
  }
1012
1014
  if (oldest && newest?.clientId !== clientId) {
@@ -1026,6 +1028,10 @@ class MergeTree {
1026
1028
  this.blockUpdatePathLengths(newSegment.parent, seq, clientId);
1027
1029
  }
1028
1030
  }
1031
+ else if (oldest && newest?.clientId === clientId) {
1032
+ newSegment.prevObliterateByInserter = newest;
1033
+ }
1034
+ saveIfLocal(newSegment);
1029
1035
  }
1030
1036
  }
1031
1037
  }
@@ -1059,16 +1065,7 @@ class MergeTree {
1059
1065
  }
1060
1066
  }
1061
1067
  insertingWalk(block, pos, refSeq, clientId, seq, context, isLastChildBlock = true) {
1062
- let _pos;
1063
- if (pos === "start") {
1064
- _pos = 0;
1065
- }
1066
- else if (pos === "end") {
1067
- _pos = this.root.mergeTree?.getLength(refSeq, clientId) ?? 0;
1068
- }
1069
- else {
1070
- _pos = pos;
1071
- }
1068
+ let _pos = pos;
1072
1069
  const children = block.children;
1073
1070
  let childIndex;
1074
1071
  let child;
@@ -1241,15 +1238,9 @@ class MergeTree {
1241
1238
  (0, zamboni_js_1.zamboniSegments)(this);
1242
1239
  }
1243
1240
  }
1244
- obliterateRange(start, end, refSeq, clientId, seq, overwrite = false, opArgs) {
1245
- errorIfOptionNotTrue(this.options, "mergeTreeEnableObliterate");
1246
- const { startPos, startSide, endPos, endSide } = (0, sequencePlace_js_1.endpointPosAndSide)(start, end);
1247
- (0, internal_1.assert)(startPos !== undefined &&
1248
- endPos !== undefined &&
1249
- startSide !== undefined &&
1250
- endSide !== undefined &&
1251
- startPos !== "end" &&
1252
- endPos !== "start", 0x9e2 /* start and end cannot be undefined because they were not passed in as undefined */);
1241
+ obliterateRangeSided(start, end, refSeq, clientId, seq, overwrite = false, opArgs) {
1242
+ const startPos = start.side === sequencePlace_js_1.Side.Before ? start.pos : start.pos + 1;
1243
+ const endPos = end.side === sequencePlace_js_1.Side.Before ? end.pos : end.pos + 1;
1253
1244
  this.ensureIntervalBoundary(startPos, refSeq, clientId);
1254
1245
  this.ensureIntervalBoundary(endPos, refSeq, clientId);
1255
1246
  let _overwrite = overwrite;
@@ -1266,20 +1257,46 @@ class MergeTree {
1266
1257
  localSeq,
1267
1258
  segmentGroup: undefined,
1268
1259
  };
1269
- const normalizedStartPos = startPos === "start" || startPos === undefined ? 0 : startPos;
1270
- const normalizedEndPos = endPos === "end" || endPos === undefined ? this.getLength(refSeq, clientId) : endPos;
1271
- const { segment: startSeg } = this.getContainingSegment(normalizedStartPos, refSeq, clientId);
1272
- const { segment: endSeg } = this.getContainingSegment(normalizedEndPos - 1, refSeq, clientId);
1260
+ const { segment: startSeg } = this.getContainingSegment(start.pos, refSeq, clientId);
1261
+ const { segment: endSeg } = this.getContainingSegment(end.pos, refSeq, clientId);
1273
1262
  (0, internal_1.assert)(startSeg !== undefined && endSeg !== undefined, 0xa3f /* segments cannot be undefined */);
1274
- obliterate.start = this.createLocalReferencePosition(startSeg, 0, ops_js_1.ReferenceType.StayOnRemove, {
1263
+ obliterate.start = this.createLocalReferencePosition(startSeg, start.side === sequencePlace_js_1.Side.Before ? 0 : Math.max(startSeg.cachedLength - 1, 0), ops_js_1.ReferenceType.StayOnRemove, {
1275
1264
  obliterate,
1276
1265
  });
1277
- obliterate.end = this.createLocalReferencePosition(endSeg, endSeg.cachedLength - 1, ops_js_1.ReferenceType.StayOnRemove, {
1266
+ obliterate.end = this.createLocalReferencePosition(endSeg, end.side === sequencePlace_js_1.Side.Before ? 0 : Math.max(endSeg.cachedLength - 1, 0), ops_js_1.ReferenceType.StayOnRemove, {
1278
1267
  obliterate,
1279
1268
  });
1269
+ // Always create a segment group for obliterate,
1270
+ // even if there are no segments currently in the obliteration range.
1271
+ // Segments may be concurrently inserted into the obliteration range,
1272
+ // at which point they are added to the segment group.
1273
+ obliterate.segmentGroup = {
1274
+ segments: [],
1275
+ localSeq,
1276
+ refSeq: this.collabWindow.currentSeq,
1277
+ obliterateInfo: obliterate,
1278
+ };
1279
+ if (this.collabWindow.collaborating && clientId === this.collabWindow.clientId) {
1280
+ this.pendingSegments.push(obliterate.segmentGroup);
1281
+ }
1282
+ this.obliterates.addOrUpdate(obliterate);
1280
1283
  const markMoved = (segment, pos, _start, _end) => {
1281
- var _a;
1284
+ if ((start.side === sequencePlace_js_1.Side.After && startPos === pos + segment.cachedLength) || // exclusive start segment
1285
+ (end.side === sequencePlace_js_1.Side.Before &&
1286
+ endPos === pos &&
1287
+ (0, perspective_js_1.isSegmentPresent)(segment, { refSeq, localSeq })) // exclusive end segment
1288
+ ) {
1289
+ // We walk these segments because we want to also walk any concurrently inserted segments between here and the obliterated segments.
1290
+ // These segments are outside of the obliteration range though, so return true to keep walking.
1291
+ return true;
1292
+ }
1282
1293
  const existingMoveInfo = (0, mergeTreeNodes_js_1.toMoveInfo)(segment);
1294
+ if (segment.prevObliterateByInserter?.seq === constants_js_1.UnassignedSequenceNumber) {
1295
+ // We chose to not obliterate this segment because we are aware of an unacked local obliteration.
1296
+ // The local obliterate has not been sequenced yet, so it is still the newest obliterate we are aware of.
1297
+ // Other clients will also choose not to obliterate this segment because the most recent obliteration has the same clientId
1298
+ return true;
1299
+ }
1283
1300
  if (clientId !== segment.clientId &&
1284
1301
  segment.seq !== undefined &&
1285
1302
  seq !== constants_js_1.UnassignedSequenceNumber &&
@@ -1320,7 +1337,6 @@ class MergeTree {
1320
1337
  if (segment.movedSeq === constants_js_1.UnassignedSequenceNumber &&
1321
1338
  clientId === this.collabWindow.clientId) {
1322
1339
  obliterate.segmentGroup = this.addToPendingList(segment, obliterate.segmentGroup, localSeq);
1323
- (_a = obliterate.segmentGroup).obliterateInfo ?? (_a.obliterateInfo = obliterate);
1324
1340
  }
1325
1341
  else {
1326
1342
  if (MergeTree.options.zamboniSegments) {
@@ -1339,8 +1355,8 @@ class MergeTree {
1339
1355
  }
1340
1356
  return true;
1341
1357
  };
1342
- this.nodeMap(refSeq, clientId, markMoved, undefined, afterMarkMoved, start, end, undefined, seq === constants_js_1.UnassignedSequenceNumber ? undefined : seq);
1343
- this.obliterates.addOrUpdate(obliterate);
1358
+ this.nodeMap(refSeq, clientId, markMoved, undefined, afterMarkMoved, start.pos, end.pos + 1, // include the segment containing the end reference
1359
+ undefined, seq === constants_js_1.UnassignedSequenceNumber ? undefined : seq);
1344
1360
  this.slideAckedRemovedSegmentReferences(localOverlapWithRefs);
1345
1361
  // opArgs == undefined => test code
1346
1362
  if (movedSegments.length > 0) {
@@ -1361,6 +1377,17 @@ class MergeTree {
1361
1377
  (0, zamboni_js_1.zamboniSegments)(this);
1362
1378
  }
1363
1379
  }
1380
+ obliterateRange(start, end, refSeq, clientId, seq, overwrite = false, opArgs) {
1381
+ errorIfOptionNotTrue(this.options, "mergeTreeEnableObliterate");
1382
+ if (this.options?.mergeTreeEnableSidedObliterate) {
1383
+ (0, internal_1.assert)(typeof start === "object" && typeof end === "object", "Start and end must be of type InteriorSequencePlace if mergeTreeEnableSidedObliterate is enabled.");
1384
+ this.obliterateRangeSided(start, end, refSeq, clientId, seq, overwrite, opArgs);
1385
+ }
1386
+ else {
1387
+ (0, internal_1.assert)(typeof start === "number" && typeof end === "number", "Start and end must be numbers if mergeTreeEnableSidedObliterate is not enabled.");
1388
+ this.obliterateRangeSided({ pos: start, side: sequencePlace_js_1.Side.Before }, { pos: end - 1, side: sequencePlace_js_1.Side.After }, refSeq, clientId, seq, overwrite, opArgs);
1389
+ }
1390
+ }
1364
1391
  markRangeRemoved(start, end, refSeq, clientId, seq, overwrite = false, opArgs) {
1365
1392
  let _overwrite = overwrite;
1366
1393
  this.ensureIntervalBoundary(start, refSeq, clientId);
@@ -1809,18 +1836,11 @@ class MergeTree {
1809
1836
  * ignored for the purposes of tracking when traversal should end.
1810
1837
  */
1811
1838
  nodeMap(refSeq, clientId, leaf, accum, post, start = 0, end, localSeq, visibilitySeq = refSeq) {
1812
- const maybeEndPos = end ?? this.nodeLength(this.root, refSeq, clientId, localSeq) ?? 0;
1813
- if (maybeEndPos === start) {
1839
+ const endPos = end ?? this.nodeLength(this.root, refSeq, clientId, localSeq) ?? 0;
1840
+ if (endPos === start) {
1814
1841
  return;
1815
1842
  }
1816
1843
  let pos = 0;
1817
- let { startPos, endPos } = (0, sequencePlace_js_1.endpointPosAndSide)(start, end);
1818
- startPos = startPos === "start" || startPos === undefined ? 0 : startPos;
1819
- endPos =
1820
- endPos === "end" || endPos === undefined
1821
- ? this.root.mergeTree?.getLength(refSeq, clientId) ?? 0
1822
- : endPos;
1823
- (0, internal_1.assert)(startPos !== "end" && endPos !== "start", 0x9e3 /* start cannot be 'end' and end cannot be 'start' */);
1824
1844
  (0, mergeTreeNodeWalk_js_1.depthFirstNodeWalk)(this.root, this.root.children[0], (node) => {
1825
1845
  if (endPos <= pos) {
1826
1846
  return mergeTreeNodeWalk_js_1.NodeAction.Exit;
@@ -1837,19 +1857,19 @@ class MergeTree {
1837
1857
  }
1838
1858
  const nextPos = pos + lenAtRefSeq;
1839
1859
  // start is beyond the current node, so we can skip it
1840
- if (typeof startPos === "number" && startPos >= nextPos) {
1860
+ if (start >= nextPos) {
1841
1861
  pos = nextPos;
1842
1862
  return mergeTreeNodeWalk_js_1.NodeAction.Skip;
1843
1863
  }
1844
1864
  if (node.isLeaf()) {
1845
- if (leaf(node, pos, refSeq, clientId, startPos - pos, endPos - pos, accum) === false) {
1865
+ if (leaf(node, pos, refSeq, clientId, start - pos, endPos - pos, accum) === false) {
1846
1866
  return mergeTreeNodeWalk_js_1.NodeAction.Exit;
1847
1867
  }
1848
1868
  pos = nextPos;
1849
1869
  }
1850
1870
  }, undefined, post === undefined
1851
1871
  ? undefined
1852
- : (block) => post(block, pos, refSeq, clientId, startPos - pos, endPos - pos, accum));
1872
+ : (block) => post(block, pos, refSeq, clientId, start - pos, endPos - pos, accum));
1853
1873
  }
1854
1874
  }
1855
1875
  exports.MergeTree = MergeTree;