@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
package/dist/mergeTree.js CHANGED
@@ -3,8 +3,31 @@
3
3
  * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
4
4
  * Licensed under the MIT License.
5
5
  */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || function (mod) {
23
+ if (mod && mod.__esModule) return mod;
24
+ var result = {};
25
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
26
+ __setModuleDefault(result, mod);
27
+ return result;
28
+ };
6
29
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.MergeTree = exports.getSlideToSegoff = exports.findRootMergeBlock = exports.errorIfOptionNotTrue = void 0;
30
+ exports.MergeTree = exports.getSlideToSegoff = exports.findRootMergeBlock = exports.errorIfOptionNotTrue = exports.isRemovedAndAcked = void 0;
8
31
  /* eslint-disable @typescript-eslint/no-non-null-assertion */
9
32
  /* eslint-disable no-bitwise */
10
33
  const internal_1 = require("@fluidframework/core-utils/internal");
@@ -27,76 +50,71 @@ const segmentInfos_js_1 = require("./segmentInfos.js");
27
50
  const segmentPropertiesManager_js_1 = require("./segmentPropertiesManager.js");
28
51
  const sequencePlace_js_1 = require("./sequencePlace.js");
29
52
  const sortedSegmentSet_js_1 = require("./sortedSegmentSet.js");
53
+ const opstampUtils = __importStar(require("./stamps.js"));
30
54
  const zamboni_js_1 = require("./zamboni.js");
31
55
  function isRemovedAndAcked(segment) {
32
56
  const removalInfo = (0, segmentInfos_js_1.toRemovalInfo)(segment);
33
- return removalInfo !== undefined && removalInfo.removedSeq !== constants_js_1.UnassignedSequenceNumber;
34
- }
35
- function isMovedAndAcked(segment) {
36
- const moveInfo = (0, segmentInfos_js_1.toMoveInfo)(segment);
37
- return moveInfo !== undefined && moveInfo.movedSeq !== constants_js_1.UnassignedSequenceNumber;
38
- }
39
- function isRemovedAndAckedOrMovedAndAcked(segment) {
40
- return isRemovedAndAcked(segment) || isMovedAndAcked(segment);
41
- }
42
- function isRemovedOrMoved(segment) {
43
- return (0, segmentInfos_js_1.isRemoved)(segment) || (0, segmentInfos_js_1.isMoved)(segment);
57
+ return removalInfo !== undefined && opstampUtils.isAcked(removalInfo.removes[0]);
44
58
  }
59
+ exports.isRemovedAndAcked = isRemovedAndAcked;
45
60
  function nodeTotalLength(mergeTree, node) {
46
61
  if (!node.isLeaf()) {
47
62
  return node.cachedLength;
48
63
  }
49
- return mergeTree.localNetLength(node);
64
+ return mergeTree.leafLength(node);
50
65
  }
51
66
  const LRUSegmentComparer = {
52
67
  min: { maxSeq: -2 },
53
68
  compare: (a, b) => a.maxSeq - b.maxSeq,
54
69
  };
55
- function ackSegment(segment, segmentGroup, opArgs) {
70
+ function ackSegment(segment, segmentGroup, opArgs, stamp) {
56
71
  const currentSegmentGroup = segment.segmentGroups?.dequeue();
57
72
  (0, internal_1.assert)(currentSegmentGroup === segmentGroup, 0x043 /* "On ack, unexpected segmentGroup!" */);
58
73
  (0, internal_1.assert)(opArgs.sequencedMessage !== undefined, 0xa6e /* must have sequencedMessage */);
59
74
  const { op, sequencedMessage: { sequenceNumber, minimumSequenceNumber }, } = opArgs;
75
+ let allowIncrementalPartialLengthsUpdate = true;
60
76
  switch (op.type) {
61
77
  case ops_js_1.MergeTreeDeltaType.ANNOTATE: {
62
78
  (0, internal_1.assert)(!!segment.propertyManager, 0x044 /* "On annotate ack, missing segment property manager!" */);
63
79
  segment.propertyManager.ack(sequenceNumber, minimumSequenceNumber, op);
64
- return true;
80
+ break;
65
81
  }
66
82
  case ops_js_1.MergeTreeDeltaType.INSERT: {
67
- (0, internal_1.assert)(segment.seq === constants_js_1.UnassignedSequenceNumber, 0x045 /* "On insert, seq number already assigned!" */);
68
- segment.seq = sequenceNumber;
69
- segment.localSeq = undefined;
70
- return true;
71
- }
72
- case ops_js_1.MergeTreeDeltaType.REMOVE: {
73
- (0, segmentInfos_js_1.assertRemoved)(segment);
74
- segment.localRemovedSeq = undefined;
75
- if (segment.removedSeq === constants_js_1.UnassignedSequenceNumber) {
76
- segment.removedSeq = sequenceNumber;
77
- return true;
78
- }
79
- return false;
83
+ (0, internal_1.assert)(opstampUtils.isLocal(segment.insert), 0x045 /* "On insert, seq number already assigned!" */);
84
+ segment.insert = {
85
+ ...stamp,
86
+ type: "insert",
87
+ };
88
+ break;
80
89
  }
90
+ case ops_js_1.MergeTreeDeltaType.REMOVE:
81
91
  case ops_js_1.MergeTreeDeltaType.OBLITERATE:
82
92
  case ops_js_1.MergeTreeDeltaType.OBLITERATE_SIDED: {
83
- (0, segmentInfos_js_1.assertMoved)(segment);
84
- const obliterateInfo = segmentGroup.obliterateInfo;
85
- (0, internal_1.assert)(obliterateInfo !== undefined, 0xa40 /* must have obliterate info */);
86
- segment.localMovedSeq = obliterateInfo.localSeq = undefined;
87
- const seqIdx = segment.movedSeqs.indexOf(constants_js_1.UnassignedSequenceNumber);
88
- (0, internal_1.assert)(seqIdx !== -1, 0x86f /* expected movedSeqs to contain unacked seq */);
89
- segment.movedSeqs[seqIdx] = sequenceNumber;
90
- if (segment.movedSeq === constants_js_1.UnassignedSequenceNumber) {
91
- segment.movedSeq = sequenceNumber;
92
- return true;
93
+ (0, segmentInfos_js_1.assertRemoved)(segment);
94
+ const latestRemove = segment.removes[segment.removes.length - 1];
95
+ (0, internal_1.assert)(opstampUtils.isLocal(latestRemove), 0xb5d /* Expected last remove to be unacked */);
96
+ (0, internal_1.assert)(segment.removes.length === 1 ||
97
+ opstampUtils.isAcked(segment.removes[segment.removes.length - 2]), 0xb5e /* Expected prior remove to be acked */);
98
+ allowIncrementalPartialLengthsUpdate = segment.removes.length === 1;
99
+ const removeStamp = {
100
+ ...stamp,
101
+ type: op.type === ops_js_1.MergeTreeDeltaType.REMOVE ? "setRemove" : "sliceRemove",
102
+ };
103
+ segment.removes[segment.removes.length - 1] = removeStamp;
104
+ const { obliterateInfo } = segmentGroup;
105
+ const hasObliterateInfo = obliterateInfo !== undefined;
106
+ const isObliterate = op.type !== ops_js_1.MergeTreeDeltaType.REMOVE;
107
+ (0, internal_1.assert)(hasObliterateInfo === isObliterate, 0xa40 /* must have obliterate info */);
108
+ if (hasObliterateInfo) {
109
+ obliterateInfo.stamp = removeStamp;
93
110
  }
94
- return false;
111
+ break;
95
112
  }
96
113
  default: {
97
114
  throw new Error(`${op.type} is in unrecognized operation type`);
98
115
  }
99
116
  }
117
+ return allowIncrementalPartialLengthsUpdate;
100
118
  }
101
119
  function errorIfOptionNotTrue(options, option) {
102
120
  if (options?.[option] !== true) {
@@ -129,9 +147,7 @@ exports.findRootMergeBlock = findRootMergeBlock;
129
147
  * SlideOnRemove references is removed.
130
148
  */
131
149
  function getSlideToSegment(segment, slidingPreference = localReference_js_1.SlidingPreference.FORWARD, cache, useNewSlidingBehavior = false) {
132
- if (!segment ||
133
- !isRemovedAndAckedOrMovedAndAcked(segment) ||
134
- segment.endpointType !== undefined) {
150
+ if (!segment || !isRemovedAndAcked(segment) || segment.endpointType !== undefined) {
135
151
  return [segment, undefined];
136
152
  }
137
153
  const cachedSegment = cache?.get(segment);
@@ -141,13 +157,12 @@ function getSlideToSegment(segment, slidingPreference = localReference_js_1.Slid
141
157
  const result = {};
142
158
  cache?.set(segment, result);
143
159
  const goFurtherToFindSlideToSegment = (seg) => {
144
- if (seg.seq !== constants_js_1.UnassignedSequenceNumber && !isRemovedAndAckedOrMovedAndAcked(seg)) {
160
+ if (opstampUtils.isAcked(seg.insert) && !isRemovedAndAcked(seg)) {
145
161
  result.seg = seg;
146
162
  return false;
147
163
  }
148
164
  if (cache !== undefined &&
149
- ((0, segmentInfos_js_1.toRemovalInfo)(seg)?.removedSeq === (0, segmentInfos_js_1.toRemovalInfo)(segment)?.removedSeq ||
150
- (0, segmentInfos_js_1.toMoveInfo)(seg)?.movedSeq === (0, segmentInfos_js_1.toMoveInfo)(segment)?.movedSeq)) {
165
+ (0, segmentInfos_js_1.toRemovalInfo)(seg)?.removes[0].seq === (0, segmentInfos_js_1.toRemovalInfo)(segment)?.removes[0].seq) {
151
166
  cache.set(seg, result);
152
167
  }
153
168
  return true;
@@ -214,10 +229,10 @@ class Obliterates {
214
229
  constructor(mergeTree) {
215
230
  this.mergeTree = mergeTree;
216
231
  /**
217
- * Array containing the all move operations within the
232
+ * Array containing the all obliterate operations within the
218
233
  * collab window.
219
234
  *
220
- * The moves are stored in sequence order which accelerates clean up in setMinSeq
235
+ * The obliterates are stored in sequence order which accelerates clean up in setMinSeq
221
236
  *
222
237
  * See https://github.com/microsoft/FluidFramework/blob/main/packages/dds/merge-tree/docs/Obliterate.md#remote-perspective
223
238
  * for additional context
@@ -232,7 +247,7 @@ class Obliterates {
232
247
  }
233
248
  setMinSeq(minSeq) {
234
249
  // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
235
- while (!this.seqOrdered.empty && this.seqOrdered.first?.data.seq <= minSeq) {
250
+ while (!this.seqOrdered.empty && this.seqOrdered.first?.data.stamp.seq <= minSeq) {
236
251
  const ob = this.seqOrdered.shift();
237
252
  this.startOrdered.remove(ob.data.start);
238
253
  this.mergeTree.removeLocalReferencePosition(ob.data.start);
@@ -240,7 +255,7 @@ class Obliterates {
240
255
  }
241
256
  }
242
257
  addOrUpdate(obliterateInfo) {
243
- const { seq, start } = obliterateInfo;
258
+ const { stamp: { seq }, start, } = obliterateInfo;
244
259
  if (seq !== constants_js_1.UnassignedSequenceNumber) {
245
260
  this.seqOrdered.push(obliterateInfo);
246
261
  }
@@ -272,12 +287,14 @@ class Obliterates {
272
287
  * @internal
273
288
  */
274
289
  class MergeTree {
290
+ get localPerspective() {
291
+ return this.collabWindow.localPerspective;
292
+ }
275
293
  constructor(options) {
276
294
  this.options = options;
277
295
  this.collabWindow = new mergeTreeNodes_js_1.CollaborationWindow();
278
296
  this.pendingSegments = new index_js_1.DoublyLinkedList();
279
297
  this.segmentsToScour = new internal_1.Heap(LRUSegmentComparer);
280
- this.localPerspective = new perspective_js_1.LocalDefaultPerspective(this.collabWindow.clientId);
281
298
  /**
282
299
  * Whether or not all blocks in the mergeTree currently have information about local partial lengths computed.
283
300
  * This information is only necessary on reconnect, and otherwise costly to bookkeep.
@@ -332,61 +349,20 @@ class MergeTree {
332
349
  return block;
333
350
  }
334
351
  /**
335
- * Compute the net length of this segment from a local perspective.
336
- * @param segment - Segment whose length to find
337
- * @param localSeq - localSeq at which to find the length of this segment. If not provided,
338
- * default is to consider the local client's current perspective. Only local sequence
339
- * numbers corresponding to un-acked operations give valid results.
352
+ * Compute the net length of this segment leaf from some perspective.
353
+ * @returns - Undefined if the segment has been removed and its removal is common knowledge to all collaborators (and therefore
354
+ * may not even be present on clients that have loaded from a summary beyond this point). Otherwise, the length of the segment.
340
355
  */
341
- localNetLength(segment, refSeq, localSeq) {
356
+ leafLength(segment, perspective = this.localPerspective) {
342
357
  const removalInfo = (0, segmentInfos_js_1.toRemovalInfo)(segment);
343
- const moveInfo = (0, segmentInfos_js_1.toMoveInfo)(segment);
344
- if (localSeq === undefined) {
345
- if (removalInfo !== undefined || moveInfo !== undefined) {
346
- if ((!!removalInfo && !(0, mergeTreeNodes_js_1.seqLTE)(removalInfo.removedSeq, this.collabWindow.minSeq)) ||
347
- (!!moveInfo && !(0, mergeTreeNodes_js_1.seqLTE)(moveInfo.movedSeq, this.collabWindow.minSeq))) {
348
- return 0;
349
- }
350
- // this segment removed and outside the collab window which means it is zamboni eligible
351
- // this also means the segment could not exist, so we should not consider it
352
- // when making decisions about conflict resolutions
353
- return undefined;
354
- }
355
- else {
356
- return segment.cachedLength;
357
- }
358
- }
359
- (0, internal_1.assert)(refSeq !== undefined, 0x398 /* localSeq provided for local length without refSeq */);
360
- (0, internal_1.assert)(segment.seq !== undefined, 0x399 /* segment with no seq in mergeTree */);
361
- const { seq } = segment;
362
- const { removedSeq, localRemovedSeq } = removalInfo ?? {};
363
- const { movedSeq, localMovedSeq } = moveInfo ?? {};
364
- if (seq === constants_js_1.UnassignedSequenceNumber) {
365
- (0, internal_1.assert)(segment.localSeq !== undefined, 0x39a /* unacked segment with undefined localSeq */);
366
- // inserted locally, still un-acked
367
- if (segment.localSeq > localSeq ||
368
- (localRemovedSeq !== undefined && localRemovedSeq <= localSeq) ||
369
- (localMovedSeq !== undefined && localMovedSeq <= localSeq)) {
370
- return 0;
371
- }
372
- const { cachedLength } = segment;
373
- return cachedLength;
374
- }
375
- else {
376
- // inserted remotely
377
- if (seq > refSeq ||
378
- (removedSeq !== undefined &&
379
- removedSeq !== constants_js_1.UnassignedSequenceNumber &&
380
- removedSeq <= refSeq) ||
381
- (movedSeq !== undefined &&
382
- movedSeq !== constants_js_1.UnassignedSequenceNumber &&
383
- movedSeq <= refSeq) ||
384
- (localRemovedSeq !== undefined && localRemovedSeq <= localSeq) ||
385
- (localMovedSeq !== undefined && localMovedSeq <= localSeq)) {
386
- return 0;
387
- }
388
- return segment.cachedLength;
358
+ if (removalInfo &&
359
+ (0, mergeTreeNodes_js_1.getMinSeqPerspective)(this.collabWindow).hasOccurred(removalInfo.removes[0])) {
360
+ // this segment's removal has already moved outside the collab window which means it is zamboni eligible
361
+ // this also means the segment could be completely absent from other client's in-memory merge trees,
362
+ // so we should not consider it when making decisions about conflict resolutions
363
+ return undefined;
389
364
  }
365
+ return perspective.isSegmentPresent(segment) ? segment.cachedLength : 0;
390
366
  }
391
367
  unlinkMarker(marker) {
392
368
  const id = marker.getId();
@@ -444,6 +420,7 @@ class MergeTree {
444
420
  this.collabWindow.minSeq = minSeq;
445
421
  this.collabWindow.collaborating = true;
446
422
  this.collabWindow.currentSeq = currentSeq;
423
+ this.collabWindow.localPerspective = new perspective_js_1.LocalDefaultPerspective(localClientId);
447
424
  this.nodeUpdateLengthNewStructure(this.root, true);
448
425
  }
449
426
  addToLRUSet(leaf, seq) {
@@ -456,8 +433,8 @@ class MergeTree {
456
433
  this.segmentsToScour.add({ segment: leaf, maxSeq: seq });
457
434
  }
458
435
  }
459
- getLength(refSeq, clientId) {
460
- return this.blockLength(this.root, refSeq, clientId);
436
+ getLength(perspective) {
437
+ return this.nodeLength(this.root, perspective) ?? 0;
461
438
  }
462
439
  /**
463
440
  * Returns the current length of the MergeTree for the local client.
@@ -465,7 +442,7 @@ class MergeTree {
465
442
  get length() {
466
443
  return this.root.cachedLength;
467
444
  }
468
- getPosition(node, refSeq, clientId, localSeq) {
445
+ getPosition(node, perspective) {
469
446
  if (node.isLeaf() && node.endpointType === "start") {
470
447
  return 0;
471
448
  }
@@ -479,15 +456,16 @@ class MergeTree {
479
456
  if ((!!prevParent && child === prevParent) || child === node) {
480
457
  break;
481
458
  }
482
- totalOffset += this.nodeLength(child, refSeq, clientId, localSeq) ?? 0;
459
+ totalOffset += this.nodeLength(child, perspective) ?? 0;
483
460
  }
484
461
  prevParent = parent;
485
462
  parent = parent.parent;
486
463
  }
487
464
  return totalOffset;
488
465
  }
489
- getContainingSegment(pos, refSeq, clientId, localSeq) {
490
- (0, internal_1.assert)(localSeq === undefined || clientId === this.collabWindow.clientId, 0x39b /* localSeq provided for non-local client */);
466
+ getContainingSegment(pos, perspective) {
467
+ (0, internal_1.assert)(perspective.localSeq === undefined ||
468
+ perspective.clientId === this.collabWindow.clientId, 0x39b /* localSeq provided for non-local client */);
491
469
  let segment;
492
470
  let offset;
493
471
  const leaf = (leafSeg, _, start) => {
@@ -495,7 +473,7 @@ class MergeTree {
495
473
  offset = start;
496
474
  return false;
497
475
  };
498
- this.nodeMap(refSeq, clientId, leaf, undefined, pos, pos + 1, localSeq);
476
+ this.nodeMap(perspective, leaf, undefined, pos, pos + 1);
499
477
  return { segment, offset };
500
478
  }
501
479
  /**
@@ -588,7 +566,7 @@ class MergeTree {
588
566
  const forwardSegmentCache = new Map();
589
567
  const backwardSegmentCache = new Map();
590
568
  for (const segment of segments) {
591
- (0, internal_1.assert)(isRemovedAndAckedOrMovedAndAcked(segment), 0x2f1 /* slideReferences from a segment which has not been removed and acked */);
569
+ (0, internal_1.assert)(isRemovedAndAcked(segment), 0x2f1 /* slideReferences from a segment which has not been removed and acked */);
592
570
  if (segment.localRefs === undefined || segment.localRefs.empty) {
593
571
  continue;
594
572
  }
@@ -608,11 +586,6 @@ class MergeTree {
608
586
  slideGroup(currentForwardSlideDestination, currentForwardSlideIsForward, currentForwardSlideGroup, forwardPred, currentForwardMaybeEndpoint);
609
587
  slideGroup(currentBackwardSlideDestination, currentBackwardSlideIsForward, currentBackwardSlideGroup, backwardPred, currentBackwardMaybeEndpoint);
610
588
  }
611
- blockLength(node, refSeq, clientId) {
612
- return this.collabWindow.collaborating && clientId !== this.collabWindow.clientId
613
- ? node.partialLengths.getPartialLength(refSeq, clientId)
614
- : (node.cachedLength ?? 0);
615
- }
616
589
  /**
617
590
  * Compute local partial length information
618
591
  *
@@ -630,62 +603,27 @@ class MergeTree {
630
603
  this.root.partialLengths = partialLengths_js_1.PartialSequenceLengths.combine(this.root, rebaseCollabWindow, true, true);
631
604
  this.localPartialsComputed = true;
632
605
  }
633
- nodeLength(node, refSeq, clientId, localSeq) {
634
- if (!this.collabWindow.collaborating || this.collabWindow.clientId === clientId) {
635
- if (node.isLeaf()) {
636
- return this.localNetLength(node, refSeq, localSeq);
637
- }
638
- else if (localSeq === undefined ||
639
- // All changes are visible. Small note on why we allow refSeq >= this.collabWindow.currentSeq rather than just equality:
640
- // merge-tree eventing occurs before the collab window is updated to account for whatever op it is processing, and we want
641
- // to support resolving positions from within the event handler which account for that op. e.g. undo-redo relies on this
642
- // behavior with local references.
643
- (localSeq === this.collabWindow.localSeq && refSeq >= this.collabWindow.currentSeq)) {
644
- // Local client sees all segments, even when collaborating
645
- return node.cachedLength;
646
- }
647
- else {
648
- this.computeLocalPartials(refSeq);
649
- // Local client should see all segments except those after localSeq.
650
- const partialLen = node.partialLengths.getPartialLength(refSeq, clientId, localSeq);
651
- partialLengths_js_1.PartialSequenceLengths.options.verifyExpected?.(this, node, refSeq, clientId, localSeq);
652
- return partialLen;
653
- }
654
- }
655
- else {
656
- // Sequence number within window
657
- if (node.isLeaf()) {
658
- const segment = node;
659
- const removalInfo = (0, segmentInfos_js_1.toRemovalInfo)(segment);
660
- const moveInfo = (0, segmentInfos_js_1.toMoveInfo)(segment);
661
- if (removalInfo !== undefined) {
662
- if ((0, mergeTreeNodes_js_1.seqLTE)(removalInfo.removedSeq, this.collabWindow.minSeq)) {
663
- return undefined;
664
- }
665
- if ((0, mergeTreeNodes_js_1.seqLTE)(removalInfo.removedSeq, refSeq) ||
666
- removalInfo.removedClientIds.includes(clientId)) {
667
- return 0;
668
- }
669
- }
670
- if (moveInfo !== undefined) {
671
- if ((0, mergeTreeNodes_js_1.seqLTE)(moveInfo.movedSeq, this.collabWindow.minSeq)) {
672
- return undefined;
673
- }
674
- if ((0, mergeTreeNodes_js_1.seqLTE)(moveInfo.movedSeq, refSeq) ||
675
- moveInfo.movedClientIds.includes(clientId)) {
676
- return 0;
677
- }
678
- }
679
- return (0, mergeTreeNodes_js_1.seqLTE)(node.seq ?? 0, refSeq) || segment.clientId === clientId
680
- ? segment.cachedLength
681
- : 0;
682
- }
683
- else {
684
- const partialLen = node.partialLengths.getPartialLength(refSeq, clientId);
685
- partialLengths_js_1.PartialSequenceLengths.options.verifyExpected?.(this, node, refSeq, clientId);
686
- return partialLen;
687
- }
688
- }
606
+ nodeLength(node, perspective) {
607
+ if (node.isLeaf()) {
608
+ return this.leafLength(node, perspective);
609
+ }
610
+ const { refSeq, clientId, localSeq } = perspective;
611
+ const isLocalPerspective = !this.collabWindow.collaborating || this.collabWindow.clientId === clientId;
612
+ if (isLocalPerspective &&
613
+ (localSeq === undefined ||
614
+ (localSeq === this.collabWindow.localSeq && refSeq >= this.collabWindow.currentSeq))) {
615
+ // All changes are visible. Small note on why we allow refSeq >= this.collabWindow.currentSeq rather than just equality:
616
+ // merge-tree eventing occurs before the collab window is updated to account for whatever op it is processing, and we want
617
+ // to support resolving positions from within the event handler which account for that op. e.g. undo-redo relies on this
618
+ // behavior with local references.
619
+ return node.cachedLength;
620
+ }
621
+ if (localSeq !== undefined) {
622
+ this.computeLocalPartials(refSeq);
623
+ }
624
+ const length = node.partialLengths.getPartialLength(refSeq, clientId, localSeq);
625
+ partialLengths_js_1.PartialSequenceLengths.options.verifyExpected?.(this, node, refSeq, clientId, localSeq);
626
+ return length;
689
627
  }
690
628
  setMinSeq(minSeq) {
691
629
  (0, internal_1.assert)(minSeq <= this.collabWindow.currentSeq, 0x04e /* "Trying to set minSeq above currentSeq of collab window!" */);
@@ -711,44 +649,61 @@ class MergeTree {
711
649
  referencePositionToLocalPosition(refPos,
712
650
  // Note: this is not `this.collabWindow.currentSeq` because we want to support resolving local reference positions to positions
713
651
  // from within event handlers, and the collab window's sequence numbers are not updated in time in all of those cases.
714
- refSeq = Number.MAX_SAFE_INTEGER, clientId = this.collabWindow.clientId, localSeq = this.collabWindow.localSeq) {
652
+ refSeq = Number.MAX_SAFE_INTEGER, clientId = this.collabWindow.clientId, localSeq = undefined) {
715
653
  const perspective = clientId === this.collabWindow.clientId
716
654
  ? localSeq === undefined
717
655
  ? this.localPerspective
718
656
  : new perspective_js_1.LocalReconnectingPerspective(refSeq, clientId, localSeq)
719
657
  : new perspective_js_1.PriorPerspective(refSeq, clientId);
720
658
  const seg = refPos.getSegment();
721
- if (!(0, mergeTreeNodes_js_1.isSegmentLeaf)(seg)) {
659
+ if (seg === undefined || !(0, mergeTreeNodes_js_1.isSegmentLeaf)(seg)) {
722
660
  // We have no idea where this reference is, because it refers to a segment which is not in the tree.
723
661
  return referencePositions_js_1.DetachedReferencePosition;
724
662
  }
725
663
  if (refPos.isLeaf()) {
726
- return this.getPosition(seg, refSeq, clientId, localSeq);
664
+ return this.getPosition(seg, perspective);
727
665
  }
728
666
  if ((0, referencePositions_js_1.refTypeIncludesFlag)(refPos, ops_js_1.ReferenceType.Transient) || seg.localRefs?.has(refPos)) {
729
667
  if (seg !== this.startOfTree &&
730
668
  seg !== this.endOfTree &&
731
669
  !perspective.isSegmentPresent(seg)) {
732
670
  const forward = refPos.slidingPreference === localReference_js_1.SlidingPreference.FORWARD;
733
- const moveInfo = (0, segmentInfos_js_1.toMoveInfo)(seg);
734
671
  const removeInfo = (0, segmentInfos_js_1.toRemovalInfo)(seg);
735
- const slideSeq = moveInfo !== undefined && moveInfo.movedSeq !== constants_js_1.UnassignedSequenceNumber
736
- ? moveInfo.movedSeq
737
- : removeInfo !== undefined && removeInfo.removedSeq !== constants_js_1.UnassignedSequenceNumber
738
- ? removeInfo.removedSeq
739
- : refSeq;
740
- const slideLocalSeq = moveInfo?.localMovedSeq ?? removeInfo?.localRemovedSeq;
741
- const slidePerspective = slideLocalSeq === undefined
672
+ const firstRemove = removeInfo?.removes[0];
673
+ const slideSeq = firstRemove !== undefined && opstampUtils.isAcked(firstRemove)
674
+ ? firstRemove.seq
675
+ : refSeq;
676
+ const slidePerspective = firstRemove?.localSeq === undefined
742
677
  ? new perspective_js_1.PriorPerspective(slideSeq, this.collabWindow.clientId)
743
- : new perspective_js_1.LocalReconnectingPerspective(slideSeq, this.collabWindow.clientId, slideLocalSeq);
744
- const slidSegment = slidePerspective.nextSegment(this, seg, forward);
745
- return (this.getPosition(slidSegment, refSeq, clientId, localSeq) +
678
+ : new perspective_js_1.LocalReconnectingPerspective(slideSeq, this.collabWindow.clientId, firstRemove.localSeq);
679
+ const slidSegment = this.nextSegment(slidePerspective, seg, forward);
680
+ return (this.getPosition(slidSegment, perspective) +
746
681
  (forward ? 0 : slidSegment.cachedLength === 0 ? 0 : slidSegment.cachedLength - 1));
747
682
  }
748
- return this.getPosition(seg, refSeq, clientId, localSeq) + refPos.getOffset();
683
+ return this.getPosition(seg, perspective) + refPos.getOffset();
749
684
  }
750
685
  return referencePositions_js_1.DetachedReferencePosition;
751
686
  }
687
+ /**
688
+ * Returns the immediately adjacent segment in the specified direction from this perspective.
689
+ * There may actually be multiple segments between the given segment and the returned segment,
690
+ * but they were either inserted after this perspective, or have been removed before this perspective.
691
+ *
692
+ * @param segment - The segment to start from.
693
+ * @param forward - The direction to search.
694
+ * @returns the next segment in the specified direction, or the start or end of the tree if there is no next segment.
695
+ */
696
+ nextSegment(perspective, segment, forward = true) {
697
+ let next;
698
+ const action = (seg) => {
699
+ if (perspective.isSegmentPresent(seg)) {
700
+ next = seg;
701
+ return mergeTreeNodeWalk_js_1.LeafAction.Exit;
702
+ }
703
+ };
704
+ (forward ? mergeTreeNodeWalk_js_1.forwardExcursion : mergeTreeNodeWalk_js_1.backwardExcursion)(segment, action);
705
+ return next ?? (forward ? this.endOfTree : this.startOfTree);
706
+ }
752
707
  /**
753
708
  * Finds the nearest reference with ReferenceType.Tile to `startPos` in the direction dictated by `forwards`.
754
709
  * Uses depthFirstNodeWalk in addition to block-accelerated functionality. The search position will be included in
@@ -761,9 +716,9 @@ class MergeTree {
761
716
  * @param markerLabel - Label of the marker to search for
762
717
  * @param forwards - Whether the string should be searched in the forward or backward direction
763
718
  */
764
- searchForMarker(startPos, clientId, markerLabel, forwards = true) {
719
+ searchForMarker(startPos, markerLabel, forwards = true) {
765
720
  let foundMarker;
766
- const { segment } = this.getContainingSegment(startPos, constants_js_1.UniversalSequenceNumber, clientId);
721
+ const { segment } = this.getContainingSegment(startPos, this.localPerspective);
767
722
  if (!(0, mergeTreeNodes_js_1.isSegmentLeaf)(segment)) {
768
723
  return undefined;
769
724
  }
@@ -787,14 +742,12 @@ class MergeTree {
787
742
  return foundMarker;
788
743
  }
789
744
  updateRoot(splitNode) {
790
- if (splitNode !== undefined) {
791
- const newRoot = this.makeBlock(2);
792
- (0, mergeTreeNodes_js_1.assignChild)(newRoot, this.root, 0, false);
793
- (0, mergeTreeNodes_js_1.assignChild)(newRoot, splitNode, 1, false);
794
- this.root = newRoot;
795
- this.nodeUpdateOrdinals(this.root);
796
- this.nodeUpdateLengthNewStructure(this.root);
797
- }
745
+ const newRoot = this.makeBlock(2);
746
+ (0, mergeTreeNodes_js_1.assignChild)(newRoot, this.root, 0, false);
747
+ (0, mergeTreeNodes_js_1.assignChild)(newRoot, splitNode, 1, false);
748
+ this.root = newRoot;
749
+ this.nodeUpdateOrdinals(this.root);
750
+ this.nodeUpdateLengthNewStructure(this.root);
798
751
  }
799
752
  /**
800
753
  * Assign sequence number to existing segment; update partial lengths to reflect the change
@@ -802,6 +755,10 @@ class MergeTree {
802
755
  */
803
756
  ackPendingSegment(opArgs) {
804
757
  const seq = opArgs.sequencedMessage.sequenceNumber;
758
+ const stamp = {
759
+ seq,
760
+ clientId: this.collabWindow.clientId,
761
+ };
805
762
  const pendingSegmentGroup = this.pendingSegments.shift()?.data;
806
763
  const nodesToUpdate = [];
807
764
  let overwrite = false;
@@ -809,8 +766,8 @@ class MergeTree {
809
766
  const deltaSegments = [];
810
767
  const overlappingRemoves = [];
811
768
  pendingSegmentGroup.segments.map((pendingSegment) => {
812
- const overlappingRemove = !ackSegment(pendingSegment, pendingSegmentGroup, opArgs);
813
- overwrite ||= overlappingRemove || (0, segmentInfos_js_1.toMoveInfo)(pendingSegment) !== undefined;
769
+ const overlappingRemove = !ackSegment(pendingSegment, pendingSegmentGroup, opArgs, stamp);
770
+ overwrite ||= overlappingRemove;
814
771
  overlappingRemoves.push(overlappingRemove);
815
772
  if (MergeTree.options.zamboniSegments) {
816
773
  this.addToLRUSet(pendingSegment, seq);
@@ -823,7 +780,7 @@ class MergeTree {
823
780
  });
824
781
  });
825
782
  if (pendingSegmentGroup.obliterateInfo !== undefined) {
826
- pendingSegmentGroup.obliterateInfo.seq = seq;
783
+ pendingSegmentGroup.obliterateInfo.stamp = { type: "sliceRemove", ...stamp };
827
784
  this.obliterates.addOrUpdate(pendingSegmentGroup.obliterateInfo);
828
785
  }
829
786
  // Perform slides after all segments have been acked, so that
@@ -837,9 +794,8 @@ class MergeTree {
837
794
  deltaSegments,
838
795
  operation: mergeTreeDeltaCallback_js_1.MergeTreeMaintenanceType.ACKNOWLEDGED,
839
796
  }, opArgs);
840
- const clientId = this.collabWindow.clientId;
841
797
  for (const node of nodesToUpdate) {
842
- this.blockUpdatePathLengths(node, seq, clientId, overwrite);
798
+ this.blockUpdatePathLengths(node, stamp, overwrite);
843
799
  }
844
800
  }
845
801
  if (MergeTree.options.zamboniSegments) {
@@ -873,11 +829,7 @@ class MergeTree {
873
829
  // TODO: error checking
874
830
  getMarkerFromId(id) {
875
831
  const marker = this.idToMarker.get(id);
876
- return marker === undefined ||
877
- (0, segmentInfos_js_1.isRemoved)(marker) ||
878
- ((0, segmentInfos_js_1.isMoved)(marker) && marker.moveDst === undefined)
879
- ? undefined
880
- : marker;
832
+ return marker === undefined || (0, segmentInfos_js_1.isRemoved)(marker) ? undefined : marker;
881
833
  }
882
834
  /**
883
835
  * Given a position specified relative to a marker id, lookup the marker
@@ -886,14 +838,14 @@ class MergeTree {
886
838
  * @param refseq - The reference sequence number at which to compute the position.
887
839
  * @param clientId - The client id with which to compute the position.
888
840
  */
889
- posFromRelativePos(relativePos, refseq = this.collabWindow.currentSeq, clientId = this.collabWindow.clientId) {
841
+ posFromRelativePos(relativePos, perspective) {
890
842
  let pos = -1;
891
843
  let marker;
892
844
  if (relativePos.id) {
893
845
  marker = this.getMarkerFromId(relativePos.id);
894
846
  }
895
847
  if ((0, mergeTreeNodes_js_1.isSegmentLeaf)(marker)) {
896
- pos = this.getPosition(marker, refseq, clientId);
848
+ pos = this.getPosition(marker, perspective);
897
849
  if (relativePos.before) {
898
850
  if (relativePos.offset !== undefined) {
899
851
  pos -= relativePos.offset;
@@ -908,14 +860,14 @@ class MergeTree {
908
860
  }
909
861
  return pos;
910
862
  }
911
- insertSegments(pos, segments, refSeq, clientId, seq, opArgs) {
912
- this.ensureIntervalBoundary(pos, refSeq, clientId);
913
- const localSeq = seq === constants_js_1.UnassignedSequenceNumber ? ++this.collabWindow.localSeq : undefined;
914
- this.blockInsert(pos, refSeq, clientId, seq, localSeq, segments);
863
+ insertSegments(pos, segments, perspective, stampArg, opArgs) {
864
+ const stamp = { ...stampArg, type: "insert" };
865
+ this.ensureIntervalBoundary(pos, perspective);
866
+ this.blockInsert(pos, perspective, stamp, segments);
915
867
  // opArgs == undefined => loading snapshot or test code
916
868
  if (opArgs !== undefined) {
917
869
  const deltaSegments = segments
918
- .filter((segment) => !(0, segmentInfos_js_1.toMoveInfo)(segment))
870
+ .filter((segment) => !(0, segmentInfos_js_1.isRemoved)(segment))
919
871
  .map((segment) => ({ segment }));
920
872
  if (deltaSegments.length > 0) {
921
873
  this.mergeTreeDeltaCallback?.(opArgs, {
@@ -926,7 +878,7 @@ class MergeTree {
926
878
  }
927
879
  if (this.collabWindow.collaborating &&
928
880
  MergeTree.options.zamboniSegments &&
929
- seq !== constants_js_1.UnassignedSequenceNumber) {
881
+ opstampUtils.isAcked(stamp)) {
930
882
  (0, zamboni_js_1.zamboniSegments)(this);
931
883
  }
932
884
  }
@@ -948,19 +900,19 @@ class MergeTree {
948
900
  if (remoteClientRefSeq < this.collabWindow.minSeq) {
949
901
  return undefined;
950
902
  }
951
- const segmentInfo = this.getContainingSegment(remoteClientPosition, remoteClientRefSeq, remoteClientId);
952
- const { currentSeq, clientId } = this.collabWindow;
903
+ const remotePerspective = new perspective_js_1.PriorPerspective(remoteClientRefSeq, remoteClientId);
904
+ const segmentInfo = this.getContainingSegment(remoteClientPosition, remotePerspective);
953
905
  if ((0, mergeTreeNodes_js_1.isSegmentLeaf)(segmentInfo?.segment)) {
954
- const segmentPosition = this.getPosition(segmentInfo.segment, currentSeq, clientId);
906
+ const segmentPosition = this.getPosition(segmentInfo.segment, this.localPerspective);
955
907
  return segmentPosition + segmentInfo.offset;
956
908
  }
957
909
  else {
958
- if (remoteClientPosition === this.getLength(remoteClientRefSeq, remoteClientId)) {
959
- return this.getLength(currentSeq, clientId);
910
+ if (remoteClientPosition === this.getLength(remotePerspective)) {
911
+ return this.getLength(this.localPerspective);
960
912
  }
961
913
  }
962
914
  }
963
- blockInsert(pos, refSeq, clientId, seq, localSeq, newSegments) {
915
+ blockInsert(pos, perspective, stamp, newSegments) {
964
916
  // Keeping this function within the scope of blockInsert for readability.
965
917
  // eslint-disable-next-line unicorn/consistent-function-scoping
966
918
  const continueFrom = (node) => {
@@ -975,16 +927,16 @@ class MergeTree {
975
927
  const saveIfLocal = (locSegment) => {
976
928
  // Save segment so we can assign sequence number when acked by server
977
929
  if (this.collabWindow.collaborating) {
978
- if (locSegment.seq === constants_js_1.UnassignedSequenceNumber &&
979
- clientId === this.collabWindow.clientId) {
980
- segmentGroup = this.addToPendingList(locSegment, segmentGroup, localSeq);
930
+ if (opstampUtils.isLocal(locSegment.insert) &&
931
+ stamp.clientId === this.collabWindow.clientId) {
932
+ segmentGroup = this.addToPendingList(locSegment, segmentGroup, stamp.localSeq);
981
933
  }
982
934
  // LocSegment.seq === 0 when coming from SharedSegmentSequence.loadBody()
983
935
  // In all other cases this has to be true (checked by addToLRUSet):
984
936
  // locSegment.seq > this.collabWindow.currentSeq
985
- else if (locSegment.seq > this.collabWindow.minSeq &&
986
- MergeTree.options.zamboniSegments) {
987
- this.addToLRUSet(locSegment, locSegment.seq);
937
+ else if (MergeTree.options.zamboniSegments &&
938
+ opstampUtils.greaterThan(locSegment.insert, (0, mergeTreeNodes_js_1.getMinSeqStamp)(this.collabWindow))) {
939
+ this.addToLRUSet(locSegment, locSegment.insert.seq);
988
940
  }
989
941
  }
990
942
  };
@@ -1000,85 +952,71 @@ class MergeTree {
1000
952
  }
1001
953
  return segmentChanges;
1002
954
  };
1003
- const insertInfo = {
1004
- clientId,
1005
- seq,
1006
- localSeq,
1007
- };
1008
955
  // TODO: build tree from segs and insert all at once
1009
956
  let insertPos = pos;
1010
957
  for (const newSegment of newSegments
1011
958
  .filter((s) => s.cachedLength > 0)
1012
- .map((s) => (0, segmentInfos_js_1.overwriteInfo)(s, insertInfo))) {
959
+ .map((s) => (0, segmentInfos_js_1.overwriteInfo)(s, { insert: stamp }))) {
1013
960
  if (mergeTreeNodes_js_1.Marker.is(newSegment)) {
1014
961
  const markerId = newSegment.getId();
1015
962
  if (markerId) {
1016
963
  this.idToMarker.set(markerId, newSegment);
1017
964
  }
1018
965
  }
1019
- const splitNode = this.insertingWalk(this.root, insertPos, refSeq, clientId, seq, {
966
+ this.insertingWalk(insertPos, perspective, stamp, {
1020
967
  leaf: onLeaf,
1021
968
  candidateSegment: newSegment,
1022
969
  continuePredicate: continueFrom,
1023
970
  });
1024
971
  if (!(0, mergeTreeNodes_js_1.isSegmentLeaf)(newSegment)) {
1025
972
  // Indicates an attempt to insert past the end of the merge-tree's content.
1026
- const errorConstructor = localSeq === undefined ? internal_2.DataProcessingError : internal_2.UsageError;
973
+ const errorConstructor = stamp.localSeq === undefined ? internal_2.DataProcessingError : internal_2.UsageError;
1027
974
  throw new errorConstructor("MergeTree insert failed", {
1028
975
  currentSeq: this.collabWindow.currentSeq,
1029
976
  minSeq: this.collabWindow.minSeq,
1030
- segSeq: insertInfo.seq,
977
+ segSeq: stamp.seq,
1031
978
  });
1032
979
  }
1033
- this.updateRoot(splitNode);
1034
980
  insertPos += newSegment.cachedLength;
1035
981
  if (!this.options?.mergeTreeEnableObliterate || this.obliterates.empty()) {
1036
982
  saveIfLocal(newSegment);
1037
983
  continue;
1038
984
  }
985
+ const overlappingAckedObliterates = [];
1039
986
  let oldest;
1040
- let normalizedOldestSeq = 0;
1041
987
  let newest;
1042
- let normalizedNewestSeq = 0;
1043
- const movedClientIds = [];
1044
- const movedSeqs = [];
1045
988
  let newestAcked;
1046
989
  let oldestUnacked;
990
+ const refSeqStamp = {
991
+ seq: perspective.refSeq,
992
+ clientId: stamp.clientId,
993
+ localSeq: stamp.localSeq,
994
+ };
1047
995
  for (const ob of this.obliterates.findOverlapping(newSegment)) {
1048
- // compute a normalized seq that takes into account local seqs
1049
- // but is still comparable to remote seqs to keep the checks below easy
1050
- // REMOTE SEQUENCE NUMBERS LOCAL SEQUENCE NUMBERS
1051
- // [0, 1, 2, 3, ..., 100, ..., 1000, ..., (MAX - MaxLocalSeq), L1, L2, L3, L4, ..., L100, ..., L1000, ...(MAX)]
1052
- const normalizedObSeq = ob.seq === constants_js_1.UnassignedSequenceNumber
1053
- ? Number.MAX_SAFE_INTEGER - this.collabWindow.localSeq + ob.localSeq
1054
- : ob.seq;
1055
- if (normalizedObSeq > refSeq) {
996
+ if (opstampUtils.greaterThan(ob.stamp, refSeqStamp)) {
1056
997
  // Any obliterate from the same client that's inserting this segment cannot cause the segment to be marked as
1057
998
  // obliterated (since that client must have performed the obliterate before this insertion).
1058
999
  // We still need to consider such obliterates when determining the winning obliterate for the insertion point,
1059
1000
  // see `obliteratePrecedingInsertion` docs.
1060
- if (clientId !== ob.clientId) {
1061
- if (oldest === undefined || normalizedOldestSeq > normalizedObSeq) {
1062
- normalizedOldestSeq = normalizedObSeq;
1063
- oldest = ob;
1064
- movedClientIds.unshift(ob.clientId);
1065
- movedSeqs.unshift(ob.seq);
1001
+ if (stamp.clientId !== ob.stamp.clientId) {
1002
+ if (opstampUtils.isAcked(ob.stamp)) {
1003
+ overlappingAckedObliterates.push(ob.stamp);
1066
1004
  }
1067
- else {
1068
- movedClientIds.push(ob.clientId);
1069
- movedSeqs.push(ob.seq);
1005
+ if (oldest === undefined || opstampUtils.lessThan(ob.stamp, oldest.stamp)) {
1006
+ oldest = ob;
1070
1007
  }
1071
1008
  }
1072
- if (newest === undefined || normalizedNewestSeq < normalizedObSeq) {
1073
- normalizedNewestSeq = normalizedObSeq;
1009
+ if (newest === undefined || opstampUtils.greaterThan(ob.stamp, newest.stamp)) {
1074
1010
  newest = ob;
1075
1011
  }
1076
- if (ob.seq !== constants_js_1.UnassignedSequenceNumber &&
1077
- (newestAcked === undefined || newestAcked.seq < ob.seq)) {
1012
+ if (opstampUtils.isAcked(ob.stamp) &&
1013
+ (newestAcked === undefined ||
1014
+ opstampUtils.greaterThan(ob.stamp, newestAcked.stamp))) {
1078
1015
  newestAcked = ob;
1079
1016
  }
1080
- if (ob.seq === constants_js_1.UnassignedSequenceNumber &&
1081
- (oldestUnacked === undefined || oldestUnacked.localSeq > ob.localSeq)) {
1017
+ if (opstampUtils.isLocal(ob.stamp) &&
1018
+ (oldestUnacked === undefined ||
1019
+ opstampUtils.greaterThan(oldestUnacked.stamp, ob.stamp))) {
1082
1020
  // There can be one local obliterate surrounding a segment if a client repeatedly obliterates
1083
1021
  // a region (ex: in the text ABCDEFG, obliterate D, then obliterate CE, then BF). In this case,
1084
1022
  // the first one that's applied will be the one that actually removes the segment.
@@ -1090,32 +1028,20 @@ class MergeTree {
1090
1028
  // See doc comment on obliteratePrecedingInsertion for more details: if the newest obliterate was performed
1091
1029
  // by the same client that's inserting this segment, we let them insert into this range and therefore don't
1092
1030
  // mark it obliterated.
1093
- if (oldest && newest?.clientId !== clientId) {
1094
- let moveInfo;
1095
- if (newestAcked === newest || newestAcked?.clientId !== clientId) {
1096
- moveInfo = {
1097
- movedClientIds,
1098
- movedSeq: oldest.seq,
1099
- movedSeqs,
1100
- localMovedSeq: oldestUnacked?.localSeq,
1101
- };
1102
- }
1103
- else {
1104
- (0, internal_1.assert)(oldestUnacked !== undefined, 0xb55 /* Expected local obliterate to be defined if newestAcked is not equal to newest */);
1105
- // There's a pending local obliterate for this range, so it will be marked as obliterated by us. However,
1106
- // all other clients are under the impression that the most recent acked obliterate won the right to insert
1107
- // in this range.
1108
- moveInfo = {
1109
- movedClientIds: [oldestUnacked.clientId],
1110
- movedSeq: oldestUnacked.seq,
1111
- movedSeqs: [oldestUnacked.seq],
1112
- localMovedSeq: oldestUnacked.localSeq,
1113
- };
1031
+ if (oldest && newest?.stamp.clientId !== stamp.clientId) {
1032
+ const removeInfo = { removes: [] };
1033
+ if (newestAcked === newest || newestAcked?.stamp.clientId !== stamp.clientId) {
1034
+ removeInfo.removes = overlappingAckedObliterates;
1035
+ // Because we found these by looking at overlapping obliterates, they are not necessarily currently sorted by seq.
1036
+ // Address that now.
1037
+ removeInfo.removes.sort(opstampUtils.compare);
1114
1038
  }
1115
- (0, segmentInfos_js_1.overwriteInfo)(newSegment, moveInfo);
1116
- if (moveInfo.localMovedSeq !== undefined) {
1117
- (0, internal_1.assert)(oldestUnacked?.segmentGroup !== undefined, 0x86c /* expected segment group to exist */);
1118
- this.addToPendingList(newSegment, oldestUnacked?.segmentGroup);
1039
+ // Note that we don't need to worry about preserving any existing remove information since the segment is new.
1040
+ (0, segmentInfos_js_1.overwriteInfo)(newSegment, removeInfo);
1041
+ if (oldestUnacked !== undefined) {
1042
+ removeInfo.removes.push(oldestUnacked.stamp);
1043
+ (0, internal_1.assert)(oldestUnacked.segmentGroup !== undefined, 0x86c /* expected segment group to exist */);
1044
+ this.addToPendingList(newSegment, oldestUnacked.segmentGroup);
1119
1045
  }
1120
1046
  if (newSegment.parent) {
1121
1047
  // The incremental update codepath in theory can handle most cases where segments are obliterated upon insertion,
@@ -1123,90 +1049,95 @@ class MergeTree {
1123
1049
  // lengths inside the inserting walk, we'd be at risk of double-counting the insertion in any case if we allow
1124
1050
  // incremental updates here.
1125
1051
  const newStructure = true;
1126
- this.blockUpdatePathLengths(newSegment.parent, moveInfo.movedSeq, clientId, newStructure);
1052
+ this.blockUpdatePathLengths(newSegment.parent, removeInfo.removes[0], newStructure);
1127
1053
  }
1128
1054
  }
1129
1055
  saveIfLocal(newSegment);
1130
1056
  }
1131
1057
  }
1132
- ensureIntervalBoundary(pos, refSeq, clientId) {
1133
- const splitNode = this.insertingWalk(this.root, pos, refSeq, clientId, constants_js_1.TreeMaintenanceSequenceNumber, { leaf: this.splitLeafSegment });
1134
- this.updateRoot(splitNode);
1058
+ ensureIntervalBoundary(pos, perspective) {
1059
+ this.insertingWalk(pos, perspective, {
1060
+ seq: constants_js_1.TreeMaintenanceSequenceNumber,
1061
+ clientId: perspective.clientId,
1062
+ }, { leaf: this.splitLeafSegment });
1135
1063
  }
1136
1064
  // Assume called only when pos == len
1137
- breakTie(pos, node, seq) {
1065
+ breakTie(pos, node, insertStamp) {
1138
1066
  if (node.isLeaf()) {
1139
1067
  if (pos !== 0) {
1140
1068
  return false;
1141
1069
  }
1142
- // normalize the seq numbers
1143
- // if the new seg is local (UnassignedSequenceNumber) give it the highest possible
1144
- // seq for comparison, as it will get a seq higher than any other seq once sequences
1145
- // if the current seg is local (UnassignedSequenceNumber) give it the second highest
1146
- // possible seq, as the highest is reserved for the previous.
1147
- const newSeq = seq === constants_js_1.UnassignedSequenceNumber ? Number.MAX_SAFE_INTEGER : seq;
1148
- const segSeq = node.seq === constants_js_1.UnassignedSequenceNumber ? Number.MAX_SAFE_INTEGER - 1 : (node.seq ?? 0);
1149
- return (newSeq > segSeq ||
1150
- ((0, segmentInfos_js_1.isMoved)(node) && node.movedSeq !== constants_js_1.UnassignedSequenceNumber && node.movedSeq > seq) ||
1070
+ return (opstampUtils.greaterThan(insertStamp, node.insert) ||
1151
1071
  ((0, segmentInfos_js_1.isRemoved)(node) &&
1152
- node.removedSeq !== constants_js_1.UnassignedSequenceNumber &&
1153
- node.removedSeq > seq));
1072
+ opstampUtils.isAcked(node.removes[0]) &&
1073
+ opstampUtils.greaterThan(node.removes[0], insertStamp)));
1154
1074
  }
1155
1075
  else {
1156
1076
  return true;
1157
1077
  }
1158
1078
  }
1159
- insertingWalk(block, pos, refSeq, clientId, seq, context, isLastChildBlock = true) {
1079
+ insertingWalk(pos, perspective, stamp, context) {
1080
+ const { remainder } = this.insertRecursive(this.root, pos, perspective, stamp, context);
1081
+ if (remainder !== undefined) {
1082
+ this.updateRoot(remainder);
1083
+ }
1084
+ }
1085
+ insertRecursive(block, pos, perspective, stamp, context, isLastChildBlock = true) {
1160
1086
  let _pos = pos;
1161
1087
  const children = block.children;
1162
1088
  let childIndex;
1163
1089
  let child;
1164
1090
  let newNode;
1165
1091
  let fromSplit;
1092
+ let hadChanges = false;
1166
1093
  for (childIndex = 0; childIndex < block.childCount; childIndex++) {
1167
1094
  child = children[childIndex];
1168
1095
  // ensure we walk down the far edge of the tree, even if all sub-tree is eligible for zamboni
1169
1096
  const isLastNonLeafBlock = isLastChildBlock && !child.isLeaf() && childIndex === block.childCount - 1;
1170
- const len = this.nodeLength(child, refSeq, clientId) ?? (isLastChildBlock ? 0 : undefined);
1097
+ const len = this.nodeLength(child, perspective) ?? (isLastChildBlock ? 0 : undefined);
1171
1098
  if (len === undefined) {
1172
1099
  // if the seg len is undefined, the segment
1173
1100
  // will be removed, so should just be skipped for now
1174
1101
  continue;
1175
1102
  }
1176
1103
  (0, internal_1.assert)(len >= 0, 0x4bc /* Length should not be negative */);
1177
- if (_pos < len || (_pos === len && this.breakTie(_pos, child, seq))) {
1104
+ if (_pos < len || (_pos === len && this.breakTie(_pos, child, stamp))) {
1178
1105
  // Found entry containing pos
1179
1106
  if (child.isLeaf()) {
1180
1107
  const segment = child;
1181
1108
  const segmentChanges = context.leaf(segment, _pos, context);
1182
1109
  if (segmentChanges.replaceCurrent) {
1110
+ hadChanges = true;
1183
1111
  (0, mergeTreeNodes_js_1.assignChild)(block, segmentChanges.replaceCurrent, childIndex, false);
1184
1112
  segmentChanges.replaceCurrent.ordinal = child.ordinal;
1185
1113
  }
1186
1114
  if (segmentChanges.next) {
1115
+ hadChanges = true;
1187
1116
  newNode = segmentChanges.next;
1188
1117
  childIndex++; // Insert after
1189
1118
  }
1190
1119
  else {
1191
- // No change
1192
- return undefined;
1120
+ return { remainder: undefined, hadChanges };
1193
1121
  }
1194
1122
  }
1195
1123
  else {
1196
1124
  const childBlock = child;
1197
1125
  // Internal node
1198
- const splitNode = this.insertingWalk(childBlock, _pos, refSeq, clientId, seq, context, isLastNonLeafBlock);
1199
- if (splitNode === undefined) {
1200
- this.blockUpdateLength(block, seq, clientId);
1201
- return undefined;
1126
+ const insertResult = this.insertRecursive(childBlock, _pos, perspective, stamp, context, isLastNonLeafBlock);
1127
+ hadChanges ||= insertResult.hadChanges;
1128
+ if (insertResult.remainder === undefined) {
1129
+ if (insertResult.hadChanges) {
1130
+ this.blockUpdateLength(block, stamp);
1131
+ }
1132
+ return insertResult;
1202
1133
  }
1203
- else if (splitNode === MergeTree.theUnfinishedNode) {
1134
+ else if (insertResult.remainder === MergeTree.theUnfinishedNode) {
1204
1135
  _pos -= len; // Act as if shifted segment
1205
1136
  continue;
1206
1137
  }
1207
1138
  else {
1208
- newNode = splitNode;
1209
- fromSplit = splitNode;
1139
+ newNode = insertResult.remainder;
1140
+ fromSplit = insertResult.remainder;
1210
1141
  childIndex++; // Insert after
1211
1142
  }
1212
1143
  }
@@ -1218,7 +1149,7 @@ class MergeTree {
1218
1149
  }
1219
1150
  if (!newNode && _pos === 0) {
1220
1151
  if (context.continuePredicate?.(block)) {
1221
- return MergeTree.theUnfinishedNode;
1152
+ return { remainder: MergeTree.theUnfinishedNode, hadChanges };
1222
1153
  }
1223
1154
  else {
1224
1155
  const segmentChanges = context.leaf(undefined, _pos, context);
@@ -1227,6 +1158,7 @@ class MergeTree {
1227
1158
  }
1228
1159
  }
1229
1160
  if (newNode) {
1161
+ hadChanges = true;
1230
1162
  for (let i = block.childCount; i > childIndex; i--) {
1231
1163
  block.children[i] = block.children[i - 1];
1232
1164
  block.children[i].index = i;
@@ -1238,19 +1170,19 @@ class MergeTree {
1238
1170
  if (fromSplit) {
1239
1171
  this.nodeUpdateOrdinals(fromSplit);
1240
1172
  }
1241
- this.blockUpdateLength(block, seq, clientId);
1242
- return undefined;
1173
+ this.blockUpdateLength(block, stamp);
1174
+ return { remainder: undefined, hadChanges };
1243
1175
  }
1244
1176
  else {
1245
1177
  // Don't update ordinals because higher block will do it
1246
1178
  const newNodeFromSplit = this.split(block);
1247
- partialLengths_js_1.PartialSequenceLengths.options.verifyExpected?.(this, block, refSeq, clientId);
1248
- partialLengths_js_1.PartialSequenceLengths.options.verifyExpected?.(this, newNodeFromSplit, refSeq, clientId);
1249
- return newNodeFromSplit;
1179
+ partialLengths_js_1.PartialSequenceLengths.options.verifyExpected?.(this, block, perspective.refSeq, stamp.clientId);
1180
+ partialLengths_js_1.PartialSequenceLengths.options.verifyExpected?.(this, newNodeFromSplit, perspective.refSeq, stamp.clientId);
1181
+ return { remainder: newNodeFromSplit, hadChanges };
1250
1182
  }
1251
1183
  }
1252
1184
  else {
1253
- return undefined;
1185
+ return { remainder: undefined, hadChanges };
1254
1186
  }
1255
1187
  }
1256
1188
  split(node) {
@@ -1285,16 +1217,14 @@ class MergeTree {
1285
1217
  * @param clientId - The id of the client making the annotate
1286
1218
  * @param seq - The sequence number of the annotate operation
1287
1219
  * @param opArgs - The op args for the annotate op. this is passed to the merge tree callback if there is one
1288
- * @param rollback - Whether this is for a local rollback and what kind
1289
1220
  */
1290
- annotateRange(start, end, propsOrAdjust, refSeq, clientId, seq, opArgs, rollback = segmentPropertiesManager_js_1.PropertiesRollback.None) {
1221
+ annotateRange(start, end, propsOrAdjust, perspective, stamp, opArgs) {
1291
1222
  if (propsOrAdjust.adjust !== undefined) {
1292
1223
  errorIfOptionNotTrue(this.options, "mergeTreeEnableAnnotateAdjust");
1293
1224
  }
1294
- this.ensureIntervalBoundary(start, refSeq, clientId);
1295
- this.ensureIntervalBoundary(end, refSeq, clientId);
1225
+ this.ensureIntervalBoundary(start, perspective);
1226
+ this.ensureIntervalBoundary(end, perspective);
1296
1227
  const deltaSegments = [];
1297
- const localSeq = seq === constants_js_1.UnassignedSequenceNumber ? ++this.collabWindow.localSeq : undefined;
1298
1228
  let segmentGroup;
1299
1229
  const opObj = propsOrAdjust.props ?? propsOrAdjust.adjust;
1300
1230
  const annotateSegment = (segment) => {
@@ -1302,23 +1232,23 @@ class MergeTree {
1302
1232
  !(mergeTreeNodes_js_1.reservedMarkerIdKey in opObj) ||
1303
1233
  opObj.markerId === segment.properties?.markerId, 0x5ad /* Cannot change the markerId of an existing marker */);
1304
1234
  const propertyManager = (segment.propertyManager ??= new segmentPropertiesManager_js_1.PropertiesManager());
1305
- const propertyDeltas = propertyManager.handleProperties(propsOrAdjust, segment, seq, this.collabWindow.minSeq, this.collabWindow.collaborating, rollback);
1306
- if (!isRemovedOrMoved(segment)) {
1235
+ const propertyDeltas = propertyManager.handleProperties(propsOrAdjust, segment, stamp.seq, this.collabWindow.minSeq, this.collabWindow.collaborating, opArgs?.rollback === true);
1236
+ if (!(0, segmentInfos_js_1.isRemoved)(segment)) {
1307
1237
  deltaSegments.push({ segment, propertyDeltas });
1308
1238
  }
1309
1239
  if (this.collabWindow.collaborating) {
1310
- if (seq === constants_js_1.UnassignedSequenceNumber) {
1311
- segmentGroup = this.addToPendingList(segment, segmentGroup, localSeq, propertyDeltas);
1240
+ if (opstampUtils.isLocal(stamp)) {
1241
+ segmentGroup = this.addToPendingList(segment, segmentGroup, stamp.localSeq, propertyDeltas);
1312
1242
  }
1313
1243
  else {
1314
1244
  if (MergeTree.options.zamboniSegments) {
1315
- this.addToLRUSet(segment, seq);
1245
+ this.addToLRUSet(segment, stamp.seq);
1316
1246
  }
1317
1247
  }
1318
1248
  }
1319
1249
  return true;
1320
1250
  };
1321
- this.nodeMap(refSeq, clientId, annotateSegment, undefined, start, end);
1251
+ this.nodeMap(perspective, annotateSegment, undefined, start, end);
1322
1252
  // OpArgs == undefined => test code
1323
1253
  if (deltaSegments.length > 0) {
1324
1254
  this.mergeTreeDeltaCallback?.(opArgs, {
@@ -1327,34 +1257,28 @@ class MergeTree {
1327
1257
  });
1328
1258
  }
1329
1259
  if (this.collabWindow.collaborating &&
1330
- seq !== constants_js_1.UnassignedSequenceNumber &&
1260
+ opstampUtils.isAcked(stamp) &&
1331
1261
  MergeTree.options.zamboniSegments) {
1332
1262
  (0, zamboni_js_1.zamboniSegments)(this);
1333
1263
  }
1334
1264
  }
1335
- obliterateRangeSided(start, end, refSeq, clientId, seq, opArgs) {
1265
+ obliterateRangeSided(start, end, perspective, stamp, opArgs) {
1336
1266
  const startPos = start.side === sequencePlace_js_1.Side.Before ? start.pos : start.pos + 1;
1337
1267
  const endPos = end.side === sequencePlace_js_1.Side.Before ? end.pos : end.pos + 1;
1338
- this.ensureIntervalBoundary(startPos, refSeq, clientId);
1339
- this.ensureIntervalBoundary(endPos, refSeq, clientId);
1268
+ this.ensureIntervalBoundary(startPos, perspective);
1269
+ this.ensureIntervalBoundary(endPos, perspective);
1340
1270
  let _overwrite = false;
1341
1271
  const localOverlapWithRefs = [];
1342
- const movedSegments = [];
1343
- const localSeq = seq === constants_js_1.UnassignedSequenceNumber ? ++this.collabWindow.localSeq : undefined;
1344
- const perspective = seq === constants_js_1.UnassignedSequenceNumber
1345
- ? this.localPerspective
1346
- : new perspective_js_1.PriorPerspective(refSeq, clientId);
1272
+ const removedSegments = [];
1347
1273
  const obliterate = {
1348
- clientId,
1349
- end: (0, localReference_js_1.createDetachedLocalReferencePosition)(undefined),
1350
- refSeq,
1351
- seq,
1352
1274
  start: (0, localReference_js_1.createDetachedLocalReferencePosition)(undefined),
1353
- localSeq,
1275
+ end: (0, localReference_js_1.createDetachedLocalReferencePosition)(undefined),
1276
+ refSeq: perspective.refSeq,
1277
+ stamp,
1354
1278
  segmentGroup: undefined,
1355
1279
  };
1356
- const { segment: startSeg } = this.getContainingSegment(start.pos, refSeq, clientId);
1357
- const { segment: endSeg } = this.getContainingSegment(end.pos, refSeq, clientId);
1280
+ const { segment: startSeg } = this.getContainingSegment(start.pos, perspective);
1281
+ const { segment: endSeg } = this.getContainingSegment(end.pos, perspective);
1358
1282
  (0, internal_1.assert)((0, mergeTreeNodes_js_1.isSegmentLeaf)(startSeg) && (0, mergeTreeNodes_js_1.isSegmentLeaf)(endSeg), 0xa3f /* segments cannot be undefined */);
1359
1283
  obliterate.start = this.createLocalReferencePosition(startSeg, start.side === sequencePlace_js_1.Side.Before ? 0 : Math.max(startSeg.cachedLength - 1, 0), ops_js_1.ReferenceType.StayOnRemove, {
1360
1284
  obliterate,
@@ -1368,15 +1292,15 @@ class MergeTree {
1368
1292
  // at which point they are added to the segment group.
1369
1293
  obliterate.segmentGroup = {
1370
1294
  segments: [],
1371
- localSeq,
1295
+ localSeq: stamp.localSeq,
1372
1296
  refSeq: this.collabWindow.currentSeq,
1373
1297
  obliterateInfo: obliterate,
1374
1298
  };
1375
- if (this.collabWindow.collaborating && clientId === this.collabWindow.clientId) {
1299
+ if (this.collabWindow.collaborating && stamp.clientId === this.collabWindow.clientId) {
1376
1300
  this.pendingSegments.push(obliterate.segmentGroup);
1377
1301
  }
1378
1302
  this.obliterates.addOrUpdate(obliterate);
1379
- const markMoved = (segment, pos) => {
1303
+ const markRemoved = (segment, pos) => {
1380
1304
  if ((start.side === sequencePlace_js_1.Side.After && startPos === pos + segment.cachedLength) || // exclusive start segment
1381
1305
  (end.side === sequencePlace_js_1.Side.Before && endPos === pos && perspective.isSegmentPresent(segment)) // exclusive end segment
1382
1306
  ) {
@@ -1384,173 +1308,136 @@ class MergeTree {
1384
1308
  // These segments are outside of the obliteration range though, so return true to keep walking.
1385
1309
  return true;
1386
1310
  }
1387
- const existingMoveInfo = (0, segmentInfos_js_1.toMoveInfo)(segment);
1311
+ const existingRemoveInfo = (0, segmentInfos_js_1.toRemovalInfo)(segment);
1388
1312
  // The "last-to-obliterate-gets-to-insert" policy described by the doc comment on `obliteratePrecedingInsertion`
1389
1313
  // is mostly handled by logic at insertion time, but we need a small bit of handling here.
1390
1314
  // Specifically, we want to avoid marking a local-only segment as obliterated when we know one of our own local obliterates
1391
1315
  // will win against the obliterate we're processing, hence the early exit.
1392
- if (segment.seq === constants_js_1.UnassignedSequenceNumber &&
1393
- segment.obliteratePrecedingInsertion?.seq === constants_js_1.UnassignedSequenceNumber &&
1394
- seq !== constants_js_1.UnassignedSequenceNumber) {
1316
+ if (opstampUtils.isLocal(segment.insert) &&
1317
+ segment.obliteratePrecedingInsertion?.stamp.seq === constants_js_1.UnassignedSequenceNumber &&
1318
+ opstampUtils.isAcked(stamp)) {
1395
1319
  // We chose to not obliterate this segment because we are aware of an unacked local obliteration.
1396
1320
  // The local obliterate has not been sequenced yet, so it is still the newest obliterate we are aware of.
1397
1321
  // Other clients will also choose not to obliterate this segment because the most recent obliteration has the same clientId
1398
1322
  return true;
1399
1323
  }
1400
1324
  // Partial lengths incrementality is not supported for overlapping obliterate/removes.
1401
- _overwrite ||= existingMoveInfo !== undefined || (0, segmentInfos_js_1.toRemovalInfo)(segment) !== undefined;
1402
- if (existingMoveInfo === undefined) {
1403
- const movedSeg = (0, segmentInfos_js_1.overwriteInfo)(segment, {
1404
- movedClientIds: [clientId],
1405
- movedSeq: seq,
1406
- localMovedSeq: localSeq,
1407
- movedSeqs: [seq],
1325
+ _overwrite ||= existingRemoveInfo !== undefined;
1326
+ // - Record the segment as removed
1327
+ // - If this was the first thing to remove the segment from the local view, add it to removedSegments
1328
+ // - Otherwise, if it was the first thing to remove the segment from the acked view, add it to localOverlapWithRefs (so we can slide them)
1329
+ if (existingRemoveInfo === undefined) {
1330
+ const removed = (0, segmentInfos_js_1.overwriteInfo)(segment, {
1331
+ removes: [stamp],
1408
1332
  });
1409
- const existingRemoval = (0, segmentInfos_js_1.toRemovalInfo)(movedSeg);
1410
- if (existingRemoval === undefined) {
1411
- movedSegments.push(movedSeg);
1412
- }
1413
- else if (existingRemoval.removedSeq === constants_js_1.UnassignedSequenceNumber &&
1414
- segment.localRefs?.empty === false) {
1415
- // We removed this locally already so we don't need to event it again, but it might have references
1416
- // that need sliding now that a move may have been acked.
1417
- localOverlapWithRefs.push(segment);
1418
- }
1333
+ removedSegments.push(removed);
1419
1334
  }
1420
1335
  else {
1421
- if (existingMoveInfo.movedSeq === constants_js_1.UnassignedSequenceNumber) {
1422
- (0, internal_1.assert)(!(0, segmentInfos_js_1.wasMovedOnInsert)(segment), 0xab4 /* Local obliterate cannot have removed a segment as soon as it was inserted */);
1423
- (0, internal_1.assert)(seq !== constants_js_1.UnassignedSequenceNumber, 0xab5 /* Cannot obliterate the same segment locally twice */);
1424
- // we moved this locally, but someone else moved it first
1425
- // so put them at the head of the list
1426
- // The list isn't ordered, but we keep the first move at the head
1427
- // for partialLengths bookkeeping purposes
1428
- existingMoveInfo.movedClientIds.unshift(clientId);
1429
- existingMoveInfo.movedSeq = seq;
1430
- existingMoveInfo.movedSeqs.unshift(seq);
1431
- if (segment.localRefs?.empty === false) {
1432
- localOverlapWithRefs.push(segment);
1433
- }
1434
- }
1435
- else {
1436
- // Do not replace earlier sequence number for move
1437
- existingMoveInfo.movedClientIds.push(clientId);
1438
- existingMoveInfo.movedSeqs.push(seq);
1336
+ // The segment has already been removed, so we don't need to add it to removedSegments. However,
1337
+ // if it's only been removed locally, we still need to slide any references that may exist on it.
1338
+ if (!opstampUtils.hasAnyAckedOperation(existingRemoveInfo.removes) &&
1339
+ segment.localRefs?.empty === false) {
1340
+ localOverlapWithRefs.push(segment);
1439
1341
  }
1342
+ opstampUtils.spliceIntoList(existingRemoveInfo.removes, stamp);
1440
1343
  }
1441
- (0, segmentInfos_js_1.assertMoved)(segment);
1442
- // Save segment so can assign moved sequence number when acked by server
1344
+ (0, segmentInfos_js_1.assertRemoved)(segment);
1345
+ // Save segment so can assign sequence number when acked by server
1443
1346
  if (this.collabWindow.collaborating) {
1444
- if (segment.movedSeq === constants_js_1.UnassignedSequenceNumber &&
1445
- clientId === this.collabWindow.clientId) {
1446
- obliterate.segmentGroup = this.addToPendingList(segment, obliterate.segmentGroup, localSeq);
1347
+ if (opstampUtils.isLocal(segment.removes[0]) &&
1348
+ stamp.clientId === this.collabWindow.clientId) {
1349
+ obliterate.segmentGroup = this.addToPendingList(segment, obliterate.segmentGroup, stamp.localSeq);
1447
1350
  }
1448
1351
  else {
1449
1352
  if (MergeTree.options.zamboniSegments) {
1450
- this.addToLRUSet(segment, seq);
1353
+ this.addToLRUSet(segment, stamp.seq);
1451
1354
  }
1452
1355
  }
1453
1356
  }
1454
1357
  return true;
1455
1358
  };
1456
- const afterMarkMoved = (node) => {
1359
+ const afterMarkRemoved = (node) => {
1457
1360
  if (_overwrite) {
1458
1361
  this.nodeUpdateLengthNewStructure(node);
1459
1362
  }
1460
1363
  else {
1461
- this.blockUpdateLength(node, seq, clientId);
1364
+ this.blockUpdateLength(node, stamp);
1462
1365
  }
1463
1366
  return true;
1464
1367
  };
1465
- this.nodeMap(refSeq, clientId, markMoved, afterMarkMoved, start.pos, end.pos + 1, // include the segment containing the end reference
1466
- undefined, seq === constants_js_1.UnassignedSequenceNumber ? undefined : seq);
1368
+ this.nodeMap(perspective, markRemoved, afterMarkRemoved, start.pos, end.pos + 1, // include the segment containing the end reference
1369
+ // Use a visibilityPerspective which includes all segments (including local ones) which are in the obliteration range.
1370
+ // This ensures that concurrently inserted segments will also be marked obliterated.
1371
+ opstampUtils.isLocal(stamp)
1372
+ ? perspective
1373
+ : new perspective_js_1.RemoteObliteratePerspective(stamp.clientId));
1467
1374
  this.slideAckedRemovedSegmentReferences(localOverlapWithRefs);
1468
1375
  // opArgs == undefined => test code
1469
1376
  if (start.pos !== end.pos || start.side !== end.side) {
1470
1377
  this.mergeTreeDeltaCallback?.(opArgs, {
1471
1378
  operation: ops_js_1.MergeTreeDeltaType.OBLITERATE,
1472
- deltaSegments: movedSegments.map((segment) => ({ segment })),
1379
+ deltaSegments: removedSegments.map((segment) => ({ segment })),
1473
1380
  });
1474
1381
  }
1475
1382
  // these events are newly removed
1476
1383
  // so we slide after eventing in case the consumer wants to make reference
1477
1384
  // changes at remove time, like add a ref to track undo redo.
1478
- if (!this.collabWindow.collaborating || clientId !== this.collabWindow.clientId) {
1479
- this.slideAckedRemovedSegmentReferences(movedSegments);
1385
+ if (!this.collabWindow.collaborating || stamp.clientId !== this.collabWindow.clientId) {
1386
+ this.slideAckedRemovedSegmentReferences(removedSegments);
1480
1387
  }
1481
1388
  if (this.collabWindow.collaborating &&
1482
- seq !== constants_js_1.UnassignedSequenceNumber &&
1389
+ opstampUtils.isAcked(stamp) &&
1483
1390
  MergeTree.options.zamboniSegments) {
1484
1391
  (0, zamboni_js_1.zamboniSegments)(this);
1485
1392
  }
1486
1393
  }
1487
- obliterateRange(start, end, refSeq, clientId, seq, opArgs) {
1394
+ obliterateRange(start, end, perspective, stampArg, opArgs) {
1488
1395
  errorIfOptionNotTrue(this.options, "mergeTreeEnableObliterate");
1396
+ const stamp = { ...stampArg, type: "sliceRemove" };
1489
1397
  if (this.options?.mergeTreeEnableSidedObliterate) {
1490
1398
  (0, internal_1.assert)(typeof start === "object" && typeof end === "object", 0xa45 /* Start and end must be of type InteriorSequencePlace if mergeTreeEnableSidedObliterate is enabled. */);
1491
- this.obliterateRangeSided(start, end, refSeq, clientId, seq, opArgs);
1399
+ this.obliterateRangeSided(start, end, perspective, stamp, opArgs);
1492
1400
  }
1493
1401
  else {
1494
1402
  (0, internal_1.assert)(typeof start === "number" && typeof end === "number", 0xa46 /* Start and end must be numbers if mergeTreeEnableSidedObliterate is not enabled. */);
1495
- this.obliterateRangeSided({ pos: start, side: sequencePlace_js_1.Side.Before }, { pos: end - 1, side: sequencePlace_js_1.Side.After }, refSeq, clientId, seq, opArgs);
1403
+ this.obliterateRangeSided({ pos: start, side: sequencePlace_js_1.Side.Before }, { pos: end - 1, side: sequencePlace_js_1.Side.After }, perspective, stamp, opArgs);
1496
1404
  }
1497
1405
  }
1498
- markRangeRemoved(start, end, refSeq, clientId, seq, opArgs) {
1406
+ markRangeRemoved(start, end, perspective, stampArg, opArgs) {
1499
1407
  let _overwrite = false;
1500
- this.ensureIntervalBoundary(start, refSeq, clientId);
1501
- this.ensureIntervalBoundary(end, refSeq, clientId);
1408
+ const stamp = { ...stampArg, type: "setRemove" };
1409
+ this.ensureIntervalBoundary(start, perspective);
1410
+ this.ensureIntervalBoundary(end, perspective);
1502
1411
  let segmentGroup;
1503
1412
  const removedSegments = [];
1504
1413
  const localOverlapWithRefs = [];
1505
- const localSeq = seq === constants_js_1.UnassignedSequenceNumber ? ++this.collabWindow.localSeq : undefined;
1506
1414
  const markRemoved = (segment, pos, _start, _end) => {
1507
1415
  const existingRemovalInfo = (0, segmentInfos_js_1.toRemovalInfo)(segment);
1508
1416
  // Partial lengths incrementality is not supported for overlapping obliterate/removes.
1509
- _overwrite ||= existingRemovalInfo !== undefined || (0, segmentInfos_js_1.toMoveInfo)(segment) !== undefined;
1417
+ _overwrite ||= existingRemovalInfo !== undefined;
1510
1418
  if (existingRemovalInfo === undefined) {
1511
1419
  const removed = (0, segmentInfos_js_1.overwriteInfo)(segment, {
1512
- removedClientIds: [clientId],
1513
- removedSeq: seq,
1514
- localRemovedSeq: localSeq,
1420
+ removes: [stamp],
1515
1421
  });
1516
- const existingMoveInfo = (0, segmentInfos_js_1.toMoveInfo)(removed);
1517
- if (existingMoveInfo === undefined) {
1518
- removedSegments.push(removed);
1519
- }
1520
- else if (existingMoveInfo.movedSeq === constants_js_1.UnassignedSequenceNumber &&
1521
- segment.localRefs?.empty === false) {
1522
- // We moved this locally already so we don't need to event it again, but it might have references
1523
- // that need sliding now that a remove may have been acked.
1524
- localOverlapWithRefs.push(segment);
1525
- }
1422
+ removedSegments.push(removed);
1526
1423
  }
1527
1424
  else {
1528
- if (existingRemovalInfo.removedSeq === constants_js_1.UnassignedSequenceNumber) {
1529
- // we removed this locally, but someone else removed it first
1530
- // so put them at the head of the list
1531
- // The list isn't ordered, but we keep the first removal at the head
1532
- // for partialLengths bookkeeping purposes
1533
- existingRemovalInfo.removedClientIds.unshift(clientId);
1534
- existingRemovalInfo.removedSeq = seq;
1535
- if (segment.localRefs?.empty === false) {
1536
- localOverlapWithRefs.push(segment);
1537
- }
1538
- }
1539
- else {
1540
- // Do not replace earlier sequence number for remove
1541
- existingRemovalInfo.removedClientIds.push(clientId);
1425
+ if (!opstampUtils.hasAnyAckedOperation(existingRemovalInfo.removes) &&
1426
+ segment.localRefs?.empty === false) {
1427
+ localOverlapWithRefs.push(segment);
1542
1428
  }
1429
+ opstampUtils.spliceIntoList(existingRemovalInfo.removes, stamp);
1543
1430
  }
1544
1431
  (0, segmentInfos_js_1.assertRemoved)(segment);
1545
1432
  // Save segment so we can assign removed sequence number when acked by server
1546
1433
  if (this.collabWindow.collaborating) {
1547
- if (segment.removedSeq === constants_js_1.UnassignedSequenceNumber &&
1548
- clientId === this.collabWindow.clientId) {
1549
- segmentGroup = this.addToPendingList(segment, segmentGroup, localSeq);
1434
+ if (opstampUtils.isLocal(segment.removes[0]) &&
1435
+ stamp.clientId === this.collabWindow.clientId) {
1436
+ segmentGroup = this.addToPendingList(segment, segmentGroup, stamp.localSeq);
1550
1437
  }
1551
1438
  else {
1552
1439
  if (MergeTree.options.zamboniSegments) {
1553
- this.addToLRUSet(segment, seq);
1440
+ this.addToLRUSet(segment, stamp.seq);
1554
1441
  }
1555
1442
  }
1556
1443
  }
@@ -1561,11 +1448,11 @@ class MergeTree {
1561
1448
  this.nodeUpdateLengthNewStructure(node);
1562
1449
  }
1563
1450
  else {
1564
- this.blockUpdateLength(node, seq, clientId);
1451
+ this.blockUpdateLength(node, stamp);
1565
1452
  }
1566
1453
  return true;
1567
1454
  };
1568
- this.nodeMap(refSeq, clientId, markRemoved, afterMarkRemoved, start, end);
1455
+ this.nodeMap(perspective, markRemoved, afterMarkRemoved, start, end);
1569
1456
  // these segments are already viewed as being removed locally and are not event-ed
1570
1457
  // so can slide non-StayOnRemove refs immediately
1571
1458
  this.slideAckedRemovedSegmentReferences(localOverlapWithRefs);
@@ -1579,11 +1466,11 @@ class MergeTree {
1579
1466
  // these events are newly removed
1580
1467
  // so we slide after eventing in case the consumer wants to make reference
1581
1468
  // changes at remove time, like add a ref to track undo redo.
1582
- if (!this.collabWindow.collaborating || clientId !== this.collabWindow.clientId) {
1469
+ if (!this.collabWindow.collaborating || stamp.clientId !== this.collabWindow.clientId) {
1583
1470
  this.slideAckedRemovedSegmentReferences(removedSegments);
1584
1471
  }
1585
1472
  if (this.collabWindow.collaborating &&
1586
- seq !== constants_js_1.UnassignedSequenceNumber &&
1473
+ opstampUtils.isAcked(stamp) &&
1587
1474
  MergeTree.options.zamboniSegments) {
1588
1475
  (0, zamboni_js_1.zamboniSegments)(this);
1589
1476
  }
@@ -1602,15 +1489,25 @@ class MergeTree {
1602
1489
  pendingSegmentGroup.segments.forEach((segment) => {
1603
1490
  const segmentSegmentGroup = segment?.segmentGroups?.pop();
1604
1491
  (0, internal_1.assert)(segmentSegmentGroup === pendingSegmentGroup, 0x3ee /* Unexpected segmentGroup in segment */);
1605
- (0, internal_1.assert)((0, segmentInfos_js_1.isRemoved)(segment) && segment.removedClientIds[0] === this.collabWindow.clientId, 0x39d /* Rollback segment removedClientId does not match local client */);
1492
+ (0, internal_1.assert)((0, segmentInfos_js_1.isRemoved)(segment) &&
1493
+ segment.removes[0].clientId === this.collabWindow.clientId &&
1494
+ segment.removes[0].type === "setRemove", 0x39d /* Rollback segment removedClientId does not match local client */);
1606
1495
  let updateNode = segment.parent;
1496
+ // This also removes obliterates, but that should be ok as we can only remove a segment once.
1497
+ // If we were able to remove it locally, that also means there are no remote removals (since rollback is synchronous).
1607
1498
  (0, segmentInfos_js_1.removeRemovalInfo)(segment);
1608
1499
  for (updateNode; updateNode !== undefined; updateNode = updateNode.parent) {
1609
- this.blockUpdateLength(updateNode, constants_js_1.UnassignedSequenceNumber, this.collabWindow.clientId);
1500
+ this.blockUpdateLength(updateNode, {
1501
+ seq: constants_js_1.UnassignedSequenceNumber,
1502
+ clientId: this.collabWindow.clientId,
1503
+ });
1610
1504
  }
1611
1505
  // Note: optional chaining short-circuits:
1612
1506
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining#short-circuiting
1613
- this.mergeTreeDeltaCallback?.({ op: (0, opBuilder_js_1.createInsertSegmentOp)(this.findRollbackPosition(segment), segment) }, {
1507
+ this.mergeTreeDeltaCallback?.({
1508
+ op: (0, opBuilder_js_1.createInsertSegmentOp)(this.findRollbackPosition(segment), segment),
1509
+ rollback: true,
1510
+ }, {
1614
1511
  operation: ops_js_1.MergeTreeDeltaType.INSERT,
1615
1512
  deltaSegments: [{ segment }],
1616
1513
  });
@@ -1630,15 +1527,27 @@ class MergeTree {
1630
1527
  (0, internal_1.assert)(segmentSegmentGroup === pendingSegmentGroup, 0x3ef /* Unexpected segmentGroup in segment */);
1631
1528
  const start = this.findRollbackPosition(segment);
1632
1529
  if (op.type === ops_js_1.MergeTreeDeltaType.INSERT) {
1633
- segment.seq = constants_js_1.UniversalSequenceNumber;
1634
- segment.localSeq = undefined;
1530
+ segment.insert = {
1531
+ type: "insert",
1532
+ seq: constants_js_1.UniversalSequenceNumber,
1533
+ clientId: this.collabWindow.clientId,
1534
+ };
1635
1535
  const removeOp = (0, opBuilder_js_1.createRemoveRangeOp)(start, start + segment.cachedLength);
1636
- this.markRangeRemoved(start, start + segment.cachedLength, constants_js_1.UniversalSequenceNumber, this.collabWindow.clientId, constants_js_1.UniversalSequenceNumber, { op: removeOp });
1536
+ const removeStamp = {
1537
+ type: "setRemove",
1538
+ seq: constants_js_1.UniversalSequenceNumber,
1539
+ clientId: this.collabWindow.clientId,
1540
+ };
1541
+ this.markRangeRemoved(start, start + segment.cachedLength, this.localPerspective, removeStamp, { op: removeOp, rollback: true });
1637
1542
  } /* op.type === MergeTreeDeltaType.ANNOTATE */
1638
1543
  else {
1639
1544
  const props = pendingSegmentGroup.previousProps[i];
1640
1545
  const annotateOp = (0, opBuilder_js_1.createAnnotateRangeOp)(start, start + segment.cachedLength, props);
1641
- this.annotateRange(start, start + segment.cachedLength, { props }, constants_js_1.UniversalSequenceNumber, this.collabWindow.clientId, constants_js_1.UniversalSequenceNumber, { op: annotateOp }, segmentPropertiesManager_js_1.PropertiesRollback.Rollback);
1546
+ const annotateStamp = {
1547
+ seq: constants_js_1.UniversalSequenceNumber,
1548
+ clientId: this.collabWindow.clientId,
1549
+ };
1550
+ this.annotateRange(start, start + segment.cachedLength, { props }, this.localPerspective, annotateStamp, { op: annotateOp, rollback: true });
1642
1551
  i++;
1643
1552
  }
1644
1553
  }
@@ -1679,7 +1588,7 @@ class MergeTree {
1679
1588
  createLocalReferencePosition(_segment, offset, refType, properties, slidingPreference, canSlideToEndpoint) {
1680
1589
  if (_segment !== "start" &&
1681
1590
  _segment !== "end" &&
1682
- isRemovedAndAckedOrMovedAndAcked(_segment) &&
1591
+ isRemovedAndAcked(_segment) &&
1683
1592
  !(0, referencePositions_js_1.refTypeIncludesFlag)(refType, ops_js_1.ReferenceType.SlideOnRemove | ops_js_1.ReferenceType.Transient | ops_js_1.ReferenceType.StayOnRemove) &&
1684
1593
  _segment.endpointType === undefined) {
1685
1594
  throw new internal_2.UsageError("Can only create SlideOnRemove or Transient local reference position on a removed or obliterated segment");
@@ -1723,15 +1632,15 @@ class MergeTree {
1723
1632
  affectedSegments.insertAfter(lastLocalSegment, segmentToSlide.data);
1724
1633
  }
1725
1634
  else if ((0, segmentInfos_js_1.isRemoved)(segmentToSlide.data)) {
1726
- (0, internal_1.assert)(segmentToSlide.data.localRemovedSeq !== undefined, 0x54d /* Removed segment that hasnt had its removal acked should be locally removed */);
1635
+ (0, internal_1.assert)(segmentToSlide.data.removes[0].seq !== undefined, 0x54d /* Removed segment that hasnt had its removal acked should be locally removed */);
1727
1636
  // Slide each locally removed item past all segments that have localSeq > lremoveItem.localSeq
1728
1637
  // but not past remotely removed segments;
1729
1638
  let cur = segmentToSlide;
1730
1639
  let scan = cur.next;
1731
1640
  while (scan !== undefined &&
1732
1641
  !isRemovedAndAcked(scan.data) &&
1733
- scan.data.localSeq !== undefined &&
1734
- scan.data.localSeq > segmentToSlide.data.localRemovedSeq) {
1642
+ scan.data.insert.localSeq !== undefined &&
1643
+ opstampUtils.greaterThan(scan.data.insert, segmentToSlide.data.removes[0])) {
1735
1644
  cur = scan;
1736
1645
  scan = scan.next;
1737
1646
  }
@@ -1816,11 +1725,11 @@ class MergeTree {
1816
1725
  }
1817
1726
  };
1818
1727
  (0, mergeTreeNodeWalk_js_1.walkAllChildSegments)(this.root, (seg) => {
1819
- if ((0, segmentInfos_js_1.isRemoved)(seg) || seg.seq === constants_js_1.UnassignedSequenceNumber) {
1728
+ if ((0, segmentInfos_js_1.isRemoved)(seg) || opstampUtils.isLocal(seg.insert)) {
1820
1729
  if (isRemovedAndAcked(seg)) {
1821
1730
  rangeContainsRemoteRemovedSegs = true;
1822
1731
  }
1823
- if (seg.seq === constants_js_1.UnassignedSequenceNumber) {
1732
+ if (opstampUtils.isLocal(seg.insert)) {
1824
1733
  rangeContainsLocalSegs = true;
1825
1734
  }
1826
1735
  currentRangeToNormalize.push(seg);
@@ -1848,7 +1757,7 @@ class MergeTree {
1848
1757
  }
1849
1758
  if (node.isLeaf()) {
1850
1759
  const segment = node;
1851
- if ((this.localNetLength(segment) ?? 0) > 0 && mergeTreeNodes_js_1.Marker.is(segment)) {
1760
+ if ((this.leafLength(segment) ?? 0) > 0 && mergeTreeNodes_js_1.Marker.is(segment)) {
1852
1761
  const markerId = segment.getId();
1853
1762
  // Also in insertMarker but need for reload segs case
1854
1763
  // can add option for this only from reload segs
@@ -1878,33 +1787,33 @@ class MergeTree {
1878
1787
  block.rightmostTiles = rightmostTiles;
1879
1788
  block.cachedLength = len;
1880
1789
  }
1881
- blockUpdatePathLengths(startBlock, seq, clientId, newStructure = false) {
1790
+ blockUpdatePathLengths(startBlock, stamp, newStructure = false) {
1882
1791
  let block = startBlock;
1883
1792
  while (block !== undefined) {
1884
1793
  if (newStructure) {
1885
1794
  this.nodeUpdateLengthNewStructure(block);
1886
1795
  }
1887
1796
  else {
1888
- this.blockUpdateLength(block, seq, clientId);
1797
+ this.blockUpdateLength(block, stamp);
1889
1798
  }
1890
1799
  block = block.parent;
1891
1800
  }
1892
1801
  }
1893
- blockUpdateLength(node, seq, clientId) {
1802
+ blockUpdateLength(node, stamp) {
1894
1803
  this.blockUpdate(node);
1895
1804
  this.localPartialsComputed = false;
1896
1805
  if (this.collabWindow.collaborating &&
1897
- seq !== constants_js_1.UnassignedSequenceNumber &&
1898
- seq !== constants_js_1.TreeMaintenanceSequenceNumber) {
1806
+ opstampUtils.isAcked(stamp) &&
1807
+ stamp.seq !== constants_js_1.TreeMaintenanceSequenceNumber) {
1899
1808
  if (node.partialLengths !== undefined &&
1900
1809
  MergeTree.options.incrementalUpdate &&
1901
- clientId !== constants_js_1.NonCollabClient) {
1902
- node.partialLengths.update(node, seq, clientId, this.collabWindow);
1810
+ stamp.clientId !== constants_js_1.NonCollabClient) {
1811
+ node.partialLengths.update(node, stamp.seq, stamp.clientId, this.collabWindow);
1903
1812
  }
1904
1813
  else {
1905
1814
  node.partialLengths = partialLengths_js_1.PartialSequenceLengths.combine(node, this.collabWindow);
1906
1815
  }
1907
- partialLengths_js_1.PartialSequenceLengths.options.verifyExpected?.(this, node, seq, clientId);
1816
+ partialLengths_js_1.PartialSequenceLengths.options.verifyExpected?.(this, node, stamp.seq, stamp.clientId);
1908
1817
  }
1909
1818
  }
1910
1819
  /**
@@ -1914,16 +1823,16 @@ class MergeTree {
1914
1823
  *
1915
1824
  * See `this.nodeMap` for additional documentation
1916
1825
  */
1917
- mapRange(handler, refSeq, clientId, accum, start, end, splitRange = false, visibilitySeq = refSeq) {
1826
+ mapRange(handler, perspective, accum, start, end, splitRange = false, visibilityPerspective = perspective) {
1918
1827
  if (splitRange) {
1919
1828
  if (start) {
1920
- this.ensureIntervalBoundary(start, refSeq, clientId);
1829
+ this.ensureIntervalBoundary(start, perspective);
1921
1830
  }
1922
1831
  if (end) {
1923
- this.ensureIntervalBoundary(end, refSeq, clientId);
1832
+ this.ensureIntervalBoundary(end, perspective);
1924
1833
  }
1925
1834
  }
1926
- this.nodeMap(refSeq, clientId, (seg, pos, _start, _end) => handler(seg, pos, refSeq, clientId, _start, _end, accum), undefined, start, end, undefined, visibilitySeq);
1835
+ this.nodeMap(perspective, (seg, pos, _start, _end) => handler(seg, pos, perspective.refSeq, perspective.clientId, _start, _end, accum), undefined, start, end, visibilityPerspective);
1927
1836
  }
1928
1837
  /**
1929
1838
  * Map over all visible segments in a given range
@@ -1948,8 +1857,8 @@ class MergeTree {
1948
1857
  * but it will not count as a segment within the range. That is, it will be
1949
1858
  * ignored for the purposes of tracking when traversal should end.
1950
1859
  */
1951
- nodeMap(refSeq, clientId, leaf, post, start = 0, end, localSeq, visibilitySeq = refSeq) {
1952
- const endPos = end ?? this.nodeLength(this.root, refSeq, clientId, localSeq) ?? 0;
1860
+ nodeMap(perspective, leaf, post, start = 0, end, visibilityPerspective = perspective) {
1861
+ const endPos = end ?? this.nodeLength(this.root, perspective) ?? 0;
1953
1862
  if (endPos === start) {
1954
1863
  return;
1955
1864
  }
@@ -1958,12 +1867,15 @@ class MergeTree {
1958
1867
  if (endPos <= pos) {
1959
1868
  return mergeTreeNodeWalk_js_1.NodeAction.Exit;
1960
1869
  }
1961
- const len = this.nodeLength(node, visibilitySeq, clientId, localSeq);
1962
- const lenAtRefSeq = (visibilitySeq === refSeq
1963
- ? len
1964
- : this.nodeLength(node, refSeq, clientId, localSeq)) ?? 0;
1965
- const isUnackedAndInObliterate = visibilitySeq !== refSeq &&
1966
- (!node.isLeaf() || node.seq === constants_js_1.UnassignedSequenceNumber);
1870
+ const len = this.nodeLength(node, visibilityPerspective);
1871
+ const lenAtRefSeq = (visibilityPerspective === perspective ? len : this.nodeLength(node, perspective)) ??
1872
+ 0;
1873
+ // NOTE: This code ensures that obliterates have a chance to mark segments which have been inserted locally
1874
+ // as also having been obliterated on the local client. With the introduction of RemoteObliteratePerspective,
1875
+ // it's feasible we could remove it if the `nodeLength` calculation also respects that perspective for blocks
1876
+ // and not just leaves.
1877
+ const isUnackedAndInObliterate = visibilityPerspective !== perspective &&
1878
+ (!node.isLeaf() || opstampUtils.isLocal(node.insert));
1967
1879
  if ((len === undefined && lenAtRefSeq === 0) ||
1968
1880
  (len === 0 && !isUnackedAndInObliterate && lenAtRefSeq === 0)) {
1969
1881
  return mergeTreeNodeWalk_js_1.NodeAction.Skip;
@@ -1989,5 +1901,10 @@ MergeTree.options = {
1989
1901
  insertAfterRemovedSegs: true,
1990
1902
  zamboniSegments: true,
1991
1903
  };
1904
+ /**
1905
+ * A sentinel value that indicates an inserting walk should continue to the next block sibling.
1906
+ * This can occur for example when tie-break forces insertion of a segment past an entire block (and
1907
+ * the inserting walk first recurses into the block before realizing that).
1908
+ */
1992
1909
  MergeTree.theUnfinishedNode = { childCount: -1 };
1993
1910
  //# sourceMappingURL=mergeTree.js.map