@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/dist/mergeTree.js CHANGED
@@ -24,12 +24,8 @@ const properties_js_1 = require("./properties.js");
24
24
  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
- const sequencePlace_js_1 = require("./sequencePlace.js");
27
+ const sortedSegmentSet_js_1 = require("./sortedSegmentSet.js");
28
28
  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
29
  function markSegmentMoved(seg, moveInfo) {
34
30
  seg.moveDst = moveInfo.moveDst;
35
31
  seg.movedClientIds = [...moveInfo.movedClientIds];
@@ -180,6 +176,67 @@ function getSlideToSegoff(segoff, slidingPreference = localReference_js_1.Slidin
180
176
  exports.getSlideToSegoff = getSlideToSegoff;
181
177
  const forwardPred = (ref) => ref.slidingPreference !== localReference_js_1.SlidingPreference.BACKWARD;
182
178
  const backwardPred = (ref) => ref.slidingPreference === localReference_js_1.SlidingPreference.BACKWARD;
179
+ class Obliterates {
180
+ constructor(mergeTree) {
181
+ this.mergeTree = mergeTree;
182
+ /**
183
+ * Array containing the all move operations within the
184
+ * collab window.
185
+ *
186
+ * The moves are stored in sequence order which accelerates clean up in setMinSeq
187
+ *
188
+ * See https://github.com/microsoft/FluidFramework/blob/main/packages/dds/merge-tree/docs/Obliterate.md#remote-perspective
189
+ * for additional context
190
+ */
191
+ // eslint-disable-next-line import/no-deprecated
192
+ this.seqOrdered = new index_js_1.DoublyLinkedList();
193
+ /**
194
+ * This contains a sorted lists of all obliterate starts
195
+ * and is used to accelerate finding overlapping obliterates
196
+ * as well as determining if there are any obliterates at all.
197
+ */
198
+ this.startOrdered = new sortedSegmentSet_js_1.SortedSegmentSet();
199
+ }
200
+ setMinSeq(minSeq) {
201
+ // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
202
+ while (!this.seqOrdered.empty && this.seqOrdered.first?.data.seq <= minSeq) {
203
+ const ob = this.seqOrdered.shift();
204
+ this.startOrdered.remove(ob.data.start);
205
+ this.mergeTree.removeLocalReferencePosition(ob.data.start);
206
+ this.mergeTree.removeLocalReferencePosition(ob.data.end);
207
+ }
208
+ }
209
+ // eslint-disable-next-line import/no-deprecated
210
+ addOrUpdate(obliterateInfo) {
211
+ const { seq, start } = obliterateInfo;
212
+ if (seq !== constants_js_1.UnassignedSequenceNumber) {
213
+ this.seqOrdered.push(obliterateInfo);
214
+ }
215
+ this.startOrdered.addOrUpdate(start);
216
+ }
217
+ empty() {
218
+ return this.startOrdered.size === 0;
219
+ }
220
+ // eslint-disable-next-line import/no-deprecated
221
+ findOverlapping(seg) {
222
+ // eslint-disable-next-line import/no-deprecated
223
+ const overlapping = [];
224
+ for (const start of this.startOrdered.items) {
225
+ if (start.getSegment().ordinal <= seg.ordinal) {
226
+ // eslint-disable-next-line import/no-deprecated
227
+ const ob = start.properties?.obliterate;
228
+ if (ob.end.getSegment().ordinal >= seg.ordinal) {
229
+ overlapping.push(ob);
230
+ }
231
+ }
232
+ else {
233
+ // the start is past the seg, so exit
234
+ break;
235
+ }
236
+ }
237
+ return overlapping;
238
+ }
239
+ }
183
240
  /**
184
241
  * @internal
185
242
  */
@@ -200,36 +257,7 @@ class MergeTree {
200
257
  // for now assume only markers have ids and so point directly at the Segment
201
258
  // if we need to have pointers to non-markers, we can change to point at local refs
202
259
  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();
260
+ this.obliterates = new Obliterates(this);
233
261
  this.splitLeafSegment = (segment, pos) => {
234
262
  if (!(pos > 0 && segment)) {
235
263
  return {};
@@ -345,7 +373,6 @@ class MergeTree {
345
373
  childIndex++, nodeIndex++ // Advance to next child & node
346
374
  ) {
347
375
  // Insert the next node into the current block
348
- // TODO Non null asserting, why is this not null?
349
376
  this.addNode(block, nodes[nodeIndex]);
350
377
  }
351
378
  // Calculate this block's info. Previously this was inlined into the above loop as a micro-optimization,
@@ -354,8 +381,7 @@ class MergeTree {
354
381
  this.blockUpdate(block);
355
382
  }
356
383
  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.
384
+ ? blocks[0] // ...then we're done. Return the root.
359
385
  : buildMergeBlock(blocks); // ...otherwise recursively build the next layer above blocks.
360
386
  };
361
387
  if (segments.length > 0) {
@@ -403,7 +429,6 @@ class MergeTree {
403
429
  while (parent) {
404
430
  const children = parent.children;
405
431
  for (let childIndex = 0; childIndex < parent.childCount; childIndex++) {
406
- // TODO Non null asserting, why is this not null?
407
432
  const child = children[childIndex];
408
433
  if ((!!prevParent && child === prevParent) || child === node) {
409
434
  break;
@@ -618,8 +643,7 @@ class MergeTree {
618
643
  (0, internal_1.assert)(this.collabWindow.minSeq <= minSeq, 0x04f /* "minSeq of collab window > target minSeq!" */);
619
644
  if (minSeq > this.collabWindow.minSeq) {
620
645
  this.collabWindow.minSeq = minSeq;
621
- const firstMoveSeqIdx = this.moveSeqs.findIndex((seq) => seq >= minSeq);
622
- this.moveSeqs = firstMoveSeqIdx === -1 ? [] : this.moveSeqs.slice(firstMoveSeqIdx);
646
+ this.obliterates.setMinSeq(minSeq);
623
647
  if (MergeTree.options.zamboniSegments) {
624
648
  (0, zamboni_js_1.zamboniSegments)(this);
625
649
  }
@@ -727,34 +751,8 @@ class MergeTree {
727
751
  const deltaSegments = [];
728
752
  const overlappingRemoves = [];
729
753
  pendingSegmentGroup.segments.map((pendingSegment) => {
730
- const localMovedSeq = pendingSegment.localMovedSeq;
731
754
  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
755
  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
756
  overlappingRemoves.push(overlappingRemove);
759
757
  if (MergeTree.options.zamboniSegments) {
760
758
  this.addToLRUSet(pendingSegment, seq);
@@ -766,6 +764,9 @@ class MergeTree {
766
764
  segment: pendingSegment,
767
765
  });
768
766
  });
767
+ if (opArgs.op.type === ops_js_1.MergeTreeDeltaType.OBLITERATE) {
768
+ this.obliterates.addOrUpdate(pendingSegmentGroup.obliterateInfo);
769
+ }
769
770
  // Perform slides after all segments have been acked, so that
770
771
  // positions after slide are final
771
772
  if (opArgs.op.type === ops_js_1.MergeTreeDeltaType.REMOVE ||
@@ -971,90 +972,54 @@ class MergeTree {
971
972
  this.updateRoot(splitNode);
972
973
  saveIfLocal(newSegment);
973
974
  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) {
975
+ if (!this.options?.mergeTreeEnableObliterate || this.obliterates.empty()) {
980
976
  continue;
981
977
  }
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;
978
+ // eslint-disable-next-line import/no-deprecated
979
+ let oldest;
980
+ let normalizedOldestSeq = 0;
981
+ // eslint-disable-next-line import/no-deprecated
982
+ let newest;
983
+ let normalizedNewestSeq = 0;
984
+ const movedClientIds = [];
985
+ const movedSeqs = [];
986
+ for (const ob of this.obliterates.findOverlapping(newSegment)) {
987
+ // compute a normalized seq that takes into account local seqs
988
+ // but is still comparable to remote seqs to keep the checks below easy
989
+ // REMOTE SEQUENCE NUMBERS LOCAL SEQUENCE NUMBERS
990
+ // [0, 1, 2, 3, ..., 100, ..., 1000, ..., (MAX - MaxLocalSeq), L1, L2, L3, L4, ..., L100, ..., L1000, ...(MAX)]
991
+ const normalizedObSeq = ob.seq === constants_js_1.UnassignedSequenceNumber
992
+ ? Number.MAX_SAFE_INTEGER - this.collabWindow.localSeq + ob.localSeq
993
+ : ob.seq;
994
+ if (normalizedObSeq > refSeq) {
995
+ if (oldest === undefined || normalizedOldestSeq > normalizedObSeq) {
996
+ normalizedOldestSeq = normalizedObSeq;
997
+ oldest = ob;
998
+ movedClientIds.unshift(ob.clientId);
999
+ movedSeqs.unshift(ob.seq);
1018
1000
  }
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;
1001
+ else {
1002
+ if (newest === undefined || normalizedNewestSeq < normalizedObSeq) {
1003
+ normalizedNewestSeq = normalizedObSeq;
1004
+ newest = ob;
1005
+ }
1006
+ movedClientIds.push(ob.clientId);
1007
+ movedSeqs.push(ob.seq);
1029
1008
  }
1030
1009
  }
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 */);
1010
+ }
1011
+ if (oldest && newest?.clientId !== clientId) {
1046
1012
  const moveInfo = {
1047
1013
  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,
1014
+ movedSeq: oldest.seq,
1015
+ movedSeqs,
1016
+ localMovedSeq: oldest.localSeq,
1017
+ wasMovedOnInsert: oldest.seq !== constants_js_1.UnassignedSequenceNumber,
1052
1018
  };
1053
1019
  markSegmentMoved(newSegment, moveInfo);
1054
1020
  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);
1021
+ (0, internal_1.assert)(oldest.segmentGroup !== undefined, 0x86c /* expected segment group to exist */);
1022
+ this.addToPendingList(newSegment, oldest.segmentGroup);
1058
1023
  }
1059
1024
  if (newSegment.parent) {
1060
1025
  this.blockUpdatePathLengths(newSegment.parent, seq, clientId);
@@ -1092,27 +1057,14 @@ class MergeTree {
1092
1057
  return true;
1093
1058
  }
1094
1059
  }
1095
- getSmallestSeqMoveOp() {
1096
- return this.moveSeqs[0] ?? (this.localMoveSeqs.size > 0 ? -1 : undefined);
1097
- }
1098
1060
  insertingWalk(block, pos, refSeq, clientId, seq, context, isLastChildBlock = true) {
1099
- let _pos;
1100
- if (pos === "start") {
1101
- _pos = 0;
1102
- }
1103
- else if (pos === "end") {
1104
- _pos = this.root.mergeTree?.getLength(refSeq, clientId) ?? 0;
1105
- }
1106
- else {
1107
- _pos = pos;
1108
- }
1061
+ let _pos = pos;
1109
1062
  const children = block.children;
1110
1063
  let childIndex;
1111
1064
  let child;
1112
1065
  let newNode;
1113
1066
  let fromSplit;
1114
1067
  for (childIndex = 0; childIndex < block.childCount; childIndex++) {
1115
- // TODO Non null asserting, why is this not null?
1116
1068
  child = children[childIndex];
1117
1069
  // ensure we walk down the far edge of the tree, even if all sub-tree is eligible for zamboni
1118
1070
  const isLastNonLeafBlock = isLastChildBlock && !child.isLeaf() && childIndex === block.childCount - 1;
@@ -1177,9 +1129,7 @@ class MergeTree {
1177
1129
  }
1178
1130
  if (newNode) {
1179
1131
  for (let i = block.childCount; i > childIndex; i--) {
1180
- // TODO Non null asserting, why is this not null?
1181
1132
  block.children[i] = block.children[i - 1];
1182
- // TODO Non null asserting, why is this not null?
1183
1133
  block.children[i].index = i;
1184
1134
  }
1185
1135
  block.assignChild(newNode, childIndex, false);
@@ -1211,7 +1161,6 @@ class MergeTree {
1211
1161
  // Update ordinals to reflect lowered child count
1212
1162
  this.nodeUpdateOrdinals(node);
1213
1163
  for (let i = 0; i < halfCount; i++) {
1214
- // TODO Non null asserting, why is this not null?
1215
1164
  newNode.assignChild(node.children[halfCount + i], i, false);
1216
1165
  node.children[halfCount + i] = undefined;
1217
1166
  }
@@ -1221,7 +1170,6 @@ class MergeTree {
1221
1170
  }
1222
1171
  nodeUpdateOrdinals(block) {
1223
1172
  for (let i = 0; i < block.childCount; i++) {
1224
- // TODO Non null asserting, why is this not null?
1225
1173
  const child = block.children[i];
1226
1174
  block.setOrdinal(child, i);
1227
1175
  if (!child.isLeaf()) {
@@ -1285,33 +1233,34 @@ class MergeTree {
1285
1233
  }
1286
1234
  obliterateRange(start, end, refSeq, clientId, seq, overwrite = false, opArgs) {
1287
1235
  errorIfOptionNotTrue(this.options, "mergeTreeEnableObliterate");
1288
- const { startPos, startSide, endPos, endSide } = (0, sequencePlace_js_1.endpointPosAndSide)(start, end);
1289
- (0, internal_1.assert)(startPos !== undefined &&
1290
- endPos !== undefined &&
1291
- startSide !== undefined &&
1292
- endSide !== undefined &&
1293
- startPos !== "end" &&
1294
- endPos !== "start", 0x9e2 /* start and end cannot be undefined because they were not passed in as undefined */);
1295
- this.ensureIntervalBoundary(startPos, refSeq, clientId);
1296
- this.ensureIntervalBoundary(endPos, refSeq, clientId);
1236
+ this.ensureIntervalBoundary(start, refSeq, clientId);
1237
+ this.ensureIntervalBoundary(end, refSeq, clientId);
1297
1238
  let _overwrite = overwrite;
1298
1239
  const localOverlapWithRefs = [];
1299
1240
  const movedSegments = [];
1300
1241
  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
1242
  // eslint-disable-next-line import/no-deprecated
1308
- let segmentGroup;
1243
+ const obliterate = {
1244
+ clientId,
1245
+ end: (0, localReference_js_1.createDetachedLocalReferencePosition)(undefined),
1246
+ refSeq,
1247
+ seq,
1248
+ start: (0, localReference_js_1.createDetachedLocalReferencePosition)(undefined),
1249
+ localSeq,
1250
+ segmentGroup: undefined,
1251
+ };
1252
+ const { segment: startSeg } = this.getContainingSegment(start, refSeq, clientId);
1253
+ const { segment: endSeg } = this.getContainingSegment(end - 1, refSeq, clientId);
1254
+ (0, internal_1.assert)(startSeg !== undefined && endSeg !== undefined, 0xa3f /* segments cannot be undefined */);
1255
+ obliterate.start = this.createLocalReferencePosition(startSeg, 0, ops_js_1.ReferenceType.StayOnRemove, {
1256
+ obliterate,
1257
+ });
1258
+ obliterate.end = this.createLocalReferencePosition(endSeg, endSeg.cachedLength - 1, ops_js_1.ReferenceType.StayOnRemove, {
1259
+ obliterate,
1260
+ });
1309
1261
  const markMoved = (segment, pos, _start, _end) => {
1262
+ var _a;
1310
1263
  const existingMoveInfo = (0, mergeTreeNodes_js_1.toMoveInfo)(segment);
1311
- if (startSide)
1312
- segment.startSide = startSide;
1313
- if (endSide)
1314
- segment.endSide = endSide;
1315
1264
  if (clientId !== segment.clientId &&
1316
1265
  segment.seq !== undefined &&
1317
1266
  seq !== constants_js_1.UnassignedSequenceNumber &&
@@ -1351,7 +1300,8 @@ class MergeTree {
1351
1300
  if (this.collabWindow.collaborating) {
1352
1301
  if (segment.movedSeq === constants_js_1.UnassignedSequenceNumber &&
1353
1302
  clientId === this.collabWindow.clientId) {
1354
- segmentGroup = this.addToPendingList(segment, segmentGroup, localSeq);
1303
+ obliterate.segmentGroup = this.addToPendingList(segment, obliterate.segmentGroup, localSeq);
1304
+ (_a = obliterate.segmentGroup).obliterateInfo ?? (_a.obliterateInfo = obliterate);
1355
1305
  }
1356
1306
  else {
1357
1307
  if (MergeTree.options.zamboniSegments) {
@@ -1371,6 +1321,7 @@ class MergeTree {
1371
1321
  return true;
1372
1322
  };
1373
1323
  this.nodeMap(refSeq, clientId, markMoved, undefined, afterMarkMoved, start, end, undefined, seq === constants_js_1.UnassignedSequenceNumber ? undefined : seq);
1324
+ this.obliterates.addOrUpdate(obliterate);
1374
1325
  this.slideAckedRemovedSegmentReferences(localOverlapWithRefs);
1375
1326
  // opArgs == undefined => test code
1376
1327
  if (movedSegments.length > 0) {
@@ -1379,9 +1330,6 @@ class MergeTree {
1379
1330
  deltaSegments: movedSegments,
1380
1331
  });
1381
1332
  }
1382
- if (segmentGroup && localSeq !== undefined) {
1383
- this.locallyMovedSegments.set(localSeq, segmentGroup);
1384
- }
1385
1333
  // these events are newly removed
1386
1334
  // so we slide after eventing in case the consumer wants to make reference
1387
1335
  // changes at remove time, like add a ref to track undo redo.
@@ -1528,7 +1476,6 @@ class MergeTree {
1528
1476
  this.markRangeRemoved(start, start + segment.cachedLength, constants_js_1.UniversalSequenceNumber, this.collabWindow.clientId, constants_js_1.UniversalSequenceNumber, false, { op: removeOp });
1529
1477
  } /* op.type === MergeTreeDeltaType.ANNOTATE */
1530
1478
  else {
1531
- // TODO Non null asserting, why is this not null?
1532
1479
  const props = pendingSegmentGroup.previousProps[i];
1533
1480
  const annotateOp = (0, opBuilder_js_1.createAnnotateRangeOp)(start, start + segment.cachedLength, props);
1534
1481
  this.annotateRange(start, start + segment.cachedLength, props, constants_js_1.UniversalSequenceNumber, this.collabWindow.clientId, constants_js_1.UniversalSequenceNumber, { op: annotateOp },
@@ -1575,7 +1522,7 @@ class MergeTree {
1575
1522
  if (_segment !== "start" &&
1576
1523
  _segment !== "end" &&
1577
1524
  isRemovedAndAckedOrMovedAndAcked(_segment) &&
1578
- !(0, referencePositions_js_1.refTypeIncludesFlag)(refType, ops_js_1.ReferenceType.SlideOnRemove | ops_js_1.ReferenceType.Transient) &&
1525
+ !(0, referencePositions_js_1.refTypeIncludesFlag)(refType, ops_js_1.ReferenceType.SlideOnRemove | ops_js_1.ReferenceType.Transient | ops_js_1.ReferenceType.StayOnRemove) &&
1579
1526
  _segment.endpointType === undefined) {
1580
1527
  throw new internal_2.UsageError("Can only create SlideOnRemove or Transient local reference position on a removed or obliterated segment");
1581
1528
  }
@@ -1648,7 +1595,6 @@ class MergeTree {
1648
1595
  }
1649
1596
  }
1650
1597
  for (let i = 0; i < newOrder.length; i++) {
1651
- // TODO Non null asserting, why is this not null?
1652
1598
  const seg = newOrder[i];
1653
1599
  const { parent, index, ordinal } = currentOrder[i];
1654
1600
  parent?.assignChild(seg, index, false);
@@ -1735,7 +1681,6 @@ class MergeTree {
1735
1681
  const rightmostTiles = (0, properties_js_1.createMap)();
1736
1682
  const leftmostTiles = (0, properties_js_1.createMap)();
1737
1683
  for (let i = 0; i < block.childCount; i++) {
1738
- // TODO Non null asserting, why is this not null?
1739
1684
  const node = block.children[i];
1740
1685
  const nodeLength = nodeTotalLength(this, node);
1741
1686
  if (nodeLength !== undefined) {
@@ -1845,18 +1790,11 @@ class MergeTree {
1845
1790
  * ignored for the purposes of tracking when traversal should end.
1846
1791
  */
1847
1792
  nodeMap(refSeq, clientId, leaf, accum, post, start = 0, end, localSeq, visibilitySeq = refSeq) {
1848
- const maybeEndPos = end ?? this.nodeLength(this.root, refSeq, clientId, localSeq) ?? 0;
1849
- if (maybeEndPos === start) {
1793
+ const endPos = end ?? this.nodeLength(this.root, refSeq, clientId, localSeq) ?? 0;
1794
+ if (endPos === start) {
1850
1795
  return;
1851
1796
  }
1852
1797
  let pos = 0;
1853
- let { startPos, endPos } = (0, sequencePlace_js_1.endpointPosAndSide)(start, end);
1854
- startPos = startPos === "start" || startPos === undefined ? 0 : startPos;
1855
- endPos =
1856
- endPos === "end" || endPos === undefined
1857
- ? this.root.mergeTree?.getLength(refSeq, clientId) ?? 0
1858
- : endPos;
1859
- (0, internal_1.assert)(startPos !== "end" && endPos !== "start", 0x9e3 /* start cannot be 'end' and end cannot be 'start' */);
1860
1798
  (0, mergeTreeNodeWalk_js_1.depthFirstNodeWalk)(this.root, this.root.children[0], (node) => {
1861
1799
  if (endPos <= pos) {
1862
1800
  return mergeTreeNodeWalk_js_1.NodeAction.Exit;
@@ -1873,19 +1811,19 @@ class MergeTree {
1873
1811
  }
1874
1812
  const nextPos = pos + lenAtRefSeq;
1875
1813
  // start is beyond the current node, so we can skip it
1876
- if (typeof startPos === "number" && startPos >= nextPos) {
1814
+ if (start >= nextPos) {
1877
1815
  pos = nextPos;
1878
1816
  return mergeTreeNodeWalk_js_1.NodeAction.Skip;
1879
1817
  }
1880
1818
  if (node.isLeaf()) {
1881
- if (leaf(node, pos, refSeq, clientId, startPos - pos, endPos - pos, accum) === false) {
1819
+ if (leaf(node, pos, refSeq, clientId, start - pos, endPos - pos, accum) === false) {
1882
1820
  return mergeTreeNodeWalk_js_1.NodeAction.Exit;
1883
1821
  }
1884
1822
  pos = nextPos;
1885
1823
  }
1886
1824
  }, undefined, post === undefined
1887
1825
  ? undefined
1888
- : (block) => post(block, pos, refSeq, clientId, startPos - pos, endPos - pos, accum));
1826
+ : (block) => post(block, pos, refSeq, clientId, start - pos, endPos - pos, accum));
1889
1827
  }
1890
1828
  }
1891
1829
  exports.MergeTree = MergeTree;