@fluidframework/merge-tree 2.3.0-288113 → 2.4.0-294316

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 (188) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/api-report/merge-tree.legacy.alpha.api.md +20 -2
  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 -5
  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 +2 -32
  17. package/dist/mergeTree.d.ts.map +1 -1
  18. package/dist/mergeTree.js +137 -199
  19. package/dist/mergeTree.js.map +1 -1
  20. package/dist/mergeTreeNodes.d.ts +16 -10
  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.partialLength.spec.js +8 -4
  65. package/dist/test/obliterate.partialLength.spec.js.map +1 -1
  66. package/dist/test/obliterate.spec.js +66 -5
  67. package/dist/test/obliterate.spec.js.map +1 -1
  68. package/dist/test/reconnectHelper.d.ts +0 -1
  69. package/dist/test/reconnectHelper.d.ts.map +1 -1
  70. package/dist/test/reconnectHelper.js +1 -1
  71. package/dist/test/reconnectHelper.js.map +1 -1
  72. package/dist/test/testClient.d.ts +1 -11
  73. package/dist/test/testClient.d.ts.map +1 -1
  74. package/dist/test/testClient.js +0 -3
  75. package/dist/test/testClient.js.map +1 -1
  76. package/dist/test/testClientLogger.d.ts.map +1 -1
  77. package/dist/test/testClientLogger.js +17 -7
  78. package/dist/test/testClientLogger.js.map +1 -1
  79. package/dist/test/testUtils.d.ts +10 -0
  80. package/dist/test/testUtils.d.ts.map +1 -1
  81. package/dist/test/testUtils.js +5 -1
  82. package/dist/test/testUtils.js.map +1 -1
  83. package/dist/zamboni.d.ts.map +1 -1
  84. package/dist/zamboni.js +0 -4
  85. package/dist/zamboni.js.map +1 -1
  86. package/lib/attributionCollection.d.ts.map +1 -1
  87. package/lib/attributionCollection.js +1 -29
  88. package/lib/attributionCollection.js.map +1 -1
  89. package/lib/client.d.ts.map +1 -1
  90. package/lib/client.js +3 -5
  91. package/lib/client.js.map +1 -1
  92. package/lib/index.d.ts +1 -1
  93. package/lib/index.d.ts.map +1 -1
  94. package/lib/index.js.map +1 -1
  95. package/lib/legacy.d.ts +1 -0
  96. package/lib/localReference.d.ts.map +1 -1
  97. package/lib/localReference.js +0 -2
  98. package/lib/localReference.js.map +1 -1
  99. package/lib/mergeTree.d.ts +2 -32
  100. package/lib/mergeTree.d.ts.map +1 -1
  101. package/lib/mergeTree.js +138 -200
  102. package/lib/mergeTree.js.map +1 -1
  103. package/lib/mergeTreeNodes.d.ts +16 -10
  104. package/lib/mergeTreeNodes.d.ts.map +1 -1
  105. package/lib/mergeTreeNodes.js +5 -2
  106. package/lib/mergeTreeNodes.js.map +1 -1
  107. package/lib/partialLengths.d.ts.map +1 -1
  108. package/lib/partialLengths.js +8 -54
  109. package/lib/partialLengths.js.map +1 -1
  110. package/lib/properties.d.ts.map +1 -1
  111. package/lib/properties.js +0 -2
  112. package/lib/properties.js.map +1 -1
  113. package/lib/revertibles.d.ts.map +1 -1
  114. package/lib/revertibles.js +0 -14
  115. package/lib/revertibles.js.map +1 -1
  116. package/lib/segmentGroupCollection.d.ts.map +1 -1
  117. package/lib/segmentGroupCollection.js +0 -2
  118. package/lib/segmentGroupCollection.js.map +1 -1
  119. package/lib/segmentPropertiesManager.d.ts.map +1 -1
  120. package/lib/segmentPropertiesManager.js +1 -3
  121. package/lib/segmentPropertiesManager.js.map +1 -1
  122. package/lib/snapshotLoader.d.ts.map +1 -1
  123. package/lib/snapshotLoader.js +1 -4
  124. package/lib/snapshotLoader.js.map +1 -1
  125. package/lib/snapshotV1.d.ts.map +1 -1
  126. package/lib/snapshotV1.js +1 -11
  127. package/lib/snapshotV1.js.map +1 -1
  128. package/lib/snapshotlegacy.d.ts.map +1 -1
  129. package/lib/snapshotlegacy.js +0 -1
  130. package/lib/snapshotlegacy.js.map +1 -1
  131. package/lib/sortedSegmentSet.d.ts +0 -1
  132. package/lib/sortedSegmentSet.d.ts.map +1 -1
  133. package/lib/sortedSegmentSet.js +1 -9
  134. package/lib/sortedSegmentSet.js.map +1 -1
  135. package/lib/sortedSet.d.ts.map +1 -1
  136. package/lib/sortedSet.js +0 -4
  137. package/lib/sortedSet.js.map +1 -1
  138. package/lib/test/client.conflictFarm.spec.d.ts.map +1 -1
  139. package/lib/test/client.conflictFarm.spec.js +37 -28
  140. package/lib/test/client.conflictFarm.spec.js.map +1 -1
  141. package/lib/test/client.replay.spec.js +1 -1
  142. package/lib/test/client.replay.spec.js.map +1 -1
  143. package/lib/test/mergeTreeOperationRunner.d.ts +2 -1
  144. package/lib/test/mergeTreeOperationRunner.d.ts.map +1 -1
  145. package/lib/test/mergeTreeOperationRunner.js +30 -12
  146. package/lib/test/mergeTreeOperationRunner.js.map +1 -1
  147. package/lib/test/obliterate.partialLength.spec.js +9 -5
  148. package/lib/test/obliterate.partialLength.spec.js.map +1 -1
  149. package/lib/test/obliterate.spec.js +67 -6
  150. package/lib/test/obliterate.spec.js.map +1 -1
  151. package/lib/test/reconnectHelper.d.ts +0 -1
  152. package/lib/test/reconnectHelper.d.ts.map +1 -1
  153. package/lib/test/reconnectHelper.js +1 -1
  154. package/lib/test/reconnectHelper.js.map +1 -1
  155. package/lib/test/testClient.d.ts +1 -11
  156. package/lib/test/testClient.d.ts.map +1 -1
  157. package/lib/test/testClient.js +0 -3
  158. package/lib/test/testClient.js.map +1 -1
  159. package/lib/test/testClientLogger.d.ts.map +1 -1
  160. package/lib/test/testClientLogger.js +18 -8
  161. package/lib/test/testClientLogger.js.map +1 -1
  162. package/lib/test/testUtils.d.ts +10 -0
  163. package/lib/test/testUtils.d.ts.map +1 -1
  164. package/lib/test/testUtils.js +3 -0
  165. package/lib/test/testUtils.js.map +1 -1
  166. package/lib/tsdoc-metadata.json +1 -1
  167. package/lib/zamboni.d.ts.map +1 -1
  168. package/lib/zamboni.js +0 -4
  169. package/lib/zamboni.js.map +1 -1
  170. package/package.json +22 -21
  171. package/src/attributionCollection.ts +14 -42
  172. package/src/client.ts +13 -11
  173. package/src/index.ts +1 -0
  174. package/src/localReference.ts +1 -3
  175. package/src/mergeTree.ts +188 -258
  176. package/src/mergeTreeNodes.ts +22 -12
  177. package/src/partialLengths.ts +23 -68
  178. package/src/properties.ts +1 -3
  179. package/src/revertibles.ts +7 -21
  180. package/src/segmentGroupCollection.ts +1 -3
  181. package/src/segmentPropertiesManager.ts +0 -1
  182. package/src/snapshotLoader.ts +2 -4
  183. package/src/snapshotV1.ts +5 -15
  184. package/src/snapshotlegacy.ts +1 -2
  185. package/src/sortedSegmentSet.ts +3 -10
  186. package/src/sortedSet.ts +2 -6
  187. package/src/zamboni.ts +4 -8
  188. 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 {
@@ -23,12 +23,8 @@ import { createMap, extend, extendIfUndefined } from "./properties.js";
23
23
  import { DetachedReferencePosition, refGetTileLabels, refHasTileLabel, refTypeIncludesFlag, } from "./referencePositions.js";
24
24
  // eslint-disable-next-line import/no-deprecated
25
25
  import { PropertiesRollback } from "./segmentPropertiesManager.js";
26
- import { endpointPosAndSide } from "./sequencePlace.js";
26
+ import { SortedSegmentSet } from "./sortedSegmentSet.js";
27
27
  import { zamboniSegments } from "./zamboni.js";
28
- function wasRemovedAfter(seg, seq) {
29
- return (seg.removedSeq !== UnassignedSequenceNumber &&
30
- (seg.removedSeq === undefined || seg.removedSeq > seq));
31
- }
32
28
  function markSegmentMoved(seg, moveInfo) {
33
29
  seg.moveDst = moveInfo.moveDst;
34
30
  seg.movedClientIds = [...moveInfo.movedClientIds];
@@ -176,6 +172,67 @@ export function getSlideToSegoff(segoff, slidingPreference = SlidingPreference.F
176
172
  }
177
173
  const forwardPred = (ref) => ref.slidingPreference !== SlidingPreference.BACKWARD;
178
174
  const backwardPred = (ref) => ref.slidingPreference === SlidingPreference.BACKWARD;
175
+ class Obliterates {
176
+ constructor(mergeTree) {
177
+ this.mergeTree = mergeTree;
178
+ /**
179
+ * Array containing the all move operations within the
180
+ * collab window.
181
+ *
182
+ * The moves are stored in sequence order which accelerates clean up in setMinSeq
183
+ *
184
+ * See https://github.com/microsoft/FluidFramework/blob/main/packages/dds/merge-tree/docs/Obliterate.md#remote-perspective
185
+ * for additional context
186
+ */
187
+ // eslint-disable-next-line import/no-deprecated
188
+ this.seqOrdered = new DoublyLinkedList();
189
+ /**
190
+ * This contains a sorted lists of all obliterate starts
191
+ * and is used to accelerate finding overlapping obliterates
192
+ * as well as determining if there are any obliterates at all.
193
+ */
194
+ this.startOrdered = new SortedSegmentSet();
195
+ }
196
+ setMinSeq(minSeq) {
197
+ // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
198
+ while (!this.seqOrdered.empty && this.seqOrdered.first?.data.seq <= minSeq) {
199
+ const ob = this.seqOrdered.shift();
200
+ this.startOrdered.remove(ob.data.start);
201
+ this.mergeTree.removeLocalReferencePosition(ob.data.start);
202
+ this.mergeTree.removeLocalReferencePosition(ob.data.end);
203
+ }
204
+ }
205
+ // eslint-disable-next-line import/no-deprecated
206
+ addOrUpdate(obliterateInfo) {
207
+ const { seq, start } = obliterateInfo;
208
+ if (seq !== UnassignedSequenceNumber) {
209
+ this.seqOrdered.push(obliterateInfo);
210
+ }
211
+ this.startOrdered.addOrUpdate(start);
212
+ }
213
+ empty() {
214
+ return this.startOrdered.size === 0;
215
+ }
216
+ // eslint-disable-next-line import/no-deprecated
217
+ findOverlapping(seg) {
218
+ // eslint-disable-next-line import/no-deprecated
219
+ const overlapping = [];
220
+ for (const start of this.startOrdered.items) {
221
+ if (start.getSegment().ordinal <= seg.ordinal) {
222
+ // eslint-disable-next-line import/no-deprecated
223
+ const ob = start.properties?.obliterate;
224
+ if (ob.end.getSegment().ordinal >= seg.ordinal) {
225
+ overlapping.push(ob);
226
+ }
227
+ }
228
+ else {
229
+ // the start is past the seg, so exit
230
+ break;
231
+ }
232
+ }
233
+ return overlapping;
234
+ }
235
+ }
179
236
  /**
180
237
  * @internal
181
238
  */
@@ -196,36 +253,7 @@ export class MergeTree {
196
253
  // for now assume only markers have ids and so point directly at the Segment
197
254
  // if we need to have pointers to non-markers, we can change to point at local refs
198
255
  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();
256
+ this.obliterates = new Obliterates(this);
229
257
  this.splitLeafSegment = (segment, pos) => {
230
258
  if (!(pos > 0 && segment)) {
231
259
  return {};
@@ -341,7 +369,6 @@ export class MergeTree {
341
369
  childIndex++, nodeIndex++ // Advance to next child & node
342
370
  ) {
343
371
  // Insert the next node into the current block
344
- // TODO Non null asserting, why is this not null?
345
372
  this.addNode(block, nodes[nodeIndex]);
346
373
  }
347
374
  // Calculate this block's info. Previously this was inlined into the above loop as a micro-optimization,
@@ -350,8 +377,7 @@ export class MergeTree {
350
377
  this.blockUpdate(block);
351
378
  }
352
379
  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.
380
+ ? blocks[0] // ...then we're done. Return the root.
355
381
  : buildMergeBlock(blocks); // ...otherwise recursively build the next layer above blocks.
356
382
  };
357
383
  if (segments.length > 0) {
@@ -399,7 +425,6 @@ export class MergeTree {
399
425
  while (parent) {
400
426
  const children = parent.children;
401
427
  for (let childIndex = 0; childIndex < parent.childCount; childIndex++) {
402
- // TODO Non null asserting, why is this not null?
403
428
  const child = children[childIndex];
404
429
  if ((!!prevParent && child === prevParent) || child === node) {
405
430
  break;
@@ -614,8 +639,7 @@ export class MergeTree {
614
639
  assert(this.collabWindow.minSeq <= minSeq, 0x04f /* "minSeq of collab window > target minSeq!" */);
615
640
  if (minSeq > this.collabWindow.minSeq) {
616
641
  this.collabWindow.minSeq = minSeq;
617
- const firstMoveSeqIdx = this.moveSeqs.findIndex((seq) => seq >= minSeq);
618
- this.moveSeqs = firstMoveSeqIdx === -1 ? [] : this.moveSeqs.slice(firstMoveSeqIdx);
642
+ this.obliterates.setMinSeq(minSeq);
619
643
  if (MergeTree.options.zamboniSegments) {
620
644
  zamboniSegments(this);
621
645
  }
@@ -723,34 +747,8 @@ export class MergeTree {
723
747
  const deltaSegments = [];
724
748
  const overlappingRemoves = [];
725
749
  pendingSegmentGroup.segments.map((pendingSegment) => {
726
- const localMovedSeq = pendingSegment.localMovedSeq;
727
750
  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
751
  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
752
  overlappingRemoves.push(overlappingRemove);
755
753
  if (MergeTree.options.zamboniSegments) {
756
754
  this.addToLRUSet(pendingSegment, seq);
@@ -762,6 +760,9 @@ export class MergeTree {
762
760
  segment: pendingSegment,
763
761
  });
764
762
  });
763
+ if (opArgs.op.type === MergeTreeDeltaType.OBLITERATE) {
764
+ this.obliterates.addOrUpdate(pendingSegmentGroup.obliterateInfo);
765
+ }
765
766
  // Perform slides after all segments have been acked, so that
766
767
  // positions after slide are final
767
768
  if (opArgs.op.type === MergeTreeDeltaType.REMOVE ||
@@ -967,90 +968,54 @@ export class MergeTree {
967
968
  this.updateRoot(splitNode);
968
969
  saveIfLocal(newSegment);
969
970
  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) {
971
+ if (!this.options?.mergeTreeEnableObliterate || this.obliterates.empty()) {
976
972
  continue;
977
973
  }
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;
974
+ // eslint-disable-next-line import/no-deprecated
975
+ let oldest;
976
+ let normalizedOldestSeq = 0;
977
+ // eslint-disable-next-line import/no-deprecated
978
+ let newest;
979
+ let normalizedNewestSeq = 0;
980
+ const movedClientIds = [];
981
+ const movedSeqs = [];
982
+ for (const ob of this.obliterates.findOverlapping(newSegment)) {
983
+ // compute a normalized seq that takes into account local seqs
984
+ // but is still comparable to remote seqs to keep the checks below easy
985
+ // REMOTE SEQUENCE NUMBERS LOCAL SEQUENCE NUMBERS
986
+ // [0, 1, 2, 3, ..., 100, ..., 1000, ..., (MAX - MaxLocalSeq), L1, L2, L3, L4, ..., L100, ..., L1000, ...(MAX)]
987
+ const normalizedObSeq = ob.seq === UnassignedSequenceNumber
988
+ ? Number.MAX_SAFE_INTEGER - this.collabWindow.localSeq + ob.localSeq
989
+ : ob.seq;
990
+ if (normalizedObSeq > refSeq) {
991
+ if (oldest === undefined || normalizedOldestSeq > normalizedObSeq) {
992
+ normalizedOldestSeq = normalizedObSeq;
993
+ oldest = ob;
994
+ movedClientIds.unshift(ob.clientId);
995
+ movedSeqs.unshift(ob.seq);
1014
996
  }
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;
997
+ else {
998
+ if (newest === undefined || normalizedNewestSeq < normalizedObSeq) {
999
+ normalizedNewestSeq = normalizedObSeq;
1000
+ newest = ob;
1001
+ }
1002
+ movedClientIds.push(ob.clientId);
1003
+ movedSeqs.push(ob.seq);
1025
1004
  }
1026
1005
  }
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 */);
1006
+ }
1007
+ if (oldest && newest?.clientId !== clientId) {
1042
1008
  const moveInfo = {
1043
1009
  movedClientIds,
1044
- movedSeq: _movedSeq ?? UnassignedSequenceNumber,
1045
- movedSeqs: _movedSeq === undefined ? [UnassignedSequenceNumber] : [_movedSeq],
1046
- localMovedSeq: _localMovedSeq,
1047
- wasMovedOnInsert: (_movedSeq ?? -1) !== UnassignedSequenceNumber,
1010
+ movedSeq: oldest.seq,
1011
+ movedSeqs,
1012
+ localMovedSeq: oldest.localSeq,
1013
+ wasMovedOnInsert: oldest.seq !== UnassignedSequenceNumber,
1048
1014
  };
1049
1015
  markSegmentMoved(newSegment, moveInfo);
1050
1016
  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);
1017
+ assert(oldest.segmentGroup !== undefined, 0x86c /* expected segment group to exist */);
1018
+ this.addToPendingList(newSegment, oldest.segmentGroup);
1054
1019
  }
1055
1020
  if (newSegment.parent) {
1056
1021
  this.blockUpdatePathLengths(newSegment.parent, seq, clientId);
@@ -1088,27 +1053,14 @@ export class MergeTree {
1088
1053
  return true;
1089
1054
  }
1090
1055
  }
1091
- getSmallestSeqMoveOp() {
1092
- return this.moveSeqs[0] ?? (this.localMoveSeqs.size > 0 ? -1 : undefined);
1093
- }
1094
1056
  insertingWalk(block, pos, refSeq, clientId, seq, context, isLastChildBlock = true) {
1095
- let _pos;
1096
- if (pos === "start") {
1097
- _pos = 0;
1098
- }
1099
- else if (pos === "end") {
1100
- _pos = this.root.mergeTree?.getLength(refSeq, clientId) ?? 0;
1101
- }
1102
- else {
1103
- _pos = pos;
1104
- }
1057
+ let _pos = pos;
1105
1058
  const children = block.children;
1106
1059
  let childIndex;
1107
1060
  let child;
1108
1061
  let newNode;
1109
1062
  let fromSplit;
1110
1063
  for (childIndex = 0; childIndex < block.childCount; childIndex++) {
1111
- // TODO Non null asserting, why is this not null?
1112
1064
  child = children[childIndex];
1113
1065
  // ensure we walk down the far edge of the tree, even if all sub-tree is eligible for zamboni
1114
1066
  const isLastNonLeafBlock = isLastChildBlock && !child.isLeaf() && childIndex === block.childCount - 1;
@@ -1173,9 +1125,7 @@ export class MergeTree {
1173
1125
  }
1174
1126
  if (newNode) {
1175
1127
  for (let i = block.childCount; i > childIndex; i--) {
1176
- // TODO Non null asserting, why is this not null?
1177
1128
  block.children[i] = block.children[i - 1];
1178
- // TODO Non null asserting, why is this not null?
1179
1129
  block.children[i].index = i;
1180
1130
  }
1181
1131
  block.assignChild(newNode, childIndex, false);
@@ -1207,7 +1157,6 @@ export class MergeTree {
1207
1157
  // Update ordinals to reflect lowered child count
1208
1158
  this.nodeUpdateOrdinals(node);
1209
1159
  for (let i = 0; i < halfCount; i++) {
1210
- // TODO Non null asserting, why is this not null?
1211
1160
  newNode.assignChild(node.children[halfCount + i], i, false);
1212
1161
  node.children[halfCount + i] = undefined;
1213
1162
  }
@@ -1217,7 +1166,6 @@ export class MergeTree {
1217
1166
  }
1218
1167
  nodeUpdateOrdinals(block) {
1219
1168
  for (let i = 0; i < block.childCount; i++) {
1220
- // TODO Non null asserting, why is this not null?
1221
1169
  const child = block.children[i];
1222
1170
  block.setOrdinal(child, i);
1223
1171
  if (!child.isLeaf()) {
@@ -1281,33 +1229,34 @@ export class MergeTree {
1281
1229
  }
1282
1230
  obliterateRange(start, end, refSeq, clientId, seq, overwrite = false, opArgs) {
1283
1231
  errorIfOptionNotTrue(this.options, "mergeTreeEnableObliterate");
1284
- const { startPos, startSide, endPos, endSide } = endpointPosAndSide(start, end);
1285
- assert(startPos !== undefined &&
1286
- endPos !== undefined &&
1287
- startSide !== undefined &&
1288
- endSide !== undefined &&
1289
- startPos !== "end" &&
1290
- endPos !== "start", 0x9e2 /* start and end cannot be undefined because they were not passed in as undefined */);
1291
- this.ensureIntervalBoundary(startPos, refSeq, clientId);
1292
- this.ensureIntervalBoundary(endPos, refSeq, clientId);
1232
+ this.ensureIntervalBoundary(start, refSeq, clientId);
1233
+ this.ensureIntervalBoundary(end, refSeq, clientId);
1293
1234
  let _overwrite = overwrite;
1294
1235
  const localOverlapWithRefs = [];
1295
1236
  const movedSegments = [];
1296
1237
  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
1238
  // eslint-disable-next-line import/no-deprecated
1304
- let segmentGroup;
1239
+ const obliterate = {
1240
+ clientId,
1241
+ end: createDetachedLocalReferencePosition(undefined),
1242
+ refSeq,
1243
+ seq,
1244
+ start: createDetachedLocalReferencePosition(undefined),
1245
+ localSeq,
1246
+ segmentGroup: undefined,
1247
+ };
1248
+ const { segment: startSeg } = this.getContainingSegment(start, refSeq, clientId);
1249
+ const { segment: endSeg } = this.getContainingSegment(end - 1, refSeq, clientId);
1250
+ assert(startSeg !== undefined && endSeg !== undefined, 0xa3f /* segments cannot be undefined */);
1251
+ obliterate.start = this.createLocalReferencePosition(startSeg, 0, ReferenceType.StayOnRemove, {
1252
+ obliterate,
1253
+ });
1254
+ obliterate.end = this.createLocalReferencePosition(endSeg, endSeg.cachedLength - 1, ReferenceType.StayOnRemove, {
1255
+ obliterate,
1256
+ });
1305
1257
  const markMoved = (segment, pos, _start, _end) => {
1258
+ var _a;
1306
1259
  const existingMoveInfo = toMoveInfo(segment);
1307
- if (startSide)
1308
- segment.startSide = startSide;
1309
- if (endSide)
1310
- segment.endSide = endSide;
1311
1260
  if (clientId !== segment.clientId &&
1312
1261
  segment.seq !== undefined &&
1313
1262
  seq !== UnassignedSequenceNumber &&
@@ -1347,7 +1296,8 @@ export class MergeTree {
1347
1296
  if (this.collabWindow.collaborating) {
1348
1297
  if (segment.movedSeq === UnassignedSequenceNumber &&
1349
1298
  clientId === this.collabWindow.clientId) {
1350
- segmentGroup = this.addToPendingList(segment, segmentGroup, localSeq);
1299
+ obliterate.segmentGroup = this.addToPendingList(segment, obliterate.segmentGroup, localSeq);
1300
+ (_a = obliterate.segmentGroup).obliterateInfo ?? (_a.obliterateInfo = obliterate);
1351
1301
  }
1352
1302
  else {
1353
1303
  if (MergeTree.options.zamboniSegments) {
@@ -1367,6 +1317,7 @@ export class MergeTree {
1367
1317
  return true;
1368
1318
  };
1369
1319
  this.nodeMap(refSeq, clientId, markMoved, undefined, afterMarkMoved, start, end, undefined, seq === UnassignedSequenceNumber ? undefined : seq);
1320
+ this.obliterates.addOrUpdate(obliterate);
1370
1321
  this.slideAckedRemovedSegmentReferences(localOverlapWithRefs);
1371
1322
  // opArgs == undefined => test code
1372
1323
  if (movedSegments.length > 0) {
@@ -1375,9 +1326,6 @@ export class MergeTree {
1375
1326
  deltaSegments: movedSegments,
1376
1327
  });
1377
1328
  }
1378
- if (segmentGroup && localSeq !== undefined) {
1379
- this.locallyMovedSegments.set(localSeq, segmentGroup);
1380
- }
1381
1329
  // these events are newly removed
1382
1330
  // so we slide after eventing in case the consumer wants to make reference
1383
1331
  // changes at remove time, like add a ref to track undo redo.
@@ -1524,7 +1472,6 @@ export class MergeTree {
1524
1472
  this.markRangeRemoved(start, start + segment.cachedLength, UniversalSequenceNumber, this.collabWindow.clientId, UniversalSequenceNumber, false, { op: removeOp });
1525
1473
  } /* op.type === MergeTreeDeltaType.ANNOTATE */
1526
1474
  else {
1527
- // TODO Non null asserting, why is this not null?
1528
1475
  const props = pendingSegmentGroup.previousProps[i];
1529
1476
  const annotateOp = createAnnotateRangeOp(start, start + segment.cachedLength, props);
1530
1477
  this.annotateRange(start, start + segment.cachedLength, props, UniversalSequenceNumber, this.collabWindow.clientId, UniversalSequenceNumber, { op: annotateOp },
@@ -1571,7 +1518,7 @@ export class MergeTree {
1571
1518
  if (_segment !== "start" &&
1572
1519
  _segment !== "end" &&
1573
1520
  isRemovedAndAckedOrMovedAndAcked(_segment) &&
1574
- !refTypeIncludesFlag(refType, ReferenceType.SlideOnRemove | ReferenceType.Transient) &&
1521
+ !refTypeIncludesFlag(refType, ReferenceType.SlideOnRemove | ReferenceType.Transient | ReferenceType.StayOnRemove) &&
1575
1522
  _segment.endpointType === undefined) {
1576
1523
  throw new UsageError("Can only create SlideOnRemove or Transient local reference position on a removed or obliterated segment");
1577
1524
  }
@@ -1644,7 +1591,6 @@ export class MergeTree {
1644
1591
  }
1645
1592
  }
1646
1593
  for (let i = 0; i < newOrder.length; i++) {
1647
- // TODO Non null asserting, why is this not null?
1648
1594
  const seg = newOrder[i];
1649
1595
  const { parent, index, ordinal } = currentOrder[i];
1650
1596
  parent?.assignChild(seg, index, false);
@@ -1731,7 +1677,6 @@ export class MergeTree {
1731
1677
  const rightmostTiles = createMap();
1732
1678
  const leftmostTiles = createMap();
1733
1679
  for (let i = 0; i < block.childCount; i++) {
1734
- // TODO Non null asserting, why is this not null?
1735
1680
  const node = block.children[i];
1736
1681
  const nodeLength = nodeTotalLength(this, node);
1737
1682
  if (nodeLength !== undefined) {
@@ -1841,18 +1786,11 @@ export class MergeTree {
1841
1786
  * ignored for the purposes of tracking when traversal should end.
1842
1787
  */
1843
1788
  nodeMap(refSeq, clientId, leaf, accum, post, start = 0, end, localSeq, visibilitySeq = refSeq) {
1844
- const maybeEndPos = end ?? this.nodeLength(this.root, refSeq, clientId, localSeq) ?? 0;
1845
- if (maybeEndPos === start) {
1789
+ const endPos = end ?? this.nodeLength(this.root, refSeq, clientId, localSeq) ?? 0;
1790
+ if (endPos === start) {
1846
1791
  return;
1847
1792
  }
1848
1793
  let pos = 0;
1849
- let { startPos, endPos } = endpointPosAndSide(start, end);
1850
- startPos = startPos === "start" || startPos === undefined ? 0 : startPos;
1851
- endPos =
1852
- endPos === "end" || endPos === undefined
1853
- ? this.root.mergeTree?.getLength(refSeq, clientId) ?? 0
1854
- : endPos;
1855
- assert(startPos !== "end" && endPos !== "start", 0x9e3 /* start cannot be 'end' and end cannot be 'start' */);
1856
1794
  depthFirstNodeWalk(this.root, this.root.children[0], (node) => {
1857
1795
  if (endPos <= pos) {
1858
1796
  return NodeAction.Exit;
@@ -1869,19 +1807,19 @@ export class MergeTree {
1869
1807
  }
1870
1808
  const nextPos = pos + lenAtRefSeq;
1871
1809
  // start is beyond the current node, so we can skip it
1872
- if (typeof startPos === "number" && startPos >= nextPos) {
1810
+ if (start >= nextPos) {
1873
1811
  pos = nextPos;
1874
1812
  return NodeAction.Skip;
1875
1813
  }
1876
1814
  if (node.isLeaf()) {
1877
- if (leaf(node, pos, refSeq, clientId, startPos - pos, endPos - pos, accum) === false) {
1815
+ if (leaf(node, pos, refSeq, clientId, start - pos, endPos - pos, accum) === false) {
1878
1816
  return NodeAction.Exit;
1879
1817
  }
1880
1818
  pos = nextPos;
1881
1819
  }
1882
1820
  }, undefined, post === undefined
1883
1821
  ? undefined
1884
- : (block) => post(block, pos, refSeq, clientId, startPos - pos, endPos - pos, accum));
1822
+ : (block) => post(block, pos, refSeq, clientId, start - pos, endPos - pos, accum));
1885
1823
  }
1886
1824
  }
1887
1825
  MergeTree.options = {