@fluidframework/merge-tree 2.2.0 → 2.3.0

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 (168) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/api-report/merge-tree.legacy.alpha.api.md +20 -0
  3. package/dist/attributionCollection.d.ts.map +1 -1
  4. package/dist/attributionCollection.js +1 -29
  5. package/dist/attributionCollection.js.map +1 -1
  6. package/dist/client.d.ts.map +1 -1
  7. package/dist/client.js +3 -4
  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/localReference.d.ts.map +1 -1
  14. package/dist/localReference.js +0 -2
  15. package/dist/localReference.js.map +1 -1
  16. package/dist/mergeTree.d.ts +1 -30
  17. package/dist/mergeTree.d.ts.map +1 -1
  18. package/dist/mergeTree.js +131 -167
  19. package/dist/mergeTree.js.map +1 -1
  20. package/dist/mergeTreeNodes.d.ts +16 -1
  21. package/dist/mergeTreeNodes.d.ts.map +1 -1
  22. package/dist/mergeTreeNodes.js +5 -2
  23. package/dist/mergeTreeNodes.js.map +1 -1
  24. package/dist/partialLengths.d.ts.map +1 -1
  25. package/dist/partialLengths.js +8 -54
  26. package/dist/partialLengths.js.map +1 -1
  27. package/dist/properties.d.ts.map +1 -1
  28. package/dist/properties.js +0 -2
  29. package/dist/properties.js.map +1 -1
  30. package/dist/revertibles.d.ts.map +1 -1
  31. package/dist/revertibles.js +0 -14
  32. package/dist/revertibles.js.map +1 -1
  33. package/dist/segmentGroupCollection.d.ts.map +1 -1
  34. package/dist/segmentGroupCollection.js +0 -2
  35. package/dist/segmentGroupCollection.js.map +1 -1
  36. package/dist/segmentPropertiesManager.d.ts.map +1 -1
  37. package/dist/segmentPropertiesManager.js +1 -3
  38. package/dist/segmentPropertiesManager.js.map +1 -1
  39. package/dist/snapshotLoader.d.ts.map +1 -1
  40. package/dist/snapshotLoader.js +1 -4
  41. package/dist/snapshotLoader.js.map +1 -1
  42. package/dist/snapshotV1.d.ts.map +1 -1
  43. package/dist/snapshotV1.js +1 -11
  44. package/dist/snapshotV1.js.map +1 -1
  45. package/dist/snapshotlegacy.d.ts.map +1 -1
  46. package/dist/snapshotlegacy.js +0 -1
  47. package/dist/snapshotlegacy.js.map +1 -1
  48. package/dist/sortedSegmentSet.d.ts +0 -1
  49. package/dist/sortedSegmentSet.d.ts.map +1 -1
  50. package/dist/sortedSegmentSet.js +1 -9
  51. package/dist/sortedSegmentSet.js.map +1 -1
  52. package/dist/sortedSet.d.ts.map +1 -1
  53. package/dist/sortedSet.js +0 -4
  54. package/dist/sortedSet.js.map +1 -1
  55. package/dist/test/client.conflictFarm.spec.d.ts.map +1 -1
  56. package/dist/test/client.conflictFarm.spec.js +36 -27
  57. package/dist/test/client.conflictFarm.spec.js.map +1 -1
  58. package/dist/test/client.replay.spec.js +1 -1
  59. package/dist/test/client.replay.spec.js.map +1 -1
  60. package/dist/test/mergeTreeOperationRunner.d.ts +2 -1
  61. package/dist/test/mergeTreeOperationRunner.d.ts.map +1 -1
  62. package/dist/test/mergeTreeOperationRunner.js +29 -11
  63. package/dist/test/mergeTreeOperationRunner.js.map +1 -1
  64. package/dist/test/obliterate.spec.js +55 -0
  65. package/dist/test/obliterate.spec.js.map +1 -1
  66. package/dist/test/reconnectHelper.d.ts +0 -1
  67. package/dist/test/reconnectHelper.d.ts.map +1 -1
  68. package/dist/test/reconnectHelper.js +1 -1
  69. package/dist/test/reconnectHelper.js.map +1 -1
  70. package/dist/test/testClientLogger.d.ts.map +1 -1
  71. package/dist/test/testClientLogger.js +17 -7
  72. package/dist/test/testClientLogger.js.map +1 -1
  73. package/dist/zamboni.d.ts.map +1 -1
  74. package/dist/zamboni.js +0 -4
  75. package/dist/zamboni.js.map +1 -1
  76. package/lib/attributionCollection.d.ts.map +1 -1
  77. package/lib/attributionCollection.js +1 -29
  78. package/lib/attributionCollection.js.map +1 -1
  79. package/lib/client.d.ts.map +1 -1
  80. package/lib/client.js +3 -4
  81. package/lib/client.js.map +1 -1
  82. package/lib/index.d.ts +1 -1
  83. package/lib/index.d.ts.map +1 -1
  84. package/lib/index.js.map +1 -1
  85. package/lib/legacy.d.ts +1 -0
  86. package/lib/localReference.d.ts.map +1 -1
  87. package/lib/localReference.js +0 -2
  88. package/lib/localReference.js.map +1 -1
  89. package/lib/mergeTree.d.ts +1 -30
  90. package/lib/mergeTree.d.ts.map +1 -1
  91. package/lib/mergeTree.js +132 -168
  92. package/lib/mergeTree.js.map +1 -1
  93. package/lib/mergeTreeNodes.d.ts +16 -1
  94. package/lib/mergeTreeNodes.d.ts.map +1 -1
  95. package/lib/mergeTreeNodes.js +5 -2
  96. package/lib/mergeTreeNodes.js.map +1 -1
  97. package/lib/partialLengths.d.ts.map +1 -1
  98. package/lib/partialLengths.js +8 -54
  99. package/lib/partialLengths.js.map +1 -1
  100. package/lib/properties.d.ts.map +1 -1
  101. package/lib/properties.js +0 -2
  102. package/lib/properties.js.map +1 -1
  103. package/lib/revertibles.d.ts.map +1 -1
  104. package/lib/revertibles.js +0 -14
  105. package/lib/revertibles.js.map +1 -1
  106. package/lib/segmentGroupCollection.d.ts.map +1 -1
  107. package/lib/segmentGroupCollection.js +0 -2
  108. package/lib/segmentGroupCollection.js.map +1 -1
  109. package/lib/segmentPropertiesManager.d.ts.map +1 -1
  110. package/lib/segmentPropertiesManager.js +1 -3
  111. package/lib/segmentPropertiesManager.js.map +1 -1
  112. package/lib/snapshotLoader.d.ts.map +1 -1
  113. package/lib/snapshotLoader.js +1 -4
  114. package/lib/snapshotLoader.js.map +1 -1
  115. package/lib/snapshotV1.d.ts.map +1 -1
  116. package/lib/snapshotV1.js +1 -11
  117. package/lib/snapshotV1.js.map +1 -1
  118. package/lib/snapshotlegacy.d.ts.map +1 -1
  119. package/lib/snapshotlegacy.js +0 -1
  120. package/lib/snapshotlegacy.js.map +1 -1
  121. package/lib/sortedSegmentSet.d.ts +0 -1
  122. package/lib/sortedSegmentSet.d.ts.map +1 -1
  123. package/lib/sortedSegmentSet.js +1 -9
  124. package/lib/sortedSegmentSet.js.map +1 -1
  125. package/lib/sortedSet.d.ts.map +1 -1
  126. package/lib/sortedSet.js +0 -4
  127. package/lib/sortedSet.js.map +1 -1
  128. package/lib/test/client.conflictFarm.spec.d.ts.map +1 -1
  129. package/lib/test/client.conflictFarm.spec.js +37 -28
  130. package/lib/test/client.conflictFarm.spec.js.map +1 -1
  131. package/lib/test/client.replay.spec.js +1 -1
  132. package/lib/test/client.replay.spec.js.map +1 -1
  133. package/lib/test/mergeTreeOperationRunner.d.ts +2 -1
  134. package/lib/test/mergeTreeOperationRunner.d.ts.map +1 -1
  135. package/lib/test/mergeTreeOperationRunner.js +30 -12
  136. package/lib/test/mergeTreeOperationRunner.js.map +1 -1
  137. package/lib/test/obliterate.spec.js +55 -0
  138. package/lib/test/obliterate.spec.js.map +1 -1
  139. package/lib/test/reconnectHelper.d.ts +0 -1
  140. package/lib/test/reconnectHelper.d.ts.map +1 -1
  141. package/lib/test/reconnectHelper.js +1 -1
  142. package/lib/test/reconnectHelper.js.map +1 -1
  143. package/lib/test/testClientLogger.d.ts.map +1 -1
  144. package/lib/test/testClientLogger.js +18 -8
  145. package/lib/test/testClientLogger.js.map +1 -1
  146. package/lib/tsdoc-metadata.json +1 -1
  147. package/lib/zamboni.d.ts.map +1 -1
  148. package/lib/zamboni.js +0 -4
  149. package/lib/zamboni.js.map +1 -1
  150. package/package.json +22 -21
  151. package/src/attributionCollection.ts +14 -42
  152. package/src/client.ts +8 -9
  153. package/src/index.ts +1 -0
  154. package/src/localReference.ts +1 -3
  155. package/src/mergeTree.ts +185 -208
  156. package/src/mergeTreeNodes.ts +22 -3
  157. package/src/partialLengths.ts +23 -68
  158. package/src/properties.ts +1 -3
  159. package/src/revertibles.ts +7 -21
  160. package/src/segmentGroupCollection.ts +1 -3
  161. package/src/segmentPropertiesManager.ts +0 -1
  162. package/src/snapshotLoader.ts +2 -4
  163. package/src/snapshotV1.ts +5 -15
  164. package/src/snapshotlegacy.ts +1 -2
  165. package/src/sortedSegmentSet.ts +3 -10
  166. package/src/sortedSet.ts +2 -6
  167. package/src/zamboni.ts +4 -8
  168. package/tsconfig.json +1 -0
package/dist/mergeTree.js CHANGED
@@ -25,11 +25,8 @@ const referencePositions_js_1 = require("./referencePositions.js");
25
25
  // eslint-disable-next-line import/no-deprecated
26
26
  const segmentPropertiesManager_js_1 = require("./segmentPropertiesManager.js");
27
27
  const sequencePlace_js_1 = require("./sequencePlace.js");
28
+ const sortedSegmentSet_js_1 = require("./sortedSegmentSet.js");
28
29
  const zamboni_js_1 = require("./zamboni.js");
29
- function wasRemovedAfter(seg, seq) {
30
- return (seg.removedSeq !== constants_js_1.UnassignedSequenceNumber &&
31
- (seg.removedSeq === undefined || seg.removedSeq > seq));
32
- }
33
30
  function markSegmentMoved(seg, moveInfo) {
34
31
  seg.moveDst = moveInfo.moveDst;
35
32
  seg.movedClientIds = [...moveInfo.movedClientIds];
@@ -180,6 +177,67 @@ function getSlideToSegoff(segoff, slidingPreference = localReference_js_1.Slidin
180
177
  exports.getSlideToSegoff = getSlideToSegoff;
181
178
  const forwardPred = (ref) => ref.slidingPreference !== localReference_js_1.SlidingPreference.BACKWARD;
182
179
  const backwardPred = (ref) => ref.slidingPreference === localReference_js_1.SlidingPreference.BACKWARD;
180
+ class Obliterates {
181
+ constructor(mergeTree) {
182
+ this.mergeTree = mergeTree;
183
+ /**
184
+ * Array containing the all move operations within the
185
+ * collab window.
186
+ *
187
+ * The moves are stored in sequence order which accelerates clean up in setMinSeq
188
+ *
189
+ * See https://github.com/microsoft/FluidFramework/blob/main/packages/dds/merge-tree/docs/Obliterate.md#remote-perspective
190
+ * for additional context
191
+ */
192
+ // eslint-disable-next-line import/no-deprecated
193
+ this.seqOrdered = new index_js_1.DoublyLinkedList();
194
+ /**
195
+ * This contains a sorted lists of all obliterate starts
196
+ * and is used to accelerate finding overlapping obliterates
197
+ * as well as determining if there are any obliterates at all.
198
+ */
199
+ this.startOrdered = new sortedSegmentSet_js_1.SortedSegmentSet();
200
+ }
201
+ setMinSeq(minSeq) {
202
+ // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
203
+ while (!this.seqOrdered.empty && this.seqOrdered.first?.data.seq <= minSeq) {
204
+ const ob = this.seqOrdered.shift();
205
+ this.startOrdered.remove(ob.data.start);
206
+ this.mergeTree.removeLocalReferencePosition(ob.data.start);
207
+ this.mergeTree.removeLocalReferencePosition(ob.data.end);
208
+ }
209
+ }
210
+ // eslint-disable-next-line import/no-deprecated
211
+ addOrUpdate(obliterateInfo) {
212
+ const { seq, start } = obliterateInfo;
213
+ if (seq !== constants_js_1.UnassignedSequenceNumber) {
214
+ this.seqOrdered.push(obliterateInfo);
215
+ }
216
+ this.startOrdered.addOrUpdate(start);
217
+ }
218
+ empty() {
219
+ return this.startOrdered.size === 0;
220
+ }
221
+ // eslint-disable-next-line import/no-deprecated
222
+ findOverlapping(seg) {
223
+ // eslint-disable-next-line import/no-deprecated
224
+ const overlapping = [];
225
+ for (const start of this.startOrdered.items) {
226
+ if (start.getSegment().ordinal <= seg.ordinal) {
227
+ // eslint-disable-next-line import/no-deprecated
228
+ const ob = start.properties?.obliterate;
229
+ if (ob.end.getSegment().ordinal >= seg.ordinal) {
230
+ overlapping.push(ob);
231
+ }
232
+ }
233
+ else {
234
+ // the start is past the seg, so exit
235
+ break;
236
+ }
237
+ }
238
+ return overlapping;
239
+ }
240
+ }
183
241
  /**
184
242
  * @internal
185
243
  */
@@ -200,36 +258,7 @@ class MergeTree {
200
258
  // for now assume only markers have ids and so point directly at the Segment
201
259
  // if we need to have pointers to non-markers, we can change to point at local refs
202
260
  this.idToMarker = new Map();
203
- /**
204
- * Array containing the sequence number of all move operations within the
205
- * collab window
206
- *
207
- * When a segment is inserted, we must traverse to the left and right of it
208
- * to determine whether the segment was inserted into an obliterated range.
209
- * By keeping track of all move seqs, we can significantly reduce the search
210
- * space we must traverse.
211
- *
212
- * Sequence numbers in `moveSeqs` are sorted to accelerate bookkeeping.
213
- *
214
- * See https://github.com/microsoft/FluidFramework/blob/main/packages/dds/merge-tree/docs/Obliterate.md#remote-perspective
215
- * for additional context
216
- */
217
- this.moveSeqs = [];
218
- /**
219
- * Similar to moveSeqs, but tracks local moves. These are not the move
220
- * operations within the collab window, but rather local moves that have
221
- * not been acked.
222
- */
223
- this.localMoveSeqs = new Set();
224
- /**
225
- * Groups of segments moved by local moves/obliterates
226
- *
227
- * When a local obliterate is acked, we must also ack segments that were
228
- * concurrently obliterated on insert. We check this segment group to find
229
- * such segments
230
- */
231
- // eslint-disable-next-line import/no-deprecated
232
- this.locallyMovedSegments = new Map();
261
+ this.obliterates = new Obliterates(this);
233
262
  this.splitLeafSegment = (segment, pos) => {
234
263
  if (!(pos > 0 && segment)) {
235
264
  return {};
@@ -345,7 +374,6 @@ class MergeTree {
345
374
  childIndex++, nodeIndex++ // Advance to next child & node
346
375
  ) {
347
376
  // Insert the next node into the current block
348
- // TODO Non null asserting, why is this not null?
349
377
  this.addNode(block, nodes[nodeIndex]);
350
378
  }
351
379
  // Calculate this block's info. Previously this was inlined into the above loop as a micro-optimization,
@@ -354,8 +382,7 @@ class MergeTree {
354
382
  this.blockUpdate(block);
355
383
  }
356
384
  return blocks.length === 1 // If there is only one block at this layer...
357
- ? // Non null asserting here because of the length check above
358
- blocks[0] // ...then we're done. Return the root.
385
+ ? blocks[0] // ...then we're done. Return the root.
359
386
  : buildMergeBlock(blocks); // ...otherwise recursively build the next layer above blocks.
360
387
  };
361
388
  if (segments.length > 0) {
@@ -403,7 +430,6 @@ class MergeTree {
403
430
  while (parent) {
404
431
  const children = parent.children;
405
432
  for (let childIndex = 0; childIndex < parent.childCount; childIndex++) {
406
- // TODO Non null asserting, why is this not null?
407
433
  const child = children[childIndex];
408
434
  if ((!!prevParent && child === prevParent) || child === node) {
409
435
  break;
@@ -618,8 +644,7 @@ class MergeTree {
618
644
  (0, internal_1.assert)(this.collabWindow.minSeq <= minSeq, 0x04f /* "minSeq of collab window > target minSeq!" */);
619
645
  if (minSeq > this.collabWindow.minSeq) {
620
646
  this.collabWindow.minSeq = minSeq;
621
- const firstMoveSeqIdx = this.moveSeqs.findIndex((seq) => seq >= minSeq);
622
- this.moveSeqs = firstMoveSeqIdx === -1 ? [] : this.moveSeqs.slice(firstMoveSeqIdx);
647
+ this.obliterates.setMinSeq(minSeq);
623
648
  if (MergeTree.options.zamboniSegments) {
624
649
  (0, zamboni_js_1.zamboniSegments)(this);
625
650
  }
@@ -727,34 +752,8 @@ class MergeTree {
727
752
  const deltaSegments = [];
728
753
  const overlappingRemoves = [];
729
754
  pendingSegmentGroup.segments.map((pendingSegment) => {
730
- const localMovedSeq = pendingSegment.localMovedSeq;
731
755
  const overlappingRemove = !pendingSegment.ack(pendingSegmentGroup, opArgs);
732
- if (opArgs.op.type === ops_js_1.MergeTreeDeltaType.OBLITERATE && localMovedSeq !== undefined) {
733
- const locallyMovedSegments = this.locallyMovedSegments.get(localMovedSeq);
734
- if (locallyMovedSegments) {
735
- // Disabling because a for of loop causes the type of segment to be ISegment, which does not have parent information stored
736
- // eslint-disable-next-line unicorn/no-array-for-each
737
- locallyMovedSegments.segments.forEach((segment) => {
738
- segment.localMovedSeq = undefined;
739
- if (!nodesToUpdate.includes(segment.parent)) {
740
- nodesToUpdate.push(segment.parent);
741
- }
742
- if (segment.movedSeq === constants_js_1.UnassignedSequenceNumber) {
743
- segment.movedSeq = seq;
744
- }
745
- });
746
- this.locallyMovedSegments.delete(localMovedSeq);
747
- }
748
- }
749
756
  overwrite = overlappingRemove || overwrite;
750
- if (opArgs.op.type === ops_js_1.MergeTreeDeltaType.OBLITERATE) {
751
- if (seq !== this.moveSeqs[this.moveSeqs.length - 1]) {
752
- this.moveSeqs.push(seq);
753
- }
754
- if (localMovedSeq !== undefined) {
755
- this.localMoveSeqs.delete(localMovedSeq);
756
- }
757
- }
758
757
  overlappingRemoves.push(overlappingRemove);
759
758
  if (MergeTree.options.zamboniSegments) {
760
759
  this.addToLRUSet(pendingSegment, seq);
@@ -766,6 +765,9 @@ class MergeTree {
766
765
  segment: pendingSegment,
767
766
  });
768
767
  });
768
+ if (opArgs.op.type === ops_js_1.MergeTreeDeltaType.OBLITERATE) {
769
+ this.obliterates.addOrUpdate(pendingSegmentGroup.obliterateInfo);
770
+ }
769
771
  // Perform slides after all segments have been acked, so that
770
772
  // positions after slide are final
771
773
  if (opArgs.op.type === ops_js_1.MergeTreeDeltaType.REMOVE ||
@@ -971,90 +973,54 @@ class MergeTree {
971
973
  this.updateRoot(splitNode);
972
974
  saveIfLocal(newSegment);
973
975
  insertPos += newSegment.cachedLength;
974
- if (!this.options?.mergeTreeEnableObliterate) {
975
- continue;
976
- }
977
- let moveUpperBound = Number.POSITIVE_INFINITY;
978
- const smallestSeqMoveOp = this.getSmallestSeqMoveOp();
979
- if (smallestSeqMoveOp === undefined) {
976
+ if (!this.options?.mergeTreeEnableObliterate || this.obliterates.empty()) {
980
977
  continue;
981
978
  }
982
- const leftAckedSegments = {};
983
- const leftLocalSegments = {};
984
- let _localMovedSeq;
985
- let _movedSeq;
986
- let movedClientIds;
987
- const findLeftMovedSegment = (seg) => {
988
- const movedSeqs = seg.movedSeqs?.filter((movedSeq) => movedSeq >= refSeq) ?? [];
989
- const localMovedSeqs = seg.localMovedSeq ? [seg.localMovedSeq] : [];
990
- for (const movedSeq of movedSeqs) {
991
- leftAckedSegments[movedSeq] = seg;
992
- }
993
- for (const localMovedSeq of localMovedSeqs) {
994
- leftLocalSegments[localMovedSeq] = seg;
995
- }
996
- if ((seg.movedSeqs?.length ?? 0) > 0 || localMovedSeqs.length > 0) {
997
- return true;
998
- }
999
- if (!isRemoved(seg) || wasRemovedAfter(seg, moveUpperBound)) {
1000
- moveUpperBound = Math.min(moveUpperBound, seg.seq ?? Number.POSITIVE_INFINITY);
1001
- }
1002
- // If we've reached a segment that existed before any of our in-collab-window move ops
1003
- // happened, no need to continue.
1004
- return moveUpperBound >= smallestSeqMoveOp;
1005
- };
1006
- const findRightMovedSegment = (seg) => {
1007
- const movedSeqs = seg.movedSeqs?.filter((movedSeq) => movedSeq >= refSeq) ?? [];
1008
- const localMovedSeqs = seg.localMovedSeq ? [seg.localMovedSeq] : [];
1009
- for (const movedSeq of movedSeqs) {
1010
- const left = leftAckedSegments[movedSeq];
1011
- if (left) {
1012
- _movedSeq = movedSeq;
1013
- const clientIdIdx = left.movedSeqs?.indexOf(movedSeq) ?? -1;
1014
- const movedClientId = left.movedClientIds?.[clientIdIdx];
1015
- (0, internal_1.assert)(movedClientId !== undefined, 0x869 /* expected client id to exist */);
1016
- movedClientIds = [movedClientId];
1017
- return false;
979
+ // eslint-disable-next-line import/no-deprecated
980
+ let oldest;
981
+ let normalizedOldestSeq = 0;
982
+ // eslint-disable-next-line import/no-deprecated
983
+ let newest;
984
+ let normalizedNewestSeq = 0;
985
+ const movedClientIds = [];
986
+ const movedSeqs = [];
987
+ for (const ob of this.obliterates.findOverlapping(newSegment)) {
988
+ // compute a normalized seq that takes into account local seqs
989
+ // but is still comparable to remote seqs to keep the checks below easy
990
+ // REMOTE SEQUENCE NUMBERS LOCAL SEQUENCE NUMBERS
991
+ // [0, 1, 2, 3, ..., 100, ..., 1000, ..., (MAX - MaxLocalSeq), L1, L2, L3, L4, ..., L100, ..., L1000, ...(MAX)]
992
+ const normalizedObSeq = ob.seq === constants_js_1.UnassignedSequenceNumber
993
+ ? Number.MAX_SAFE_INTEGER - this.collabWindow.localSeq + ob.localSeq
994
+ : ob.seq;
995
+ if (normalizedObSeq > refSeq) {
996
+ if (oldest === undefined || normalizedOldestSeq > normalizedObSeq) {
997
+ normalizedOldestSeq = normalizedObSeq;
998
+ oldest = ob;
999
+ movedClientIds.unshift(ob.clientId);
1000
+ movedSeqs.unshift(ob.seq);
1018
1001
  }
1019
- }
1020
- for (const localMovedSeq of localMovedSeqs) {
1021
- const left = leftLocalSegments[localMovedSeq];
1022
- if (left) {
1023
- _localMovedSeq = localMovedSeq;
1024
- const clientIdIdx = left.movedSeqs?.indexOf(constants_js_1.UnassignedSequenceNumber) ?? -1;
1025
- const movedClientId = left.movedClientIds?.[clientIdIdx];
1026
- (0, internal_1.assert)(movedClientId !== undefined, 0x86a /* expected client id to exist */);
1027
- movedClientIds = [movedClientId];
1028
- return false;
1002
+ else {
1003
+ if (newest === undefined || normalizedNewestSeq < normalizedObSeq) {
1004
+ normalizedNewestSeq = normalizedObSeq;
1005
+ newest = ob;
1006
+ }
1007
+ movedClientIds.push(ob.clientId);
1008
+ movedSeqs.push(ob.seq);
1029
1009
  }
1030
1010
  }
1031
- if ((seg.movedSeqs?.length ?? 0) || localMovedSeqs.length > 0) {
1032
- return true;
1033
- }
1034
- if (!isRemoved(seg) || wasRemovedAfter(seg, moveUpperBound)) {
1035
- moveUpperBound = Math.min(moveUpperBound, seg.seq ?? Number.POSITIVE_INFINITY);
1036
- }
1037
- // If we've reached a segment that existed before any of our in-collab-window move ops
1038
- // happened, no need to continue.
1039
- return moveUpperBound >= smallestSeqMoveOp;
1040
- };
1041
- (0, mergeTreeNodeWalk_js_1.backwardExcursion)(newSegment, findLeftMovedSegment);
1042
- moveUpperBound = Number.POSITIVE_INFINITY;
1043
- (0, mergeTreeNodeWalk_js_1.forwardExcursion)(newSegment, findRightMovedSegment);
1044
- if (_localMovedSeq !== undefined || _movedSeq !== undefined) {
1045
- (0, internal_1.assert)(movedClientIds !== undefined, 0x86b /* movedClientIds should be set if local/moved seq is set */);
1011
+ }
1012
+ if (oldest && newest?.clientId !== clientId) {
1046
1013
  const moveInfo = {
1047
1014
  movedClientIds,
1048
- movedSeq: _movedSeq ?? constants_js_1.UnassignedSequenceNumber,
1049
- movedSeqs: _movedSeq === undefined ? [constants_js_1.UnassignedSequenceNumber] : [_movedSeq],
1050
- localMovedSeq: _localMovedSeq,
1051
- wasMovedOnInsert: (_movedSeq ?? -1) !== constants_js_1.UnassignedSequenceNumber,
1015
+ movedSeq: oldest.seq,
1016
+ movedSeqs,
1017
+ localMovedSeq: oldest.localSeq,
1018
+ wasMovedOnInsert: oldest.seq !== constants_js_1.UnassignedSequenceNumber,
1052
1019
  };
1053
1020
  markSegmentMoved(newSegment, moveInfo);
1054
1021
  if (moveInfo.localMovedSeq !== undefined) {
1055
- const movedSegmentGroup = this.locallyMovedSegments.get(moveInfo.localMovedSeq);
1056
- (0, internal_1.assert)(movedSegmentGroup !== undefined, 0x86c /* expected segment group to exist */);
1057
- this.addToPendingList(newSegment, movedSegmentGroup, localSeq);
1022
+ (0, internal_1.assert)(oldest.segmentGroup !== undefined, 0x86c /* expected segment group to exist */);
1023
+ this.addToPendingList(newSegment, oldest.segmentGroup);
1058
1024
  }
1059
1025
  if (newSegment.parent) {
1060
1026
  this.blockUpdatePathLengths(newSegment.parent, seq, clientId);
@@ -1092,9 +1058,6 @@ class MergeTree {
1092
1058
  return true;
1093
1059
  }
1094
1060
  }
1095
- getSmallestSeqMoveOp() {
1096
- return this.moveSeqs[0] ?? (this.localMoveSeqs.size > 0 ? -1 : undefined);
1097
- }
1098
1061
  insertingWalk(block, pos, refSeq, clientId, seq, context, isLastChildBlock = true) {
1099
1062
  let _pos;
1100
1063
  if (pos === "start") {
@@ -1112,7 +1075,6 @@ class MergeTree {
1112
1075
  let newNode;
1113
1076
  let fromSplit;
1114
1077
  for (childIndex = 0; childIndex < block.childCount; childIndex++) {
1115
- // TODO Non null asserting, why is this not null?
1116
1078
  child = children[childIndex];
1117
1079
  // ensure we walk down the far edge of the tree, even if all sub-tree is eligible for zamboni
1118
1080
  const isLastNonLeafBlock = isLastChildBlock && !child.isLeaf() && childIndex === block.childCount - 1;
@@ -1177,9 +1139,7 @@ class MergeTree {
1177
1139
  }
1178
1140
  if (newNode) {
1179
1141
  for (let i = block.childCount; i > childIndex; i--) {
1180
- // TODO Non null asserting, why is this not null?
1181
1142
  block.children[i] = block.children[i - 1];
1182
- // TODO Non null asserting, why is this not null?
1183
1143
  block.children[i].index = i;
1184
1144
  }
1185
1145
  block.assignChild(newNode, childIndex, false);
@@ -1211,7 +1171,6 @@ class MergeTree {
1211
1171
  // Update ordinals to reflect lowered child count
1212
1172
  this.nodeUpdateOrdinals(node);
1213
1173
  for (let i = 0; i < halfCount; i++) {
1214
- // TODO Non null asserting, why is this not null?
1215
1174
  newNode.assignChild(node.children[halfCount + i], i, false);
1216
1175
  node.children[halfCount + i] = undefined;
1217
1176
  }
@@ -1221,7 +1180,6 @@ class MergeTree {
1221
1180
  }
1222
1181
  nodeUpdateOrdinals(block) {
1223
1182
  for (let i = 0; i < block.childCount; i++) {
1224
- // TODO Non null asserting, why is this not null?
1225
1183
  const child = block.children[i];
1226
1184
  block.setOrdinal(child, i);
1227
1185
  if (!child.isLeaf()) {
@@ -1298,20 +1256,30 @@ class MergeTree {
1298
1256
  const localOverlapWithRefs = [];
1299
1257
  const movedSegments = [];
1300
1258
  const localSeq = seq === constants_js_1.UnassignedSequenceNumber ? ++this.collabWindow.localSeq : undefined;
1301
- if (seq !== constants_js_1.UnassignedSequenceNumber && seq !== this.moveSeqs[this.moveSeqs.length - 1]) {
1302
- this.moveSeqs.push(seq);
1303
- }
1304
- else if (seq === constants_js_1.UnassignedSequenceNumber && localSeq !== undefined) {
1305
- this.localMoveSeqs.add(localSeq);
1306
- }
1307
1259
  // eslint-disable-next-line import/no-deprecated
1308
- let segmentGroup;
1260
+ const obliterate = {
1261
+ clientId,
1262
+ end: (0, localReference_js_1.createDetachedLocalReferencePosition)(undefined),
1263
+ refSeq,
1264
+ seq,
1265
+ start: (0, localReference_js_1.createDetachedLocalReferencePosition)(undefined),
1266
+ localSeq,
1267
+ segmentGroup: undefined,
1268
+ };
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);
1273
+ (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, {
1275
+ obliterate,
1276
+ });
1277
+ obliterate.end = this.createLocalReferencePosition(endSeg, endSeg.cachedLength - 1, ops_js_1.ReferenceType.StayOnRemove, {
1278
+ obliterate,
1279
+ });
1309
1280
  const markMoved = (segment, pos, _start, _end) => {
1281
+ var _a;
1310
1282
  const existingMoveInfo = (0, mergeTreeNodes_js_1.toMoveInfo)(segment);
1311
- if (startSide)
1312
- segment.startSide = startSide;
1313
- if (endSide)
1314
- segment.endSide = endSide;
1315
1283
  if (clientId !== segment.clientId &&
1316
1284
  segment.seq !== undefined &&
1317
1285
  seq !== constants_js_1.UnassignedSequenceNumber &&
@@ -1351,7 +1319,8 @@ class MergeTree {
1351
1319
  if (this.collabWindow.collaborating) {
1352
1320
  if (segment.movedSeq === constants_js_1.UnassignedSequenceNumber &&
1353
1321
  clientId === this.collabWindow.clientId) {
1354
- segmentGroup = this.addToPendingList(segment, segmentGroup, localSeq);
1322
+ obliterate.segmentGroup = this.addToPendingList(segment, obliterate.segmentGroup, localSeq);
1323
+ (_a = obliterate.segmentGroup).obliterateInfo ?? (_a.obliterateInfo = obliterate);
1355
1324
  }
1356
1325
  else {
1357
1326
  if (MergeTree.options.zamboniSegments) {
@@ -1371,6 +1340,7 @@ class MergeTree {
1371
1340
  return true;
1372
1341
  };
1373
1342
  this.nodeMap(refSeq, clientId, markMoved, undefined, afterMarkMoved, start, end, undefined, seq === constants_js_1.UnassignedSequenceNumber ? undefined : seq);
1343
+ this.obliterates.addOrUpdate(obliterate);
1374
1344
  this.slideAckedRemovedSegmentReferences(localOverlapWithRefs);
1375
1345
  // opArgs == undefined => test code
1376
1346
  if (movedSegments.length > 0) {
@@ -1379,9 +1349,6 @@ class MergeTree {
1379
1349
  deltaSegments: movedSegments,
1380
1350
  });
1381
1351
  }
1382
- if (segmentGroup && localSeq !== undefined) {
1383
- this.locallyMovedSegments.set(localSeq, segmentGroup);
1384
- }
1385
1352
  // these events are newly removed
1386
1353
  // so we slide after eventing in case the consumer wants to make reference
1387
1354
  // changes at remove time, like add a ref to track undo redo.
@@ -1528,7 +1495,6 @@ class MergeTree {
1528
1495
  this.markRangeRemoved(start, start + segment.cachedLength, constants_js_1.UniversalSequenceNumber, this.collabWindow.clientId, constants_js_1.UniversalSequenceNumber, false, { op: removeOp });
1529
1496
  } /* op.type === MergeTreeDeltaType.ANNOTATE */
1530
1497
  else {
1531
- // TODO Non null asserting, why is this not null?
1532
1498
  const props = pendingSegmentGroup.previousProps[i];
1533
1499
  const annotateOp = (0, opBuilder_js_1.createAnnotateRangeOp)(start, start + segment.cachedLength, props);
1534
1500
  this.annotateRange(start, start + segment.cachedLength, props, constants_js_1.UniversalSequenceNumber, this.collabWindow.clientId, constants_js_1.UniversalSequenceNumber, { op: annotateOp },
@@ -1575,7 +1541,7 @@ class MergeTree {
1575
1541
  if (_segment !== "start" &&
1576
1542
  _segment !== "end" &&
1577
1543
  isRemovedAndAckedOrMovedAndAcked(_segment) &&
1578
- !(0, referencePositions_js_1.refTypeIncludesFlag)(refType, ops_js_1.ReferenceType.SlideOnRemove | ops_js_1.ReferenceType.Transient) &&
1544
+ !(0, referencePositions_js_1.refTypeIncludesFlag)(refType, ops_js_1.ReferenceType.SlideOnRemove | ops_js_1.ReferenceType.Transient | ops_js_1.ReferenceType.StayOnRemove) &&
1579
1545
  _segment.endpointType === undefined) {
1580
1546
  throw new internal_2.UsageError("Can only create SlideOnRemove or Transient local reference position on a removed or obliterated segment");
1581
1547
  }
@@ -1648,7 +1614,6 @@ class MergeTree {
1648
1614
  }
1649
1615
  }
1650
1616
  for (let i = 0; i < newOrder.length; i++) {
1651
- // TODO Non null asserting, why is this not null?
1652
1617
  const seg = newOrder[i];
1653
1618
  const { parent, index, ordinal } = currentOrder[i];
1654
1619
  parent?.assignChild(seg, index, false);
@@ -1735,7 +1700,6 @@ class MergeTree {
1735
1700
  const rightmostTiles = (0, properties_js_1.createMap)();
1736
1701
  const leftmostTiles = (0, properties_js_1.createMap)();
1737
1702
  for (let i = 0; i < block.childCount; i++) {
1738
- // TODO Non null asserting, why is this not null?
1739
1703
  const node = block.children[i];
1740
1704
  const nodeLength = nodeTotalLength(this, node);
1741
1705
  if (nodeLength !== undefined) {