@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
@@ -308,8 +308,7 @@ function revertLocalRemove(
308
308
  driver.insertFromSpec(realPos, props.segSpec);
309
309
  const insertSegment = mergeTreeWithRevert.getContainingSegment(
310
310
  realPos,
311
- mergeTreeWithRevert.collabWindow.currentSeq,
312
- mergeTreeWithRevert.collabWindow.clientId,
311
+ mergeTreeWithRevert.localPerspective,
313
312
  ).segment;
314
313
  assertSegmentLeaf(insertSegment);
315
314
 
@@ -395,11 +394,7 @@ function revertLocalAnnotate(
395
394
  }
396
395
 
397
396
  function getPosition(mergeTreeWithRevert: MergeTreeWithRevert, segment: ISegmentLeaf): number {
398
- return mergeTreeWithRevert.getPosition(
399
- segment,
400
- mergeTreeWithRevert.collabWindow.currentSeq,
401
- mergeTreeWithRevert.collabWindow.clientId,
402
- );
397
+ return mergeTreeWithRevert.getPosition(segment, mergeTreeWithRevert.localPerspective);
403
398
  }
404
399
 
405
400
  /**
@@ -7,13 +7,13 @@ import { assert, isObject } from "@fluidframework/core-utils/internal";
7
7
 
8
8
  import { UnassignedSequenceNumber } from "./constants.js";
9
9
  import { ISegmentInternal, ISegmentPrivate, MergeBlock } from "./mergeTreeNodes.js";
10
- import type { ReferencePosition } from "./referencePositions.js";
10
+ import type { InsertOperationStamp, RemoveOperationStamp } from "./stamps.js";
11
11
 
12
12
  export interface StringToType {
13
13
  "string": string;
14
14
  "number": number;
15
15
  "object": object;
16
- "array": [];
16
+ "array": unknown[];
17
17
  "boolean": boolean;
18
18
  }
19
19
 
@@ -46,25 +46,8 @@ export function propInstanceOf<P extends string, T>(
46
46
  /**
47
47
  * Contains insertion information associated to an {@link ISegment}.
48
48
  */
49
- export interface IInsertionInfo {
50
- /**
51
- * Short clientId for the client that inserted this segment.
52
- */
53
- clientId: number;
54
- /**
55
- * Local seq at which this segment was inserted.
56
- * This is defined if and only if the insertion of the segment is pending ack, i.e. `seq` is UnassignedSequenceNumber.
57
- * Once the segment is acked, this field is cleared.
58
- *
59
- * @privateRemarks
60
- * See {@link CollaborationWindow.localSeq} for more information on the semantics of localSeq.
61
- */
62
- localSeq?: number;
63
- /**
64
- * Seq at which this segment was inserted.
65
- * If undefined, it is assumed the segment was inserted prior to the collab window's minimum sequence number.
66
- */
67
- seq: number;
49
+ export interface IHasInsertionInfo {
50
+ insert: InsertOperationStamp;
68
51
  }
69
52
 
70
53
  /**
@@ -73,10 +56,14 @@ export interface IInsertionInfo {
73
56
  * @param segmentLike - The segment-like object to convert.
74
57
  * @returns The insertion info object if the conversion is possible, otherwise undefined.
75
58
  */
76
- export const toInsertionInfo = (segmentLike: unknown): IInsertionInfo | undefined =>
77
- hasProp(segmentLike, "clientId", "number") && hasProp(segmentLike, "seq", "number")
78
- ? segmentLike
59
+ export const toInsertionInfo = (segmentLike: unknown): IHasInsertionInfo | undefined => {
60
+ return segmentLike !== undefined &&
61
+ hasProp(segmentLike, "insert", "object") &&
62
+ hasProp(segmentLike.insert, "clientId", "number") &&
63
+ hasProp(segmentLike.insert, "seq", "number")
64
+ ? (segmentLike as IHasInsertionInfo)
79
65
  : undefined;
66
+ };
80
67
 
81
68
  /**
82
69
  * A type-guard which determines if the segment has insertion info, and
@@ -85,7 +72,7 @@ export const toInsertionInfo = (segmentLike: unknown): IInsertionInfo | undefine
85
72
  * @param segmentLike - The segment-like object to check.
86
73
  * @returns True if the segment has insertion info, otherwise false.
87
74
  */
88
- export const isInserted = (segmentLike: unknown): segmentLike is IInsertionInfo =>
75
+ export const isInserted = (segmentLike: unknown): segmentLike is IHasInsertionInfo =>
89
76
  toInsertionInfo(segmentLike) !== undefined;
90
77
 
91
78
  /**
@@ -94,9 +81,9 @@ export const isInserted = (segmentLike: unknown): segmentLike is IInsertionInfo
94
81
  * @param segmentLike - The segment-like object to check.
95
82
  * @throws Will throw an error if the segment does not have insertion info.
96
83
  */
97
- export const assertInserted: <T extends Partial<IInsertionInfo> | undefined>(
98
- segmentLike: ISegmentInternal | Partial<IInsertionInfo> | T,
99
- ) => asserts segmentLike is IInsertionInfo | Exclude<T, Partial<IInsertionInfo>> = (
84
+ export const assertInserted: <T extends Partial<IHasInsertionInfo> | undefined>(
85
+ segmentLike: ISegmentInternal | Partial<IHasInsertionInfo> | T,
86
+ ) => asserts segmentLike is IHasInsertionInfo | Exclude<T, Partial<IHasInsertionInfo>> = (
100
87
  segmentLike,
101
88
  ) =>
102
89
  assert(
@@ -181,24 +168,15 @@ export const removeMergeNodeInfo: (nodeLike: IMergeNodeInfo) => asserts nodeLike
181
168
  });
182
169
 
183
170
  /**
184
- * Contains removal information associated to an {@link ISegment}.
171
+ * Contains removal information associated with an {@link ISegment}.
172
+ *
173
+ * Segments can be removed concurrently by multiple clients.
185
174
  */
186
- export interface IRemovalInfo {
187
- /**
188
- * Local seq at which this segment was removed, if the removal is yet-to-be acked.
189
- */
190
- localRemovedSeq?: number;
175
+ export interface IHasRemovalInfo {
191
176
  /**
192
- * Seq at which this segment was removed.
177
+ * Operation stamps which have removed this segment. This list is sorted by stamp order, where removes[0] is the earliest removal.
193
178
  */
194
- removedSeq: number;
195
- /**
196
- * List of client IDs that have removed this segment.
197
- * The client that actually removed the segment (i.e. whose removal op was sequenced first) is stored as the first
198
- * client in this list. Other clients in the list have all issued concurrent ops to remove the segment.
199
- * @remarks When this list has length \> 1, this is referred to as the "overlapping remove" case.
200
- */
201
- removedClientIds: number[];
179
+ removes: RemoveOperationStamp[];
202
180
  }
203
181
 
204
182
  /**
@@ -207,11 +185,14 @@ export interface IRemovalInfo {
207
185
  * @param segmentLike - The segment-like object to convert.
208
186
  * @returns The removal info object if the conversion is possible, otherwise undefined.
209
187
  */
210
- export const toRemovalInfo = (segmentLike: unknown): IRemovalInfo | undefined =>
211
- hasProp(segmentLike, "removedClientIds", "array") &&
212
- hasProp(segmentLike, "removedSeq", "number")
213
- ? segmentLike
188
+ export const toRemovalInfo = (segmentLike: unknown): IHasRemovalInfo | undefined => {
189
+ return hasProp(segmentLike, "removes", "array") &&
190
+ segmentLike.removes.length > 0 &&
191
+ hasProp(segmentLike.removes[0], "clientId", "number") &&
192
+ hasProp(segmentLike.removes[0], "seq", "number")
193
+ ? (segmentLike as IHasRemovalInfo)
214
194
  : undefined;
195
+ };
215
196
 
216
197
  /**
217
198
  * A type-guard which determines if the segment has removal info, and
@@ -220,7 +201,10 @@ export const toRemovalInfo = (segmentLike: unknown): IRemovalInfo | undefined =>
220
201
  * @param segmentLike - The segment-like object to check.
221
202
  * @returns True if the segment has removal info, otherwise false.
222
203
  */
223
- export const isRemoved = (segmentLike: unknown): segmentLike is IRemovalInfo =>
204
+ // export const isRemoved = (segmentLike: unknown): segmentLike is IHasRemovalInfo =>
205
+ // toRemovalInfo(segmentLike) !== undefined;
206
+
207
+ export const isRemoved = (segmentLike: unknown): segmentLike is IHasRemovalInfo =>
224
208
  toRemovalInfo(segmentLike) !== undefined;
225
209
 
226
210
  /**
@@ -229,9 +213,11 @@ export const isRemoved = (segmentLike: unknown): segmentLike is IRemovalInfo =>
229
213
  * @param segmentLike - The segment-like object to check.
230
214
  * @throws Will throw an error if the segment does not have removal info.
231
215
  */
232
- export const assertRemoved: <T extends Partial<IRemovalInfo> | undefined>(
233
- segmentLike: ISegmentInternal | Partial<IRemovalInfo> | T,
234
- ) => asserts segmentLike is IRemovalInfo | Exclude<T, Partial<IRemovalInfo>> = (segmentLike) =>
216
+ export const assertRemoved: <T extends Partial<IHasRemovalInfo> | undefined>(
217
+ segmentLike: ISegmentInternal | Partial<IHasRemovalInfo> | T,
218
+ ) => asserts segmentLike is IHasRemovalInfo | Exclude<T, Partial<IHasRemovalInfo>> = (
219
+ segmentLike,
220
+ ) =>
235
221
  assert(segmentLike === undefined || isRemoved(segmentLike), 0xaa2 /* must be removalInfo */);
236
222
 
237
223
  /**
@@ -241,83 +227,15 @@ export const assertRemoved: <T extends Partial<IRemovalInfo> | undefined>(
241
227
  * ensures no further usage of the removed removal info is allowed. if continued use is required other
242
228
  * type coercion methods should be use to correctly re-type the variable.
243
229
  */
244
- export const removeRemovalInfo: (nodeLike: IRemovalInfo) => asserts nodeLike is never = (
230
+ export const removeRemovalInfo: (nodeLike: IHasRemovalInfo) => asserts nodeLike is never = (
245
231
  nodeLike,
246
232
  ) =>
247
- Object.assign<IRemovalInfo, Record<keyof IRemovalInfo, undefined>>(nodeLike, {
248
- localRemovedSeq: undefined,
249
- removedClientIds: undefined,
250
- removedSeq: undefined,
233
+ Object.assign<IHasRemovalInfo, Record<keyof IHasRemovalInfo, undefined>>(nodeLike, {
234
+ removes: undefined,
251
235
  });
252
236
 
253
237
  /**
254
- * Tracks information about when and where this segment was moved to.
255
- *
256
- * Note that merge-tree does not currently support moving and only supports
257
- * obliterate. The fields below include "move" in their names to avoid renaming
258
- * in the future, when moves _are_ supported.
259
- */
260
- export interface IMoveInfo {
261
- /**
262
- * Local seq at which this segment was moved if the move is yet-to-be
263
- * acked.
264
- */
265
- localMovedSeq?: number;
266
-
267
- /**
268
- * The first seq at which this segment was moved.
269
- */
270
- movedSeq: number;
271
-
272
- /**
273
- * All seqs at which this segment was moved. In the case of overlapping,
274
- * concurrent moves this array will contain multiple seqs.
275
- *
276
- * The seq at `movedSeqs[i]` corresponds to the client id at `movedClientIds[i]`.
277
- *
278
- * The first element corresponds to the seq of the first move
279
- */
280
- movedSeqs: number[];
281
-
282
- /**
283
- * A reference to the inserted destination segment corresponding to this
284
- * segment's move.
285
- *
286
- * If undefined, the move was an obliterate.
287
- *
288
- * Currently this field is unused, as we only support obliterate operations
289
- */
290
- moveDst?: ReferencePosition;
291
-
292
- /**
293
- * List of client IDs that have moved this segment.
294
- *
295
- * The client that actually moved the segment (i.e. whose move op was sequenced
296
- * first) is stored as the first client in this list. Other clients in the
297
- * list have all issued concurrent ops to move the segment.
298
- */
299
- movedClientIds: number[];
300
- }
301
-
302
- export const toMoveInfo = (segmentLike: unknown): IMoveInfo | undefined =>
303
- hasProp(segmentLike, "movedClientIds", "array") &&
304
- hasProp(segmentLike, "movedSeq", "number") &&
305
- hasProp(segmentLike, "movedSeqs", "array")
306
- ? segmentLike
307
- : undefined;
308
-
309
- /**
310
- * A type-guard which determines if the segment has move info, and
311
- * returns true if it does, along with applying strong typing.
312
- *
313
- * @param segmentLike - The segment-like object to check.
314
- * @returns True if the segment has move info, otherwise false.
315
- */
316
- export const isMoved = (segmentLike: unknown): segmentLike is IMoveInfo =>
317
- toMoveInfo(segmentLike) !== undefined;
318
-
319
- /**
320
- * Returns whether this segment was marked moved as soon as its insertion was acked.
238
+ * Returns whether this segment was marked removed as soon as its insertion was acked.
321
239
  *
322
240
  * This can happen when an an insert occurs concurrent to an obliterate over the range the segment was inserted into,
323
241
  * and the obliterate was sequenced first.
@@ -325,32 +243,21 @@ export const isMoved = (segmentLike: unknown): segmentLike is IMoveInfo =>
325
243
  * When this happens, the segment is only ever visible to the client that inserted the segment
326
244
  * (and only until that client has seen the obliterate which removed their segment).
327
245
  */
328
- export function wasMovedOnInsert(segment: IInsertionInfo & ISegmentPrivate): boolean {
329
- const moveInfo = toMoveInfo(segment);
330
- const movedSeq = moveInfo?.movedSeq;
331
- if (movedSeq === undefined || movedSeq === UnassignedSequenceNumber) {
246
+ export function wasRemovedOnInsert(segment: IHasInsertionInfo & ISegmentPrivate): boolean {
247
+ const removeInfo = toRemovalInfo(segment);
248
+ const removedSeq = removeInfo?.removes[0].seq;
249
+ if (removedSeq === undefined || removedSeq === UnassignedSequenceNumber) {
332
250
  return false;
333
251
  }
334
252
 
335
- const insertSeq = segment.seq;
336
- return insertSeq === UnassignedSequenceNumber || insertSeq > movedSeq;
253
+ const insertSeq = segment.insert.seq;
254
+ return insertSeq === UnassignedSequenceNumber || insertSeq > removedSeq;
337
255
  }
338
256
 
339
- /**
340
- * Asserts that the segment has move info. Usage of this function should not produce a user facing error.
341
- *
342
- * @param segmentLike - The segment-like object to check.
343
- * @throws Will throw an error if the segment does not have move info.
344
- */
345
- export const assertMoved: <T extends Partial<IMoveInfo> | undefined>(
346
- segmentLike: ISegmentInternal | Partial<IMoveInfo> | T,
347
- ) => asserts segmentLike is IMoveInfo | Exclude<T, Partial<IMoveInfo>> = (segmentLike) =>
348
- assert(segmentLike === undefined || isMoved(segmentLike), 0xaa3 /* must be moveInfo */);
349
-
350
257
  /**
351
258
  * A union type representing any segment info.
352
259
  */
353
- export type SegmentInfo = IMergeNodeInfo | IInsertionInfo | IMoveInfo | IRemovalInfo;
260
+ export type SegmentInfo = IMergeNodeInfo | IHasInsertionInfo | IHasRemovalInfo;
354
261
 
355
262
  /**
356
263
  * A type representing a segment with additional info.
@@ -14,20 +14,6 @@ import type {
14
14
  } from "./ops.js";
15
15
  import { MapLike, PropertySet, clone, createMap } from "./properties.js";
16
16
 
17
- /**
18
- * @internal
19
- */
20
- export enum PropertiesRollback {
21
- /**
22
- * Not in a rollback
23
- */
24
- None,
25
-
26
- /**
27
- * Rollback
28
- */
29
- Rollback,
30
- }
31
17
  /**
32
18
  * Minimally copies properties and the property manager from source to destination.
33
19
  * @internal
@@ -203,9 +189,9 @@ export class PropertiesManager {
203
189
  seq: number,
204
190
  msn: number,
205
191
  collaborating: boolean = false,
206
- rollback: PropertiesRollback = PropertiesRollback.None,
192
+ rollback: boolean = false,
207
193
  ): MapLike<unknown> {
208
- if (rollback === PropertiesRollback.Rollback) {
194
+ if (rollback) {
209
195
  return this.rollbackProperties(op, seg, collaborating);
210
196
  }
211
197
  const rtn = applyChanges(op, seg, seq, (properties, deltas, key, value) => {
@@ -25,11 +25,11 @@ import { NonCollabClient, UniversalSequenceNumber } from "./constants.js";
25
25
  import { MergeTree } from "./mergeTree.js";
26
26
  import { ISegmentPrivate } from "./mergeTreeNodes.js";
27
27
  import { IJSONSegment } from "./ops.js";
28
+ import { PriorPerspective } from "./perspective.js";
28
29
  import {
29
- IRemovalInfo,
30
+ IHasRemovalInfo,
30
31
  overwriteInfo,
31
- type IInsertionInfo,
32
- type IMoveInfo,
32
+ type IHasInsertionInfo,
33
33
  type SegmentWithInfo,
34
34
  } from "./segmentInfos.js";
35
35
  import {
@@ -39,6 +39,8 @@ import {
39
39
  } from "./snapshotChunks.js";
40
40
  import { SnapshotV1 } from "./snapshotV1.js";
41
41
  import { SnapshotLegacy } from "./snapshotlegacy.js";
42
+ import type { RemoveOperationStamp } from "./stamps.js";
43
+ import * as opstampUtils from "./stamps.js";
42
44
 
43
45
  export class SnapshotLoader {
44
46
  private readonly logger: ITelemetryLoggerExt;
@@ -102,16 +104,21 @@ export class SnapshotLoader {
102
104
 
103
105
  private readonly specToSegment = (
104
106
  spec: IJSONSegment | IJSONSegmentWithMergeInfo,
105
- ): SegmentWithInfo<IInsertionInfo> => {
107
+ ): SegmentWithInfo<IHasInsertionInfo> => {
106
108
  if (hasMergeInfo(spec)) {
107
- const seg = overwriteInfo<IInsertionInfo>(this.client.specToSegment(spec.json), {
108
- clientId:
109
- spec.client === undefined
110
- ? NonCollabClient
111
- : this.client.getOrAddShortClientId(spec.client),
112
- seq: spec.seq ?? UniversalSequenceNumber,
109
+ const seg = overwriteInfo<IHasInsertionInfo>(this.client.specToSegment(spec.json), {
110
+ insert: {
111
+ type: "insert",
112
+ seq: spec.seq ?? UniversalSequenceNumber,
113
+ clientId:
114
+ spec.client === undefined
115
+ ? NonCollabClient
116
+ : this.client.getOrAddShortClientId(spec.client),
117
+ },
113
118
  });
114
119
 
120
+ const removes: RemoveOperationStamp[] = [];
121
+
115
122
  if (spec.removedSeq !== undefined) {
116
123
  // this format had a bug where it didn't store all the overlap clients
117
124
  // this is for back compat, so we change the singular id to an array
@@ -122,32 +129,58 @@ export class SnapshotLoader {
122
129
  spec.removedClientIds ??= [specAsBuggyFormat.removedClient];
123
130
  }
124
131
  assert(spec.removedClientIds !== undefined, 0xaac /* must have removedClient ids */);
125
- overwriteInfo<IRemovalInfo>(seg, {
126
- removedSeq: spec.removedSeq,
127
- removedClientIds: spec.removedClientIds.map((id) =>
128
- this.client.getOrAddShortClientId(id),
132
+ const firstRemovedSeq = spec.removedSeq;
133
+ // TODO:AB#32299: To correctly support perspectives from other clients which don't assume they have seen
134
+ // all ops, we need to actually record these in the summary. For now we use fake data, and it turns
135
+ // out ok since none of these values end up being used. (specifically, the 'firstRemovedSeq' is fake
136
+ // for all values other than the actual first remove).
137
+ // This issue only affects V1 summaries, as the strategy in snapshotlegacy avoids storing merge info directly.
138
+ removes.push(
139
+ ...spec.removedClientIds.map(
140
+ (id) =>
141
+ ({
142
+ type: "setRemove",
143
+ seq: firstRemovedSeq,
144
+ clientId: this.client.getOrAddShortClientId(id),
145
+ }) as const,
129
146
  ),
130
- });
147
+ );
131
148
  }
132
149
  if (spec.movedSeq !== undefined) {
133
150
  assert(
134
151
  spec.movedClientIds !== undefined && spec.movedSeqs !== undefined,
135
152
  0xaa5 /* must have movedIds ids */,
136
153
  );
137
- overwriteInfo<IMoveInfo>(seg, {
138
- movedSeq: spec.movedSeq,
139
- movedSeqs: spec.movedSeqs,
140
- movedClientIds: spec.movedClientIds.map((id) =>
141
- this.client.getOrAddShortClientId(id),
154
+ assert(
155
+ spec.movedClientIds.length === spec.movedSeqs.length,
156
+ 0xb5f /* Expected same length for client ids and seqs */,
157
+ );
158
+
159
+ removes.push(
160
+ ...spec.movedClientIds.map(
161
+ (id, i) =>
162
+ ({
163
+ type: "sliceRemove",
164
+ seq: spec.movedSeqs![i],
165
+ clientId: this.client.getOrAddShortClientId(id),
166
+ }) as const,
142
167
  ),
143
- });
168
+ );
169
+ }
170
+
171
+ if (removes.length > 0) {
172
+ removes.sort(opstampUtils.compare);
173
+ overwriteInfo<IHasRemovalInfo>(seg, { removes });
144
174
  }
145
175
 
146
176
  return seg;
147
177
  }
148
178
  return overwriteInfo(this.client.specToSegment(spec), {
149
- seq: UniversalSequenceNumber,
150
- clientId: NonCollabClient,
179
+ insert: {
180
+ type: "insert",
181
+ seq: UniversalSequenceNumber,
182
+ clientId: NonCollabClient,
183
+ },
151
184
  });
152
185
  };
153
186
 
@@ -206,7 +239,7 @@ export class SnapshotLoader {
206
239
  }
207
240
 
208
241
  let chunksWithAttribution = chunk1.attribution === undefined ? 0 : 1;
209
- const segs: SegmentWithInfo<IInsertionInfo>[] = [];
242
+ const segs: SegmentWithInfo<IHasInsertionInfo>[] = [];
210
243
  let lengthSofar = chunk1.length;
211
244
  for (
212
245
  let chunkIndex = 1;
@@ -243,19 +276,18 @@ export class SnapshotLoader {
243
276
 
244
277
  // Helper to insert segments at the end of the MergeTree.
245
278
  const mergeTree = this.mergeTree;
246
- const append = (segments: ISegmentPrivate[], cli: number, seq: number): void => {
279
+ const append = (segments: ISegmentPrivate[], clientId: number, seq: number): void => {
247
280
  mergeTree.insertSegments(
248
281
  mergeTree.root.cachedLength ?? 0,
249
282
  segments,
250
- /* refSeq: */ UniversalSequenceNumber,
251
- cli,
252
- seq,
283
+ new PriorPerspective(UniversalSequenceNumber, clientId),
284
+ { seq, clientId },
253
285
  undefined,
254
286
  );
255
287
  };
256
288
 
257
289
  // Helpers to batch-insert segments that are below the min seq
258
- const batch: SegmentWithInfo<IInsertionInfo>[] = [];
290
+ const batch: SegmentWithInfo<IHasInsertionInfo>[] = [];
259
291
  const flushBatch = (): void => {
260
292
  if (batch.length > 0) {
261
293
  append(batch, NonCollabClient, UniversalSequenceNumber);
@@ -263,7 +295,7 @@ export class SnapshotLoader {
263
295
  };
264
296
 
265
297
  for (const seg of segs) {
266
- const { clientId, seq } = seg;
298
+ const { clientId, seq } = seg.insert;
267
299
  // If the segment can be batch inserted, add it to the 'batch' array. Otherwise, flush
268
300
  // any batched segments and then insert the current segment individually.
269
301
  if (clientId === NonCollabClient && seq === UniversalSequenceNumber) {
package/src/snapshotV1.ts CHANGED
@@ -19,13 +19,13 @@ import {
19
19
  } from "@fluidframework/telemetry-utils/internal";
20
20
 
21
21
  import { IAttributionCollection } from "./attributionCollection.js";
22
- import { UnassignedSequenceNumber } from "./constants.js";
22
+ import { NonCollabClient } from "./constants.js";
23
23
  import { MergeTree } from "./mergeTree.js";
24
24
  import { walkAllChildSegments } from "./mergeTreeNodeWalk.js";
25
25
  import { ISegmentPrivate } from "./mergeTreeNodes.js";
26
26
  import type { IJSONSegment } from "./ops.js";
27
27
  import { PropertySet, matchProperties } from "./properties.js";
28
- import { assertInserted, isMoved, isRemoved } from "./segmentInfos.js";
28
+ import { assertInserted, isRemoved } from "./segmentInfos.js";
29
29
  import {
30
30
  IJSONSegmentWithMergeInfo,
31
31
  JsonSegmentSpecs,
@@ -36,6 +36,8 @@ import {
36
36
  type VersionedMergeTreeChunk,
37
37
  } from "./snapshotChunks.js";
38
38
  import { SnapshotLegacy } from "./snapshotlegacy.js";
39
+ import type { OperationStamp } from "./stamps.js";
40
+ import * as opstampUtils from "./stamps.js";
39
41
 
40
42
  export class SnapshotV1 {
41
43
  // Split snapshot into two entries - headers (small) and body (overflow) for faster loading initial content
@@ -190,6 +192,7 @@ export class SnapshotV1 {
190
192
  extractSync(): JsonSegmentSpecs[] {
191
193
  const mergeTree = this.mergeTree;
192
194
  const minSeq = this.header.minSequenceNumber;
195
+ const minSeqStamp: OperationStamp = { seq: minSeq, clientId: NonCollabClient };
193
196
 
194
197
  let originalSegments = 0;
195
198
  let segmentsAfterCombine = 0;
@@ -232,11 +235,10 @@ export class SnapshotV1 {
232
235
  // b) The segment was removed at or below the MSN. Pending ops can no longer reference this
233
236
  // segment, and therefore we can discard it.
234
237
  if (
235
- segment.seq === UnassignedSequenceNumber ||
236
- (isRemoved(segment) && segment.removedSeq <= minSeq) ||
237
- (isMoved(segment) && segment.movedSeq <= minSeq)
238
+ opstampUtils.isLocal(segment.insert) ||
239
+ (isRemoved(segment) && opstampUtils.lte(segment.removes[0], minSeqStamp))
238
240
  ) {
239
- if (segment.seq !== UnassignedSequenceNumber) {
241
+ if (opstampUtils.isAcked(segment.insert)) {
240
242
  originalSegments += 1;
241
243
  }
242
244
  return true;
@@ -248,10 +250,8 @@ export class SnapshotV1 {
248
250
  // (seq, client, etc.) This information is only needed if the segment is above the MSN (and doesn't
249
251
  // have a pending remove.)
250
252
  if (
251
- segment.seq <= minSeq && // Segment is below the MSN, and...
252
- (!isRemoved(segment) || // .. Segment has not been removed, or...
253
- segment.removedSeq === UnassignedSequenceNumber) && // .. Removal op to be delivered on reconnect
254
- (!isMoved(segment) || segment.movedSeq === UnassignedSequenceNumber)
253
+ opstampUtils.lte(segment.insert, minSeqStamp) && // Segment is below the MSN, and...
254
+ (!isRemoved(segment) || opstampUtils.isLocal(segment.removes[0]))
255
255
  ) {
256
256
  // This segment is below the MSN, which means that future ops will not reference it. Attempt to
257
257
  // coalesce the new segment with the previous (if any).
@@ -285,38 +285,46 @@ export class SnapshotV1 {
285
285
  json: segment.toJSONObject() as IJSONSegment,
286
286
  };
287
287
  // If the segment insertion is above the MSN, record the insertion merge info.
288
- if (segment.seq > minSeq) {
289
- raw.seq = segment.seq;
290
- raw.client = this.getLongClientId(segment.clientId);
288
+ if (opstampUtils.greaterThan(segment.insert, minSeqStamp)) {
289
+ raw.seq = segment.insert.seq;
290
+ raw.client = this.getLongClientId(segment.insert.clientId);
291
291
  }
292
+
292
293
  // We have already dispensed with removed segments below the MSN and removed segments with unassigned
293
294
  // sequence numbers. Any remaining removal info should be preserved.
294
- if (isRemoved(segment)) {
295
+ if (isRemoved(segment) && segment.removes.some((r) => r.type === "setRemove")) {
296
+ const removes = segment.removes.filter((r) => r.type === "setRemove");
297
+ const firstRemove = removes[0];
295
298
  assert(
296
- segment.removedSeq !== UnassignedSequenceNumber && segment.removedSeq > minSeq,
299
+ opstampUtils.isAcked(firstRemove) &&
300
+ opstampUtils.greaterThan(firstRemove, minSeqStamp),
297
301
  0x065 /* "On removal info preservation, segment has invalid removed sequence number!" */,
298
302
  );
299
- raw.removedSeq = segment.removedSeq;
303
+ // TODO:AB#32299 By not preserving sequence numbers other than the first move,
304
+ // We drop data here which will be necessary to allow perspectives of remote clients that
305
+ // don't include all of their ops. This should be remedied at some point.
306
+ raw.removedSeq = firstRemove.seq;
300
307
 
301
308
  // back compat for when we split overlap and removed client
302
- raw.removedClient =
303
- segment.removedClientIds === undefined
304
- ? undefined
305
- : this.getLongClientId(segment.removedClientIds[0]);
309
+ // This can be removed when we can safely assume no clients running Fluid packages <= 0.58 will ever
310
+ // load a document produced by a version beyond the removal. It is vestigial in the meantime.
311
+ raw.removedClient = this.getLongClientId(firstRemove.clientId);
306
312
 
307
- raw.removedClientIds = segment.removedClientIds?.map((id) =>
308
- this.getLongClientId(id),
309
- );
313
+ raw.removedClientIds = removes.map(({ clientId }) => this.getLongClientId(clientId));
310
314
  }
311
315
 
312
- if (isMoved(segment)) {
316
+ if (isRemoved(segment) && segment.removes.some((r) => r.type === "sliceRemove")) {
317
+ // In this format, we used the term "move" to refer to a sliceRemove/obliterate.
318
+ const moves = segment.removes.filter((r) => r.type === "sliceRemove");
319
+ const firstMove = moves[0];
313
320
  assert(
314
- segment.movedSeq !== UnassignedSequenceNumber && segment.movedSeq > minSeq,
321
+ opstampUtils.isAcked(firstMove) &&
322
+ opstampUtils.greaterThan(firstMove, minSeqStamp),
315
323
  0x873 /* On move info preservation, segment has invalid moved sequence number! */,
316
324
  );
317
- raw.movedSeq = segment.movedSeq;
318
- raw.movedSeqs = segment.movedSeqs;
319
- raw.movedClientIds = segment.movedClientIds?.map((id) => this.getLongClientId(id));
325
+ raw.movedSeq = firstMove.seq;
326
+ raw.movedSeqs = moves.map(({ seq }) => seq);
327
+ raw.movedClientIds = moves.map(({ clientId }) => this.getLongClientId(clientId));
320
328
  }
321
329
 
322
330
  // Sanity check that we are preserving either the seq > minSeq or a (re)moved segment's info.