@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/lib/mergeTree.js CHANGED
@@ -9,7 +9,7 @@ import { DataProcessingError, UsageError } from "@fluidframework/telemetry-utils
9
9
  import { DoublyLinkedList } from "./collections/index.js";
10
10
  import { NonCollabClient, TreeMaintenanceSequenceNumber, UnassignedSequenceNumber, UniversalSequenceNumber, } from "./constants.js";
11
11
  import { EndOfTreeSegment, StartOfTreeSegment } from "./endOfTreeSegment.js";
12
- import { LocalReferenceCollection, SlidingPreference, anyLocalReferencePosition, filterLocalReferencePositions, } from "./localReference.js";
12
+ import { LocalReferenceCollection, SlidingPreference, anyLocalReferencePosition, createDetachedLocalReferencePosition, filterLocalReferencePositions, } from "./localReference.js";
13
13
  import { MergeTreeMaintenanceType, } from "./mergeTreeDeltaCallback.js";
14
14
  import { NodeAction, backwardExcursion, depthFirstNodeWalk, forwardExcursion, walkAllChildSegments, } from "./mergeTreeNodeWalk.js";
15
15
  import {
@@ -24,11 +24,8 @@ import { DetachedReferencePosition, refGetTileLabels, refHasTileLabel, refTypeIn
24
24
  // eslint-disable-next-line import/no-deprecated
25
25
  import { PropertiesRollback } from "./segmentPropertiesManager.js";
26
26
  import { endpointPosAndSide } from "./sequencePlace.js";
27
+ import { SortedSegmentSet } from "./sortedSegmentSet.js";
27
28
  import { zamboniSegments } from "./zamboni.js";
28
- function wasRemovedAfter(seg, seq) {
29
- return (seg.removedSeq !== UnassignedSequenceNumber &&
30
- (seg.removedSeq === undefined || seg.removedSeq > seq));
31
- }
32
29
  function markSegmentMoved(seg, moveInfo) {
33
30
  seg.moveDst = moveInfo.moveDst;
34
31
  seg.movedClientIds = [...moveInfo.movedClientIds];
@@ -176,6 +173,67 @@ export function getSlideToSegoff(segoff, slidingPreference = SlidingPreference.F
176
173
  }
177
174
  const forwardPred = (ref) => ref.slidingPreference !== SlidingPreference.BACKWARD;
178
175
  const backwardPred = (ref) => ref.slidingPreference === SlidingPreference.BACKWARD;
176
+ class Obliterates {
177
+ constructor(mergeTree) {
178
+ this.mergeTree = mergeTree;
179
+ /**
180
+ * Array containing the all move operations within the
181
+ * collab window.
182
+ *
183
+ * The moves are stored in sequence order which accelerates clean up in setMinSeq
184
+ *
185
+ * See https://github.com/microsoft/FluidFramework/blob/main/packages/dds/merge-tree/docs/Obliterate.md#remote-perspective
186
+ * for additional context
187
+ */
188
+ // eslint-disable-next-line import/no-deprecated
189
+ this.seqOrdered = new DoublyLinkedList();
190
+ /**
191
+ * This contains a sorted lists of all obliterate starts
192
+ * and is used to accelerate finding overlapping obliterates
193
+ * as well as determining if there are any obliterates at all.
194
+ */
195
+ this.startOrdered = new SortedSegmentSet();
196
+ }
197
+ setMinSeq(minSeq) {
198
+ // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
199
+ while (!this.seqOrdered.empty && this.seqOrdered.first?.data.seq <= minSeq) {
200
+ const ob = this.seqOrdered.shift();
201
+ this.startOrdered.remove(ob.data.start);
202
+ this.mergeTree.removeLocalReferencePosition(ob.data.start);
203
+ this.mergeTree.removeLocalReferencePosition(ob.data.end);
204
+ }
205
+ }
206
+ // eslint-disable-next-line import/no-deprecated
207
+ addOrUpdate(obliterateInfo) {
208
+ const { seq, start } = obliterateInfo;
209
+ if (seq !== UnassignedSequenceNumber) {
210
+ this.seqOrdered.push(obliterateInfo);
211
+ }
212
+ this.startOrdered.addOrUpdate(start);
213
+ }
214
+ empty() {
215
+ return this.startOrdered.size === 0;
216
+ }
217
+ // eslint-disable-next-line import/no-deprecated
218
+ findOverlapping(seg) {
219
+ // eslint-disable-next-line import/no-deprecated
220
+ const overlapping = [];
221
+ for (const start of this.startOrdered.items) {
222
+ if (start.getSegment().ordinal <= seg.ordinal) {
223
+ // eslint-disable-next-line import/no-deprecated
224
+ const ob = start.properties?.obliterate;
225
+ if (ob.end.getSegment().ordinal >= seg.ordinal) {
226
+ overlapping.push(ob);
227
+ }
228
+ }
229
+ else {
230
+ // the start is past the seg, so exit
231
+ break;
232
+ }
233
+ }
234
+ return overlapping;
235
+ }
236
+ }
179
237
  /**
180
238
  * @internal
181
239
  */
@@ -196,36 +254,7 @@ export class MergeTree {
196
254
  // for now assume only markers have ids and so point directly at the Segment
197
255
  // if we need to have pointers to non-markers, we can change to point at local refs
198
256
  this.idToMarker = new Map();
199
- /**
200
- * Array containing the sequence number of all move operations within the
201
- * collab window
202
- *
203
- * When a segment is inserted, we must traverse to the left and right of it
204
- * to determine whether the segment was inserted into an obliterated range.
205
- * By keeping track of all move seqs, we can significantly reduce the search
206
- * space we must traverse.
207
- *
208
- * Sequence numbers in `moveSeqs` are sorted to accelerate bookkeeping.
209
- *
210
- * See https://github.com/microsoft/FluidFramework/blob/main/packages/dds/merge-tree/docs/Obliterate.md#remote-perspective
211
- * for additional context
212
- */
213
- this.moveSeqs = [];
214
- /**
215
- * Similar to moveSeqs, but tracks local moves. These are not the move
216
- * operations within the collab window, but rather local moves that have
217
- * not been acked.
218
- */
219
- this.localMoveSeqs = new Set();
220
- /**
221
- * Groups of segments moved by local moves/obliterates
222
- *
223
- * When a local obliterate is acked, we must also ack segments that were
224
- * concurrently obliterated on insert. We check this segment group to find
225
- * such segments
226
- */
227
- // eslint-disable-next-line import/no-deprecated
228
- this.locallyMovedSegments = new Map();
257
+ this.obliterates = new Obliterates(this);
229
258
  this.splitLeafSegment = (segment, pos) => {
230
259
  if (!(pos > 0 && segment)) {
231
260
  return {};
@@ -341,7 +370,6 @@ export class MergeTree {
341
370
  childIndex++, nodeIndex++ // Advance to next child & node
342
371
  ) {
343
372
  // Insert the next node into the current block
344
- // TODO Non null asserting, why is this not null?
345
373
  this.addNode(block, nodes[nodeIndex]);
346
374
  }
347
375
  // Calculate this block's info. Previously this was inlined into the above loop as a micro-optimization,
@@ -350,8 +378,7 @@ export class MergeTree {
350
378
  this.blockUpdate(block);
351
379
  }
352
380
  return blocks.length === 1 // If there is only one block at this layer...
353
- ? // Non null asserting here because of the length check above
354
- blocks[0] // ...then we're done. Return the root.
381
+ ? blocks[0] // ...then we're done. Return the root.
355
382
  : buildMergeBlock(blocks); // ...otherwise recursively build the next layer above blocks.
356
383
  };
357
384
  if (segments.length > 0) {
@@ -399,7 +426,6 @@ export class MergeTree {
399
426
  while (parent) {
400
427
  const children = parent.children;
401
428
  for (let childIndex = 0; childIndex < parent.childCount; childIndex++) {
402
- // TODO Non null asserting, why is this not null?
403
429
  const child = children[childIndex];
404
430
  if ((!!prevParent && child === prevParent) || child === node) {
405
431
  break;
@@ -614,8 +640,7 @@ export class MergeTree {
614
640
  assert(this.collabWindow.minSeq <= minSeq, 0x04f /* "minSeq of collab window > target minSeq!" */);
615
641
  if (minSeq > this.collabWindow.minSeq) {
616
642
  this.collabWindow.minSeq = minSeq;
617
- const firstMoveSeqIdx = this.moveSeqs.findIndex((seq) => seq >= minSeq);
618
- this.moveSeqs = firstMoveSeqIdx === -1 ? [] : this.moveSeqs.slice(firstMoveSeqIdx);
643
+ this.obliterates.setMinSeq(minSeq);
619
644
  if (MergeTree.options.zamboniSegments) {
620
645
  zamboniSegments(this);
621
646
  }
@@ -723,34 +748,8 @@ export class MergeTree {
723
748
  const deltaSegments = [];
724
749
  const overlappingRemoves = [];
725
750
  pendingSegmentGroup.segments.map((pendingSegment) => {
726
- const localMovedSeq = pendingSegment.localMovedSeq;
727
751
  const overlappingRemove = !pendingSegment.ack(pendingSegmentGroup, opArgs);
728
- if (opArgs.op.type === MergeTreeDeltaType.OBLITERATE && localMovedSeq !== undefined) {
729
- const locallyMovedSegments = this.locallyMovedSegments.get(localMovedSeq);
730
- if (locallyMovedSegments) {
731
- // Disabling because a for of loop causes the type of segment to be ISegment, which does not have parent information stored
732
- // eslint-disable-next-line unicorn/no-array-for-each
733
- locallyMovedSegments.segments.forEach((segment) => {
734
- segment.localMovedSeq = undefined;
735
- if (!nodesToUpdate.includes(segment.parent)) {
736
- nodesToUpdate.push(segment.parent);
737
- }
738
- if (segment.movedSeq === UnassignedSequenceNumber) {
739
- segment.movedSeq = seq;
740
- }
741
- });
742
- this.locallyMovedSegments.delete(localMovedSeq);
743
- }
744
- }
745
752
  overwrite = overlappingRemove || overwrite;
746
- if (opArgs.op.type === MergeTreeDeltaType.OBLITERATE) {
747
- if (seq !== this.moveSeqs[this.moveSeqs.length - 1]) {
748
- this.moveSeqs.push(seq);
749
- }
750
- if (localMovedSeq !== undefined) {
751
- this.localMoveSeqs.delete(localMovedSeq);
752
- }
753
- }
754
753
  overlappingRemoves.push(overlappingRemove);
755
754
  if (MergeTree.options.zamboniSegments) {
756
755
  this.addToLRUSet(pendingSegment, seq);
@@ -762,6 +761,9 @@ export class MergeTree {
762
761
  segment: pendingSegment,
763
762
  });
764
763
  });
764
+ if (opArgs.op.type === MergeTreeDeltaType.OBLITERATE) {
765
+ this.obliterates.addOrUpdate(pendingSegmentGroup.obliterateInfo);
766
+ }
765
767
  // Perform slides after all segments have been acked, so that
766
768
  // positions after slide are final
767
769
  if (opArgs.op.type === MergeTreeDeltaType.REMOVE ||
@@ -967,90 +969,54 @@ export class MergeTree {
967
969
  this.updateRoot(splitNode);
968
970
  saveIfLocal(newSegment);
969
971
  insertPos += newSegment.cachedLength;
970
- if (!this.options?.mergeTreeEnableObliterate) {
971
- continue;
972
- }
973
- let moveUpperBound = Number.POSITIVE_INFINITY;
974
- const smallestSeqMoveOp = this.getSmallestSeqMoveOp();
975
- if (smallestSeqMoveOp === undefined) {
972
+ if (!this.options?.mergeTreeEnableObliterate || this.obliterates.empty()) {
976
973
  continue;
977
974
  }
978
- const leftAckedSegments = {};
979
- const leftLocalSegments = {};
980
- let _localMovedSeq;
981
- let _movedSeq;
982
- let movedClientIds;
983
- const findLeftMovedSegment = (seg) => {
984
- const movedSeqs = seg.movedSeqs?.filter((movedSeq) => movedSeq >= refSeq) ?? [];
985
- const localMovedSeqs = seg.localMovedSeq ? [seg.localMovedSeq] : [];
986
- for (const movedSeq of movedSeqs) {
987
- leftAckedSegments[movedSeq] = seg;
988
- }
989
- for (const localMovedSeq of localMovedSeqs) {
990
- leftLocalSegments[localMovedSeq] = seg;
991
- }
992
- if ((seg.movedSeqs?.length ?? 0) > 0 || localMovedSeqs.length > 0) {
993
- return true;
994
- }
995
- if (!isRemoved(seg) || wasRemovedAfter(seg, moveUpperBound)) {
996
- moveUpperBound = Math.min(moveUpperBound, seg.seq ?? Number.POSITIVE_INFINITY);
997
- }
998
- // If we've reached a segment that existed before any of our in-collab-window move ops
999
- // happened, no need to continue.
1000
- return moveUpperBound >= smallestSeqMoveOp;
1001
- };
1002
- const findRightMovedSegment = (seg) => {
1003
- const movedSeqs = seg.movedSeqs?.filter((movedSeq) => movedSeq >= refSeq) ?? [];
1004
- const localMovedSeqs = seg.localMovedSeq ? [seg.localMovedSeq] : [];
1005
- for (const movedSeq of movedSeqs) {
1006
- const left = leftAckedSegments[movedSeq];
1007
- if (left) {
1008
- _movedSeq = movedSeq;
1009
- const clientIdIdx = left.movedSeqs?.indexOf(movedSeq) ?? -1;
1010
- const movedClientId = left.movedClientIds?.[clientIdIdx];
1011
- assert(movedClientId !== undefined, 0x869 /* expected client id to exist */);
1012
- movedClientIds = [movedClientId];
1013
- return false;
975
+ // eslint-disable-next-line import/no-deprecated
976
+ let oldest;
977
+ let normalizedOldestSeq = 0;
978
+ // eslint-disable-next-line import/no-deprecated
979
+ let newest;
980
+ let normalizedNewestSeq = 0;
981
+ const movedClientIds = [];
982
+ const movedSeqs = [];
983
+ for (const ob of this.obliterates.findOverlapping(newSegment)) {
984
+ // compute a normalized seq that takes into account local seqs
985
+ // but is still comparable to remote seqs to keep the checks below easy
986
+ // REMOTE SEQUENCE NUMBERS LOCAL SEQUENCE NUMBERS
987
+ // [0, 1, 2, 3, ..., 100, ..., 1000, ..., (MAX - MaxLocalSeq), L1, L2, L3, L4, ..., L100, ..., L1000, ...(MAX)]
988
+ const normalizedObSeq = ob.seq === UnassignedSequenceNumber
989
+ ? Number.MAX_SAFE_INTEGER - this.collabWindow.localSeq + ob.localSeq
990
+ : ob.seq;
991
+ if (normalizedObSeq > refSeq) {
992
+ if (oldest === undefined || normalizedOldestSeq > normalizedObSeq) {
993
+ normalizedOldestSeq = normalizedObSeq;
994
+ oldest = ob;
995
+ movedClientIds.unshift(ob.clientId);
996
+ movedSeqs.unshift(ob.seq);
1014
997
  }
1015
- }
1016
- for (const localMovedSeq of localMovedSeqs) {
1017
- const left = leftLocalSegments[localMovedSeq];
1018
- if (left) {
1019
- _localMovedSeq = localMovedSeq;
1020
- const clientIdIdx = left.movedSeqs?.indexOf(UnassignedSequenceNumber) ?? -1;
1021
- const movedClientId = left.movedClientIds?.[clientIdIdx];
1022
- assert(movedClientId !== undefined, 0x86a /* expected client id to exist */);
1023
- movedClientIds = [movedClientId];
1024
- return false;
998
+ else {
999
+ if (newest === undefined || normalizedNewestSeq < normalizedObSeq) {
1000
+ normalizedNewestSeq = normalizedObSeq;
1001
+ newest = ob;
1002
+ }
1003
+ movedClientIds.push(ob.clientId);
1004
+ movedSeqs.push(ob.seq);
1025
1005
  }
1026
1006
  }
1027
- if ((seg.movedSeqs?.length ?? 0) || localMovedSeqs.length > 0) {
1028
- return true;
1029
- }
1030
- if (!isRemoved(seg) || wasRemovedAfter(seg, moveUpperBound)) {
1031
- moveUpperBound = Math.min(moveUpperBound, seg.seq ?? Number.POSITIVE_INFINITY);
1032
- }
1033
- // If we've reached a segment that existed before any of our in-collab-window move ops
1034
- // happened, no need to continue.
1035
- return moveUpperBound >= smallestSeqMoveOp;
1036
- };
1037
- backwardExcursion(newSegment, findLeftMovedSegment);
1038
- moveUpperBound = Number.POSITIVE_INFINITY;
1039
- forwardExcursion(newSegment, findRightMovedSegment);
1040
- if (_localMovedSeq !== undefined || _movedSeq !== undefined) {
1041
- assert(movedClientIds !== undefined, 0x86b /* movedClientIds should be set if local/moved seq is set */);
1007
+ }
1008
+ if (oldest && newest?.clientId !== clientId) {
1042
1009
  const moveInfo = {
1043
1010
  movedClientIds,
1044
- movedSeq: _movedSeq ?? UnassignedSequenceNumber,
1045
- movedSeqs: _movedSeq === undefined ? [UnassignedSequenceNumber] : [_movedSeq],
1046
- localMovedSeq: _localMovedSeq,
1047
- wasMovedOnInsert: (_movedSeq ?? -1) !== UnassignedSequenceNumber,
1011
+ movedSeq: oldest.seq,
1012
+ movedSeqs,
1013
+ localMovedSeq: oldest.localSeq,
1014
+ wasMovedOnInsert: oldest.seq !== UnassignedSequenceNumber,
1048
1015
  };
1049
1016
  markSegmentMoved(newSegment, moveInfo);
1050
1017
  if (moveInfo.localMovedSeq !== undefined) {
1051
- const movedSegmentGroup = this.locallyMovedSegments.get(moveInfo.localMovedSeq);
1052
- assert(movedSegmentGroup !== undefined, 0x86c /* expected segment group to exist */);
1053
- this.addToPendingList(newSegment, movedSegmentGroup, localSeq);
1018
+ assert(oldest.segmentGroup !== undefined, 0x86c /* expected segment group to exist */);
1019
+ this.addToPendingList(newSegment, oldest.segmentGroup);
1054
1020
  }
1055
1021
  if (newSegment.parent) {
1056
1022
  this.blockUpdatePathLengths(newSegment.parent, seq, clientId);
@@ -1088,9 +1054,6 @@ export class MergeTree {
1088
1054
  return true;
1089
1055
  }
1090
1056
  }
1091
- getSmallestSeqMoveOp() {
1092
- return this.moveSeqs[0] ?? (this.localMoveSeqs.size > 0 ? -1 : undefined);
1093
- }
1094
1057
  insertingWalk(block, pos, refSeq, clientId, seq, context, isLastChildBlock = true) {
1095
1058
  let _pos;
1096
1059
  if (pos === "start") {
@@ -1108,7 +1071,6 @@ export class MergeTree {
1108
1071
  let newNode;
1109
1072
  let fromSplit;
1110
1073
  for (childIndex = 0; childIndex < block.childCount; childIndex++) {
1111
- // TODO Non null asserting, why is this not null?
1112
1074
  child = children[childIndex];
1113
1075
  // ensure we walk down the far edge of the tree, even if all sub-tree is eligible for zamboni
1114
1076
  const isLastNonLeafBlock = isLastChildBlock && !child.isLeaf() && childIndex === block.childCount - 1;
@@ -1173,9 +1135,7 @@ export class MergeTree {
1173
1135
  }
1174
1136
  if (newNode) {
1175
1137
  for (let i = block.childCount; i > childIndex; i--) {
1176
- // TODO Non null asserting, why is this not null?
1177
1138
  block.children[i] = block.children[i - 1];
1178
- // TODO Non null asserting, why is this not null?
1179
1139
  block.children[i].index = i;
1180
1140
  }
1181
1141
  block.assignChild(newNode, childIndex, false);
@@ -1207,7 +1167,6 @@ export class MergeTree {
1207
1167
  // Update ordinals to reflect lowered child count
1208
1168
  this.nodeUpdateOrdinals(node);
1209
1169
  for (let i = 0; i < halfCount; i++) {
1210
- // TODO Non null asserting, why is this not null?
1211
1170
  newNode.assignChild(node.children[halfCount + i], i, false);
1212
1171
  node.children[halfCount + i] = undefined;
1213
1172
  }
@@ -1217,7 +1176,6 @@ export class MergeTree {
1217
1176
  }
1218
1177
  nodeUpdateOrdinals(block) {
1219
1178
  for (let i = 0; i < block.childCount; i++) {
1220
- // TODO Non null asserting, why is this not null?
1221
1179
  const child = block.children[i];
1222
1180
  block.setOrdinal(child, i);
1223
1181
  if (!child.isLeaf()) {
@@ -1294,20 +1252,30 @@ export class MergeTree {
1294
1252
  const localOverlapWithRefs = [];
1295
1253
  const movedSegments = [];
1296
1254
  const localSeq = seq === UnassignedSequenceNumber ? ++this.collabWindow.localSeq : undefined;
1297
- if (seq !== UnassignedSequenceNumber && seq !== this.moveSeqs[this.moveSeqs.length - 1]) {
1298
- this.moveSeqs.push(seq);
1299
- }
1300
- else if (seq === UnassignedSequenceNumber && localSeq !== undefined) {
1301
- this.localMoveSeqs.add(localSeq);
1302
- }
1303
1255
  // eslint-disable-next-line import/no-deprecated
1304
- let segmentGroup;
1256
+ const obliterate = {
1257
+ clientId,
1258
+ end: createDetachedLocalReferencePosition(undefined),
1259
+ refSeq,
1260
+ seq,
1261
+ start: createDetachedLocalReferencePosition(undefined),
1262
+ localSeq,
1263
+ segmentGroup: undefined,
1264
+ };
1265
+ const normalizedStartPos = startPos === "start" || startPos === undefined ? 0 : startPos;
1266
+ const normalizedEndPos = endPos === "end" || endPos === undefined ? this.getLength(refSeq, clientId) : endPos;
1267
+ const { segment: startSeg } = this.getContainingSegment(normalizedStartPos, refSeq, clientId);
1268
+ const { segment: endSeg } = this.getContainingSegment(normalizedEndPos - 1, refSeq, clientId);
1269
+ assert(startSeg !== undefined && endSeg !== undefined, 0xa3f /* segments cannot be undefined */);
1270
+ obliterate.start = this.createLocalReferencePosition(startSeg, 0, ReferenceType.StayOnRemove, {
1271
+ obliterate,
1272
+ });
1273
+ obliterate.end = this.createLocalReferencePosition(endSeg, endSeg.cachedLength - 1, ReferenceType.StayOnRemove, {
1274
+ obliterate,
1275
+ });
1305
1276
  const markMoved = (segment, pos, _start, _end) => {
1277
+ var _a;
1306
1278
  const existingMoveInfo = toMoveInfo(segment);
1307
- if (startSide)
1308
- segment.startSide = startSide;
1309
- if (endSide)
1310
- segment.endSide = endSide;
1311
1279
  if (clientId !== segment.clientId &&
1312
1280
  segment.seq !== undefined &&
1313
1281
  seq !== UnassignedSequenceNumber &&
@@ -1347,7 +1315,8 @@ export class MergeTree {
1347
1315
  if (this.collabWindow.collaborating) {
1348
1316
  if (segment.movedSeq === UnassignedSequenceNumber &&
1349
1317
  clientId === this.collabWindow.clientId) {
1350
- segmentGroup = this.addToPendingList(segment, segmentGroup, localSeq);
1318
+ obliterate.segmentGroup = this.addToPendingList(segment, obliterate.segmentGroup, localSeq);
1319
+ (_a = obliterate.segmentGroup).obliterateInfo ?? (_a.obliterateInfo = obliterate);
1351
1320
  }
1352
1321
  else {
1353
1322
  if (MergeTree.options.zamboniSegments) {
@@ -1367,6 +1336,7 @@ export class MergeTree {
1367
1336
  return true;
1368
1337
  };
1369
1338
  this.nodeMap(refSeq, clientId, markMoved, undefined, afterMarkMoved, start, end, undefined, seq === UnassignedSequenceNumber ? undefined : seq);
1339
+ this.obliterates.addOrUpdate(obliterate);
1370
1340
  this.slideAckedRemovedSegmentReferences(localOverlapWithRefs);
1371
1341
  // opArgs == undefined => test code
1372
1342
  if (movedSegments.length > 0) {
@@ -1375,9 +1345,6 @@ export class MergeTree {
1375
1345
  deltaSegments: movedSegments,
1376
1346
  });
1377
1347
  }
1378
- if (segmentGroup && localSeq !== undefined) {
1379
- this.locallyMovedSegments.set(localSeq, segmentGroup);
1380
- }
1381
1348
  // these events are newly removed
1382
1349
  // so we slide after eventing in case the consumer wants to make reference
1383
1350
  // changes at remove time, like add a ref to track undo redo.
@@ -1524,7 +1491,6 @@ export class MergeTree {
1524
1491
  this.markRangeRemoved(start, start + segment.cachedLength, UniversalSequenceNumber, this.collabWindow.clientId, UniversalSequenceNumber, false, { op: removeOp });
1525
1492
  } /* op.type === MergeTreeDeltaType.ANNOTATE */
1526
1493
  else {
1527
- // TODO Non null asserting, why is this not null?
1528
1494
  const props = pendingSegmentGroup.previousProps[i];
1529
1495
  const annotateOp = createAnnotateRangeOp(start, start + segment.cachedLength, props);
1530
1496
  this.annotateRange(start, start + segment.cachedLength, props, UniversalSequenceNumber, this.collabWindow.clientId, UniversalSequenceNumber, { op: annotateOp },
@@ -1571,7 +1537,7 @@ export class MergeTree {
1571
1537
  if (_segment !== "start" &&
1572
1538
  _segment !== "end" &&
1573
1539
  isRemovedAndAckedOrMovedAndAcked(_segment) &&
1574
- !refTypeIncludesFlag(refType, ReferenceType.SlideOnRemove | ReferenceType.Transient) &&
1540
+ !refTypeIncludesFlag(refType, ReferenceType.SlideOnRemove | ReferenceType.Transient | ReferenceType.StayOnRemove) &&
1575
1541
  _segment.endpointType === undefined) {
1576
1542
  throw new UsageError("Can only create SlideOnRemove or Transient local reference position on a removed or obliterated segment");
1577
1543
  }
@@ -1644,7 +1610,6 @@ export class MergeTree {
1644
1610
  }
1645
1611
  }
1646
1612
  for (let i = 0; i < newOrder.length; i++) {
1647
- // TODO Non null asserting, why is this not null?
1648
1613
  const seg = newOrder[i];
1649
1614
  const { parent, index, ordinal } = currentOrder[i];
1650
1615
  parent?.assignChild(seg, index, false);
@@ -1731,7 +1696,6 @@ export class MergeTree {
1731
1696
  const rightmostTiles = createMap();
1732
1697
  const leftmostTiles = createMap();
1733
1698
  for (let i = 0; i < block.childCount; i++) {
1734
- // TODO Non null asserting, why is this not null?
1735
1699
  const node = block.children[i];
1736
1700
  const nodeLength = nodeTotalLength(this, node);
1737
1701
  if (nodeLength !== undefined) {