@fluidframework/merge-tree 2.30.0 → 2.31.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 (308) hide show
  1. package/CHANGELOG.md +403 -399
  2. package/api-report/merge-tree.legacy.alpha.api.md +1 -0
  3. package/dist/MergeTreeTextHelper.d.ts +9 -3
  4. package/dist/MergeTreeTextHelper.d.ts.map +1 -1
  5. package/dist/MergeTreeTextHelper.js +5 -5
  6. package/dist/MergeTreeTextHelper.js.map +1 -1
  7. package/dist/client.d.ts +7 -13
  8. package/dist/client.d.ts.map +1 -1
  9. package/dist/client.js +136 -110
  10. package/dist/client.js.map +1 -1
  11. package/dist/endOfTreeSegment.d.ts +12 -8
  12. package/dist/endOfTreeSegment.d.ts.map +1 -1
  13. package/dist/endOfTreeSegment.js +2 -4
  14. package/dist/endOfTreeSegment.js.map +1 -1
  15. package/dist/index.d.ts +6 -3
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +2 -3
  18. package/dist/index.js.map +1 -1
  19. package/dist/mergeTree.d.ts +37 -23
  20. package/dist/mergeTree.d.ts.map +1 -1
  21. package/dist/mergeTree.js +400 -483
  22. package/dist/mergeTree.js.map +1 -1
  23. package/dist/mergeTreeDeltaCallback.d.ts +4 -8
  24. package/dist/mergeTreeDeltaCallback.d.ts.map +1 -1
  25. package/dist/mergeTreeDeltaCallback.js.map +1 -1
  26. package/dist/mergeTreeNodes.d.ts +32 -10
  27. package/dist/mergeTreeNodes.d.ts.map +1 -1
  28. package/dist/mergeTreeNodes.js +43 -28
  29. package/dist/mergeTreeNodes.js.map +1 -1
  30. package/dist/partialLengths.d.ts +2 -2
  31. package/dist/partialLengths.d.ts.map +1 -1
  32. package/dist/partialLengths.js +181 -109
  33. package/dist/partialLengths.js.map +1 -1
  34. package/dist/perspective.d.ts +8 -27
  35. package/dist/perspective.d.ts.map +1 -1
  36. package/dist/perspective.js +7 -67
  37. package/dist/perspective.js.map +1 -1
  38. package/dist/revertibles.d.ts.map +1 -1
  39. package/dist/revertibles.js +2 -2
  40. package/dist/revertibles.js.map +1 -1
  41. package/dist/segmentInfos.d.ts +20 -106
  42. package/dist/segmentInfos.d.ts.map +1 -1
  43. package/dist/segmentInfos.js +28 -42
  44. package/dist/segmentInfos.js.map +1 -1
  45. package/dist/segmentPropertiesManager.d.ts +1 -14
  46. package/dist/segmentPropertiesManager.d.ts.map +1 -1
  47. package/dist/segmentPropertiesManager.js +3 -17
  48. package/dist/segmentPropertiesManager.js.map +1 -1
  49. package/dist/snapshotLoader.d.ts.map +1 -1
  50. package/dist/snapshotLoader.js +62 -19
  51. package/dist/snapshotLoader.js.map +1 -1
  52. package/dist/snapshotV1.d.ts.map +1 -1
  53. package/dist/snapshotV1.js +55 -24
  54. package/dist/snapshotV1.js.map +1 -1
  55. package/dist/snapshotlegacy.d.ts.map +1 -1
  56. package/dist/snapshotlegacy.js +6 -9
  57. package/dist/snapshotlegacy.js.map +1 -1
  58. package/dist/stamps.d.ts +1 -1
  59. package/dist/stamps.js +1 -1
  60. package/dist/stamps.js.map +1 -1
  61. package/dist/test/Insertion.perf.spec.js +6 -51
  62. package/dist/test/Insertion.perf.spec.js.map +1 -1
  63. package/dist/test/PartialLengths.perf.spec.js +18 -25
  64. package/dist/test/PartialLengths.perf.spec.js.map +1 -1
  65. package/dist/test/Removal.perf.spec.js +13 -41
  66. package/dist/test/Removal.perf.spec.js.map +1 -1
  67. package/dist/test/beastTest.spec.d.ts.map +1 -1
  68. package/dist/test/beastTest.spec.js +41 -66
  69. package/dist/test/beastTest.spec.js.map +1 -1
  70. package/dist/test/client.annotateMarker.spec.js +1 -11
  71. package/dist/test/client.annotateMarker.spec.js.map +1 -1
  72. package/dist/test/client.applyMsg.spec.js +14 -14
  73. package/dist/test/client.applyMsg.spec.js.map +1 -1
  74. package/dist/test/client.getPosition.spec.js +1 -1
  75. package/dist/test/client.getPosition.spec.js.map +1 -1
  76. package/dist/test/client.localReference.spec.js +1 -1
  77. package/dist/test/client.localReference.spec.js.map +1 -1
  78. package/dist/test/client.rollback.spec.js +49 -58
  79. package/dist/test/client.rollback.spec.js.map +1 -1
  80. package/dist/test/client.rollbackFarm.spec.js +1 -1
  81. package/dist/test/client.rollbackFarm.spec.js.map +1 -1
  82. package/dist/test/client.searchForMarker.spec.js +4 -21
  83. package/dist/test/client.searchForMarker.spec.js.map +1 -1
  84. package/dist/test/index.d.ts +2 -2
  85. package/dist/test/index.d.ts.map +1 -1
  86. package/dist/test/index.js +2 -6
  87. package/dist/test/index.js.map +1 -1
  88. package/dist/test/mergeTree.annotate.deltaCallback.spec.js +14 -59
  89. package/dist/test/mergeTree.annotate.deltaCallback.spec.js.map +1 -1
  90. package/dist/test/mergeTree.annotate.spec.js +47 -63
  91. package/dist/test/mergeTree.annotate.spec.js.map +1 -1
  92. package/dist/test/mergeTree.insert.deltaCallback.spec.js +9 -62
  93. package/dist/test/mergeTree.insert.deltaCallback.spec.js.map +1 -1
  94. package/dist/test/mergeTree.insertingWalk.spec.js +59 -125
  95. package/dist/test/mergeTree.insertingWalk.spec.js.map +1 -1
  96. package/dist/test/mergeTree.markRangeRemoved.deltaCallback.spec.js +12 -93
  97. package/dist/test/mergeTree.markRangeRemoved.deltaCallback.spec.js.map +1 -1
  98. package/dist/test/mergeTree.markRangeRemoved.spec.js +10 -7
  99. package/dist/test/mergeTree.markRangeRemoved.spec.js.map +1 -1
  100. package/dist/test/mergeTree.walk.spec.js +2 -14
  101. package/dist/test/mergeTree.walk.spec.js.map +1 -1
  102. package/dist/test/mergeTreeOperationRunner.js +2 -2
  103. package/dist/test/mergeTreeOperationRunner.js.map +1 -1
  104. package/dist/test/obliterate.concurrent.spec.js +18 -23
  105. package/dist/test/obliterate.concurrent.spec.js.map +1 -1
  106. package/dist/test/obliterate.partialLength.spec.js +166 -136
  107. package/dist/test/obliterate.partialLength.spec.js.map +1 -1
  108. package/dist/test/obliterate.spec.js +16 -126
  109. package/dist/test/obliterate.spec.js.map +1 -1
  110. package/dist/test/partialLength.spec.js +28 -196
  111. package/dist/test/partialLength.spec.js.map +1 -1
  112. package/dist/test/perspective.spec.js +34 -0
  113. package/dist/test/perspective.spec.js.map +1 -1
  114. package/dist/test/propertyManager.spec.js +1 -1
  115. package/dist/test/propertyManager.spec.js.map +1 -1
  116. package/dist/test/resetPendingSegmentsToOp.spec.js +0 -2
  117. package/dist/test/resetPendingSegmentsToOp.spec.js.map +1 -1
  118. package/dist/test/segmentGroupCollection.spec.js +10 -4
  119. package/dist/test/segmentGroupCollection.spec.js.map +1 -1
  120. package/dist/test/testClient.d.ts +1 -0
  121. package/dist/test/testClient.d.ts.map +1 -1
  122. package/dist/test/testClient.js +16 -26
  123. package/dist/test/testClient.js.map +1 -1
  124. package/dist/test/testClientLogger.d.ts.map +1 -1
  125. package/dist/test/testClientLogger.js +3 -10
  126. package/dist/test/testClientLogger.js.map +1 -1
  127. package/dist/test/testServer.d.ts +2 -1
  128. package/dist/test/testServer.d.ts.map +1 -1
  129. package/dist/test/testServer.js +7 -5
  130. package/dist/test/testServer.js.map +1 -1
  131. package/dist/test/testUtils.d.ts +36 -56
  132. package/dist/test/testUtils.d.ts.map +1 -1
  133. package/dist/test/testUtils.js +68 -77
  134. package/dist/test/testUtils.js.map +1 -1
  135. package/dist/test/text.d.ts +2 -2
  136. package/dist/test/text.d.ts.map +1 -1
  137. package/dist/test/text.js +5 -2
  138. package/dist/test/text.js.map +1 -1
  139. package/dist/textSegment.d.ts +0 -6
  140. package/dist/textSegment.d.ts.map +1 -1
  141. package/dist/textSegment.js.map +1 -1
  142. package/dist/zamboni.d.ts.map +1 -1
  143. package/dist/zamboni.js +53 -26
  144. package/dist/zamboni.js.map +1 -1
  145. package/lib/MergeTreeTextHelper.d.ts +9 -3
  146. package/lib/MergeTreeTextHelper.d.ts.map +1 -1
  147. package/lib/MergeTreeTextHelper.js +5 -5
  148. package/lib/MergeTreeTextHelper.js.map +1 -1
  149. package/lib/client.d.ts +7 -13
  150. package/lib/client.d.ts.map +1 -1
  151. package/lib/client.js +117 -116
  152. package/lib/client.js.map +1 -1
  153. package/lib/endOfTreeSegment.d.ts +12 -8
  154. package/lib/endOfTreeSegment.d.ts.map +1 -1
  155. package/lib/endOfTreeSegment.js +2 -4
  156. package/lib/endOfTreeSegment.js.map +1 -1
  157. package/lib/index.d.ts +6 -3
  158. package/lib/index.d.ts.map +1 -1
  159. package/lib/index.js +1 -1
  160. package/lib/index.js.map +1 -1
  161. package/lib/mergeTree.d.ts +37 -23
  162. package/lib/mergeTree.d.ts.map +1 -1
  163. package/lib/mergeTree.js +381 -488
  164. package/lib/mergeTree.js.map +1 -1
  165. package/lib/mergeTreeDeltaCallback.d.ts +4 -8
  166. package/lib/mergeTreeDeltaCallback.d.ts.map +1 -1
  167. package/lib/mergeTreeDeltaCallback.js.map +1 -1
  168. package/lib/mergeTreeNodes.d.ts +32 -10
  169. package/lib/mergeTreeNodes.d.ts.map +1 -1
  170. package/lib/mergeTreeNodes.js +42 -29
  171. package/lib/mergeTreeNodes.js.map +1 -1
  172. package/lib/partialLengths.d.ts +2 -2
  173. package/lib/partialLengths.d.ts.map +1 -1
  174. package/lib/partialLengths.js +160 -111
  175. package/lib/partialLengths.js.map +1 -1
  176. package/lib/perspective.d.ts +8 -27
  177. package/lib/perspective.d.ts.map +1 -1
  178. package/lib/perspective.js +8 -68
  179. package/lib/perspective.js.map +1 -1
  180. package/lib/revertibles.d.ts.map +1 -1
  181. package/lib/revertibles.js +2 -2
  182. package/lib/revertibles.js.map +1 -1
  183. package/lib/segmentInfos.d.ts +20 -106
  184. package/lib/segmentInfos.d.ts.map +1 -1
  185. package/lib/segmentInfos.js +26 -37
  186. package/lib/segmentInfos.js.map +1 -1
  187. package/lib/segmentPropertiesManager.d.ts +1 -14
  188. package/lib/segmentPropertiesManager.d.ts.map +1 -1
  189. package/lib/segmentPropertiesManager.js +2 -16
  190. package/lib/segmentPropertiesManager.js.map +1 -1
  191. package/lib/snapshotLoader.d.ts.map +1 -1
  192. package/lib/snapshotLoader.js +39 -19
  193. package/lib/snapshotLoader.js.map +1 -1
  194. package/lib/snapshotV1.d.ts.map +1 -1
  195. package/lib/snapshotV1.js +34 -26
  196. package/lib/snapshotV1.js.map +1 -1
  197. package/lib/snapshotlegacy.d.ts.map +1 -1
  198. package/lib/snapshotlegacy.js +7 -10
  199. package/lib/snapshotlegacy.js.map +1 -1
  200. package/lib/stamps.d.ts +1 -1
  201. package/lib/stamps.js +1 -1
  202. package/lib/stamps.js.map +1 -1
  203. package/lib/test/Insertion.perf.spec.js +6 -51
  204. package/lib/test/Insertion.perf.spec.js.map +1 -1
  205. package/lib/test/PartialLengths.perf.spec.js +18 -25
  206. package/lib/test/PartialLengths.perf.spec.js.map +1 -1
  207. package/lib/test/Removal.perf.spec.js +13 -41
  208. package/lib/test/Removal.perf.spec.js.map +1 -1
  209. package/lib/test/beastTest.spec.d.ts.map +1 -1
  210. package/lib/test/beastTest.spec.js +42 -67
  211. package/lib/test/beastTest.spec.js.map +1 -1
  212. package/lib/test/client.annotateMarker.spec.js +1 -11
  213. package/lib/test/client.annotateMarker.spec.js.map +1 -1
  214. package/lib/test/client.applyMsg.spec.js +14 -14
  215. package/lib/test/client.applyMsg.spec.js.map +1 -1
  216. package/lib/test/client.getPosition.spec.js +1 -1
  217. package/lib/test/client.getPosition.spec.js.map +1 -1
  218. package/lib/test/client.localReference.spec.js +1 -1
  219. package/lib/test/client.localReference.spec.js.map +1 -1
  220. package/lib/test/client.rollback.spec.js +50 -59
  221. package/lib/test/client.rollback.spec.js.map +1 -1
  222. package/lib/test/client.rollbackFarm.spec.js +1 -1
  223. package/lib/test/client.rollbackFarm.spec.js.map +1 -1
  224. package/lib/test/client.searchForMarker.spec.js +4 -21
  225. package/lib/test/client.searchForMarker.spec.js.map +1 -1
  226. package/lib/test/index.d.ts +2 -2
  227. package/lib/test/index.d.ts.map +1 -1
  228. package/lib/test/index.js +1 -1
  229. package/lib/test/index.js.map +1 -1
  230. package/lib/test/mergeTree.annotate.deltaCallback.spec.js +15 -60
  231. package/lib/test/mergeTree.annotate.deltaCallback.spec.js.map +1 -1
  232. package/lib/test/mergeTree.annotate.spec.js +48 -64
  233. package/lib/test/mergeTree.annotate.spec.js.map +1 -1
  234. package/lib/test/mergeTree.insert.deltaCallback.spec.js +10 -63
  235. package/lib/test/mergeTree.insert.deltaCallback.spec.js.map +1 -1
  236. package/lib/test/mergeTree.insertingWalk.spec.js +61 -127
  237. package/lib/test/mergeTree.insertingWalk.spec.js.map +1 -1
  238. package/lib/test/mergeTree.markRangeRemoved.deltaCallback.spec.js +13 -94
  239. package/lib/test/mergeTree.markRangeRemoved.deltaCallback.spec.js.map +1 -1
  240. package/lib/test/mergeTree.markRangeRemoved.spec.js +10 -7
  241. package/lib/test/mergeTree.markRangeRemoved.spec.js.map +1 -1
  242. package/lib/test/mergeTree.walk.spec.js +2 -14
  243. package/lib/test/mergeTree.walk.spec.js.map +1 -1
  244. package/lib/test/mergeTreeOperationRunner.js +3 -3
  245. package/lib/test/mergeTreeOperationRunner.js.map +1 -1
  246. package/lib/test/obliterate.concurrent.spec.js +18 -23
  247. package/lib/test/obliterate.concurrent.spec.js.map +1 -1
  248. package/lib/test/obliterate.partialLength.spec.js +167 -137
  249. package/lib/test/obliterate.partialLength.spec.js.map +1 -1
  250. package/lib/test/obliterate.spec.js +17 -127
  251. package/lib/test/obliterate.spec.js.map +1 -1
  252. package/lib/test/partialLength.spec.js +29 -197
  253. package/lib/test/partialLength.spec.js.map +1 -1
  254. package/lib/test/perspective.spec.js +34 -0
  255. package/lib/test/perspective.spec.js.map +1 -1
  256. package/lib/test/propertyManager.spec.js +2 -2
  257. package/lib/test/propertyManager.spec.js.map +1 -1
  258. package/lib/test/resetPendingSegmentsToOp.spec.js +0 -2
  259. package/lib/test/resetPendingSegmentsToOp.spec.js.map +1 -1
  260. package/lib/test/segmentGroupCollection.spec.js +10 -4
  261. package/lib/test/segmentGroupCollection.spec.js.map +1 -1
  262. package/lib/test/testClient.d.ts +1 -0
  263. package/lib/test/testClient.d.ts.map +1 -1
  264. package/lib/test/testClient.js +18 -28
  265. package/lib/test/testClient.js.map +1 -1
  266. package/lib/test/testClientLogger.d.ts.map +1 -1
  267. package/lib/test/testClientLogger.js +3 -10
  268. package/lib/test/testClientLogger.js.map +1 -1
  269. package/lib/test/testServer.d.ts +2 -1
  270. package/lib/test/testServer.d.ts.map +1 -1
  271. package/lib/test/testServer.js +7 -5
  272. package/lib/test/testServer.js.map +1 -1
  273. package/lib/test/testUtils.d.ts +36 -56
  274. package/lib/test/testUtils.d.ts.map +1 -1
  275. package/lib/test/testUtils.js +66 -48
  276. package/lib/test/testUtils.js.map +1 -1
  277. package/lib/test/text.d.ts +2 -2
  278. package/lib/test/text.d.ts.map +1 -1
  279. package/lib/test/text.js +6 -3
  280. package/lib/test/text.js.map +1 -1
  281. package/lib/textSegment.d.ts +0 -6
  282. package/lib/textSegment.d.ts.map +1 -1
  283. package/lib/textSegment.js.map +1 -1
  284. package/lib/tsdoc-metadata.json +1 -1
  285. package/lib/zamboni.d.ts.map +1 -1
  286. package/lib/zamboni.js +32 -28
  287. package/lib/zamboni.js.map +1 -1
  288. package/package.json +17 -20
  289. package/src/MergeTreeTextHelper.ts +17 -12
  290. package/src/client.ts +141 -197
  291. package/src/endOfTreeSegment.ts +11 -8
  292. package/src/index.ts +4 -3
  293. package/src/mergeTree.ts +482 -633
  294. package/src/mergeTreeDeltaCallback.ts +4 -8
  295. package/src/mergeTreeNodes.ts +66 -45
  296. package/src/partialLengths.ts +181 -137
  297. package/src/perspective.ts +17 -95
  298. package/src/revertibles.ts +2 -7
  299. package/src/segmentInfos.ts +48 -141
  300. package/src/segmentPropertiesManager.ts +2 -16
  301. package/src/snapshotLoader.ts +62 -30
  302. package/src/snapshotV1.ts +36 -28
  303. package/src/snapshotlegacy.ts +7 -16
  304. package/src/stamps.ts +1 -1
  305. package/src/textSegment.ts +0 -13
  306. package/src/zamboni.ts +38 -32
  307. package/tsconfig.json +1 -0
  308. package/prettier.config.cjs +0 -8
@@ -3,10 +3,11 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
  import { assert } from "@fluidframework/core-utils/internal";
6
- import { UnassignedSequenceNumber } from "./constants.js";
7
- import { seqLTE, } from "./mergeTreeNodes.js";
8
- import { toRemovalInfo, toMoveInfo, assertInserted, wasMovedOnInsert, } from "./segmentInfos.js";
6
+ import { getMinSeqStamp, } from "./mergeTreeNodes.js";
7
+ import { LocalDefaultPerspective, LocalReconnectingPerspective, PriorPerspective, } from "./perspective.js";
8
+ import { toRemovalInfo, assertInserted, wasRemovedOnInsert } from "./segmentInfos.js";
9
9
  import { SortedSet } from "./sortedSet.js";
10
+ import * as opstampUtils from "./stamps.js";
10
11
  class PartialSequenceLengthsSet extends SortedSet {
11
12
  compare(a, b) {
12
13
  return a.seq - b.seq;
@@ -308,8 +309,8 @@ export class PartialSequenceLengths {
308
309
  if (child.isLeaf()) {
309
310
  // Leaf segment
310
311
  const segment = child;
311
- if (wasMovedOnInsert(segment)) {
312
- PartialSequenceLengths.accountForMoveOnInsert(combinedPartialLengths, segment, collabWindow);
312
+ if (wasRemovedOnInsert(segment)) {
313
+ PartialSequenceLengths.accountForRemoveOnInsert(combinedPartialLengths, segment, collabWindow);
313
314
  }
314
315
  else {
315
316
  PartialSequenceLengths.accountForInsertion(combinedPartialLengths, segment, collabWindow);
@@ -321,22 +322,24 @@ export class PartialSequenceLengths {
321
322
  return combinedPartialLengths;
322
323
  }
323
324
  /**
324
- * Assuming this segment was moved on insertion, inserts length information about that operation
325
+ * Assuming this segment was removed on insertion, inserts length information about that operation
325
326
  * into the appropriate per-client adjustments (the overall view needs no such adjustment since
326
327
  * from an observing client's perspective, the segment never exists).
327
328
  */
328
- static accountForMoveOnInsert(combinedPartialLengths, segment, collabWindow) {
329
+ static accountForRemoveOnInsert(combinedPartialLengths, segment, collabWindow) {
329
330
  assertInserted(segment);
330
- const moveInfo = toMoveInfo(segment);
331
- assert(moveInfo !== undefined && wasMovedOnInsert(segment), 0xab7 /* Segment was not moved on insert */);
332
- if (moveInfo.movedSeq <= collabWindow.minSeq) {
331
+ const removeInfo = toRemovalInfo(segment);
332
+ assert(removeInfo !== undefined && wasRemovedOnInsert(segment), 0xab7 /* Segment was not removed on insert */);
333
+ const firstRemove = removeInfo?.removes[0];
334
+ if (opstampUtils.lte(firstRemove, getMinSeqStamp(collabWindow))) {
333
335
  // This segment was obliterated as soon as it was inserted, and everyone was aware of the obliterate.
334
336
  // Thus every single client treats this segment as length 0 from every perspective, and no adjustments
335
337
  // are necessary.
336
338
  return;
337
339
  }
338
- const isLocal = segment.seq === UnassignedSequenceNumber;
339
- const clientId = segment.clientId;
340
+ const { insert, cachedLength } = segment;
341
+ const isLocal = opstampUtils.isLocal(insert);
342
+ const { clientId } = insert;
340
343
  const partials = isLocal
341
344
  ? combinedPartialLengths.unsequencedRecords?.partialLengths
342
345
  : combinedPartialLengths.partialLengths;
@@ -346,33 +349,51 @@ export class PartialSequenceLengths {
346
349
  }
347
350
  if (isLocal) {
348
351
  // Implication -> this is a local segment which will be obliterated as soon as it is acked.
349
- // For refSeqs preceding that movedSeq and localSeqs following the localSeq, it will be visible.
352
+ // For refSeqs preceding that removedSeq and localSeqs following the localSeq, it will be visible.
350
353
  // For the rest, it will not be visible.
351
354
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
352
- const localSeq = segment.localSeq;
355
+ const localSeq = insert.localSeq;
353
356
  partials.addOrUpdate({
354
357
  seq: localSeq,
355
358
  len: 0,
356
- seglen: segment.cachedLength,
359
+ seglen: cachedLength,
357
360
  clientId,
358
361
  });
359
362
  combinedPartialLengths.addLocalAdjustment({
360
- refSeq: moveInfo.movedSeq,
363
+ refSeq: firstRemove.seq,
361
364
  localSeq,
362
- seglen: -segment.cachedLength,
365
+ seglen: -cachedLength,
363
366
  });
367
+ const lastRemove = removeInfo.removes[removeInfo.removes.length - 1];
368
+ if (opstampUtils.isLocal(lastRemove)) {
369
+ // In addition to a remote sliceRemove causing this segment to be removed as soon as its insertion is acked,
370
+ // the local client has also removed it before its insertion was acked.
371
+ // It will therefore not be visible for a local reconnecting perspective beyond the removed localSeq.
372
+ partials.addOrUpdate({
373
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
374
+ seq: lastRemove.localSeq,
375
+ len: 0,
376
+ seglen: -cachedLength,
377
+ clientId,
378
+ });
379
+ combinedPartialLengths.addLocalAdjustment({
380
+ refSeq: firstRemove.seq,
381
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
382
+ localSeq: lastRemove.localSeq,
383
+ seglen: cachedLength,
384
+ });
385
+ }
364
386
  }
365
387
  else {
366
388
  // Segment was obliterated on insert. Generally this means it should be visible only to the
367
389
  // inserting client (in which case we add an adjustment to only that client's perspective),
368
390
  // but if that client has also removed it, we don't need to add anything.
369
- const removeInfo = toRemovalInfo(segment);
370
- const wasRemovedByInsertingClient = removeInfo !== undefined && removeInfo.removedClientIds.includes(clientId);
371
- const wasMovedByInsertingClient = moveInfo !== undefined && moveInfo.movedClientIds.includes(clientId);
372
- if (!wasRemovedByInsertingClient && !wasMovedByInsertingClient) {
373
- const moveSeq = moveInfo?.movedSeq;
374
- assert(moveSeq !== undefined, 0xab8 /* ObliterateOnInsertion implies moveSeq is defined */);
375
- combinedPartialLengths.addClientAdjustment(clientId, moveSeq, segment.cachedLength);
391
+ const wasRemovedByInsertingClient = removeInfo !== undefined &&
392
+ removeInfo.removes.some((remove) => remove.clientId === clientId);
393
+ if (!wasRemovedByInsertingClient) {
394
+ const removeSeq = firstRemove?.seq;
395
+ assert(removeSeq !== undefined, 0xab8 /* ObliterateOnInsertion implies removeSeq is defined */);
396
+ combinedPartialLengths.addClientAdjustment(clientId, removeSeq, cachedLength);
376
397
  }
377
398
  }
378
399
  }
@@ -382,15 +403,15 @@ export class PartialSequenceLengths {
382
403
  */
383
404
  static accountForInsertion(combinedPartialLengths, segment, collabWindow) {
384
405
  assertInserted(segment);
385
- if (segment.seq !== undefined && seqLTE(segment.seq, collabWindow.minSeq)) {
406
+ if (opstampUtils.lte(segment.insert, getMinSeqStamp(collabWindow))) {
386
407
  combinedPartialLengths.minLength += segment.cachedLength;
387
408
  return;
388
409
  }
389
- const isLocal = segment.seq === UnassignedSequenceNumber;
410
+ const { insert, cachedLength: segmentLen } = segment;
411
+ const isLocal = opstampUtils.isLocal(insert);
390
412
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
391
- const seqOrLocalSeq = isLocal ? segment.localSeq : segment.seq;
392
- const segmentLen = segment.cachedLength;
393
- const clientId = segment.clientId;
413
+ const seqOrLocalSeq = isLocal ? insert.localSeq : insert.seq;
414
+ const clientId = insert.clientId;
394
415
  const partials = isLocal
395
416
  ? combinedPartialLengths.unsequencedRecords?.partialLengths
396
417
  : combinedPartialLengths.partialLengths;
@@ -423,55 +444,27 @@ export class PartialSequenceLengths {
423
444
  static accountForRemoval(combinedPartialLengths, segment, collabWindow) {
424
445
  assertInserted(segment);
425
446
  const removalInfo = toRemovalInfo(segment);
426
- const moveInfo = toMoveInfo(segment);
427
- if (!removalInfo && !moveInfo) {
447
+ if (!removalInfo) {
428
448
  return;
429
449
  }
430
- if ((removalInfo?.removedSeq !== undefined &&
431
- seqLTE(removalInfo.removedSeq, collabWindow.minSeq)) ||
432
- (moveInfo?.movedSeq !== undefined && seqLTE(moveInfo.movedSeq, collabWindow.minSeq))) {
450
+ const firstRemove = removalInfo?.removes[0];
451
+ const minSeqStamp = getMinSeqStamp(collabWindow);
452
+ if (firstRemove !== undefined && opstampUtils.lte(firstRemove, minSeqStamp)) {
433
453
  combinedPartialLengths.minLength -= segment.cachedLength;
434
454
  return;
435
455
  }
436
- const removalIsLocal = !!removalInfo && removalInfo.removedSeq === UnassignedSequenceNumber;
437
- const moveIsLocal = !!moveInfo && moveInfo.movedSeq === UnassignedSequenceNumber;
438
- const isLocalInsertion = segment.seq === UnassignedSequenceNumber;
439
- const isOnlyLocalRemoval = removalIsLocal && (!moveInfo || moveIsLocal);
440
- const isOnlyLocalMove = moveIsLocal && (!removalInfo || removalIsLocal);
441
- const isLocal = isLocalInsertion || isOnlyLocalRemoval || isOnlyLocalMove;
442
- if (segment.seq === UnassignedSequenceNumber &&
443
- !(removalIsLocal && (!moveInfo || moveIsLocal)) &&
444
- !(moveIsLocal && (!removalInfo || removalIsLocal))) {
445
- throw new Error("Should have handled this codepath in wasMovedOnInsertion");
456
+ const removalIsLocal = !!firstRemove && opstampUtils.isLocal(firstRemove);
457
+ const isLocalInsertion = opstampUtils.isLocal(segment.insert);
458
+ const isOnlyLocalRemoval = removalIsLocal;
459
+ const isLocal = isLocalInsertion || isOnlyLocalRemoval;
460
+ if (isLocalInsertion && !removalIsLocal) {
461
+ throw new Error("Should have handled this codepath in wasRemovedOnInsertion");
446
462
  }
447
463
  const lenDelta = -segment.cachedLength;
448
- let clientId;
449
- let seqOrLocalSeq;
450
- // it's not possible to have an overlapping obliterate and remove that are both local
451
- assert((!moveIsLocal && !removalIsLocal) || moveIsLocal !== removalIsLocal, 0x870 /* overlapping local obliterate and remove */);
452
- const clientsWithRemoveOrObliterate = new Set([
453
- ...(removalInfo?.removedClientIds ?? []),
454
- ...(moveInfo?.movedClientIds ?? []),
455
- ]);
456
- const removeHappenedFirst = removalInfo &&
457
- (!moveInfo ||
458
- moveIsLocal ||
459
- (!removalIsLocal && moveInfo.movedSeq > removalInfo.removedSeq));
460
- if (removeHappenedFirst) {
461
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
462
- seqOrLocalSeq = removalIsLocal ? removalInfo.localRemovedSeq : removalInfo.removedSeq;
463
- // The client who performed the remove is always stored
464
- // in the first position of removalInfo.
465
- clientId = removalInfo.removedClientIds[0];
466
- }
467
- else {
468
- assert(moveInfo !== undefined, 0xab9 /* Expected move to exist if remove either did not exist or didn't happen first */);
469
- // The client who performed the move is always stored
470
- // in the first position of moveInfo.
471
- clientId = moveInfo.movedClientIds[0];
472
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
473
- seqOrLocalSeq = moveIsLocal ? moveInfo.localMovedSeq : moveInfo.movedSeq;
474
- }
464
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
465
+ const seqOrLocalSeq = removalIsLocal ? firstRemove.localSeq : firstRemove.seq;
466
+ const clientId = firstRemove.clientId;
467
+ const clientsWithRemoveOrObliterate = new Set(removalInfo?.removes.map((stamp) => stamp.clientId));
475
468
  const partials = isLocal
476
469
  ? combinedPartialLengths.unsequencedRecords?.partialLengths
477
470
  : combinedPartialLengths.partialLengths;
@@ -480,15 +473,30 @@ export class PartialSequenceLengths {
480
473
  return;
481
474
  }
482
475
  if (isLocal) {
483
- // The segment is either inserted only locally or removed/moved only locally.
476
+ // The segment is either inserted only locally or removed only locally.
484
477
  // We already accounted for the insertion in the accountForInsertion codepath.
485
- // Only thing left to do is account for the removal.
486
- partials.addOrUpdate({
487
- seq: seqOrLocalSeq,
488
- clientId,
489
- len: 0,
490
- seglen: lenDelta,
491
- });
478
+ // Only thing left to do is account for the removal in local partial lengths.
479
+ // One thing to be careful about is that the removal should only apply to perspectives that saw
480
+ // the segment's insertion in the first place.
481
+ // When the segment's insertion was common knowledge (at or below minSeq) or also by the local client,
482
+ // all possible perspectives will have seen it.
483
+ if (opstampUtils.lte(segment.insert, minSeqStamp) ||
484
+ collabWindow.clientId === segment.insert.clientId) {
485
+ partials.addOrUpdate({
486
+ seq: seqOrLocalSeq,
487
+ clientId,
488
+ len: 0,
489
+ seglen: lenDelta,
490
+ });
491
+ }
492
+ else {
493
+ // ... otherwise, it's only visible to reconnecting perspectives above the seq of the insert.
494
+ combinedPartialLengths.addLocalAdjustment({
495
+ localSeq: seqOrLocalSeq,
496
+ refSeq: segment.insert.seq,
497
+ seglen: lenDelta,
498
+ });
499
+ }
492
500
  }
493
501
  else {
494
502
  partials.addOrUpdate({
@@ -500,7 +508,7 @@ export class PartialSequenceLengths {
500
508
  for (const id of clientsWithRemoveOrObliterate) {
501
509
  if (id === collabWindow.clientId) {
502
510
  // The local client also removed or obliterated this segment.
503
- const localSeq = moveInfo?.localMovedSeq ?? removalInfo?.localRemovedSeq;
511
+ const { localSeq } = removalInfo.removes[removalInfo.removes.length - 1];
504
512
  if (localSeq === undefined) {
505
513
  // Sure, the local client did it--but that change was already acked.
506
514
  // No need to account for it in the unsequenced records.
@@ -511,13 +519,50 @@ export class PartialSequenceLengths {
511
519
  // Local partial but its computation isn't required.
512
520
  continue;
513
521
  }
514
- assert(localSeq !== undefined, 0xaba /* Local client was in move/removed client ids but segment has no local seq for either */);
515
- unsequencedRecords.partialLengths.addOrUpdate({
516
- seq: localSeq,
517
- clientId: collabWindow.clientId,
518
- seglen: lenDelta,
519
- len: 0,
520
- });
522
+ assert(localSeq !== undefined, 0xaba /* Local client was in removed client ids but segment has no local seq for either */);
523
+ // Here...
524
+ // - The segment was removed locally at `localSeq`
525
+ // - The segment was also removed remotely at `seqOrLocalSeq`
526
+ // - We're ensuring that partial lengths works for arbitrary `LocalReconnectingPerspective`s.
527
+ //
528
+ // Visualize an arbitrary local reconnecting perspective in 2d space, where the x-axis is `seq` and the y-axis is `localSeq`.
529
+ // The events that have occurred to this segment divide the space into up to 6 regions:
530
+ //
531
+ // (localSeq)
532
+ // | | |
533
+ // | 1 | 2 | 3
534
+ // local remove |-----------------------------
535
+ // | | |
536
+ // | 4 | 5 | 6
537
+ // |----------------------------- (seq)
538
+ // insert remove
539
+ // In all regions but region 5, the segment has length 0 (it was not inserted yet in regions 1 or 4, and removed locally and/or remotely
540
+ // in regions 2, 3, and 6).
541
+ // `accountForInsertion` already added a partial lengths adjustment of +segment.cachedLength for regions 2, 3, 5, and 6.
542
+ // Above in this function, we've added a partial lengths adjustment of -segment.cachedLength for regions 3 and 6.
543
+ //
544
+ // Note that in this picture:
545
+ // - Adding entries to `unsequencedRecords.partialLengths` is like adding adjustments that affect anything above a given Y value
546
+ // - Adding entries to `combinedPartialLengths.partialLengths` is like adding adjustments that affect anything above a given X value
547
+ // - Adding entries with `addLocalAdjustment` is like adding adjustments that affect anything above a given X *and* Y value
548
+ // The remainder this block adds the necessary adjustments to make the length appear 0 in region 2 as well, keeping in mind that
549
+ // region 1 may or may not exist depending on if the insertion is in the collab window.
550
+ if (opstampUtils.isAcked(segment.insert) &&
551
+ opstampUtils.greaterThan(segment.insert, minSeqStamp)) {
552
+ combinedPartialLengths.addLocalAdjustment({
553
+ refSeq: segment.insert.seq,
554
+ localSeq,
555
+ seglen: lenDelta,
556
+ });
557
+ }
558
+ else {
559
+ unsequencedRecords.partialLengths.addOrUpdate({
560
+ seq: localSeq,
561
+ clientId: collabWindow.clientId,
562
+ seglen: lenDelta,
563
+ len: 0,
564
+ });
565
+ }
521
566
  // Because we've included deltas which take effect when either of localSeq or refSeq are high enough,
522
567
  // we need to offset this with an adjustment that takes effect when both are high enough.
523
568
  combinedPartialLengths.addLocalAdjustment({
@@ -533,15 +578,16 @@ export class PartialSequenceLengths {
533
578
  }
534
579
  else {
535
580
  // Note that all clients that have a remove or obliterate operation on this segment
536
- // use the seq of the winning move/obliterate in their per-client adjustments!
581
+ // use the seq of the winning obliterate in their per-client adjustments!
537
582
  combinedPartialLengths.addClientAdjustment(id, seqOrLocalSeq, lenDelta);
538
583
  // Also ensure that all these clients have seen the segment as inserted before being removed
539
- // This is technically not necessary for removes (we never ask for the length of this block with
584
+ // This is technically not necessary for setRemoves (we never ask for the length of this block with
540
585
  // respect to a refSeq which this entry would affect), but it's simpler to just add it here.
541
586
  // We already add this entry as part of the accountForInsertion codepath for the client that
542
587
  // actually did insert the segment, hence not doing so [again] here.
543
- if (segment.seq > collabWindow.minSeq && id !== segment.clientId) {
544
- combinedPartialLengths.addClientAdjustment(id, segment.seq, segment.cachedLength);
588
+ if (opstampUtils.greaterThan(segment.insert, minSeqStamp) &&
589
+ id !== segment.insert.clientId) {
590
+ combinedPartialLengths.addClientAdjustment(id, segment.insert.seq, segment.cachedLength);
545
591
  }
546
592
  }
547
593
  }
@@ -585,15 +631,14 @@ export class PartialSequenceLengths {
585
631
  if (child.isLeaf()) {
586
632
  const segment = child;
587
633
  const removalInfo = toRemovalInfo(segment);
588
- const moveInfo = toMoveInfo(segment);
589
- if (seq === segment.seq) {
590
- // if this segment was moved on insert, its length should
634
+ const firstRemove = removalInfo?.removes[0];
635
+ if (seq === segment.insert.seq) {
636
+ // if this segment was removed on insert, its length should
591
637
  // only be visible to the inserting client
592
- if (segment.seq !== undefined &&
593
- moveInfo &&
594
- moveInfo.movedSeq < segment.seq &&
595
- wasMovedOnInsert(segment)) {
596
- this.addClientAdjustment(clientId, moveInfo.movedSeq, segment.cachedLength);
638
+ if (segment.insert.seq !== undefined &&
639
+ firstRemove !== undefined &&
640
+ wasRemovedOnInsert(segment)) {
641
+ this.addClientAdjustment(clientId, firstRemove.seq, segment.cachedLength);
597
642
  failIncrementalPropagation = true;
598
643
  }
599
644
  else {
@@ -601,13 +646,13 @@ export class PartialSequenceLengths {
601
646
  this.addClientAdjustment(clientId, seq, segment.cachedLength);
602
647
  }
603
648
  }
604
- const earlierDeletion = Math.min(removalInfo?.removedSeq ?? Number.MAX_VALUE, moveInfo?.movedSeq ?? Number.MAX_VALUE);
605
- if (segment.seq !== UnassignedSequenceNumber && seq === earlierDeletion) {
649
+ if (opstampUtils.isAcked(segment.insert) && seq === firstRemove?.seq) {
606
650
  seqSeglen -= segment.cachedLength;
607
651
  if (clientId !== collabWindow.clientId) {
608
652
  this.addClientAdjustment(clientId, seq, -segment.cachedLength);
609
- if (segment.seq > collabWindow.minSeq && segment.clientId !== clientId) {
610
- this.addClientAdjustment(clientId, segment.seq, segment.cachedLength);
653
+ if (opstampUtils.greaterThan(segment.insert, getMinSeqStamp(collabWindow)) &&
654
+ segment.insert.clientId !== clientId) {
655
+ this.addClientAdjustment(clientId, segment.insert.seq, segment.cachedLength);
611
656
  failIncrementalPropagation = true;
612
657
  }
613
658
  }
@@ -682,13 +727,12 @@ export class PartialSequenceLengths {
682
727
  assert(this.unsequencedRecords !== undefined, 0x39f /* Local getPartialLength invoked without computing local partials. */);
683
728
  const unsequencedPartialLengths = this.unsequencedRecords.partialLengths;
684
729
  // Local segments at or before localSeq should also be included
685
- const local = unsequencedPartialLengths.latestLeq(localSeq);
686
- if (local) {
687
- length += local.len;
688
- // Lastly, we must add in any additional adjustment due to double-counting removes and obliterations
689
- // removing local-only segments.
690
- length += this.computeOverallRefSeqAdjustment(refSeq, localSeq);
691
- }
730
+ length += unsequencedPartialLengths.latestLeq(localSeq)?.len ?? 0;
731
+ // Lastly, we must add in any additional adjustment that should only take effect when both
732
+ // refSeq AND localSeq are above some threshold. This accounts for things like double-counting
733
+ // local+remote removes, or only subtracting the length out of a local remove if we've also seen
734
+ // the insert of the segment it affects. (see addLocalAdjustment usages for examples)
735
+ length += this.computeOverallRefSeqAdjustment(refSeq, localSeq);
692
736
  }
693
737
  return length;
694
738
  }
@@ -839,13 +883,18 @@ export function verifyExpectedPartialLengths(mergeTree, node, refSeq, clientId,
839
883
  const partialLen = node.partialLengths?.getPartialLength(refSeq, clientId, localSeq);
840
884
  let expected = 0;
841
885
  const nodesToVisit = [node];
886
+ const perspective = clientId === mergeTree.collabWindow.clientId
887
+ ? localSeq === undefined
888
+ ? new LocalDefaultPerspective(clientId)
889
+ : new LocalReconnectingPerspective(refSeq, clientId, localSeq)
890
+ : new PriorPerspective(refSeq, clientId);
842
891
  while (nodesToVisit.length > 0) {
843
892
  const thisNode = nodesToVisit.pop();
844
893
  if (!thisNode) {
845
894
  continue;
846
895
  }
847
896
  if (thisNode.isLeaf()) {
848
- expected += mergeTree["nodeLength"](thisNode, refSeq, clientId, localSeq) ?? 0;
897
+ expected += mergeTree["nodeLength"](thisNode, perspective) ?? 0;
849
898
  }
850
899
  else {
851
900
  nodesToVisit.push(...thisNode.children.slice(0, thisNode.childCount));